using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Theriapolis.Core.Rules.Stats;
using Theriapolis.Game.CodexUI.Core;
using Theriapolis.Game.CodexUI.Drag;
using Theriapolis.Game.CodexUI.Widgets;
using Theriapolis.Game.CodexUI.Screens;
using Theriapolis.Game.UI;

namespace Theriapolis.Game.CodexUI.Steps;

/// <summary>
/// Step V — Abilities. Method tabs at the top, dashed-bordered pool of
/// draggable value tiles below, six ability rows (drop targets) below
/// that. The right side of the pool row hosts the inline action buttons:
/// Reroll (roll mode only), Auto-assign, Clear.
/// </summary>
public static class StepStats
{
    public static CodexWidget Build(CodexCharacterCreationScreen s, CodexAtlas atlas, CodexHoverPopover popover, DragDropController drag)
    {
        var col = new Column { Spacing = 14 };
        col.Add(StepCommon.PageIntro(
            "Folio V — Of Aptitudes",
            "Set your Abilities",
            "Six numbers describe what your body and mind can do. Drag values from the pool onto the abilities — or click a value, then click an ability."));

        // Method tabs
        var tabs = new Row { Spacing = 0 };
        tabs.Add(new MethodTab("Standard Array", !s.UseRoll, atlas,
                  () => { s.UseRoll = false; s.InitStandardArrayPool(); s.InvalidateLayout(); }));
        tabs.Add(new MethodTab("Roll 4d6 — drop lowest", s.UseRoll, atlas,
                  () => { s.UseRoll = true;  s.RollAndPool();         s.InvalidateLayout(); }));
        col.Add(tabs);

        // Pool row (with action buttons)
        col.Add(new PoolBox(s, atlas, drag));

        // Roll history
        if (s.UseRoll && s.StatHistory.Count > 1)
        {
            string hist = string.Join("   ", s.StatHistory.Take(s.StatHistory.Count - 1).TakeLast(3).Select(h => "[" + string.Join(", ", h) + "]"));
            col.Add(new CodexLabel("Previous rolls: " + hist, CodexFonts.MonoTagSmall, CodexColors.InkMute));
        }

        // Six ability rows
        foreach (var ab in CodexCopy.AbilityOrder)
        {
            var row = new CodexAbilityRow(ab, atlas, drag)
            {
                Assigned = s.StatAssign.TryGetValue(ab, out var v) ? v : (int?)null,
                Bonus = s.TotalBonus(ab),
                LongName = CodexCopy.AbilityLabels[ab],
                IsPrimary = s.IsPrimary(ab),
                BonusSourceText = ComposeBonusSourceText(s, ab),
            };
            row.OnSlotClick = () =>
            {
                if (row.Assigned is int existing)
                {
                    s.StatPool.Add(existing);
                    s.StatAssign.Remove(ab);
                    s.PendingPoolIdx = null;
                    s.InvalidateLayout();
                }
                else if (s.PendingPoolIdx is int pidx && pidx < s.StatPool.Count)
                {
                    s.StatAssign[ab] = s.StatPool[pidx];
                    s.StatPool.RemoveAt(pidx);
                    s.PendingPoolIdx = null;
                    s.InvalidateLayout();
                }
            };
            row.OnDragStart = payload => drag.BeginDrag(payload, (sb, p) => DrawDieGhost(sb, atlas, p, payload.Value));
            col.Add(row);
        }
        return col;
    }

    private static string ComposeBonusSourceText(CodexCharacterCreationScreen s, AbilityId ab)
    {
        var parts = new System.Collections.Generic.List<string>();
        int cm = s.CladeMod(ab);
        int sm = s.SpeciesMod(ab);
        if (cm != 0) parts.Add($"{s.Clade?.Name ?? "Clade"} {(cm >= 0 ? "+" : "")}{cm}");
        if (sm != 0) parts.Add($"{s.Species?.Name ?? "Species"} {(sm >= 0 ? "+" : "")}{sm}");
        return string.Join(" · ", parts);
    }

