AI/ActionSystem/Actions/WanderAction.cs
namespace HC3;

public sealed class WanderAction : AgentAction
{
	public override float Score()
	{
		return 1f;
	}

	public override void StartAction()
	{
		base.StartAction();

		Agent.Controller.IsRunning = false;

		NavFlags flags = NavFlags.Default;
		if ( TryFindRoamPoint( out Vector3 goal, flags ) )
		{
			Controller.Navigate( goal, flags );
		}
	}

	public override Status TickAction()
	{
		return Controller.IsNavigating ? Status.Running : Status.Success;
	}

	public bool TryFindRoamPoint( out Vector3 location, NavFlags flags )
	{
		location = Vector3.Zero;

		var gridPos = GridManager.WorldToGridPosition3D( WorldPosition );
		int regionId = GridManager.GetRegion( gridPos );

		var walkableCells = GridNavigation.Instance.GetNavablePaths( regionId, flags );
		if ( !walkableCells.Any() )
			return false;

		// Sample random candidates and pick the farthest one, no LINQ allocations
		Vector3Int? farthestCell = null;
		float farthestDist = -1f;
		int sampled = 0;

		foreach ( var pos in walkableCells )
		{
			if ( !IsRoamable( pos ) )
				continue;

			// Reservoir sampling: keep up to 20 random candidates
			if ( sampled < 20 || Random.Next( sampled + 1 ) < 20 )
			{
				float dist = (pos - gridPos).Length;
				if ( dist > farthestDist )
				{
					farthestDist = dist;
					farthestCell = pos;
				}
			}
			sampled++;
		}

		if ( farthestCell is null )
			return false;

		location = GridManager.GridToWorldPosition( farthestCell.Value ) + GridManager.CentreOffset;
		return true;
	}

	private static bool IsRoamable( Vector3Int gridPos )
	{
		var cell = GridManager.Instance?.GetCell( new Vector2Int( gridPos.x, gridPos.y ) );
		if ( cell == null ) return false;

		if ( cell.HasComponent<RideEntranceExit>() ) return false;
		if ( cell.HasComponent<Queue>( q => q.Ride.IsValid() ) ) return false;

		return true;
	}

	public override ActionDisplayInfo? GetDisplay()
	{
		return new ActionDisplayInfo(
			"explore_nearby",
			"Wandering around", 0
		);
	}
}