using Theriapolis.Core.Data;
using Theriapolis.Core.Items;
using Theriapolis.Core.Rules.Character;
using Theriapolis.Core.Rules.Stats;

namespace Theriapolis.Core.Persistence;

/// <summary>
/// Converts between the live <see cref="Character"/> model and its flat
/// serializable snapshot <see cref="PlayerCharacterState"/>. Restore needs a
/// <see cref="ContentResolver"/> so it can re-attach to immutable defs by id.
/// </summary>
public static class CharacterCodec
{
    public static PlayerCharacterState Capture(Character c)
    {
        var state = new PlayerCharacterState
        {
            CladeId         = c.Clade.Id,
            SpeciesId       = c.Species.Id,
            ClassId         = c.ClassDef.Id,
            BackgroundId    = c.Background.Id,
            STR             = c.Abilities.STR,
            DEX             = c.Abilities.DEX,
            CON             = c.Abilities.CON,
            INT             = c.Abilities.INT,
            WIS             = c.Abilities.WIS,
            CHA             = c.Abilities.CHA,
            Level           = c.Level,
            Xp              = c.Xp,
            MaxHp           = c.MaxHp,
            CurrentHp       = c.CurrentHp,
            ExhaustionLevel = c.ExhaustionLevel,
            FightingStyle     = c.FightingStyle,
            RageUsesRemaining = c.RageUsesRemaining,
            CurrencyFang      = c.CurrencyFang,
            SubclassId        = c.SubclassId,
            LearnedFeatureIds = c.LearnedFeatureIds.ToArray(),
            LevelUpHistory    = c.LevelUpHistory.Select(h => new LevelUpRecordState
            {
                Level            = h.Level,
                HpGained         = h.HpGained,
                HpWasAveraged    = h.HpWasAveraged,
                HpHitDieResult   = h.HpHitDieResult,
                SubclassChosen   = h.SubclassChosen ?? "",
                AsiKeys          = h.AsiAdjustmentsKeys,
                AsiValues        = h.AsiAdjustmentsValues,
                FeaturesUnlocked = h.FeaturesUnlocked,
            }).ToArray(),
            // Phase 6.5 M4 — hybrid state. Null for purebred PCs.
            // Phase 6.5 M5 adds ActiveMaskTier.
            Hybrid = c.Hybrid is null ? null : new HybridStateSnapshot
            {
                SireClade      = c.Hybrid.SireClade,
                SireSpecies    = c.Hybrid.SireSpecies,
                DamClade       = c.Hybrid.DamClade,
                DamSpecies     = c.Hybrid.DamSpecies,
                DominantParent = (byte)c.Hybrid.DominantParent,
                PassingActive  = c.Hybrid.PassingActive,
                NpcsWhoKnow    = c.Hybrid.NpcsWhoKnow.ToArray(),
                ActiveMaskTier = (byte)c.Hybrid.ActiveMaskTier,
            },
        };

        var skills = new byte[c.SkillProficiencies.Count];
        int i = 0;
        foreach (var s in c.SkillProficiencies) skills[i++] = (byte)s;
        state.SkillProficiencies = skills;

        var conds = new byte[c.Conditions.Count];
        i = 0;
        foreach (var x in c.Conditions) conds[i++] = (byte)x;
        state.Conditions = conds;

        var inv = new InventoryItemState[c.Inventory.Items.Count];
        for (int k = 0; k < c.Inventory.Items.Count; k++)
        {
            var it = c.Inventory.Items[k];
            inv[k] = new InventoryItemState
            {
                ItemId     = it.Def.Id,
                Qty        = it.Qty,
                Condition  = it.Condition,
                EquippedAt = it.EquippedAt is { } slot ? (byte)slot : null,
            };
        }
        state.Inventory = inv;

        return state;
    }

