AI/ActionSystem/Actions/Guest/SabotageAction.cs
namespace HC3;

public sealed class SabotageAction : AgentAction
{
	[Sync( SyncFlags.FromHost )]
	private BasicRide Target { get; set; }

	private Guest Guest => Agent as Guest;

	public override float Score()
	{
		if ( !Guest.IsValid() || Guest.Delinquency < 0.85f ) return 0f;
		if ( Target.IsValid() ) return 300f;

		// Pick random available ride without allocating filtered array
		BasicRide candidate = null;
		int count = 0;
		foreach ( var b in Scene.GetAll<BasicRide>() )
		{
			if ( !b.IsAvailable() || b.Durability <= 0f || b.IsBroken )
				continue;

			// Reservoir sampling: each valid item has equal probability
			count++;
			if ( Game.Random.Next( count ) == 0 )
				candidate = b;
		}
		if ( count == 0 ) return 0f;

		Target = candidate;
		return Target.IsValid() ? 300f : 0f;
	}

	public override void StartAction()
	{
		Controller.ClearNavigation();
	}

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

		var targetPos = Target.WorldPosition;

		if ( !GridManager.IsWalkable( Agent.WorldPosition, targetPos ) )
		{
			Target = null;
			return Status.Failure;
		}

		if ( !Controller.Navigate( targetPos ) )
			return Status.Running;

		Guest.CommitCrime( 0.5f );

		// TODO: maybe they stand there for a while breaking it with an animation?
		// For now, let's just have it fuck the durability up a bit.
		Target.Damage( Game.Random.Float( 0.3f, 0.8f ) );
		Target = null;

		return Status.Success;
	}

	public override void StopAction()
	{
		Target = null;
	}

	public override ActionDisplayInfo? GetDisplay()
	{
		if ( !Target.IsValid() ) return null;

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

		return new ActionDisplayInfo(
			"visibility_off",
			$"Sabotage",
			progress
		);
	}
}