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

namespace Theriapolis.Core.Rules.Reputation;

/// <summary>
/// Phase 6 M2 — combines the three reputation layers into a single
/// integer disposition score per <c>reputation.md §I-4</c>:
///
///   EffectiveDisposition(npc, pc) =
///       CladeBiasFor(npc.BiasProfile, pc.Clade, sizeDiff(npc, pc))
///     + FactionWeightedSum(npc.Faction, pc.FactionStandings)
///     + PersonalDisposition(npc.Id)
///
/// Computed lazily — the inputs change too often (faction propagation,
/// time decay) to justify caching. Computation is O(1).
/// </summary>
public static class EffectiveDisposition
{
    /// <summary>
    /// Final blended score (clamped <c>±C.REP_MAX</c>) for how
    /// <paramref name="npc"/> currently feels about
    /// <paramref name="pc"/>.
    /// </summary>
    public static int For(
        NpcActor npc,
        Rules.Character.Character pc,
        PlayerReputation rep,
        ContentResolver content,
        WorldState? world = null,
        ulong worldSeed = 0)
    {
        return Breakdown(npc, pc, rep, content, world, worldSeed).Total;
    }

    /// <summary>
    /// Componentised view used by the disposition tooltip + reputation
    /// screen. Each field carries the contribution of one layer so the
    /// UI can answer "why does so-and-so hate me?" without re-deriving.
    /// </summary>
    public static EffectiveDispositionBreakdown Breakdown(
        NpcActor npc,
        Rules.Character.Character pc,
        PlayerReputation rep,
        ContentResolver content,
        WorldState? world = null,
        ulong worldSeed = 0)
    {
        int cladeBias  = ResolveCladeBias(npc, pc, content);
        int sizeBias   = SizeDifferentialModifier(npc, pc, content);
        int factionMod = ResolveFactionMod(npc, rep, content, world, worldSeed);
        int personal   = ResolvePersonal(npc, rep);

        int total = System.Math.Clamp(cladeBias + sizeBias + factionMod + personal,
                                      C.REP_MIN, C.REP_MAX);

        return new EffectiveDispositionBreakdown(
            cladeBias, sizeBias, factionMod, personal, total,
            DispositionLabels.For(total));
    }

    // ── Layer 1 — Clade bias ──────────────────────────────────────────────

    private static int ResolveCladeBias(NpcActor npc, Rules.Character.Character pc, ContentResolver content)
    {
        if (string.IsNullOrEmpty(npc.BiasProfileId)) return 0;
        if (!content.BiasProfiles.TryGetValue(npc.BiasProfileId, out var profile)) return 0;

        // Phase 6.5 M5 — hybrid bias layering. When the PC is hybrid AND
        // this NPC has personally detected the hybrid status (memory tag
        // "knows_hybrid"), the profile's HybridBias modifier is added to
        // the clade-bias. Pre-detection, the PC reads as their presenting
        // (dominant) clade and HybridBias is *not* applied.
        int bias = profile.CladeBias.TryGetValue(pc.Clade.Id, out int b) ? b : 0;
        if (pc.IsHybrid && NpcKnowsPlayerIsHybrid(npc, pc))
            bias += profile.HybridBias;
        return bias;
    }

    /// <summary>
    /// Phase 6.5 M5 — true if the NPC's <see cref="PersonalDisposition.Memory"/>
    /// contains the <c>"knows_hybrid"</c> flag (set by
    /// <see cref="Rules.Character.PassingCheck"/> on a successful detection).
    /// Falls back to the PC-side <see cref="Rules.Character.HybridState.NpcsWhoKnow"/>
    /// list when the NPC has no personal-disposition record yet (which can
    /// happen for casual encounters).
    /// </summary>
    public static bool NpcKnowsPlayerIsHybrid(NpcActor npc, Rules.Character.Character pc)
    {
        if (pc.Hybrid is null) return false;
        if (pc.Hybrid.NpcsWhoKnow.Contains(npc.Id)) return true;
        // The NPC's PersonalDisposition lives on the player-rep dictionary;
        // this call site doesn't have access. The PC-side NpcsWhoKnow set
        // is the authoritative mirror written by PassingCheck after every
        // detection — sufficient for the disposition layer to consult.
        return false;
    }

