using Theriapolis.Core.Rules.Stats;
using Theriapolis.Core.Util;

namespace Theriapolis.Core.Rules.Combat;

/// <summary>
/// Size-aware spatial helpers for combat. Combatants occupy
/// <see cref="Stats.SizeExtensions.FootprintTiles"/>² tactical tiles
/// anchored at their integer <see cref="Combatant.Position"/>; this helper
/// computes edge-to-edge Chebyshev distance and reach predicates.
/// </summary>
public static class ReachAndCover
{
    /// <summary>
    /// Edge-to-edge Chebyshev distance — number of empty tiles between two
    /// footprints. Adjacent (sharing an edge or corner) returns 0; one
    /// empty tile between returns 1; overlapping returns 0.
    /// </summary>
    public static int EdgeToEdgeChebyshev(Combatant a, Combatant b)
    {
        int aSize = a.Size.FootprintTiles();
        int bSize = b.Size.FootprintTiles();
        int aMinX = (int)System.Math.Floor(a.Position.X);
        int aMinY = (int)System.Math.Floor(a.Position.Y);
        int aMaxX = aMinX + aSize - 1;
        int aMaxY = aMinY + aSize - 1;
        int bMinX = (int)System.Math.Floor(b.Position.X);
        int bMinY = (int)System.Math.Floor(b.Position.Y);
        int bMaxX = bMinX + bSize - 1;
        int bMaxY = bMinY + bSize - 1;

        // Per-axis gap: positive = number of tile-steps to bring edges to
        // touching (then -1 because touching = 0 empty tiles between).
        int dx = System.Math.Max(0, System.Math.Max(aMinX - bMaxX, bMinX - aMaxX) - 1);
        int dy = System.Math.Max(0, System.Math.Max(aMinY - bMaxY, bMinY - aMaxY) - 1);
        return System.Math.Max(dx, dy);
    }

    /// <summary>True if <paramref name="defender"/> is within the attack's reach (melee) or short range (ranged).</summary>
    public static bool IsInReach(Combatant attacker, Combatant defender, AttackOption attack)
    {
        int dist = EdgeToEdgeChebyshev(attacker, defender);
        if (attack.IsRanged)
            return dist <= attack.RangeLongTiles;
        return dist <= attack.ReachTiles;
    }

    /// <summary>True if the defender sits past short range (disadvantage on the attack).</summary>
    public static bool IsLongRange(Combatant attacker, Combatant defender, AttackOption attack)
    {
        if (!attack.IsRanged) return false;
        int dist = EdgeToEdgeChebyshev(attacker, defender);
        return dist > attack.RangeShortTiles && dist <= attack.RangeLongTiles;
    }

    /// <summary>
    /// One step of greedy movement toward <paramref name="goal"/>. Returns
    /// the new position one tile closer in 8-connected (Chebyshev) space.
    /// Movement budget is ignored — the caller is responsible for charging it.
    /// </summary>
    public static Vec2 StepToward(Vec2 from, Vec2 goal)
    {
        int dx = System.Math.Sign(goal.X - from.X);
        int dy = System.Math.Sign(goal.Y - from.Y);
        return new Vec2((int)from.X + dx, (int)from.Y + dy);
    }
}
