using Theriapolis.Core.Rules.Combat;

namespace Theriapolis.Core.Entities.Ai;

/// <summary>
/// Read-only view of the world the AI behavior consults during a turn.
/// Wraps the live <see cref="Encounter"/> + a sight predicate so behaviors
/// stay testable in isolation (M5 unit tests pass an always-clear predicate;
/// the live game wires in the tactical-tile sampler).
///
/// Behaviors call into the resolver to actually mutate the encounter; this
/// context is purely for reading the situation.
/// </summary>
public sealed class AiContext
{
    public Encounter Encounter { get; }

    /// <summary>Returns true if the tile at (tx, ty) blocks line-of-sight.</summary>
    public System.Func<int, int, bool> IsLosBlocked { get; }

    public AiContext(Encounter encounter, System.Func<int, int, bool>? isLosBlocked = null)
    {
        Encounter    = encounter;
        IsLosBlocked = isLosBlocked ?? LineOfSight.AlwaysClear;
    }

    /// <summary>Find the closest hostile combatant to the given actor, or null if none.</summary>
    public Combatant? FindClosestHostile(Combatant self)
    {
        Combatant? best = null;
        int bestDist = int.MaxValue;
        foreach (var c in Encounter.Participants)
        {
            if (c.Id == self.Id) continue;
            if (c.IsDown) continue;
            if (!IsHostileTo(self.Allegiance, c.Allegiance)) continue;
            int d = ReachAndCover.EdgeToEdgeChebyshev(self, c);
            if (d < bestDist) { best = c; bestDist = d; }
        }
        return best;
    }

    /// <summary>
    /// Phase 6.5 M1 — find the closest *ally* to the given actor (excludes
    /// self). Allies share the player-side allegiance: Player or Allied.
    /// Returns null when none qualify (which is the typical M1 case — the
    /// player is alone — and disables ally-targeted features cleanly).
    /// </summary>
    public Combatant? FindClosestAlly(Combatant self)
    {
        Combatant? best = null;
        int bestDist = int.MaxValue;
        bool selfPlayerSide = self.Allegiance == Rules.Character.Allegiance.Player
                           || self.Allegiance == Rules.Character.Allegiance.Allied;
        if (!selfPlayerSide) return null;
        foreach (var c in Encounter.Participants)
        {
            if (c.Id == self.Id) continue;
            if (c.IsDown) continue;
            bool cPlayerSide = c.Allegiance == Rules.Character.Allegiance.Player
                            || c.Allegiance == Rules.Character.Allegiance.Allied;
            if (!cPlayerSide) continue;
            int d = ReachAndCover.EdgeToEdgeChebyshev(self, c);
            if (d < bestDist) { best = c; bestDist = d; }
        }
        return best;
    }

    /// <summary>
    /// Phase 6.5 M1 — find the lowest-HP friendly target (self or any
    /// ally), preferring the most damaged. Returns null only when every
    /// friendly is at full HP. Used as the auto-target for healing
    /// features when the player presses the heal hotkey without picking
    /// explicitly.
    /// </summary>
    public Combatant? FindMostDamagedFriendly(Combatant self)
    {
        Combatant? best = null;
        int bestDeficit = -1;
        bool selfPlayerSide = self.Allegiance == Rules.Character.Allegiance.Player
                           || self.Allegiance == Rules.Character.Allegiance.Allied;
        if (!selfPlayerSide) return null;
        foreach (var c in Encounter.Participants)
        {
            if (c.IsDown) continue;
            bool cPlayerSide = c.Allegiance == Rules.Character.Allegiance.Player
                            || c.Allegiance == Rules.Character.Allegiance.Allied;
            if (!cPlayerSide) continue;
            int deficit = c.MaxHp - c.CurrentHp;
            if (deficit > bestDeficit) { best = c; bestDeficit = deficit; }
        }
        return bestDeficit > 0 ? best : null;
    }

    /// <summary>True if the two allegiances should target each other in combat.</summary>
    public static bool IsHostileTo(
        Rules.Character.Allegiance a,
        Rules.Character.Allegiance b)
    {
        bool aIsPlayerSide = a == Rules.Character.Allegiance.Player || a == Rules.Character.Allegiance.Allied;
        bool bIsPlayerSide = b == Rules.Character.Allegiance.Player || b == Rules.Character.Allegiance.Allied;
        if (aIsPlayerSide && b == Rules.Character.Allegiance.Hostile) return true;
        if (bIsPlayerSide && a == Rules.Character.Allegiance.Hostile) return true;
        return false;
    }
}
