using Godot;
using System.Collections.Generic;
using Theriapolis.GodotHost.Scenes.Widgets;
using Theriapolis.GodotHost.UI;

namespace Theriapolis.GodotHost.Scenes;

/// <summary>
/// Right-rail summary of the in-progress character. Sections, top-down:
///   1. Name (or placeholder until Step VIII).
///   2. Lineage details — 2-column grid:
///        purebred:   Clade | Species, then Calling | Background.
///        hybrid:     Sire | Dam (column headers); each parent's
///                    clade and species below; Calling | Background.
///   3. Attributes — final score + d20 modifier per ability.
///   4. Pills — traits + skills selected so far, with hover popovers.
///
/// One Refresh() rebuild per draft change; the panel is small enough
/// that partial-update logic isn't worth the complexity.
/// </summary>
public partial class Aside : MarginContainer
{
    private CharacterDraft? _draft;
    private VBoxContainer _content = null!;

    public override void _Ready()
    {
        AddThemeConstantOverride("margin_left", 18);
        AddThemeConstantOverride("margin_right", 18);
        AddThemeConstantOverride("margin_top", 18);
        AddThemeConstantOverride("margin_bottom", 18);

        // Wrap content in a ScrollContainer so the Aside's intrinsic
        // height stays bounded by the panel's allocated size — without
        // this, an over-tall summary (lots of pills) forces the parent
        // Layout to expand and pushes the navbar off the viewport.
        var scroll = new ScrollContainer
        {
            SizeFlagsHorizontal = SizeFlags.ExpandFill,
            SizeFlagsVertical   = SizeFlags.ExpandFill,
            HorizontalScrollMode = ScrollContainer.ScrollMode.Disabled,
        };
        AddChild(scroll);

        _content = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
        _content.AddThemeConstantOverride("separation", 18);
        scroll.AddChild(_content);
    }

    public void SetDraft(CharacterDraft draft)
    {
        _draft = draft;
        _draft.Changed += Refresh;
        Refresh();
    }

    private void Refresh()
    {
        if (_draft is null || _content is null) return;
        foreach (var c in _content.GetChildren()) c.QueueFree();

        BuildName();
        BuildDetailsGrid();
        BuildAttributes();
        BuildPills();
    }

    // ──────────────────────────────────────────────────────────────────────
    // Section 1 — Name

    private void BuildName()
    {
        var name = string.IsNullOrEmpty(_draft!.CharacterName) ? "Unnamed" : _draft.CharacterName;
        _content.AddChild(new Label
        {
            Text = name,
            HorizontalAlignment = HorizontalAlignment.Center,
            ThemeTypeVariation = "H3",
        });
        _content.AddChild(new HSeparator());
    }

    // ──────────────────────────────────────────────────────────────────────
    // Section 2 — Lineage details (2-column grid)

    private void BuildDetailsGrid()
    {
        string sexLabel = _draft!.Sex switch
        {
            "male"   => "Male",
            "female" => "Female",
            _        => "",
        };

        if (_draft.IsHybrid)
        {
            // Hybrid layout: SIRE / DAM column headers above the parent
            // detail rows, then the Calling / Background row spans both
            // halves of the same kind of grid.
            var headers = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
            headers.AddThemeConstantOverride("separation", 12);
            _content.AddChild(headers);
            headers.AddChild(MakeColumnHeader("SIRE" + (_draft.DominantParent == "sire" ? " ★" : "")));
            headers.AddChild(MakeColumnHeader("DAM"  + (_draft.DominantParent == "dam"  ? " ★" : "")));

            var lineageGrid = MakeFullWidthGrid();
            _content.AddChild(lineageGrid);
            lineageGrid.AddChild(MakeCell("Clade",   CodexContent.Clade(_draft.SireCladeId)?.Name));
            lineageGrid.AddChild(MakeCell("Clade",   CodexContent.Clade(_draft.DamCladeId)?.Name));
            lineageGrid.AddChild(MakeCell("Species", CodexContent.SpeciesById(_draft.SireSpeciesId)?.Name));
            lineageGrid.AddChild(MakeCell("Species", CodexContent.SpeciesById(_draft.DamSpeciesId)?.Name));
            // Sex is a character-level field, not per-parent — span left
            // column with an empty filler in the right.
            lineageGrid.AddChild(MakeCell("Sex", sexLabel));
            lineageGrid.AddChild(new Control());
        }
        else
        {
            var lineageGrid = MakeFullWidthGrid();
            _content.AddChild(lineageGrid);
            lineageGrid.AddChild(MakeCell("Clade",   CodexContent.Clade(_draft.CladeId)?.Name));
            lineageGrid.AddChild(MakeCell("Species", CodexContent.SpeciesById(_draft.SpeciesId)?.Name));
            lineageGrid.AddChild(MakeCell("Sex", sexLabel));
            lineageGrid.AddChild(new Control());
        }

        // Calling + Background — last row of the lineage block, with
        // Subclass tucked underneath Calling in the same column.
        var callingGrid = MakeFullWidthGrid();
        _content.AddChild(callingGrid);
        callingGrid.AddChild(MakeCell("Calling",    CodexContent.Class(_draft.ClassId)?.Name));
        callingGrid.AddChild(MakeCell("Background", CodexContent.Background(_draft.BackgroundId)?.Name));

        var subclassDef = System.Array.Find(CodexContent.Subclasses, s => s.Id == _draft.SubclassId);
        callingGrid.AddChild(MakeCell("Subclass", subclassDef?.Name));
        callingGrid.AddChild(new Control());  // empty cell to align grid

        _content.AddChild(new HSeparator());
    }