    public static Character Restore(PlayerCharacterState state, ContentResolver content)
    {
        if (!content.Clades.TryGetValue(state.CladeId, out var clade))
            throw new InvalidDataException($"Save references unknown clade '{state.CladeId}'.");
        if (!content.Species.TryGetValue(state.SpeciesId, out var species))
            throw new InvalidDataException($"Save references unknown species '{state.SpeciesId}'.");
        if (!content.Classes.TryGetValue(state.ClassId, out var classDef))
            throw new InvalidDataException($"Save references unknown class '{state.ClassId}'.");
        if (!content.Backgrounds.TryGetValue(state.BackgroundId, out var bg))
            throw new InvalidDataException($"Save references unknown background '{state.BackgroundId}'.");

        var abilities = new AbilityScores(state.STR, state.DEX, state.CON, state.INT, state.WIS, state.CHA);
        var c = new Character(clade, species, classDef, bg, abilities)
        {
            Level           = state.Level,
            Xp              = state.Xp,
            MaxHp           = state.MaxHp,
            CurrentHp       = state.CurrentHp,
            ExhaustionLevel = state.ExhaustionLevel,
            FightingStyle     = state.FightingStyle,
            RageUsesRemaining = state.RageUsesRemaining,
            CurrencyFang      = state.CurrencyFang,
            SubclassId        = state.SubclassId ?? "",
        };

        // Phase 6.5 M0 — restore learned features + level-up history.
        if (state.LearnedFeatureIds is not null)
            foreach (var fid in state.LearnedFeatureIds)
                c.LearnedFeatureIds.Add(fid);

        if (state.LevelUpHistory is not null)
        {
            foreach (var h in state.LevelUpHistory)
            {
                c.LevelUpHistory.Add(new LevelUpRecord
                {
                    Level                = h.Level,
                    HpGained             = h.HpGained,
                    HpWasAveraged        = h.HpWasAveraged,
                    HpHitDieResult       = h.HpHitDieResult,
                    SubclassChosen       = string.IsNullOrEmpty(h.SubclassChosen) ? null : h.SubclassChosen,
                    AsiAdjustmentsKeys   = h.AsiKeys ?? Array.Empty<byte>(),
                    AsiAdjustmentsValues = h.AsiValues ?? Array.Empty<int>(),
                    FeaturesUnlocked     = h.FeaturesUnlocked ?? Array.Empty<string>(),
                });
            }
        }

        // Phase 6.5 M4 — restore hybrid state when present.
        if (state.Hybrid is not null)
        {
            c.Hybrid = new HybridState
            {
                SireClade      = state.Hybrid.SireClade,
                SireSpecies    = state.Hybrid.SireSpecies,
                DamClade       = state.Hybrid.DamClade,
                DamSpecies     = state.Hybrid.DamSpecies,
                DominantParent = (ParentLineage)state.Hybrid.DominantParent,
                PassingActive  = state.Hybrid.PassingActive,
                ActiveMaskTier = (ScentMaskTier)state.Hybrid.ActiveMaskTier,
            };
            foreach (int npcId in state.Hybrid.NpcsWhoKnow ?? Array.Empty<int>())
                c.Hybrid.NpcsWhoKnow.Add(npcId);
        }

        foreach (var s in state.SkillProficiencies) c.SkillProficiencies.Add((SkillId)s);
        foreach (var x in state.Conditions)         c.Conditions.Add((Condition)x);

        foreach (var it in state.Inventory)
        {
            if (!content.Items.TryGetValue(it.ItemId, out var def))
                throw new InvalidDataException($"Save references unknown item '{it.ItemId}'.");
            var inst = c.Inventory.Add(def, it.Qty);
            inst.Condition = it.Condition;
            if (it.EquippedAt is { } slotByte)
            {
                var slot = (EquipSlot)slotByte;
                if (!c.Inventory.TryEquip(inst, slot, out var err))
                    throw new InvalidDataException($"Could not re-equip '{it.ItemId}' into {slot}: {err}");
            }
        }

        return c;
    }
}
