using Theriapolis.Core.Data;
using Theriapolis.Core.Entities;
using Theriapolis.Core.Rules.Character;
using Theriapolis.Core.World;

namespace Theriapolis.Core.Rules.Reputation;

/// <summary>
/// Phase 6 M5 — faction-driven NPC allegiance flips. Per the plan §4.6:
///
///   Patrol aggression: a friendly/neutral NPC with a faction id flips
///   their <see cref="Actor.Allegiance"/> to <see cref="Allegiance.Hostile"/>
///   when the player's local standing with that faction crosses the
///   <see cref="DispositionLabel.Hostile"/> threshold (≤ -51).
///
/// Sticky once Hostile: the flip doesn't bounce back if standing
/// recovers mid-tick — only on chunk re-stream (NPC despawns + reloads
/// fresh from template). This avoids flickering allegiance between
/// frames and matches CRPG convention ("you killed a brigand who saw
/// you stab a guard last week — they remember").
/// </summary>
public static class FactionAggression
{
    /// <summary>
    /// Walk every faction-affiliated NPC. Flip non-hostile ones to
    /// Hostile when the player's local standing with their faction
    /// crosses the HOSTILE threshold. Returns the number of NPCs flipped
    /// this tick.
    ///
    /// Patrol-aggro reads faction standing directly rather than through
    /// the disposition lens — a constable doesn't care about your clade
    /// or your personal history with them; they care that their faction
    /// says you're wanted.
    /// </summary>
    public static int UpdateAllegiances(
        ActorManager actors,
        Rules.Character.Character pc,
        PlayerReputation rep,
        ContentResolver content,
        WorldState world,
        ulong worldSeed)
    {
        if (pc is null) return 0;
        int flipped = 0;
        foreach (var npc in actors.Npcs)
        {
            if (!npc.IsAlive) continue;
            if (npc.Allegiance == Allegiance.Hostile) continue;
            if (npc.Allegiance == Allegiance.Player) continue;

            // Phase 6.5 M7 — sticky betrayal aggro fires unconditionally,
            // independent of faction id (it could be a betrayed lone wolf).
            if (npc.PermanentAggroAfterBetrayal)
            {
                npc.Allegiance = Allegiance.Hostile;
                flipped++;
                continue;
            }

            if (string.IsNullOrEmpty(npc.FactionId)) continue;

            int factionStanding = ResolveFactionStanding(npc, rep, content, world, worldSeed);
            if (factionStanding <= C.REP_HOSTILE_THRESHOLD)
            {
                npc.Allegiance = Allegiance.Hostile;
                flipped++;
            }
        }
        return flipped;
    }

    /// <summary>
    /// Local faction standing as perceived by this NPC's home settlement
    /// (post-propagation), or the global standing if no home is set.
    /// </summary>
    private static int ResolveFactionStanding(
        NpcActor npc, PlayerReputation rep, ContentResolver content,
        WorldState world, ulong worldSeed)
    {
        if (npc.HomeSettlementId is { } hid)
        {
            foreach (var s in world.Settlements)
                if (s.Id == hid)
                    return RepPropagation.LocalStandingFor(npc.FactionId, s, worldSeed,
                        rep.Ledger, content.Factions);
        }
        return rep.Factions.Get(npc.FactionId);
    }
}