    private static GridContainer MakeFullWidthGrid()
    {
        var grid = new GridContainer { Columns = 2, SizeFlagsHorizontal = SizeFlags.ExpandFill };
        grid.AddThemeConstantOverride("h_separation", 12);
        grid.AddThemeConstantOverride("v_separation", 8);
        return grid;
    }

    private static Control MakeColumnHeader(string label)
    {
        // Centered label + underline; sized to take half the parent
        // HBoxContainer width so SIRE and DAM align over their data
        // columns.
        var col = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
        col.AddThemeConstantOverride("separation", 2);
        col.AddChild(new Label
        {
            Text = label,
            HorizontalAlignment = HorizontalAlignment.Center,
            ThemeTypeVariation = "Eyebrow",
        });
        col.AddChild(new HSeparator());
        return col;
    }

    private static Control MakeCell(string label, string? value)
    {
        var v = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
        v.AddThemeConstantOverride("separation", 2);

        // Smaller font on the label tag — keeps the row compact in the
        // narrow side rail.
        var lbl = new Label { Text = label.ToUpperInvariant(), ThemeTypeVariation = "Eyebrow" };
        v.AddChild(lbl);

        // Autowrap on the value so long names ("Hybrid Underground")
        // wrap rather than push the whole panel wider than its alloc.
        var val = new Label
        {
            Text = string.IsNullOrEmpty(value) ? "—" : value,
            AutowrapMode = TextServer.AutowrapMode.WordSmart,
            CustomMinimumSize = new Vector2(0, 0),
        };
        v.AddChild(val);
        return v;
    }

    // ──────────────────────────────────────────────────────────────────────
    // Section 3 — Attributes (final score + modifier)

    private void BuildAttributes()
    {
        _content.AddChild(new Label { Text = "ATTRIBUTES", ThemeTypeVariation = "Eyebrow" });

        // Self-contained sub-panel so the attributes table never widens
        // beyond the Aside's own rect. Columns: ab | bonus | final | d20.
        // Cells have explicit min widths but DON'T ExpandFill, so the
        // grid's total width is bounded by the cell mins instead of
        // taking whatever parent width is available.
        var grid = new GridContainer { Columns = 4 };
        grid.AddThemeConstantOverride("h_separation", 6);
        grid.AddThemeConstantOverride("v_separation", 6);
        _content.AddChild(grid);

        foreach (var ab in SkillsCatalog.Abilities)
        {
            int baseScore = AbilityCalc.BaseScore(ab, _draft!);
            int bonus     = AbilityCalc.TotalBonus(ab, _draft!);
            int final     = baseScore + bonus;
            int dMod      = AbilityCalc.D20Modifier(final);

            grid.AddChild(new Label
            {
                Text = ab,
                CustomMinimumSize = new Vector2(36, 0),
            });

            // Bonus chip — only render when non-zero. +0 entries get an
            // empty Control so the column stays aligned without the
            // panel chrome of an empty TraitChip pushing the row wider.
            if (bonus != 0)
            {
                grid.AddChild(new Widgets.TraitChip
                {
                    TraitName = AbilityCalc.FormatSigned(bonus),
                    Description = AbilityCalc.FormatBreakdown(AbilityCalc.Sources(ab, _draft!)),
                    Tag = "bonus",
                });
            }
            else
            {
                grid.AddChild(new Control { CustomMinimumSize = new Vector2(40, 0) });
            }

            grid.AddChild(new Label
            {
                Text = baseScore == 0 ? "—" : final.ToString(),
                HorizontalAlignment = HorizontalAlignment.Right,
                CustomMinimumSize = new Vector2(36, 0),
            });

            grid.AddChild(new Label
            {
                Text = baseScore == 0 ? "" : AbilityCalc.FormatSigned(dMod),
                HorizontalAlignment = HorizontalAlignment.Right,
                CustomMinimumSize = new Vector2(36, 0),
            });
        }

        _content.AddChild(new HSeparator());
    }

