using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Myra.Graphics2D;
using Myra.Graphics2D.Brushes;
using Myra.Graphics2D.UI;
using Theriapolis.Core;
using Theriapolis.Core.Data;
using Theriapolis.Core.Rules.Character;
using Theriapolis.Core.Rules.Stats;
using Theriapolis.Core.Util;
using Theriapolis.Game.UI;

namespace Theriapolis.Game.Screens;

/// <summary>
/// Phase 5 M5 character-creation wizard. 7-step illuminated-codex flow per
/// the Claude Design handoff (`_design_handoff/character_creation/`):
/// Clade → Species → Calling → History → Abilities → Skills → Sign.
///
/// The right-hand aside summarises the character as it builds; an aborted
/// run from the back button returns to the title without committing. The
/// final Confirm button calls <see cref="CharacterBuilder.Build"/> with the
/// resolver's items table so the new character arrives with their starting
/// kit equipped.
///
/// Differences from the React design:
///   - Drag-and-drop stat assignment → click-pick-then-click-place, since
///     Myra doesn't ship native drag-drop. The pool highlights the selected
///     value; the next ability slot click consumes it. Click a filled slot
///     to return its value to the pool.
///   - Hover popovers with full trait descriptions → "Selected" detail line
///     at the bottom of the aside panel that updates on click.
///   - Illuminated-codex visual styling → semi-transparent dark panel with
///     Myra's default fonts. Full art-direction port (parchment background,
///     gilded accents, serif display fonts) is M6+ theme work.
/// </summary>
public sealed class CharacterCreationScreen : IScreen
{
    private readonly ulong _seed;
    private Game1 _game = null!;
    private Desktop _desktop = null!;
    private VerticalStackPanel _root = null!;

    // Loaded content
    private ContentResolver _content = null!;
    private CladeDef[]      _clades      = null!;
    private SpeciesDef[]    _allSpecies  = null!;
    private ClassDef[]      _classes     = null!;
    private BackgroundDef[] _backgrounds = null!;

    // Wizard state
    private int _step;
    private CladeDef?      _clade;
    private SpeciesDef?    _species;
    private ClassDef?      _class;
    private BackgroundDef? _background;
    private string _name = "Wanderer";

    // Stat assignment state
    private bool _useRoll;
    private readonly List<int> _statPool = new();
    private readonly Dictionary<AbilityId, int> _statAssign = new();
    private readonly List<int[]> _statHistory = new();
    private int? _pendingPoolIdx;  // index in _statPool of currently-selected value (click-pick-place)

    // Skill state
    private readonly HashSet<SkillId> _chosenSkills = new();

    // Stat-roll seeding (per the Phase 5 plan §4.2 / DESIGN_INTENT lock).
    private readonly long _gameStartMs;
    private long _msAtScreenOpen;

    // Detail panel (replaces hover popovers) — last clicked trait/skill/feature.
    private string _detailTitle = "";
    private string _detailBody  = "";

    private static readonly string[] StepNames = new[]
    {
        "Clade", "Species", "Calling", "History", "Abilities", "Skills", "Sign",
    };

    public CharacterCreationScreen(ulong seed)
    {
        _seed        = seed;
        _gameStartMs = System.Environment.TickCount64;
    }

    public void Initialize(Game1 game)
    {
        _game = game;
        _msAtScreenOpen = System.Environment.TickCount64 - _gameStartMs;

        var loader = new ContentLoader(_game.ContentDataDirectory);
        _content     = new ContentResolver(loader);
        _clades      = _content.Clades.Values.OrderBy(c => c.Id).ToArray();
        _allSpecies  = _content.Species.Values.OrderBy(s => s.Id).ToArray();
        _classes     = _content.Classes.Values.OrderBy(c => c.Id).ToArray();
        _backgrounds = _content.Backgrounds.Values.OrderBy(b => b.Id).ToArray();

        // Defaults so the player can press Confirm immediately.
        _clade      = _clades.FirstOrDefault(c => c.Id == "canidae")     ?? _clades[0];
        _species    = _allSpecies.FirstOrDefault(s => s.CladeId == _clade.Id);
        _class      = _classes.FirstOrDefault(c => c.Id == "fangsworn")  ?? _classes[0];
        _background = _backgrounds.FirstOrDefault(b => b.Id == "pack_raised") ?? _backgrounds[0];
        InitStandardArrayPool();
        AutoPickSkills();

        BuildUI();
    }

    // ── Layout ───────────────────────────────────────────────────────────

