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

namespace Theriapolis.GodotHost.Scenes.Steps;

/// <summary>
/// Step VII — Skills. Direct port of <c>StepSkills</c> in
/// <c>src/steps.jsx</c>: 18 skills laid out in 6 ability groups
/// (STR / DEX / CON / INT / WIS / CHA). Background-granted skills are
/// pre-checked and locked; the user picks <c>class.SkillsChoose</c>
/// more from <c>class.SkillOptions</c>. Hover any skill row → popover
/// with the description.
///
/// State:
///   - "locked"      → granted by background, can't toggle (✓ shown)
///   - "checked"     → user-picked from class options (✓ shown)
///   - "available"   → in class options, unchecked
///   - "unavailable" → not in class options, dimmed and unclickable
/// </summary>
public partial class StepSkills : VBoxContainer, IStep
{
    private CharacterDraft _draft = null!;
    private Label _countLabel = null!;
    private GridContainer _groupsGrid = null!;

    public void Bind(CharacterDraft draft)
    {
        _draft = draft;
        _draft.Changed += () => Callable.From(Refresh).CallDeferred();
        Build();
    }

    public string? Validate() => WizardValidation.Validate(6, _draft);

    private void Build()
    {
        AddThemeConstantOverride("separation", 16);

        var intro = new VBoxContainer();
        intro.AddThemeConstantOverride("separation", 6);
        AddChild(intro);
        intro.AddChild(new Label { Text = "FOLIO VII · SKILLS", ThemeTypeVariation = "Eyebrow" });
        intro.AddChild(new Label { Text = "Choose Your Skills", ThemeTypeVariation = "H2" });
        intro.AddChild(new Label
        {
            Text = "Your background grants two skills automatically (sealed). From your "
                 + "calling's offered list, choose the rest. Hover any skill for its codex "
                 + "reading.",
            AutowrapMode = TextServer.AutowrapMode.WordSmart,
        });

        _countLabel = new Label { Text = "0 / 0 chosen", ThemeTypeVariation = "Meta" };
        AddChild(_countLabel);

        _groupsGrid = new GridContainer
        {
            Columns = 2,
            SizeFlagsHorizontal = SizeFlags.ExpandFill,
        };
        _groupsGrid.AddThemeConstantOverride("h_separation", 18);
        _groupsGrid.AddThemeConstantOverride("v_separation", 18);
        AddChild(_groupsGrid);

        Refresh();
    }

    private void Refresh()
    {
        if (_groupsGrid is null) return;

        var cls = CodexContent.Class(_draft.ClassId);
        var bg  = CodexContent.Background(_draft.BackgroundId);
        int required = cls?.SkillsChoose ?? 0;

        var lockedFromBg = new HashSet<string>(bg?.SkillProficiencies ?? System.Array.Empty<string>());
        var classOptions = new HashSet<string>(cls?.SkillOptions ?? System.Array.Empty<string>());
        var chosen = new HashSet<string>(_draft.ChosenSkills);

        _countLabel.Text = cls is null
            ? "Pick a calling first."
            : $"{chosen.Count} / {required} chosen   +{lockedFromBg.Count} sealed by background";

        foreach (var c in _groupsGrid.GetChildren()) c.QueueFree();

        if (cls is null) return;

        foreach (var ability in SkillsCatalog.Abilities)
            _groupsGrid.AddChild(BuildAbilityGroup(ability, lockedFromBg, classOptions, chosen, required));
    }

    private Control BuildAbilityGroup(string ability,
                                       HashSet<string> lockedFromBg,
                                       HashSet<string> classOptions,
                                       HashSet<string> chosen,
                                       int required)
    {
        var panel = new PanelContainer
        {
            SizeFlagsHorizontal = SizeFlags.ExpandFill,
            ThemeTypeVariation = "Card",
        };
        var col = new VBoxContainer();
        col.AddThemeConstantOverride("separation", 4);
        panel.AddChild(col);

        // Header — full ability name + abbreviation.
        var header = new HBoxContainer();
        header.AddThemeConstantOverride("separation", 8);
        col.AddChild(header);
        header.AddChild(new Label { Text = SkillsCatalog.AbilityFullName[ability], ThemeTypeVariation = "H3" });
        var spacer = new Control { SizeFlagsHorizontal = SizeFlags.ExpandFill };
        header.AddChild(spacer);
        header.AddChild(new Label { Text = ability, ThemeTypeVariation = "Eyebrow" });

        foreach (var s in SkillsCatalog.ByAbility(ability))
            col.AddChild(BuildSkillRow(s, lockedFromBg, classOptions, chosen, required));

        return panel;
    }

