using Godot;

namespace Theriapolis.GodotHost.Scenes.Widgets;

/// <summary>
/// Card-style PanelContainer helpers. The codex Theme defines three
/// styleboxes for type-variation "Card":
///   - "panel"           → unselected look (Bg2 fill, Rule border)
///   - "panel_hover"     → gild border, slightly heavier weight
///   - "panel_selected"  → seal-red border + soft red shadow
///
/// State is held in Godot meta on the card so hover and selected can be
/// driven independently by different call sites (Make wires the hover
/// signals; SetSelected is called by step Refresh handlers). The active
/// stylebox is picked by Apply: selected beats hover beats default.
/// </summary>
public static class CodexCard
{
    private const string SelectedMeta = "codex_card_selected";
    private const string HoverMeta    = "codex_card_hover";

    /// <summary>
    /// Creates a PanelContainer with ThemeTypeVariation = "Card" plus
    /// hover signal wiring. MouseEntered marks hover; MouseExited defers
    /// a recheck against the card's global rect so moving the cursor
    /// from the card body onto an inner Button (which captures the
    /// parent's MouseExited via mouse-filter Stop) does not clear the
    /// hover state — the cursor is still visually within the card.
    /// </summary>
    public static PanelContainer Make()
    {
        var card = new PanelContainer
        {
            ThemeTypeVariation = "Card",
            MouseFilter = Control.MouseFilterEnum.Stop,
        };
        card.MouseEntered += () => SetHover(card, true);
        card.MouseExited  += () =>
            Callable.From(() => RecheckHover(card)).CallDeferred();
        return card;
    }

    private static void RecheckHover(PanelContainer card)
    {
        if (!GodotObject.IsInstanceValid(card)) return;
        // Hover stays true as long as the cursor is anywhere within the
        // card's rect (including over any child control). Drop only when
        // the cursor has truly left the card area.
        bool stillOver = card.GetGlobalRect().HasPoint(card.GetGlobalMousePosition());
        SetHover(card, stillOver);
    }

    public static void SetSelected(PanelContainer card, bool selected)
    {
        card.SetMeta(SelectedMeta, selected);
        ApplyOrDefer(card);
    }

    private static void SetHover(PanelContainer card, bool hover)
    {
        card.SetMeta(HoverMeta, hover);
        ApplyOrDefer(card);
    }

    /// <summary>
    /// Apply now if the card is already in the scene tree, otherwise defer
    /// until end-of-frame so the parent theme cascade is reachable. Step
    /// builders call SetSelected on a freshly-created card before
    /// AddChild — the theme isn't visible at that point and HasThemeStylebox
    /// returns false, which previously meant the override silently dropped
    /// and only re-attached when MouseEntered later re-ran Apply.
    /// </summary>
    private static void ApplyOrDefer(PanelContainer card)
    {
        if (card.IsInsideTree())
        {
            Apply(card);
            return;
        }
        Callable.From(() =>
        {
            if (GodotObject.IsInstanceValid(card)) Apply(card);
        }).CallDeferred();
    }

    private static void Apply(PanelContainer card)
    {
        bool selected = card.HasMeta(SelectedMeta) && (bool)card.GetMeta(SelectedMeta);
        bool hover    = card.HasMeta(HoverMeta)    && (bool)card.GetMeta(HoverMeta);

        // Priority: selected > hover > default. The default branch removes
        // the override so the type variation's "panel" stylebox applies.
        StringName picked = selected ? "panel_selected"
                          : hover    ? "panel_hover"
                                     : "panel";

        if (picked == "panel" || !card.HasThemeStylebox(picked, "Card"))
            card.RemoveThemeStyleboxOverride("panel");
        else
            card.AddThemeStyleboxOverride("panel", card.GetThemeStylebox(picked, "Card"));
    }
}