    private void BuildUI()
    {
        _root = new VerticalStackPanel
        {
            Spacing             = 6,
            HorizontalAlignment = HorizontalAlignment.Stretch,
            VerticalAlignment   = VerticalAlignment.Stretch,
            Padding             = new Thickness(16, 12, 16, 12),
        };

        // Header
        _root.Widgets.Add(new Label
        {
            Text = $"THERIAPOLIS — Codex of Becoming   ·   Folio {CodexCopy.Romanize(_step + 1)} of VII — {StepNames[_step]}",
            HorizontalAlignment = HorizontalAlignment.Center,
        });
        _root.Widgets.Add(new Label
        {
            Text = $"Seed 0x{_seed:X}",
            HorizontalAlignment = HorizontalAlignment.Center,
        });
        _root.Widgets.Add(new Label { Text = " " });

        // Stepper
        _root.Widgets.Add(BuildStepper());
        _root.Widgets.Add(new Label { Text = " " });

        // Two-column main area: page-main + aside.
        var twoCol = new HorizontalStackPanel
        {
            Spacing             = 18,
            HorizontalAlignment = HorizontalAlignment.Stretch,
        };
        twoCol.Widgets.Add(BuildCurrentStep());
        twoCol.Widgets.Add(BuildAside());
        _root.Widgets.Add(twoCol);

        // Nav bar
        _root.Widgets.Add(new Label { Text = " " });
        _root.Widgets.Add(BuildNav());

        _desktop = new Desktop { Root = _root };
    }

    private void Rebuild() => BuildUI();

    private Widget BuildStepper()
    {
        var row = new HorizontalStackPanel { Spacing = 4, HorizontalAlignment = HorizontalAlignment.Center };
        int firstIncomplete = -1;
        for (int i = 0; i < StepNames.Length; i++) if (ValidateStep(i) is not null) { firstIncomplete = i; break; }

        for (int i = 0; i < StepNames.Length; i++)
        {
            bool isCurrent  = i == _step;
            bool isComplete = ValidateStep(i) is null && !isCurrent;
            bool locked     = i > _step && firstIncomplete != -1 && firstIncomplete < i;
            string mark     = locked ? "✕" : (isComplete ? "✓" : CodexCopy.Romanize(i + 1));
            string label    = $"{mark} {StepNames[i]}";
            int idx = i;
            var btn = new TextButton
            {
                Text    = isCurrent ? "→ " + label : "  " + label,
                Padding = new Thickness(8, 4, 8, 4),
                Enabled = !locked,
            };
            if (!locked) btn.Click += (_, _) => { _step = idx; Rebuild(); };
            row.Widgets.Add(btn);
        }
        return row;
    }

    private Widget BuildCurrentStep()
    {
        var panel = new VerticalStackPanel
        {
            Spacing = 6,
            Width   = 720,
            Padding = new Thickness(12, 10, 12, 10),
            Background = new SolidBrush(new Color(15, 15, 25, 220)),
        };
        switch (_step)
        {
            case 0: BuildStepClade(panel);      break;
            case 1: BuildStepSpecies(panel);    break;
            case 2: BuildStepClass(panel);      break;
            case 3: BuildStepBackground(panel); break;
            case 4: BuildStepStats(panel);      break;
            case 5: BuildStepSkills(panel);     break;
            case 6: BuildStepReview(panel);     break;
        }
        return panel;
    }

    private Widget BuildAside()
    {
        var col = new VerticalStackPanel
        {
            Spacing             = 6,
            Width               = 320,
            Padding             = new Thickness(12, 10, 12, 10),
            Background          = new SolidBrush(new Color(8, 8, 16, 230)),
            HorizontalAlignment = HorizontalAlignment.Right,
        };
        col.Widgets.Add(new Label { Text = "— THE SUBJECT —", HorizontalAlignment = HorizontalAlignment.Center });
        col.Widgets.Add(new Label { Text = " " });

        col.Widgets.Add(new Label { Text = "Name" });
        col.Widgets.Add(new Label
        {
            Text = string.IsNullOrWhiteSpace(_name) ? "  (unnamed)" : "  " + _name,
        });
        col.Widgets.Add(new Label { Text = " " });

        col.Widgets.Add(new Label { Text = "Lineage" });
        col.Widgets.Add(new Label
        {
            Text = $"  {_species?.Name ?? "—"}  ({_clade?.Name ?? "—"} · {CodexCopy.SizeLabel(_species?.Size ?? "")})",
        });
        col.Widgets.Add(new Label { Text = " " });

        col.Widgets.Add(new Label { Text = "Calling & History" });
        col.Widgets.Add(new Label
        {
            Text = $"  {_class?.Name ?? "—"} (d{_class?.HitDie ?? 0})\n  {_background?.Name ?? "—"}",
        });
        col.Widgets.Add(new Label { Text = " " });

        col.Widgets.Add(new Label { Text = "Abilities" });
        col.Widgets.Add(new Label { Text = FormatAbilityStrip() });
        col.Widgets.Add(new Label { Text = " " });

        int totalSkills = _chosenSkills.Count + (_background?.SkillProficiencies.Length ?? 0);
        col.Widgets.Add(new Label { Text = $"Skills · {totalSkills}" });
        col.Widgets.Add(new Label { Text = "  " + FormatSkillSummary() });

        if (!string.IsNullOrEmpty(_detailTitle))
        {
            col.Widgets.Add(new Label { Text = " " });
            col.Widgets.Add(new Label { Text = "— Selected —" });
            col.Widgets.Add(new Label { Text = "  " + _detailTitle });
            col.Widgets.Add(new Label { Text = WordWrap(_detailBody, 38) });
        }

        return col;
    }

