using Theriapolis.Core.Entities;
using Theriapolis.Core.Util;

namespace Theriapolis.Core.Rules.Combat;

/// <summary>
/// Per-tick check used by <see cref="Game.Screens.PlayScreen"/>:
/// "is there a hostile NPC within encounter trigger range that has line of
/// sight?" Returns the closest qualifying actor (or null) so the caller can
/// kick off an encounter.
///
/// Friendly / Neutral proximity is the same shape but uses a tighter radius
/// — see <see cref="FindInteractCandidate"/>.
/// </summary>
public static class EncounterTrigger
{
    /// <summary>
    /// Find the closest live <em>hostile</em> NPC within
    /// <see cref="C.ENCOUNTER_TRIGGER_TILES"/> of the player that the
    /// <paramref name="losBlocked"/> predicate can see (no blocking tile
    /// between). Returns null if none found.
    /// </summary>
    public static NpcActor? FindHostileTrigger(
        ActorManager actors,
        System.Func<int, int, bool>? losBlocked = null)
    {
        var player = actors.Player;
        if (player is null) return null;

        var blocked = losBlocked ?? LineOfSight.AlwaysClear;
        NpcActor? best = null;
        int bestDistSq = C.ENCOUNTER_TRIGGER_TILES * C.ENCOUNTER_TRIGGER_TILES;

        foreach (var npc in actors.Npcs)
        {
            if (!npc.IsAlive) continue;
            if (npc.Allegiance != Rules.Character.Allegiance.Hostile) continue;
            int distSq = ChebyshevDistSq(player.Position, npc.Position);
            if (distSq > bestDistSq) continue;
            if (!LineOfSight.HasLine(player.Position, npc.Position, blocked)) continue;
            if (best is null || distSq < bestDistSq) { best = npc; bestDistSq = distSq; }
        }
        return best;
    }

    /// <summary>
    /// Friendly / Neutral NPCs within
    /// <see cref="C.INTERACT_PROMPT_TILES"/> of the player. The HUD shows
    /// "[F] Talk to ..." for the closest match.
    /// </summary>
    public static NpcActor? FindInteractCandidate(
        ActorManager actors,
        System.Func<int, int, bool>? losBlocked = null)
    {
        var player = actors.Player;
        if (player is null) return null;

        var blocked = losBlocked ?? LineOfSight.AlwaysClear;
        NpcActor? best = null;
        int bestDistSq = C.INTERACT_PROMPT_TILES * C.INTERACT_PROMPT_TILES;

        foreach (var npc in actors.Npcs)
        {
            if (!npc.IsAlive) continue;
            if (npc.Allegiance != Rules.Character.Allegiance.Friendly &&
                npc.Allegiance != Rules.Character.Allegiance.Neutral) continue;
            int distSq = ChebyshevDistSq(player.Position, npc.Position);
            if (distSq > bestDistSq) continue;
            if (!LineOfSight.HasLine(player.Position, npc.Position, blocked)) continue;
            if (best is null || distSq < bestDistSq) { best = npc; bestDistSq = distSq; }
        }
        return best;
    }

    private static int ChebyshevDistSq(Vec2 a, Vec2 b)
    {
        int dx = (int)System.Math.Abs(a.X - b.X);
        int dy = (int)System.Math.Abs(a.Y - b.Y);
        int d  = System.Math.Max(dx, dy);
        return d * d;
    }
}
