using Godot;

namespace Theriapolis.GodotHost.Scenes.Widgets;

/// <summary>
/// Shared overlay layer that owns one reusable trait popover panel.
/// Per GODOT_PORTING_GUIDE.md §6.1 — TraitChip triggers ask
/// <see cref="Instance"/> to show the popover at their global rect; the
/// popover hides as soon as the trigger fires MouseExited.
///
/// The popover itself is MouseFilter=Ignore so it never intercepts
/// input — clicks pass through to the chip's parent (card selection),
/// scroll wheel events go to the underlying ScrollContainer, and the
/// chip's hover state stays accurate when the cursor moves onto the
/// popover area (the cursor is registered as "outside the chip", so
/// MouseExited fires and we hide). This lets adjacent chips fire
/// reliably even when the previous popover overlaps them spatially.
///
/// One PopoverLayer per scene; lives as a CanvasLayer child of
/// Wizard.tscn so popovers float above every step's content. Mirrors
/// <c>src/trait-hint.jsx</c>'s viewport-clamp / flip-above behaviour.
/// </summary>
public partial class PopoverLayer : CanvasLayer
{
    public static PopoverLayer? Instance { get; private set; }

    private const float ArrowOffsetPx  = 6f;
    private const int   ViewportPadPx  = 8;

    private PanelContainer _popup = null!;
    private Label _titleLabel = null!;
    private Label _tagLabel = null!;
    private Label _descLabel = null!;

    public override void _EnterTree()
    {
        Instance = this;
    }

    public override void _ExitTree()
    {
        if (Instance == this) Instance = null;
    }

    public override void _Ready()
    {
        Layer = 100;
        BuildPopover();
        // Theme inheritance walks Control descendants only — CanvasLayer is
        // a plain Node, so it breaks the propagation chain from the Wizard
        // Control above. _Ready is bottom-up, so the parent Wizard hasn't
        // assigned its codex theme yet — defer the lookup until parent's
        // _Ready has run, then pull its theme onto the popup directly.
        CallDeferred(MethodName.InheritParentTheme);
    }

    private void InheritParentTheme()
    {
        if (GetParent() is Control parentControl && parentControl.Theme is not null)
            _popup.Theme = parentControl.Theme;
    }

    private void BuildPopover()
    {
        // Ignore so clicks/scroll/hover all pass through to whatever's
        // beneath. The popover is purely a visual readout; the chip
        // owns the lifecycle entirely. ThemeTypeVariation pulls the
        // CodexPopover stylebox (parchment bg2 + gild border + rounded
        // corners + soft shadow) defined in CodexTheme.
        _popup = new PanelContainer
        {
            Visible = false,
            MouseFilter = Control.MouseFilterEnum.Ignore,
            ZIndex = 100,
            ThemeTypeVariation = "CodexPopover",
        };
        AddChild(_popup);

        var v = new VBoxContainer { CustomMinimumSize = new Vector2(240, 0) };
        v.AddThemeConstantOverride("separation", 6);
        _popup.AddChild(v);

        var nameRow = new HBoxContainer();
        nameRow.AddThemeConstantOverride("separation", 10);
        v.AddChild(nameRow);

        // Display-serif title at H3 size (20px) — pulls the trait name
        // out of the body copy below.
        _titleLabel = new Label { ThemeTypeVariation = "H3" };
        nameRow.AddChild(_titleLabel);

        // Mono uppercase tag label, vertically centred against the title.
        _tagLabel = new Label
        {
            Visible = false,
            ThemeTypeVariation = "Eyebrow",
            VerticalAlignment = VerticalAlignment.Center,
            SizeFlagsVertical = Control.SizeFlags.ShrinkCenter,
        };
        nameRow.AddChild(_tagLabel);

        _descLabel = new Label
        {
            AutowrapMode = TextServer.AutowrapMode.WordSmart,
            CustomMinimumSize = new Vector2(220, 0),
            ThemeTypeVariation = "CardBody",
        };
        v.AddChild(_descLabel);
    }

    public void ShowFor(Control trigger, string title, string description, string tag, bool detriment)
    {
        _titleLabel.Text = title;
        _descLabel.Text = description;
        _tagLabel.Visible = !string.IsNullOrEmpty(tag) || detriment;
        _tagLabel.Text = !string.IsNullOrEmpty(tag) ? tag.ToUpperInvariant()
                                                    : (detriment ? "DETRIMENT" : "");

        // Detriment popover swaps to the seal-bordered stylebox via
        // override; non-detriment clears the override so the default
        // CodexPopover panel takes effect again.
        if (detriment && _popup.HasThemeStylebox("panel_detriment", "CodexPopover"))
        {
            var box = _popup.GetThemeStylebox("panel_detriment", "CodexPopover");
            _popup.AddThemeStyleboxOverride("panel", box);
        }
        else
        {
            _popup.RemoveThemeStyleboxOverride("panel");
        }

        _popup.Visible = true;
        _popup.ResetSize();
        Reposition(trigger);
    }

    /// <summary>Hide the popover. Was previously a 80ms-grace timer when
    /// the popover stayed alive across chip→popover hover transitions, but
    /// the popover is now non-interactive so there's no transition to
    /// cover for — close immediately.</summary>
    public void ScheduleClose() => _popup.Visible = false;

    private void Reposition(Control trigger)
    {
        var trigRect = trigger.GetGlobalRect();
        var popSize  = _popup.GetCombinedMinimumSize();
        var vp       = trigger.GetViewportRect().Size;

        // Default: under trigger, left-aligned with trigger.
        float left = trigRect.Position.X;
        float top  = trigRect.End.Y + ArrowOffsetPx;

        // Flip above if no room below and there's room above.
        if (top + popSize.Y + ViewportPadPx > vp.Y &&
            trigRect.Position.Y - ArrowOffsetPx - popSize.Y >= ViewportPadPx)
        {
            top = trigRect.Position.Y - ArrowOffsetPx - popSize.Y;
        }

        // Clamp horizontally + vertically to keep popover in-viewport.
        left = Mathf.Clamp(left, ViewportPadPx, vp.X - popSize.X - ViewportPadPx);
        top  = Mathf.Clamp(top,  ViewportPadPx, vp.Y - popSize.Y - ViewportPadPx);
        _popup.Position = new Vector2(left, top);
    }
}
