using Godot;

namespace Theriapolis.GodotHost.UI.Widgets;

/// <summary>
/// Hoverable text trigger that shows a floating popover with name + optional
/// tag + description. Mirrors <c>src/trait-hint.jsx</c> from the React
/// prototype — viewport-clamp horizontally and vertically, flip above/below
/// based on available space, 80ms grace period when moving from trigger to
/// popover so the popover stays open across the gap.
///
/// Used by future TraitName, SkillChip, BonusPill widgets — the React
/// version layers className differences on top of this same primitive.
/// </summary>
public partial class CodexPopover : Control
{
    [Export] public string TriggerText { get; set; } = "trait";
    [Export] public string Title { get; set; } = "";
    [Export] public string Tag { get; set; } = "";
    [Export] public string Description { get; set; } = "";
    [Export] public bool Detriment { get; set; }

    private const float GracePeriodSec = 0.08f;  // ~80 ms — matches trait-hint.jsx
    private const float ArrowOffset    = 6f;
    private const int   ViewportPad    = 8;

    private Label _trigger = null!;
    private PanelContainer? _popover;
    private Timer _closeTimer = null!;
    private bool _hovering;

    public override void _Ready()
    {
        _trigger = new Label
        {
            Text = string.IsNullOrEmpty(Title) ? TriggerText : Title,
            ThemeTypeVariation = "Label",
            MouseFilter = MouseFilterEnum.Stop,
        };
        _trigger.MouseEntered += OnTriggerEntered;
        _trigger.MouseExited += OnTriggerExited;
        AddChild(_trigger);

        _closeTimer = new Timer { OneShot = true, WaitTime = GracePeriodSec };
        _closeTimer.Timeout += HidePopover;
        AddChild(_closeTimer);

        CustomMinimumSize = _trigger.GetMinimumSize();
    }

    public override void _Notification(int what)
    {
        // Make sure the popover is freed if the trigger is removed.
        if (what == NotificationExitTree && _popover is not null)
        {
            _popover.QueueFree();
            _popover = null;
        }
    }

    private void OnTriggerEntered()
    {
        _hovering = true;
        _closeTimer.Stop();
        ShowPopover();
    }

    private void OnTriggerExited()
    {
        _hovering = false;
        _closeTimer.Start();
    }

    private void OnPopoverEntered()
    {
        _closeTimer.Stop();
    }

    private void OnPopoverExited()
    {
        _closeTimer.Start();
    }

    private void ShowPopover()
    {
        if (_popover is not null && IsInstanceValid(_popover)) { Reposition(); return; }

        _popover = BuildPopoverPanel();
        _popover.MouseEntered += OnPopoverEntered;
        _popover.MouseExited += OnPopoverExited;

        var canvas = new CanvasLayer { Layer = 100 };
        canvas.Name = "CodexPopoverCanvas";
        canvas.AddChild(_popover);
        // Attach the canvas layer to the scene's root viewport so the popover
        // floats above every other UI element regardless of where the trigger
        // lives in the tree.
        GetTree().Root.AddChild(canvas);

        // One frame later, the panel has its laid-out size; reposition with
        // viewport clamp + flip-above logic.
        CallDeferred(MethodName.Reposition);
    }

    private void HidePopover()
    {
        if (_popover is null) return;
        // Free the canvas layer parent (which owns the popover).
        _popover.GetParent()?.QueueFree();
        _popover = null;
    }

    private PanelContainer BuildPopoverPanel()
    {
        var panel = new PanelContainer
        {
            ThemeTypeVariation = "CodexPopover",
            MouseFilter = MouseFilterEnum.Pass,
            ZIndex = 100,
            // Detriment popovers swap to the seal-coloured stylebox.
            // Theme stylebox names live under "panel" for the default and
            // "panel_detriment" for the variant; we set whichever via override.
        };
        if (Detriment && panel.HasThemeStylebox("panel_detriment", "CodexPopover"))
        {
            var box = panel.GetThemeStylebox("panel_detriment", "CodexPopover");
            panel.AddThemeStyleboxOverride("panel", box);
        }

        var vbox = new VBoxContainer { CustomMinimumSize = new Vector2(220, 0) };
        panel.AddChild(vbox);

        var nameRow = new HBoxContainer();
        nameRow.AddChild(new Label
        {
            Text = string.IsNullOrEmpty(Title) ? TriggerText : Title,
            ThemeTypeVariation = "H3",
        });
        if (!string.IsNullOrEmpty(Tag))
        {
            var tagPill = new Label
            {
                Text = Tag.ToUpperInvariant(),
                ThemeTypeVariation = "ValidationOk",
            };
            nameRow.AddChild(tagPill);
        }
        if (Detriment)
        {
            var detPill = new Label
            {
                Text = "DETRIMENT",
                ThemeTypeVariation = "ValidationOk",
            };
            nameRow.AddChild(detPill);
        }
        vbox.AddChild(nameRow);

        if (!string.IsNullOrEmpty(Description))
        {
            var desc = new Label
            {
                Text = Description,
                AutowrapMode = TextServer.AutowrapMode.WordSmart,
                ThemeTypeVariation = "CardBody",
            };
            vbox.AddChild(desc);
        }

        return panel;
    }

    private void Reposition()
    {
        if (_popover is null || !IsInstanceValid(_popover)) return;

        var viewport = GetViewport().GetVisibleRect();
        var trig = _trigger.GetGlobalRect();
        var size = _popover.GetCombinedMinimumSize();

        float left = trig.Position.X;
        float top = trig.Position.Y + trig.Size.Y + ArrowOffset;
        bool flippedAbove = false;

        if (top + size.Y + ViewportPad > viewport.Size.Y &&
            trig.Position.Y - ArrowOffset - size.Y >= ViewportPad)
        {
            top = trig.Position.Y - ArrowOffset - size.Y;
            flippedAbove = true;
        }

        if (left + size.X + ViewportPad > viewport.Size.X)
            left = viewport.Size.X - size.X - ViewportPad;
        if (left < ViewportPad) left = ViewportPad;

        if (top + size.Y + ViewportPad > viewport.Size.Y)
            top = viewport.Size.Y - size.Y - ViewportPad;
        if (top < ViewportPad) top = ViewportPad;

        _popover.Position = new Vector2(left, top);
        _popover.Size = size;
        // flippedAbove can drive a future arrow image; we don't render an
        // arrow in M5 — the React version's CSS pseudo-element doesn't
        // map cleanly onto Godot's StyleBox. Border + shadow is sufficient.
        _ = flippedAbove;
    }

    public override void _Process(double delta)
    {
        // If the trigger is still mounted but the popover got orphaned for any
        // reason, drop our reference so a fresh hover re-creates it.
        if (_popover is not null && !IsInstanceValid(_popover)) _popover = null;
    }
}