    private Widget BuildNav()
    {
        var row = new HorizontalStackPanel
        {
            Spacing             = 16,
            HorizontalAlignment = HorizontalAlignment.Center,
        };
        var back = new TextButton { Text = "← Back", Width = 120, Enabled = _step > 0 };
        back.Click += (_, _) => { _step--; Rebuild(); };
        row.Widgets.Add(back);

        var stepError  = ValidateStep(_step);
        bool allValid  = AllStepsValid();
        string status  = stepError is not null
            ? "✘ " + stepError
            : (_step < 6 ? "✓ Folio complete" : (allValid ? "✓ Ready to sign" : "✘ Some folios remain"));
        row.Widgets.Add(new Label { Text = $"   {status}   " });

        if (_step < StepNames.Length - 1)
        {
            var next = new TextButton { Text = "Next ›", Width = 120, Enabled = stepError is null };
            next.Click += (_, _) => { _step++; Rebuild(); };
            row.Widgets.Add(next);
        }
        else
        {
            var confirm = new TextButton { Text = "Confirm & Begin", Width = 200, Enabled = allValid };
            confirm.Click += (_, _) => OnConfirm();
            row.Widgets.Add(confirm);
        }
        return row;
    }

    // ── Step builders ────────────────────────────────────────────────────

    private void BuildStepClade(VerticalStackPanel page)
    {
        page.Widgets.Add(new Label { Text = "Folio I — Of Bloodlines" });
        page.Widgets.Add(new Label { Text = "Choose your Clade. The body you were born to — the broad shape of your gait,\nthe fall of your shadow, the words your scent carries before you speak." });
        page.Widgets.Add(new Label { Text = " " });

        // Group predator / prey for visual scan.
        page.Widgets.Add(new Label { Text = "── Predators ──" });
        AddCladeRow(page, _clades.Where(c => c.Kind == "predator"));
        page.Widgets.Add(new Label { Text = " " });
        page.Widgets.Add(new Label { Text = "── Prey ──" });
        AddCladeRow(page, _clades.Where(c => c.Kind == "prey"));
    }

    private void AddCladeRow(VerticalStackPanel page, System.Collections.Generic.IEnumerable<CladeDef> clades)
    {
        var row = new HorizontalStackPanel { Spacing = 6 };
        foreach (var c in clades)
        {
            string mods = string.Join(" ", c.AbilityMods.Select(kv => $"{kv.Key} {CodexCopy.Signed(kv.Value)}"));
            string langs = string.Join(", ", c.Languages.Select(CodexCopy.LanguageName));
            string label = (_clade == c ? "→ " : "  ") + c.Name + "\n  " + mods + "\n  langs: " + langs;
            var btn = new TextButton { Text = label, Width = 220, Padding = new Thickness(6, 4, 6, 4) };
            var clade = c;
            btn.Click += (_, _) =>
            {
                _clade = clade;
                if (_species is null || _species.CladeId != clade.Id)
                    _species = _allSpecies.FirstOrDefault(s => s.CladeId == clade.Id);
                if (clade.Traits.Length > 0) ShowDetail(clade.Traits[0].Name, clade.Traits[0].Description);
                Rebuild();
            };
            row.Widgets.Add(btn);
        }
        page.Widgets.Add(row);
    }

    private void BuildStepSpecies(VerticalStackPanel page)
    {
        page.Widgets.Add(new Label { Text = $"Folio II — Of Lineage within {_clade?.Name ?? "—"}" });
        page.Widgets.Add(new Label { Text = "Choose your Species. The species refines what the clade began —\ndifferent statures, ranges, and inheritances." });
        page.Widgets.Add(new Label { Text = " " });

        var filtered = _allSpecies.Where(s => _clade is null || s.CladeId == _clade.Id).ToArray();
        // Render in rows of 3.
        for (int i = 0; i < filtered.Length; i += 3)
        {
            var row = new HorizontalStackPanel { Spacing = 6 };
            for (int j = i; j < System.Math.Min(filtered.Length, i + 3); j++)
            {
                var s = filtered[j];
                string mods = string.Join(" ", s.AbilityMods.Select(kv => $"{kv.Key} {CodexCopy.Signed(kv.Value)}"));
                string traitNames = string.Join(", ", s.Traits.Take(2).Select(t => t.Name));
                string label = (_species == s ? "→ " : "  ") + s.Name + "\n  " +
                               $"{CodexCopy.SizeLabel(s.Size)} · {s.BaseSpeedFt} ft\n  " +
                               (string.IsNullOrEmpty(mods) ? "(no mods)" : mods) + "\n  " +
                               (string.IsNullOrEmpty(traitNames) ? "" : traitNames);
                var btn = new TextButton { Text = label, Width = 230, Padding = new Thickness(6, 4, 6, 4) };
                var sp = s;
                btn.Click += (_, _) =>
                {
                    _species = sp;
                    if (sp.Traits.Length > 0) ShowDetail(sp.Traits[0].Name, sp.Traits[0].Description);
                    Rebuild();
                };
                row.Widgets.Add(btn);
            }
            page.Widgets.Add(row);
        }
    }