    private Control BuildSkillRow(SkillsCatalog.SkillEntry skill,
                                   HashSet<string> lockedFromBg,
                                   HashSet<string> classOptions,
                                   HashSet<string> chosen,
                                   int required)
    {
        bool fromBg     = lockedFromBg.Contains(skill.JsonId);
        bool fromClass  = classOptions.Contains(skill.JsonId);
        bool isChecked  = chosen.Contains(skill.JsonId);
        bool clickable  = fromClass && !fromBg;

        var row = new PanelContainer
        {
            MouseFilter = MouseFilterEnum.Stop,
            ThemeTypeVariation = "SkillRow",
        };

        // Visual state: dim unavailable rows, gild-tint background-locked,
        // brighten chosen. Theming pass will replace Modulate with proper
        // styleboxes per state.
        if (fromBg)
            row.Modulate = new Color(1f, 0.95f, 0.7f);
        else if (!fromClass)
            row.Modulate = new Color(1f, 1f, 1f, 0.4f);
        else if (isChecked)
            row.Modulate = new Color(0.95f, 1f, 0.95f);

        var rowH = new HBoxContainer { MouseFilter = MouseFilterEnum.Pass };
        rowH.AddThemeConstantOverride("separation", 8);
        row.AddChild(rowH);

        // Fixed-width slot for the checkbox so the name doesn't jump
        // horizontally when toggling — "[✓]" and "[ ]" render at slightly
        // different widths in proportional fonts.
        var checkbox = new Label
        {
            Text = (fromBg || isChecked) ? "[✓]" : (clickable ? "[ ]" : "[—]"),
            CustomMinimumSize = new Vector2(36, 0),
            HorizontalAlignment = HorizontalAlignment.Center,
            MouseFilter = MouseFilterEnum.Ignore,
        };
        rowH.AddChild(checkbox);

        // Skill name — the only hover trigger for the popover. MouseFilter
        // = Stop so MouseEntered/Exited fire on this label specifically;
        // GuiInput handles click here too so toggling works whether you
        // click the name or the row's other areas.
        var nameLabel = new Label
        {
            Text = skill.Label,
            SizeFlagsHorizontal = SizeFlags.ExpandFill,
            MouseFilter = MouseFilterEnum.Stop,
        };
        rowH.AddChild(nameLabel);
        nameLabel.MouseEntered += () =>
            PopoverLayer.Instance?.ShowFor(nameLabel, skill.Label, skill.Description, skill.Ability, false);
        nameLabel.MouseExited += () =>
            PopoverLayer.Instance?.ScheduleClose();
        if (clickable)
        {
            nameLabel.GuiInput += (InputEvent e) =>
            {
                if (e is InputEventMouseButton mb && mb.Pressed && mb.ButtonIndex == MouseButton.Left)
                    Toggle(skill.JsonId, isChecked, required);
            };
        }

        rowH.AddChild(new Label
        {
            Text = fromBg ? "BG" : (fromClass ? "CLASS" : "—"),
            MouseFilter = MouseFilterEnum.Ignore,
        });

        // Click anywhere else on the row → also toggles. Hover here doesn't
        // trigger the popover; only the name label does.
        if (clickable)
        {
            row.GuiInput += (InputEvent e) =>
            {
                if (e is InputEventMouseButton mb && mb.Pressed && mb.ButtonIndex == MouseButton.Left)
                    Toggle(skill.JsonId, isChecked, required);
            };
        }

        return row;
    }

    private void Toggle(string skillId, bool isCurrentlyChecked, int required)
    {
        var newChosen = new Godot.Collections.Array<string>(_draft.ChosenSkills);
        if (isCurrentlyChecked)
        {
            newChosen.Remove(skillId);
        }
        else if (newChosen.Count < required)
        {
            newChosen.Add(skillId);
        }
        else
        {
            return; // already at the cap; no-op
        }
        _draft.Patch(new Godot.Collections.Dictionary
        {
            { "chosen_skills", newChosen },
        });
    }
}