    // ──────────────────────────────────────────────────────────────────────
    // Section 4 — Pills (traits, skills, features chosen so far)

    private void BuildPills()
    {
        _content.AddChild(new Label { Text = "TRAITS · FEATS · SKILLS", ThemeTypeVariation = "Eyebrow" });

        var flow = new HFlowContainer();
        flow.AddThemeConstantOverride("h_separation", 6);
        flow.AddThemeConstantOverride("v_separation", 6);
        _content.AddChild(flow);

        // Clade — purebred: full trait+detriment list of the one clade.
        // Hybrid: only the player-picked clade traits per parent (2/1 split),
        // but ALL clade detriments from BOTH parents per doc rule.
        if (_draft!.IsHybrid)
        {
            AddPickedCladeTraits(flow, CodexContent.Clade(_draft.SireCladeId), _draft.SireChosenCladeTraits);
            AddPickedCladeTraits(flow, CodexContent.Clade(_draft.DamCladeId),  _draft.DamChosenCladeTraits);
            AddCladeDetriments(flow, CodexContent.Clade(_draft.SireCladeId));
            AddCladeDetriments(flow, CodexContent.Clade(_draft.DamCladeId));
        }
        else
        {
            AddCladeTraits(flow, CodexContent.Clade(_draft.CladeId));
        }

        // Species — purebred shows everything from the single species.
        // Hybrid shows only the picked trait + picked detriment per parent
        // (single-pick each, per doc) plus the four universal detriments.
        if (_draft.IsHybrid)
        {
            var sireSp = CodexContent.SpeciesById(_draft.SireSpeciesId);
            var damSp  = CodexContent.SpeciesById(_draft.DamSpeciesId);
            AddPickedSpeciesPick(flow, sireSp,
                _draft.SireChosenSpeciesTrait, _draft.SireChosenSpeciesDetriment);
            AddPickedSpeciesPick(flow, damSp,
                _draft.DamChosenSpeciesTrait, _draft.DamChosenSpeciesDetriment);
            AddVariantContent(flow, sireSp, _draft.ResolveVariantId(sireSp, "sire"));
            AddVariantContent(flow, damSp,  _draft.ResolveVariantId(damSp,  "dam"));

            // Universal hybrid detriments — every hybrid has all four.
            foreach (var (name, desc) in UniversalHybridDetriments)
                flow.AddChild(new TraitChip { TraitName = name, Description = desc, Detriment = true });
        }
        else
        {
            var sp = CodexContent.SpeciesById(_draft.SpeciesId);
            AddSpeciesTraits(flow, sp);
            AddVariantContent(flow, sp, _draft.ResolveVariantId(sp, ""));
        }

        // Class level-1 features.
        var cls = CodexContent.Class(_draft.ClassId);
        if (cls is not null)
        {
            var lvl1 = System.Array.Find(cls.LevelTable, e => e.Level == 1);
            if (lvl1 is not null)
            {
                foreach (var fid in lvl1.Features)
                {
                    if (!cls.FeatureDefinitions.TryGetValue(fid, out var fd)) continue;
                    if (fd.Kind == "stub" || fid.StartsWith("subclass_")) continue;
                    flow.AddChild(new TraitChip
                    {
                        TraitName = fd.Name,
                        Description = fd.Description,
                        Tag = fd.Kind,
                    });
                }
            }
        }

        // Background feature + granted skills.
        var bg = CodexContent.Background(_draft.BackgroundId);
        if (bg is not null && !string.IsNullOrEmpty(bg.FeatureName))
        {
            flow.AddChild(new TraitChip
            {
                TraitName = bg.FeatureName,
                Description = bg.FeatureDescription,
                Tag = "history",
            });
        }

        // Skills — background-locked first, then user-chosen class skills.
        if (bg is not null)
        {
            foreach (var skillId in bg.SkillProficiencies)
                AddSkillChip(flow, skillId, "BG");
        }
        foreach (var skillId in _draft.ChosenSkills)
            AddSkillChip(flow, skillId, "skill");
    }

    private static void AddCladeTraits(HFlowContainer flow, Theriapolis.Core.Data.CladeDef? clade)
    {
        if (clade is null) return;
        foreach (var t in clade.Traits)
            flow.AddChild(new TraitChip { TraitName = t.Name, Description = t.Description });
        foreach (var d in clade.Detriments)
            flow.AddChild(new TraitChip { TraitName = d.Name, Description = d.Description, Detriment = true });
    }

