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

namespace Theriapolis.Game.CodexUI.Drag;

/// <summary>
/// Screen-level coordinator for click-drag interactions. Source widgets call
/// <see cref="BeginDrag"/> on left-mouse-down within their bounds and supply
/// (a) an arbitrary payload object — game-specific state, e.g. <c>StatPoolPayload</c> —
/// and (b) a ghost callback that paints a small follow-the-cursor visual.
///
/// Drop targets register via <see cref="RegisterTarget"/>; on left-mouse-up
/// the controller hit-tests in registration order and fires <see cref="OnDrop"/>
/// with the matching target's id. Pressing <see cref="Keys.Escape"/> mid-drag
/// cancels and fires <see cref="OnCancel"/>.
///
/// One-controller-per-screen; concrete screens own the instance and pass it
/// down to widgets that participate in drag-drop.
/// </summary>
public sealed class DragDropController
{
    public bool IsDragging => _payload is not null;
    public object? Payload => _payload;
    public Point CursorPosition { get; private set; }

    private object? _payload;
    private System.Action<SpriteBatch, Point>? _ghost;

    private readonly System.Collections.Generic.List<DropTarget> _targets = new();

    public event System.Action<object, string>? OnDrop;       // (payload, targetId)
    public event System.Action<object>? OnCancel;
    public event System.Action<object, Point>? OnDropAnywhere; // fired when drop lands outside any registered target

    public void BeginDrag(object payload, System.Action<SpriteBatch, Point> ghost)
    {
        _payload = payload;
        _ghost = ghost;
    }

    public void RegisterTarget(string id, Rectangle bounds)
        => _targets.Add(new DropTarget(id, bounds));

    public void ClearTargets() => _targets.Clear();

    public void Update(GameTime gt, CodexInput input)
    {
        CursorPosition = input.MousePosition;
        if (!IsDragging) { _targets.Clear(); return; }

        if (input.KeyJustPressed(Keys.Escape))
        {
            OnCancel?.Invoke(_payload!);
            _payload = null;
            _ghost = null;
            _targets.Clear();
            return;
        }

        if (input.LeftJustReleased)
        {
            string? hit = null;
            foreach (var t in _targets)
                if (t.Bounds.Contains(input.MousePosition)) { hit = t.Id; break; }
            if (hit is not null)      OnDrop?.Invoke(_payload!, hit);
            else                       OnDropAnywhere?.Invoke(_payload!, input.MousePosition);
            _payload = null;
            _ghost = null;
            _targets.Clear();
        }
    }

    public void Draw(SpriteBatch sb)
    {
        if (_payload is null || _ghost is null) return;
        _ghost(sb, CursorPosition);
    }

    private readonly struct DropTarget
    {
        public readonly string Id;
        public readonly Rectangle Bounds;
        public DropTarget(string id, Rectangle bounds) { Id = id; Bounds = bounds; }
    }
}

/// <summary>
/// Payload type for the stat-assignment drag-drop dance. Mirrors the React
/// design's <c>{from, value, idx, ability}</c> object so behavior ports verbatim.
/// </summary>
public sealed class StatPoolPayload
{
    public required string Source { get; init; }   // "pool" or "slot"
    public required int    Value  { get; init; }
    public int? PoolIdx { get; init; }              // index in pool list when Source == "pool"
    public Theriapolis.Core.Rules.Stats.AbilityId? Ability { get; init; } // when Source == "slot"
}
