namespace Sandbox;

public struct PhysicsTraceResult
{
	/// <summary>
	/// Whether the trace hit something or not
	/// </summary>
	public bool Hit;

	/// <summary>
	/// Whether the trace started in a solid
	/// </summary>
	public bool StartedSolid;

	/// <summary>
	/// The start position of the trace
	/// </summary>
	public Vector3 StartPosition;

	/// <summary>
	/// The end or hit position of the trace
	/// </summary>
	public Vector3 EndPosition;

	/// <summary>
	/// The hit position of the trace
	/// </summary>
	public Vector3 HitPosition;

	/// <summary>
	/// The hit surface normal (direction vector)
	/// </summary>
	public Vector3 Normal;

	/// <summary>
	/// A fraction [0..1] of where the trace hit between the start and the original end positions
	/// </summary>
	public float Fraction;

	/// <summary>
	/// The physics object that was hit, if any
	/// </summary>
	public PhysicsBody Body;

	/// <summary>
	/// The physics shape that was hit, if any
	/// </summary>
	public PhysicsShape Shape;

	/// <summary>
	/// The physical properties of the hit surface
	/// </summary>
	public Surface Surface;

	/// <summary>
	/// The id of the hit bone (either from hitbox or physics shape)
	/// </summary>
	public readonly int Bone => Shape.IsValid() ? Shape.BoneIndex : -1;

	/// <summary>
	/// The direction of the trace ray
	/// </summary>
	public Vector3 Direction;

	/// <summary>
	/// The triangle index hit, if we hit a mesh <see cref="PhysicsShape">physics shape</see>
	/// </summary>
	public int Triangle;

	/// <summary>
	/// The tags that the hit shape had
	/// </summary>
	public string[] Tags;

	/// <summary>
	/// The distance between start and end positions.
	/// </summary>
	public readonly float Distance => Vector3.DistanceBetween( StartPosition, EndPosition );

	[ThreadStatic] internal static HashSet<string> tagBuilder;

	internal PhysicsTrace.Request.Shape StartShape;

	internal unsafe static PhysicsTraceResult From( in PhysicsTrace.Result result, in PhysicsTrace.Request.Shape shape )
	{
		tagBuilder ??= new();
		tagBuilder.Clear();

		//
		// I don't love this shit, for the amount of churn it's going to create.
		// In the future I suspect that we either copy all the tags id's raw and offer
		// things like HasTag for efficient access.. or we keep it like this and just
		// have something on the request like Trace.IncludeTagsInResult() to get them all.
		//
		for ( int i = 0; i < 16; i++ )
		{
			if ( result.Tags[i] == 0 ) break;

			var t = StringToken.GetValue( result.Tags[i] ); // sadface
			if ( t != null )
			{
				tagBuilder.Add( t.ToLowerInvariant() );
			}
		}

		var direction = Vector3.Direction( result.StartPos, result.EndPos );

		return new PhysicsTraceResult
		{
			Hit = result.Fraction < 1,
			StartedSolid = result.StartedInSolid != 0,
			StartPosition = result.StartPos,
			EndPosition = result.EndPos,
			HitPosition = result.HitPos,
			Normal = result.Normal,
			Fraction = result.Fraction,
			Direction = direction,
			Triangle = result.TriangleIndex,
			Tags = tagBuilder.Count > 0 ? tagBuilder.ToArray() : Array.Empty<string>(), // sadface

			// TODO - maybe we populate these on access?
			Surface = Surface.FindByIndex( result.SurfaceProperty ),
			Body = HandleIndex.Get<PhysicsBody>( result.PhysicsBodyHandle )?.SelfOrParent,
			Shape = HandleIndex.Get<PhysicsShape>( result.PhysicsShapeHandle ),

			StartShape = shape
		};
	}
}