    private static void DrawDieGhost(SpriteBatch sb, CodexAtlas atlas, Point cursor, int value)
    {
        var rect = new Rectangle(cursor.X - 28, cursor.Y - 28, 56, 56);
        var border = CodexColors.Gild;
        sb.Draw(atlas.Pixel, rect, new Color(CodexColors.Bg.R, CodexColors.Bg.G, CodexColors.Bg.B, (byte)200));
        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);
        var font = CodexFonts.DisplayMedium;
        string label = value.ToString();
        var sz = font.MeasureString(label);
        font.DrawText(sb, label, new Vector2(rect.X + (rect.Width - sz.X) / 2f, rect.Y + (rect.Height - font.LineHeight) / 2f), CodexColors.Ink);
    }
}

internal sealed class MethodTab : CodexWidget
{
    private readonly string _label;
    private readonly bool _active;
    private readonly System.Action _onClick;
    private readonly CodexAtlas _atlas;
    private readonly FontStashSharp.SpriteFontBase _font = CodexFonts.DisplaySmall;
    private bool _hovered;

    public MethodTab(string label, bool active, CodexAtlas atlas, System.Action onClick)
    {
        _label = label;
        _active = active;
        _atlas = atlas;
        _onClick = onClick;
    }

    protected override Point MeasureCore(Point available)
    {
        var s = _font.MeasureString(_label);
        return new Point((int)s.X + 36, (int)System.MathF.Ceiling(_font.LineHeight) + 20);
    }
    protected override void ArrangeCore(Rectangle bounds) { }

    public override void Update(GameTime gt, CodexInput input)
    {
        _hovered = ContainsPoint(input.MousePosition);
        if (_hovered && input.LeftJustReleased) _onClick();
    }

    public override void Draw(SpriteBatch sb, GameTime gt)
    {
        sb.Draw(_atlas.Pixel, new Rectangle(Bounds.X, Bounds.Bottom - 1, Bounds.Width, 1), CodexColors.Rule);
        if (_active)
            sb.Draw(_atlas.Pixel, new Rectangle(Bounds.X, Bounds.Bottom - 2, Bounds.Width, 2), CodexColors.Gild);
        var color = _active ? CodexColors.Ink : (_hovered ? CodexColors.Gild : CodexColors.InkMute);
        var s = _font.MeasureString(_label);
        _font.DrawText(sb, _label, new Vector2(Bounds.X + (Bounds.Width - s.X) / 2f,
                                                Bounds.Y + (Bounds.Height - _font.LineHeight) / 2f - 2),
                       color);
    }
}

/// <summary>
/// Pool widget that holds the draggable value tiles + the inline action
/// buttons. Acts as a drop target so values dragged out of a slot can land
/// back in the pool.
/// </summary>
internal sealed class PoolBox : CodexWidget
{
    private readonly CodexCharacterCreationScreen _s;
    private readonly CodexAtlas _atlas;
    private readonly DragDropController _drag;
    private readonly System.Collections.Generic.List<CodexPoolDie> _dice = new();
    private readonly System.Collections.Generic.List<CodexButton> _actions = new();

    public PoolBox(CodexCharacterCreationScreen s, CodexAtlas atlas, DragDropController drag)
    {
        _s = s;
        _atlas = atlas;
        _drag = drag;
    }