    private void BuildStepClass(VerticalStackPanel page)
    {
        page.Widgets.Add(new Label { Text = "Folio III — Of Vocations" });
        page.Widgets.Add(new Label { Text = "Choose your Calling. Each shapes how you fight, treat, parley, or unmake the world.\n★ Suits Clade marks callings recommended for your chosen clade." });
        page.Widgets.Add(new Label { Text = " " });

        for (int i = 0; i < _classes.Length; i += 2)
        {
            var row = new HorizontalStackPanel { Spacing = 6 };
            for (int j = i; j < System.Math.Min(_classes.Length, i + 2); j++)
            {
                var c = _classes[j];
                bool suits = _clade is not null && CodexCopy.IsSuited(c.Id, _clade.Id);
                string suitTag = suits ? "  ★" : "";
                var lvl1 = System.Array.Find(c.LevelTable, e => e.Level == 1);
                string features = lvl1 is null ? "" : string.Join(", ",
                    lvl1.Features.Where(f => f != "asi" && f != "subclass_select" && f != "subclass_feature")
                                 .Select(f => c.FeatureDefinitions.TryGetValue(f, out var fd) ? fd.Name : f));
                string label = (_class == c ? "→ " : "  ") + c.Name + suitTag + "\n  " +
                               $"d{c.HitDie} · {string.Join("/", c.PrimaryAbility)} · saves {string.Join("/", c.Saves)}\n  " +
                               $"Picks {c.SkillsChoose} skill(s)\n  " + features;
                var btn = new TextButton { Text = label, Width = 350, Padding = new Thickness(6, 4, 6, 4) };
                var cls = c;
                btn.Click += (_, _) =>
                {
                    _class = cls;
                    AutoPickSkills();
                    if (lvl1 is not null && lvl1.Features.Length > 0)
                    {
                        var firstReal = lvl1.Features.FirstOrDefault(f => f != "asi" && f != "subclass_select" && f != "subclass_feature");
                        if (firstReal is not null && cls.FeatureDefinitions.TryGetValue(firstReal, out var fd))
                            ShowDetail(fd.Name, fd.Description);
                    }
                    Rebuild();
                };
                row.Widgets.Add(btn);
            }
            page.Widgets.Add(row);
        }
    }

    private void BuildStepBackground(VerticalStackPanel page)
    {
        page.Widgets.Add(new Label { Text = "Folio IV — Of Histories" });
        page.Widgets.Add(new Label { Text = "Choose your Background. The clade gives you body, the calling gives you craft;\nbackground gives you a past — debts, contacts, scars, the way you sleep." });
        page.Widgets.Add(new Label { Text = " " });

        for (int i = 0; i < _backgrounds.Length; i += 2)
        {
            var row = new HorizontalStackPanel { Spacing = 6 };
            for (int j = i; j < System.Math.Min(_backgrounds.Length, i + 2); j++)
            {
                var b = _backgrounds[j];
                string skills = string.Join(", ", b.SkillProficiencies.Select(CodexCopy.SkillName));
                string label = (_background == b ? "→ " : "  ") + b.Name + "\n  " +
                               b.FeatureName + "\n  Skills: " + skills;
                var btn = new TextButton { Text = label, Width = 350, Padding = new Thickness(6, 4, 6, 4) };
                var bg = b;
                btn.Click += (_, _) =>
                {
                    _background = bg;
                    ShowDetail(bg.FeatureName, bg.FeatureDescription);
                    Rebuild();
                };
                row.Widgets.Add(btn);
            }
            page.Widgets.Add(row);
        }
    }