    private static int SizeDifferentialModifier(NpcActor npc, Rules.Character.Character pc, ContentResolver content)
    {
        // Size index: Small=1, Medium=2, MediumLarge=3, Large=4. Differential is pc.size − npc.size.
        if (npc.Resident is null) return 0;
        if (string.IsNullOrEmpty(npc.Resident.Species)) return 0;
        if (!content.Species.TryGetValue(npc.Resident.Species, out var npcSpecies)) return 0;

        int npcIdx = SizeIndex(SizeExtensions.FromJson(npcSpecies.Size));
        int pcIdx  = SizeIndex(pc.Size);
        int diff   = pcIdx - npcIdx;
        // Per reputation.md §I-1 size differential table.
        return diff switch
        {
             0 =>  0,
             1 => -3,
             2 => -8,
             3 => -8,
            -1 =>  2,
            -2 =>  5,
            _  =>  5,
        };
    }

    private static int SizeIndex(SizeCategory s) => s switch
    {
        SizeCategory.Tiny        => 0,
        SizeCategory.Small       => 1,
        SizeCategory.Medium      => 2,
        SizeCategory.MediumLarge => 3,
        SizeCategory.Large       => 4,
        SizeCategory.Huge        => 5,
        _                         => 2,
    };

    // ── Layer 2 — Faction modifier ────────────────────────────────────────

    /// <summary>
    /// Derived modifier from the player's faction standings, weighted by
    /// how much *this NPC* cares about each faction.
    ///
    /// Phase 6 M5: when the NPC has a <see cref="NpcActor.HomeSettlementId"/>
    /// AND a non-null <paramref name="world"/> is supplied, the local
    /// (post-propagation, post-decay) standing in their settlement is used
    /// instead of the global standing. Otherwise falls back to the M2
    /// global lookup.
    ///
    /// Bias-profile <c>faction_affinity</c> hints layer on top — a Covenant
    /// Faithful amplifies their Enforcer alignment even if not formally
    /// affiliated.
    /// </summary>
    private static int ResolveFactionMod(
        NpcActor npc, PlayerReputation rep, ContentResolver content,
        WorldState? world, ulong worldSeed)
    {
        float total = 0f;

        // Resolve the NPC's home settlement (if any) for local-standing lookups.
        Settlement? home = null;
        if (world is not null && npc.HomeSettlementId is { } hid)
        {
            foreach (var s in world.Settlements)
                if (s.Id == hid) { home = s; break; }
        }

        // Half-magnitude weight for the NPC's own affiliation.
        if (!string.IsNullOrEmpty(npc.FactionId))
        {
            int standing = home is not null
                ? RepPropagation.LocalStandingFor(npc.FactionId, home, worldSeed, rep.Ledger, content.Factions)
                : rep.Factions.Get(npc.FactionId);
            total += standing * 0.5f;
        }

        // Bias-profile faction-affinity layering: Covenant Faithful npcs
        // care about the Enforcers' standing even if not affiliated.
        if (!string.IsNullOrEmpty(npc.BiasProfileId)
            && content.BiasProfiles.TryGetValue(npc.BiasProfileId, out var profile))
        {
            foreach (var (factionId, affinity) in profile.FactionAffinity)
            {
                int standing = home is not null
                    ? RepPropagation.LocalStandingFor(factionId, home, worldSeed, rep.Ledger, content.Factions)
                    : rep.Factions.Get(factionId);
                // Smaller weight than direct affiliation (×0.25) so the bias
                // profile colours rather than dominates.
                total += standing * (affinity / 100f) * 0.25f;
            }
        }

        return (int)System.Math.Round(total);
    }

    // ── Layer 3 — Personal disposition ────────────────────────────────────

    private static int ResolvePersonal(NpcActor npc, PlayerReputation rep)
    {
        if (string.IsNullOrEmpty(npc.RoleTag)) return 0;
        return rep.Personal.TryGetValue(npc.RoleTag, out var p) ? p.Score : 0;
    }
}

/// <summary>Component view of an <see cref="EffectiveDisposition"/> result.</summary>
public readonly record struct EffectiveDispositionBreakdown(
    int CladeBias,
    int SizeDifferential,
    int FactionModifier,
    int Personal,
    int Total,
    DispositionLabel Label);