    private void Rebuild()
    {
        _dice.Clear();
        for (int i = 0; i < _s.StatPool.Count; i++)
        {
            int idx = i;
            int v = _s.StatPool[i];
            var die = new CodexPoolDie(v, idx, _atlas, _drag)
            {
                IsSelected = _s.PendingPoolIdx == idx,
            };
            die.OnDragStart = _ => { /* drag begin handled inside the die */ };
            die.OnClick = () => { _s.PendingPoolIdx = (_s.PendingPoolIdx == idx ? null : (int?)idx); _s.InvalidateLayout(); };
            _dice.Add(die);
        }

        _actions.Clear();
        if (_s.UseRoll)
            _actions.Add(new CodexButton("Reroll", _atlas, CodexButtonVariant.Small,
                                          onClick: () => { _s.RollAndPool(); _s.InvalidateLayout(); }));
        var auto = new CodexButton("Auto-assign", _atlas, CodexButtonVariant.Small,
                                    onClick: () => { _s.AutoAssignByClassPriority(); _s.InvalidateLayout(); });
        auto.Enabled = _s.StatPool.Count > 0;
        _actions.Add(auto);
        var clear = new CodexButton("Clear", _atlas, CodexButtonVariant.Small,
                                     onClick: () => { _s.ClearAssignments(); _s.InvalidateLayout(); });
        clear.Enabled = _s.StatAssign.Count > 0;
        _actions.Add(clear);
    }

    protected override Point MeasureCore(Point available)
    {
        Rebuild();
        return new Point(available.X, 90);
    }

    protected override void ArrangeCore(Rectangle bounds)
    {
        // Pool dice on the left, actions on the right.
        int x = bounds.X + 14;
        int y = bounds.Y + (bounds.Height - 56) / 2;
        foreach (var d in _dice)
        {
            var s = d.Measure(new Point(56, 56));
            d.Arrange(new Rectangle(x, y, s.X, s.Y));
            x += s.X + CodexDensity.ColGap;
        }
        // Right-aligned action stack.
        int rightX = bounds.X + bounds.Width - 14;
        for (int i = _actions.Count - 1; i >= 0; i--)
        {
            var a = _actions[i];
            var s = a.Measure(new Point(160, 32));
            rightX -= s.X;
            a.Arrange(new Rectangle(rightX, y + (56 - s.Y) / 2, s.X, s.Y));
            rightX -= 8;
        }
    }

    public override void Update(GameTime gt, CodexInput input)
    {
        foreach (var d in _dice) d.Update(gt, input);
        foreach (var a in _actions) a.Update(gt, input);
        if (_drag.IsDragging) _drag.RegisterTarget("pool", Bounds);
    }

    public override void Draw(SpriteBatch sb, GameTime gt)
    {
        bool over = _drag.IsDragging && Bounds.Contains(_drag.CursorPosition);
        var fill = over
            ? new Color(CodexColors.Seal.R, CodexColors.Seal.G, CodexColors.Seal.B, (byte)20)
            : new Color(CodexColors.Gild.R, CodexColors.Gild.G, CodexColors.Gild.B, (byte)8);
        sb.Draw(_atlas.Pixel, Bounds, fill);
        // Dashed border (4-px dashes / 3-px gaps)
        var border = over ? CodexColors.Seal : CodexColors.Rule;
        for (int x = Bounds.X; x < Bounds.Right; x += 7)
        {
            int w = System.Math.Min(4, Bounds.Right - x);
            sb.Draw(_atlas.Pixel, new Rectangle(x, Bounds.Y, w, 1), border);
            sb.Draw(_atlas.Pixel, new Rectangle(x, Bounds.Bottom - 1, w, 1), border);
        }
        for (int y = Bounds.Y; y < Bounds.Bottom; y += 7)
        {
            int h = System.Math.Min(4, Bounds.Bottom - y);
            sb.Draw(_atlas.Pixel, new Rectangle(Bounds.X, y, 1, h), border);
            sb.Draw(_atlas.Pixel, new Rectangle(Bounds.Right - 1, y, 1, h), border);
        }

        if (_dice.Count == 0)
        {
            var font = CodexFonts.MonoTagSmall;
            string msg = "ALL VALUES ASSIGNED. DRAG FROM A SLOT TO RETURN.";
            var s = font.MeasureString(msg);
            font.DrawText(sb, msg,
                          new Vector2(Bounds.X + 14, Bounds.Y + (Bounds.Height - font.LineHeight) / 2f),
                          CodexColors.InkMute);
        }
        foreach (var d in _dice) d.Draw(sb, gt);
        foreach (var a in _actions) a.Draw(sb, gt);
    }
}