    private void BuildStepStats(VerticalStackPanel page)
    {
        page.Widgets.Add(new Label { Text = "Folio V — Of Aptitudes" });
        page.Widgets.Add(new Label { Text = "Set your Abilities. Click a value in the pool to select it,\nthen click an ability to assign. Click a filled slot to return its value to the pool." });
        page.Widgets.Add(new Label { Text = " " });

        // Method tabs
        var tabs = new HorizontalStackPanel { Spacing = 8 };
        var arrayBtn = new TextButton { Text = (!_useRoll ? "→ " : "  ") + "Standard Array", Width = 220 };
        arrayBtn.Click += (_, _) => { _useRoll = false; InitStandardArrayPool(); Rebuild(); };
        tabs.Widgets.Add(arrayBtn);
        var rollBtn  = new TextButton { Text = (_useRoll ? "→ " : "  ") + "Roll 4d6 — drop lowest", Width = 260 };
        rollBtn.Click += (_, _) => { _useRoll = true; RollAndPool(); Rebuild(); };
        tabs.Widgets.Add(rollBtn);
        page.Widgets.Add(tabs);
        page.Widgets.Add(new Label { Text = " " });

        // Pool
        page.Widgets.Add(new Label { Text = "Pool (click to select):" });
        var poolRow = new HorizontalStackPanel { Spacing = 6 };
        if (_statPool.Count == 0)
            poolRow.Widgets.Add(new Label { Text = "  (all values assigned — click a slot to return its value)" });
        for (int i = 0; i < _statPool.Count; i++)
        {
            int idx = i;
            int v   = _statPool[i];
            bool selected = _pendingPoolIdx == idx;
            var dieBtn = new TextButton
            {
                Text = (selected ? "[" + v + "]" : " " + v + " "),
                Padding = new Thickness(8, 4, 8, 4),
            };
            dieBtn.Click += (_, _) => { _pendingPoolIdx = (selected ? null : (int?)idx); Rebuild(); };
            poolRow.Widgets.Add(dieBtn);
        }
        // Inline action buttons
        if (_useRoll)
        {
            var reroll = new TextButton { Text = "Reroll", Padding = new Thickness(6, 4, 6, 4) };
            reroll.Click += (_, _) => { RollAndPool(); Rebuild(); };
            poolRow.Widgets.Add(reroll);
        }
        var auto = new TextButton { Text = "Auto-assign", Padding = new Thickness(6, 4, 6, 4), Enabled = _statPool.Count > 0 };
        auto.Click += (_, _) => { AutoAssignByClassPriority(); Rebuild(); };
        poolRow.Widgets.Add(auto);
        var clear = new TextButton { Text = "Clear", Padding = new Thickness(6, 4, 6, 4), Enabled = _statAssign.Count > 0 };
        clear.Click += (_, _) => { ClearAssignments(); Rebuild(); };
        poolRow.Widgets.Add(clear);
        page.Widgets.Add(poolRow);
        page.Widgets.Add(new Label { Text = " " });

        // Roll history (last 3 prior rolls)
        if (_useRoll && _statHistory.Count > 1)
        {
            var prev = _statHistory.Take(_statHistory.Count - 1).TakeLast(3);
            string hist = string.Join("   ", prev.Select(h => "[" + string.Join(", ", h) + "]"));
            page.Widgets.Add(new Label { Text = "Previous rolls: " + hist });
            page.Widgets.Add(new Label { Text = " " });
        }

        // Ability rows
        foreach (var ab in CodexCopy.AbilityOrder)
        {
            int? assigned = _statAssign.TryGetValue(ab, out var v) ? v : null;
            int cladeMod   = ModFromDict(_clade?.AbilityMods, ab);
            int speciesMod = ModFromDict(_species?.AbilityMods, ab);
            int totalBonus = cladeMod + speciesMod;
            int finalScore = (assigned ?? 0) + totalBonus;
            int finalMod   = AbilityScores.Mod(finalScore);
            bool isPrimary = _class?.PrimaryAbility.Contains(ab.ToString()) == true;
            string primaryTag = isPrimary ? " *" : "";
            string bonusTag   = totalBonus == 0 ? "" : $"  ({CodexCopy.Signed(totalBonus)} from clade+species)";

            string slotText = assigned is null ? "  [ — ]  " : $"  [ {assigned} ]  ";
            string fullText = $"{ab}{primaryTag}  {CodexCopy.AbilityLabels[ab]}\n" +
                              $"{slotText}{bonusTag}\n" +
                              (assigned is null ? "" : $"  Final: {finalScore} ({CodexCopy.Signed(finalMod)})");
            var rowBtn = new TextButton { Text = fullText, Width = 660, Padding = new Thickness(6, 4, 6, 4) };
            var ability = ab;
            var assignedSnap = assigned;
            rowBtn.Click += (_, _) =>
            {
                if (assignedSnap is null && _pendingPoolIdx is int pidx)
                {
                    int val = _statPool[pidx];
                    _statPool.RemoveAt(pidx);
                    _statAssign[ability] = val;
                    _pendingPoolIdx = null;
                }
                else if (assignedSnap is int v2)
                {
                    _statPool.Add(v2);
                    _statAssign.Remove(ability);
                }
                Rebuild();
            };
            page.Widgets.Add(rowBtn);
        }
    }

