using FontStashSharp;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Theriapolis.Game.CodexUI.Core;

namespace Theriapolis.Game.CodexUI.Widgets;

/// <summary>
/// Single floating popover panel. The screen owns one instance; widgets
/// (chips, bonus pills) request it to show by calling <see cref="Show"/>
/// with their trigger bounds + content. Visibility decays automatically
/// when the trigger no longer reports as hovered. Position is clamped to
/// the viewport so popovers near the right/bottom edges flip to fit.
///
/// Mirrors the React design's <c>.trait-hint</c>: parchment fill, gilded
/// border, italic display title + tag pill, body paragraph in serif body
/// face. The "Plainly Reading" footnote is supported via <see cref="Reading"/>.
/// </summary>
public sealed class CodexHoverPopover : CodexWidget
{
    private readonly CodexAtlas _atlas;
    private string _title = "";
    private string _body = "";
    private string? _tag;
    private string? _reading;
    private bool _detriment;
    private Rectangle _triggerBounds;
    private bool _showRequestedThisFrame;
    public bool IsShown { get; private set; }
    public string? Reading { get => _reading; set => _reading = value; }

    public CodexHoverPopover(CodexAtlas atlas)
    {
        _atlas = atlas;
    }

    /// <summary>
    /// Request the popover. Called from a widget's Update when it detects
    /// hover. The popover stays visible only as long as some widget requests
    /// it each frame.
    /// </summary>
    public void Show(Rectangle triggerBounds, string title, string body, string? tag = null, bool detriment = false)
    {
        _triggerBounds = triggerBounds;
        _title = title;
        _body = body;
        _tag = tag;
        _detriment = detriment;
        _showRequestedThisFrame = true;
    }

    protected override Point MeasureCore(Point available) => Point.Zero;
    protected override void ArrangeCore(Rectangle bounds) { }

    public override void Update(GameTime gt, CodexInput input)
    {
        IsShown = _showRequestedThisFrame;
        _showRequestedThisFrame = false;
    }

    public override void Draw(SpriteBatch sb, GameTime gt)
    {
        if (!IsShown) return;

        const int width = 320;
        var titleFont = CodexFonts.SerifItalic;
        var bodyFont = CodexFonts.SerifBody;
        var tagFont = CodexFonts.MonoTagSmall;

        // Wrap body text to the width.
        var titleLines = CodexLabel.WrapText(_title, titleFont, width - 32);
        var bodyLines  = CodexLabel.WrapText(_body, bodyFont, width - 32);
        var readingLines = string.IsNullOrEmpty(_reading) ? System.Array.Empty<string>() : CodexLabel.WrapText(_reading!, bodyFont, width - 32);

        int height = 14
                   + (int)(titleFont.LineHeight * titleLines.Length)
                   + 6
                   + (int)(bodyFont.LineHeight * bodyLines.Length)
                   + (readingLines.Length > 0 ? 8 + (int)(bodyFont.LineHeight * readingLines.Length) + 4 : 0)
                   + 12;

        // Position — prefer below the trigger, flip above if it doesn't
        // fit there, and as a last resort clamp to whichever edge gives
        // more room. Earlier code clamped only with `if (y < 8) y = 8`,
        // which would push the popover off the bottom whenever the
        // trigger sat near the viewport's bottom edge and the popover
        // didn't fit above either.
        int x = _triggerBounds.X;
        if (x + width > _viewport.Width) x = _viewport.Width - width - 8;
        if (x < 8) x = 8;

        int spaceBelow = _viewport.Height - _triggerBounds.Bottom - 6;
        int spaceAbove = _triggerBounds.Y - 6;
        int y;
        if (height <= spaceBelow)
        {
            y = _triggerBounds.Bottom + 6;
        }
        else if (height <= spaceAbove)
        {
            y = _triggerBounds.Y - height - 6;
        }
        else
        {
            // Doesn't fit either side; clamp so the popover sits within
            // the viewport with at least an 8-px margin on the limiting side.
            y = System.Math.Max(8, _viewport.Height - height - 8);
        }

        var rect = new Rectangle(x, y, width, height);

        // Background
        sb.Draw(_atlas.Pixel, rect, CodexColors.Bg2);
        Color border = _detriment ? CodexColors.Seal : CodexColors.Gild;
        sb.Draw(_atlas.Pixel, new Rectangle(rect.X, rect.Y, rect.Width, 1), border);
        sb.Draw(_atlas.Pixel, new Rectangle(rect.X, rect.Bottom - 1, rect.Width, 1), border);
        sb.Draw(_atlas.Pixel, new Rectangle(rect.X, rect.Y, 1, rect.Height), border);
        sb.Draw(_atlas.Pixel, new Rectangle(rect.Right - 1, rect.Y, 1, rect.Height), border);

        int cy = rect.Y + 12;
        // Title (+ optional tag)
        foreach (var line in titleLines)
        {
            titleFont.DrawText(sb, line, new Vector2(rect.X + 16, cy), CodexColors.Ink);
            cy += (int)titleFont.LineHeight;
        }
        if (!string.IsNullOrEmpty(_tag))
        {
            var tagSize = tagFont.MeasureString(_tag);
            int tagX = rect.X + 16;
            int tagY = cy;
            sb.Draw(_atlas.Pixel, new Rectangle(tagX, tagY, (int)tagSize.X + 12, (int)tagFont.LineHeight + 4), Color.Transparent);
            sb.Draw(_atlas.Pixel, new Rectangle(tagX, tagY, (int)tagSize.X + 12, 1), CodexColors.Seal);
            sb.Draw(_atlas.Pixel, new Rectangle(tagX, tagY + (int)tagFont.LineHeight + 3, (int)tagSize.X + 12, 1), CodexColors.Seal);
            sb.Draw(_atlas.Pixel, new Rectangle(tagX, tagY, 1, (int)tagFont.LineHeight + 4), CodexColors.Seal);
            sb.Draw(_atlas.Pixel, new Rectangle(tagX + (int)tagSize.X + 11, tagY, 1, (int)tagFont.LineHeight + 4), CodexColors.Seal);
            tagFont.DrawText(sb, _tag, new Vector2(tagX + 6, tagY + 2), CodexColors.Seal);
            cy += (int)tagFont.LineHeight + 6;
        }
        else cy += 6;

        // Body
        foreach (var line in bodyLines)
        {
            bodyFont.DrawText(sb, line, new Vector2(rect.X + 16, cy), CodexColors.InkSoft);
            cy += (int)bodyFont.LineHeight;
        }

        if (readingLines.Length > 0)
        {
            cy += 4;
            sb.Draw(_atlas.Pixel, new Rectangle(rect.X + 16, cy, rect.Width - 32, 1), new Color(CodexColors.Rule.R, CodexColors.Rule.G, CodexColors.Rule.B, (byte)128));
            cy += 6;
            foreach (var line in readingLines)
            {
                bodyFont.DrawText(sb, line, new Vector2(rect.X + 16, cy), CodexColors.InkMute);
                cy += (int)bodyFont.LineHeight;
            }
        }

        Reading = null; // consumed
    }

    private Rectangle _viewport = new(0, 0, 1280, 800);
    public void UpdateViewport(Rectangle vp) => _viewport = vp;
}
