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

namespace Theriapolis.Game.CodexUI.Widgets;

/// <summary>
/// Vertical scroll container. Measures its child at unbounded height to
/// learn the full content size, then arranges the child shifted by
/// <see cref="ScrollOffset"/>. Mouse-wheel input changes the offset; bounds
/// hit-testing for hover/click still uses screen-space, so widgets that
/// scroll out of view simply stop receiving cursor events.
///
/// Drawing is uncapped — child widgets draw in their offset positions, so
/// content above/below the visible band can spill into adjacent regions.
/// The screen's stepper and nav bar are painted with opaque backgrounds
/// to mask this overflow, which is cheaper than scissor clipping and avoids
/// the SpriteBatch end/restart dance.
/// </summary>
public sealed class ScrollPanel : CodexWidget
{
    public CodexWidget? Child { get; set; }
    public int ScrollOffset { get; private set; }
    private int _contentHeight;
    private readonly CodexAtlas _atlas;

    /// <summary>
    /// Fires whenever the wheel changes the scroll offset. The wizard
    /// uses this to persist offset across <c>InvalidateLayout</c>: the
    /// rebuilt tree creates a new <see cref="ScrollPanel"/>, but the
    /// stored value gets re-applied via <see cref="SetInitialScroll"/>.
    /// </summary>
    public System.Action<int>? OnScrollChanged { get; set; }

    public ScrollPanel(CodexAtlas atlas, CodexWidget? child = null)
    {
        _atlas = atlas;
        Child = child;
        if (child is not null) child.Parent = this;
    }

    /// <summary>Restore a saved offset before the first measure-arrange pass runs.</summary>
    public void SetInitialScroll(int offset) => ScrollOffset = offset;

    protected override Point MeasureCore(Point available)
    {
        if (Child is null)
        {
            _contentHeight = 0;
            return new Point(available.X, available.Y);
        }
        var s = Child.Measure(new Point(System.Math.Max(0, available.X - 8), int.MaxValue / 2));
        _contentHeight = s.Y;
        return new Point(available.X, available.Y);
    }

    protected override void ArrangeCore(Rectangle bounds)
    {
        ClampScroll();
        Child?.Arrange(new Rectangle(bounds.X, bounds.Y - ScrollOffset,
                                     System.Math.Max(0, bounds.Width - 8), _contentHeight));
    }

    public override void Update(GameTime gt, CodexInput input)
    {
        if (Bounds.Contains(input.MousePosition) && input.ScrollDelta != 0)
        {
            ScrollOffset -= input.ScrollDelta / 2;
            ClampScroll();
            Child?.Arrange(new Rectangle(Bounds.X, Bounds.Y - ScrollOffset,
                                         System.Math.Max(0, Bounds.Width - 8), _contentHeight));
            OnScrollChanged?.Invoke(ScrollOffset);
        }

        // Nest a clip into the visible viewport so children scrolled out
        // of view don't register hover/click. Intersect with any outer
        // clip the parent already set so we never widen its scope.
        var prevClip = input.GetMouseClip();
        var newClip  = prevClip is Rectangle p ? Rectangle.Intersect(p, Bounds) : Bounds;
        input.SetMouseClip(newClip);
        Child?.Update(gt, input);
        if (prevClip is Rectangle r) input.SetMouseClip(r);
        else                          input.ClearMouseClip();
    }

    public override void Draw(SpriteBatch sb, GameTime gt)
    {
        Child?.Draw(sb, gt);

        // Scrollbar thumb on the right edge — only when content overflows.
        if (_contentHeight > Bounds.Height)
        {
            int trackX = Bounds.Right - 4;
            int trackH = Bounds.Height;
            sb.Draw(_atlas.Pixel, new Rectangle(trackX, Bounds.Y, 2, trackH),
                    new Color(CodexColors.Rule.R, CodexColors.Rule.G, CodexColors.Rule.B, (byte)80));

            int thumbH = System.Math.Max(24, (int)((float)trackH * trackH / _contentHeight));
            float t = (float)ScrollOffset / System.Math.Max(1, _contentHeight - trackH);
            int thumbY = Bounds.Y + (int)((trackH - thumbH) * t);
            sb.Draw(_atlas.Pixel, new Rectangle(trackX, thumbY, 2, thumbH), CodexColors.Gild);
        }
    }

    private void ClampScroll()
    {
        int max = System.Math.Max(0, _contentHeight - Bounds.Height);
        if (ScrollOffset < 0) ScrollOffset = 0;
        if (ScrollOffset > max) ScrollOffset = max;
    }
}