    private void BuildStepSkills(VerticalStackPanel page)
    {
        page.Widgets.Add(new Label { Text = "Folio VI — Of Trained Hands" });
        page.Widgets.Add(new Label
        {
            Text = $"Choose your Skills. Background grants {_background?.SkillProficiencies.Length ?? 0} sealed; class lets you pick {_class?.SkillsChoose ?? 0} more.",
        });
        page.Widgets.Add(new Label
        {
            Text = $"Chosen: {_chosenSkills.Count} / {_class?.SkillsChoose ?? 0}   ·   Sealed by background: {_background?.SkillProficiencies.Length ?? 0}",
        });
        page.Widgets.Add(new Label { Text = " " });

        var bgLocked  = new HashSet<string>(_background?.SkillProficiencies ?? System.Array.Empty<string>(), System.StringComparer.OrdinalIgnoreCase);
        var classOpts = new HashSet<string>(_class?.SkillOptions ?? System.Array.Empty<string>(), System.StringComparer.OrdinalIgnoreCase);

        // Group by ability
        var grouped = new Dictionary<AbilityId, List<string>>();
        foreach (var ab in CodexCopy.AbilityOrder) grouped[ab] = new List<string>();
        foreach (var skillId in AllSkillIds())
        {
            var ab = CodexCopy.SkillAbility(skillId);
            grouped[ab].Add(skillId);
        }

        foreach (var ab in CodexCopy.AbilityOrder)
        {
            page.Widgets.Add(new Label { Text = $"── {CodexCopy.AbilityLabels[ab]} ({ab}) ──" });
            foreach (var skillId in grouped[ab])
            {
                bool fromBg    = bgLocked.Contains(skillId);
                bool fromClass = classOpts.Contains(skillId);
                bool checkedNow;
                try { checkedNow = _chosenSkills.Contains(SkillIdExtensions.FromJson(skillId)); }
                catch { checkedNow = false; }
                string mark = fromBg ? "[BG]" : (checkedNow ? "[✓]" : (fromClass ? "[ ]" : "[—]"));
                string label = $"  {mark}  {CodexCopy.SkillName(skillId)}" +
                               (fromBg ? "  (sealed by background)" : (fromClass ? "" : "  (not offered by class)"));
                var btn = new TextButton
                {
                    Text    = label,
                    Width   = 600,
                    Padding = new Thickness(4, 2, 4, 2),
                    Enabled = fromClass && !fromBg,
                };
                if (fromClass && !fromBg)
                {
                    var sid = skillId;
                    btn.Click += (_, _) =>
                    {
                        SkillId enumId;
                        try { enumId = SkillIdExtensions.FromJson(sid); }
                        catch { return; }
                        if (_chosenSkills.Contains(enumId))
                        {
                            _chosenSkills.Remove(enumId);
                        }
                        else if (_chosenSkills.Count < (_class?.SkillsChoose ?? 0))
                        {
                            _chosenSkills.Add(enumId);
                        }
                        ShowDetail(CodexCopy.SkillName(sid), CodexCopy.SkillDescription(sid));
                        Rebuild();
                    };
                }
                page.Widgets.Add(btn);
            }
        }
    }

    private void BuildStepReview(VerticalStackPanel page)
    {
        page.Widgets.Add(new Label { Text = "Folio VII — Of Names & Witness" });
        page.Widgets.Add(new Label { Text = "Sign the Codex. The name you sign here is the one the world will speak." });
        page.Widgets.Add(new Label { Text = " " });

        // Name input
        var nameRow = new HorizontalStackPanel { Spacing = 8 };
        nameRow.Widgets.Add(new Label { Text = "Name:", VerticalAlignment = VerticalAlignment.Center });
        var nameInput = new TextBox { Text = _name, Width = 360 };
        nameInput.TextChanged += (_, _) => _name = nameInput.Text ?? "";
        nameRow.Widgets.Add(nameInput);
        page.Widgets.Add(nameRow);
        page.Widgets.Add(new Label { Text = " " });

        // Lineage block
        page.Widgets.Add(new Label { Text = "── Lineage ──" });
        page.Widgets.Add(new Label
        {
            Text = $"  {_clade?.Name ?? "—"} / {_species?.Name ?? "—"} ({CodexCopy.SizeLabel(_species?.Size ?? "")})",
        });
        page.Widgets.Add(MakeEditLink("Edit ›", 0));

        // Calling+History
        page.Widgets.Add(new Label { Text = " " });
        page.Widgets.Add(new Label { Text = "── Calling & History ──" });
        page.Widgets.Add(new Label
        {
            Text = $"  {_class?.Name ?? "—"} (d{_class?.HitDie ?? 0} · {string.Join("/", _class?.PrimaryAbility ?? new string[0])})\n  Background: {_background?.Name ?? "—"}",
        });
        page.Widgets.Add(MakeEditLink("Edit Calling ›", 2));

        // Final abilities
        page.Widgets.Add(new Label { Text = " " });
        page.Widgets.Add(new Label { Text = "── Final Abilities ──" });
        page.Widgets.Add(new Label { Text = "  " + FormatAbilityStrip() });
        page.Widgets.Add(MakeEditLink("Edit Abilities ›", 4));

        // Skills
        page.Widgets.Add(new Label { Text = " " });
        page.Widgets.Add(new Label { Text = "── Skills ──" });
        page.Widgets.Add(new Label { Text = "  " + FormatSkillSummary() });
        page.Widgets.Add(MakeEditLink("Edit Skills ›", 5));

        // Starting kit
        page.Widgets.Add(new Label { Text = " " });
        page.Widgets.Add(new Label { Text = "── Starting Kit ──" });
        if (_class?.StartingKit is null || _class.StartingKit.Length == 0)
            page.Widgets.Add(new Label { Text = "  (no kit configured)" });
        else
        {
            foreach (var entry in _class.StartingKit)
            {
                string equipTag = entry.AutoEquip ? $"  [equipped: {entry.EquipSlot}]" : "";
                string qtyTag   = entry.Qty > 1 ? $"  ×{entry.Qty}" : "";
                page.Widgets.Add(new Label { Text = $"  • {CodexCopy.ItemName(entry.ItemId)}{qtyTag}{equipTag}" });
            }
        }
    }

