using System;
using System.Collections.Generic;
using Godot;

namespace Theriapolis.GodotHost.UI.Widgets;

/// <summary>
/// Codex stepper. Mirrors <c>.stepper / .step</c> in the React prototype:
/// horizontal grid of N steps, each with a Roman numeral and an uppercased
/// name. Per-step state drives colour and the gild-coloured underline:
/// Active (current), Complete (✓ + seal), Locked (✕ + dimmed). Click on
/// any non-locked step emits <see cref="StepClicked"/>.
///
/// Lock semantics mirror <c>app.jsx</c>: a step is locked iff some
/// earlier step has unmet validation. The owning screen passes a per-step
/// <see cref="StepState"/> array; this widget just renders.
/// </summary>
public partial class CodexStepper : HBoxContainer
{
    public enum StepState { Pending, Active, Complete, Locked }

    [Signal] public delegate void StepClickedEventHandler(int index);

    private readonly List<StepEntry> _entries = new();

    public void SetSteps(IReadOnlyList<string> names, IReadOnlyList<StepState> states)
    {
        if (names.Count != states.Count)
            throw new ArgumentException("names and states must be the same length");

        // Rebuild children from scratch — N is small (<=8) so this is cheap.
        foreach (var child in GetChildren())
            child.QueueFree();
        _entries.Clear();

        for (int i = 0; i < names.Count; i++)
        {
            var entry = BuildStep(i, names[i], states[i], isLast: i == names.Count - 1);
            AddChild(entry.Container);
            _entries.Add(entry);
        }
    }

    private StepEntry BuildStep(int index, string name, StepState state, bool isLast)
    {
        // Outer cell — VBoxContainer with mouse handling on a button child
        // so we get press events for click-to-jump. SizeFlagsHorizontal
        // expand makes each cell take an equal share of the width.
        var btn = new Button
        {
            ThemeTypeVariation = "GhostButton",
            Flat = true,
            FocusMode = FocusModeEnum.None,
            CustomMinimumSize = new Vector2(0, 64),
            SizeFlagsHorizontal = SizeFlags.ExpandFill,
            ToggleMode = false,
        };
        // Build a vbox child for two stacked labels. Button isn't a
        // Container, so the vbox doesn't get auto-laid-out — anchor it to
        // fill the button's rect explicitly. Without this, the vbox sits at
        // (0,0) with intrinsic size and the labels render in the top-left
        // corner instead of centered.
        var vbox = new VBoxContainer
        {
            MouseFilter = MouseFilterEnum.Ignore,
            Alignment = BoxContainer.AlignmentMode.Center,
        };
        vbox.AnchorRight = 1f;
        vbox.AnchorBottom = 1f;
        vbox.OffsetLeft = 0;
        vbox.OffsetRight = 0;
        vbox.OffsetTop = 0;
        vbox.OffsetBottom = 0;
        btn.AddChild(vbox);

        var num = new Label
        {
            Text = state == StepState.Locked ? "✕" : Roman(index + 1),
            HorizontalAlignment = HorizontalAlignment.Center,
            ThemeTypeVariation = "StepperNum",
            MouseFilter = MouseFilterEnum.Ignore,
        };
        vbox.AddChild(num);

        var lbl = new Label
        {
            Text = name.ToUpperInvariant(),
            HorizontalAlignment = HorizontalAlignment.Center,
            ThemeTypeVariation = "StepperName",
            MouseFilter = MouseFilterEnum.Ignore,
        };
        vbox.AddChild(lbl);

        ApplyStateColors(num, lbl, state);

        if (state != StepState.Locked)
            btn.Pressed += () => EmitSignal(SignalName.StepClicked, index);
        else
            btn.Disabled = true;

        // The active step gets a 2-px gild underline. Implement as a
        // ColorRect bottom-anchored at -1 within the button.
        if (state == StepState.Active)
        {
            var underline = new ColorRect
            {
                Color = TryGetThemeColor("font_color", "Gild") ?? new Color("#b48a3c"),
                MouseFilter = MouseFilterEnum.Ignore,
            };
            underline.AnchorTop = 1.0f;
            underline.AnchorBottom = 1.0f;
            underline.AnchorLeft = 0.14f;
            underline.AnchorRight = 0.86f;
            underline.OffsetTop = -2;
            underline.OffsetBottom = 0;
            btn.AddChild(underline);
        }

        return new StepEntry(btn, num, lbl);
    }

    private static void ApplyStateColors(Label num, Label name, StepState state)
    {
        // Default theme colours come from the StepperNum/StepperName variations
        // (ink-mute). State overrides bring active steps to ink and complete
        // to seal-red. Locked uses the dim default plus reduced opacity.
        Color? numColor = state switch
        {
            StepState.Active   => TryGetGlobalThemeColor("Ink", new Color("#2b1d10")),
            StepState.Complete => TryGetGlobalThemeColor("Seal", new Color("#7a1f12")),
            _                  => null,
        };
        Color? nameColor = state switch
        {
            StepState.Active   => TryGetGlobalThemeColor("Ink", new Color("#2b1d10")),
            _                  => null,
        };

        if (numColor.HasValue)  num.AddThemeColorOverride("font_color", numColor.Value);
        if (nameColor.HasValue) name.AddThemeColorOverride("font_color", nameColor.Value);

        if (state == StepState.Complete)
            num.Text = "✓ " + num.Text;

        if (state == StepState.Locked)
        {
            num.Modulate  = new Color(1, 1, 1, 0.45f);
            name.Modulate = new Color(1, 1, 1, 0.45f);
        }
    }

    private static Color? TryGetGlobalThemeColor(string name, Color fallback) => fallback;

    private Color? TryGetThemeColor(string property, string variation)
    {
        if (HasThemeColor(property, variation)) return GetThemeColor(property, variation);
        return null;
    }

    private static string Roman(int n) => n switch
    {
        1 => "I",
        2 => "II",
        3 => "III",
        4 => "IV",
        5 => "V",
        6 => "VI",
        7 => "VII",
        8 => "VIII",
        9 => "IX",
        10 => "X",
        _ => n.ToString(),
    };

    private readonly record struct StepEntry(Button Container, Label Num, Label Name);
}