    private static void AddSpeciesTraits(HFlowContainer flow, Theriapolis.Core.Data.SpeciesDef? species)
    {
        if (species is null) return;
        foreach (var t in species.Traits)
            flow.AddChild(new TraitChip { TraitName = t.Name, Description = t.Description });
        foreach (var d in species.Detriments)
            flow.AddChild(new TraitChip { TraitName = d.Name, Description = d.Description, Detriment = true });
    }

    /// <summary>Hybrid: only the chosen subset of clade traits.</summary>
    private static void AddPickedCladeTraits(HFlowContainer flow, Theriapolis.Core.Data.CladeDef? clade,
                                              Godot.Collections.Array<string> chosen)
    {
        if (clade is null) return;
        foreach (var t in clade.Traits)
            if (chosen.Contains(t.Id))
                flow.AddChild(new TraitChip { TraitName = t.Name, Description = t.Description });
    }

    /// <summary>Hybrid: all clade detriments from this parent (both inherited).</summary>
    private static void AddCladeDetriments(HFlowContainer flow, Theriapolis.Core.Data.CladeDef? clade)
    {
        if (clade is null) return;
        foreach (var d in clade.Detriments)
            flow.AddChild(new TraitChip { TraitName = d.Name, Description = d.Description, Detriment = true });
    }

    /// <summary>Render the resolved variant's extra traits/detriments,
    /// if any. <paramref name="variantId"/> is the variant key (e.g. "male"
    /// or "sheep"); empty when no resolution applies.</summary>
    private static void AddVariantContent(HFlowContainer flow,
                                           Theriapolis.Core.Data.SpeciesDef? species,
                                           string variantId)
    {
        if (species is null || string.IsNullOrEmpty(variantId)) return;
        var variant = System.Array.Find(species.Variants, v => v.Id == variantId);
        if (variant is null) return;
        foreach (var t in variant.Traits)
            flow.AddChild(new TraitChip { TraitName = t.Name, Description = t.Description });
        foreach (var d in variant.Detriments)
            flow.AddChild(new TraitChip { TraitName = d.Name, Description = d.Description, Detriment = true });
    }

    /// <summary>Hybrid: one chosen species trait + one chosen species detriment.</summary>
    private static void AddPickedSpeciesPick(HFlowContainer flow, Theriapolis.Core.Data.SpeciesDef? species,
                                              string chosenTraitId, string chosenDetrimentId)
    {
        if (species is null) return;
        foreach (var t in species.Traits)
            if (t.Id == chosenTraitId)
                flow.AddChild(new TraitChip { TraitName = t.Name, Description = t.Description });
        foreach (var d in species.Detriments)
            if (d.Id == chosenDetrimentId)
                flow.AddChild(new TraitChip { TraitName = d.Name, Description = d.Description, Detriment = true });
    }

    /// <summary>
    /// Hardcoded from theriapolis-rpg-clades.md "Universal Hybrid Detriments"
    /// (lines 749-753). Every hybrid character has all four. The schema
    /// doesn't carry these yet — they live in the clades doc as canonical
    /// rules text.
    /// </summary>
    private static readonly (string Name, string Description)[] UniversalHybridDetriments =
    {
        ("Scent Dysphoria",
            "Your scent broadcasts conflicting Clade signals. Creatures with scent ability "
          + "who detect you must make a WIS save (DC 10) or experience instinctive unease, "
          + "imposing disadvantage on their first CHA check with you. Scent-masking products "
          + "can suppress this for 1d4 hours but fail under stress."),
        ("Illegible Body Language",
            "Your tail and ear movements blend two Clade grammars. Disadvantage on nonverbal "
          + "communication checks (CHA checks relying on body language) with purebred creatures. "
          + "Other hybrids read you normally."),
        ("Social Stigma",
            "In non-progressive settlements, disadvantage on CHA checks with strangers and on "
          + "checks to secure housing, employment, or official services. Even in progressive "
          + "areas, the first CHA check with any new NPC is made at -2 until you've established "
          + "rapport."),
        ("Medical Incompatibility",
            "Healing potions and magical healing function at 75% effectiveness (round down). "
          + "Medical treatment calibrated for purebred physiologies works imperfectly on your "
          + "blended body. Hybrid-specific medicine exists but is expensive and rare."),
    };

    private static void AddSkillChip(HFlowContainer flow, string skillId, string tag)
    {
        var s = SkillsCatalog.Get(skillId);
        if (s is null) return;
        flow.AddChild(new TraitChip
        {
            TraitName = s.Label,
            Description = s.Description,
            Tag = tag,
        });
    }
}