    private TextButton MakeEditLink(string text, int targetStep)
    {
        var btn = new TextButton { Text = text, Padding = new Thickness(6, 2, 6, 2) };
        btn.Click += (_, _) => { _step = targetStep; Rebuild(); };
        return btn;
    }

    // ── State helpers ────────────────────────────────────────────────────

    private void InitStandardArrayPool()
    {
        _statPool.Clear();
        foreach (int v in AbilityScores.StandardArray) _statPool.Add(v);
        _statAssign.Clear();
        _pendingPoolIdx = null;
    }

    private void RollAndPool()
    {
        ulong msNow = (ulong)(System.Environment.TickCount64 - _gameStartMs);
        var rng = SeededRng.ForSubsystem(_seed, C.RNG_STAT_ROLL ^ msNow);
        var vals = new int[6];
        for (int i = 0; i < 6; i++) vals[i] = CharacterBuilder.Roll4d6DropLowest(rng);
        _statHistory.Add(vals);
        _statPool.Clear();
        foreach (var v in vals) _statPool.Add(v);
        _statAssign.Clear();
        _pendingPoolIdx = null;
    }

    private void AutoAssignByClassPriority()
    {
        var primary = _class?.PrimaryAbility ?? System.Array.Empty<string>();
        var order = new List<string>();
        foreach (var p in primary) order.Add(p.ToUpperInvariant());
        foreach (var a in new[] { "CON", "DEX", "STR", "WIS", "INT", "CHA" })
            if (!order.Contains(a)) order.Add(a);

        var available = _statPool.OrderByDescending(x => x).ToList();
        // Honour any already-pinned abilities; fill the rest from the pool.
        var emptyAbilities = new List<AbilityId>();
        foreach (var s in order)
        {
            if (TryParseAbility(s, out var ab) && !_statAssign.ContainsKey(ab))
                emptyAbilities.Add(ab);
        }
        for (int i = 0; i < emptyAbilities.Count && i < available.Count; i++)
        {
            _statAssign[emptyAbilities[i]] = available[i];
        }
        // Rebuild the pool from leftovers.
        _statPool.Clear();
        for (int i = emptyAbilities.Count; i < available.Count; i++) _statPool.Add(available[i]);
        _pendingPoolIdx = null;
    }

    private void ClearAssignments()
    {
        foreach (var v in _statAssign.Values) _statPool.Add(v);
        _statAssign.Clear();
        _pendingPoolIdx = null;
    }

    private void AutoPickSkills()
    {
        _chosenSkills.Clear();
        if (_class is null) return;
        int n = _class.SkillsChoose;
        foreach (var raw in _class.SkillOptions)
        {
            if (_chosenSkills.Count >= n) break;
            try { _chosenSkills.Add(SkillIdExtensions.FromJson(raw)); } catch { /* unknown */ }
        }
    }

    private void ShowDetail(string title, string body)
    {
        _detailTitle = title ?? "";
        _detailBody  = body  ?? "";
    }

    // ── Validation ───────────────────────────────────────────────────────

    private string? ValidateStep(int i)
    {
        if (i == 0) return _clade is null ? "Pick a clade." : null;
        if (i == 1) return _species is null ? "Pick a species." : null;
        if (i == 2) return _class is null ? "Pick a calling." : null;
        if (i == 3) return _background is null ? "Pick a background." : null;
        if (i == 4) return _statAssign.Count == 6 ? null : $"Assign all six abilities ({_statAssign.Count}/6).";
        if (i == 5)
        {
            int need = _class?.SkillsChoose ?? 0;
            return _chosenSkills.Count == need ? null : $"Pick exactly {need} skill(s) ({_chosenSkills.Count}/{need}).";
        }
        if (i == 6) return string.IsNullOrWhiteSpace(_name) ? "Enter a name." : null;
        return null;
    }

