AI/ActionSystem/Actions/Staff/ArrestSuspectAction.cs
namespace HC3;

public sealed class ArrestSuspectAction : AgentAction
{
	private enum ArrestStage
	{
		Chasing,
		Escorting
	}

	[Sync( SyncFlags.FromHost )]
	private Guest Suspect { get; set; }

	private Staff Staff => Agent as Staff;

	private ArrestStage Stage { get; set; }

	public override float Score()
	{
		if ( !Staff.IsValid() ) return 0f;
		if ( Suspect.IsValid() ) return 999f;

		// TODO: maybe we only try to arrest guests who have a high suspicion level
		// that we can see, or are in a radius to us. For now just pick one.

		// Pick random suspect without allocating filtered array
		Guest candidate = null;
		int count = 0;
		foreach ( var x in Scene.GetAll<Guest>() )
		{
			if ( x.Building.IsValid() ) continue;
			if ( x.IsArrested || x.IsPursued ) continue;
			if ( x.SuspicionLevel < Guest.ArrestSuspicionThreshold ) continue;

			count++;
			if ( Game.Random.Next( count ) == 0 )
				candidate = x;
		}

		Suspect = candidate;

		return Suspect.IsValid() ? 999f : 0f;
	}

	public override void StartAction()
	{
		Staff.Controller.IsRunning = true;
		Stage = ArrestStage.Chasing;

		if ( Suspect.IsValid() )
			Suspect.IsPursued = true;

		Controller.ClearNavigation();
	}

	public override Status TickAction()
	{
		if ( !Suspect.IsValid() )
			return Status.Failure;

		if ( Suspect.GameObject.Parent is not Sandbox.Scene )
			return Status.Failure;

		var targetPosition = Suspect.WorldPosition;

		if ( Stage == ArrestStage.Escorting )
		{
			var entrance = Scene.GetAll<ParkEntrance>().FirstOrDefault();
			targetPosition = entrance.WorldPosition + entrance.WorldRotation.Forward * GridManager.GridSize;
		}

		if ( !GridManager.IsWalkable( Agent.WorldPosition, targetPosition ) )
			return Status.Running;

		var flags = NavFlags.Default.WithFlag( NavFlags.Owned, false );

		if ( !Controller.Navigate( targetPosition, flags ) )
			return Status.Running;

		if ( Stage == ArrestStage.Chasing )
		{
			Staff.Controller.IsRunning = false;
			Suspect.IsArrested = true;
			Stage = ArrestStage.Escorting;

			return Status.Running;
		}

		Staff.IncreaseStat( StaffStats.GuestsArrested );
		Stats.Increment( "staff.arrests_made" );

		Suspect = null;

		return Status.Success;
	}

	public override void StopAction()
	{
		Staff.Controller.IsRunning = false;

		if ( Suspect.IsValid() )
			Suspect.IsPursued = false;

		Suspect = null;
	}

	public override ActionDisplayInfo? GetDisplay()
	{
		if ( Suspect == null ) return null;

		var targetPosition = Suspect.WorldPosition;

		if ( Stage == ArrestStage.Escorting )
		{
			var entrance = Scene.GetAll<ParkEntrance>().FirstOrDefault();
			targetPosition = entrance.WorldPosition + entrance.WorldRotation.Forward * GridManager.GridSize;
		}

		var distance = Agent.WorldPosition.Distance( targetPosition );
		var progress = 1f - (distance / 1500f).Clamp( 0f, 1f );

		return new ActionDisplayInfo(
			"security",
			$"Arresting {Suspect.FullName}",
			progress
		);
	}
}