namespace NeonSprawl.Server.Game.PositionState;

/// <summary>Pure movement validation for <see cref="MoveCommandRequest"/> targets (NS-19).</summary>
/// <remarks>
/// <para><b>Precedence</b> when multiple rules fail: <see cref="MoveCommandReasonCodes.HorizontalStepExceeded"/>,
/// then <see cref="MoveCommandReasonCodes.VerticalStepExceeded"/>, then <see cref="MoveCommandReasonCodes.OutOfBounds"/>.</para>
/// <para>Vertical uses <c>abs(ΔY)</c>, so <b>descending</b> (target Y lower than current) is allowed when the
/// drop is within <see cref="MovementValidationOptions.MaxVerticalStep"/> — same as climbing.</para>
/// </remarks>
public static class MoveCommandValidation
{
    /// <summary>Returns false and sets <paramref name="reasonCode"/> when the move must be rejected.</summary>
    public static bool TryValidate(
        in PositionSnapshot from,
        PositionVector to,
        MovementValidationOptions rules,
        out string reasonCode)
    {
        reasonCode = "";

        if (rules.HorizontalStepEnabled)
        {
            var dx = to.X - from.X;
            var dz = to.Z - from.Z;
            var horizontal = Math.Sqrt(dx * dx + dz * dz);
            if (horizontal > rules.MaxHorizontalStep)
            {
                reasonCode = MoveCommandReasonCodes.HorizontalStepExceeded;
                return false;
            }
        }

        var dy = Math.Abs(to.Y - from.Y);
        if (dy > rules.MaxVerticalStep)
        {
            reasonCode = MoveCommandReasonCodes.VerticalStepExceeded;
            return false;
        }

        if (rules.DistrictBoundsEnabled)
        {
            if (to.X < rules.DistrictMinX || to.X > rules.DistrictMaxX ||
                to.Y < rules.DistrictMinY || to.Y > rules.DistrictMaxY ||
                to.Z < rules.DistrictMinZ || to.Z > rules.DistrictMaxZ)
            {
                reasonCode = MoveCommandReasonCodes.OutOfBounds;
                return false;
            }
        }

        return true;
    }
}