    private bool AllStepsValid()
    {
        for (int i = 0; i < StepNames.Length; i++) if (ValidateStep(i) is not null) return false;
        return true;
    }

    // ── Confirm ──────────────────────────────────────────────────────────

    private void OnConfirm()
    {
        if (!AllStepsValid()) return;
        var b = new CharacterBuilder
        {
            Clade        = _clade,
            Species      = _species,
            ClassDef     = _class,
            Background   = _background,
            BaseAbilities = new AbilityScores(
                _statAssign.GetValueOrDefault(AbilityId.STR),
                _statAssign.GetValueOrDefault(AbilityId.DEX),
                _statAssign.GetValueOrDefault(AbilityId.CON),
                _statAssign.GetValueOrDefault(AbilityId.INT),
                _statAssign.GetValueOrDefault(AbilityId.WIS),
                _statAssign.GetValueOrDefault(AbilityId.CHA)),
            Name = _name,
        };
        foreach (var s in _chosenSkills) b.ChooseSkill(s);
        var character = b.Build(_content.Items);

        _game.Screens.Pop();
        _game.Screens.Push(new WorldGenProgressScreen(_seed, pendingCharacter: character, pendingName: _name));
    }

    // ── Formatters ───────────────────────────────────────────────────────

    private string FormatAbilityStrip()
    {
        var parts = new List<string>();
        foreach (var ab in CodexCopy.AbilityOrder)
        {
            int? assigned = _statAssign.TryGetValue(ab, out var v) ? v : null;
            int cladeMod   = ModFromDict(_clade?.AbilityMods, ab);
            int speciesMod = ModFromDict(_species?.AbilityMods, ab);
            if (assigned is null) { parts.Add($"{ab} —"); continue; }
            int finalScore = assigned.Value + cladeMod + speciesMod;
            parts.Add($"{ab} {finalScore}({CodexCopy.Signed(AbilityScores.Mod(finalScore))})");
        }
        return string.Join("  ", parts);
    }

    private string FormatSkillSummary()
    {
        var skills = new List<string>();
        if (_background is not null)
            foreach (var s in _background.SkillProficiencies) skills.Add(CodexCopy.SkillName(s) + "*");
        foreach (var s in _chosenSkills.OrderBy(x => x.ToString())) skills.Add(s.ToString());
        if (skills.Count == 0) return "(none yet)";
        return string.Join(", ", skills) + "    (* = sealed by background)";
    }

    private static int ModFromDict(System.Collections.Generic.IReadOnlyDictionary<string, int>? d, AbilityId ab)
    {
        if (d is null) return 0;
        return d.TryGetValue(ab.ToString(), out var v) ? v : 0;
    }

    private static bool TryParseAbility(string raw, out AbilityId id)
    {
        switch (raw.ToUpperInvariant())
        {
            case "STR": id = AbilityId.STR; return true;
            case "DEX": id = AbilityId.DEX; return true;
            case "CON": id = AbilityId.CON; return true;
            case "INT": id = AbilityId.INT; return true;
            case "WIS": id = AbilityId.WIS; return true;
            case "CHA": id = AbilityId.CHA; return true;
            default:    id = AbilityId.STR; return false;
        }
    }

    private static System.Collections.Generic.IEnumerable<string> AllSkillIds() => new[]
    {
        "athletics", "acrobatics", "sleight_of_hand", "stealth",
        "arcana", "history", "investigation", "nature", "religion",
        "animal_handling", "insight", "medicine", "perception", "survival",
        "deception", "intimidation", "performance", "persuasion",
    };

    /// <summary>Soft word-wrap for the detail-panel body. Splits on spaces; crude but adequate.</summary>
    private static string WordWrap(string text, int maxCols)
    {
        if (string.IsNullOrEmpty(text)) return "";
        var sb = new System.Text.StringBuilder();
        int col = 0;
        foreach (var word in text.Split(' '))
        {
            if (col + word.Length + 1 > maxCols) { sb.Append('\n'); sb.Append("  "); col = 2; }
            else if (col > 2) { sb.Append(' '); col++; }
            else if (col == 0) { sb.Append("  "); col = 2; }
            sb.Append(word);
            col += word.Length;
        }
        return sb.ToString();
    }

    // ── Lifecycle ────────────────────────────────────────────────────────

    public void Update(GameTime gt)
    {
        if (Keyboard.GetState().IsKeyDown(Keys.Escape)) _game.Screens.Pop();
    }

    public void Draw(GameTime gt, SpriteBatch sb)
    {
        _game.GraphicsDevice.Clear(new Color(15, 15, 25));
        _desktop.Render();
    }

    public void Deactivate() { }
    public void Reactivate() { }
}
