using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Theriapolis.Game.CodexUI.Core;

public enum HAlign { Left, Center, Right, Stretch }
public enum VAlign { Top,  Middle, Bottom, Stretch }

/// <summary>
/// Container widget that stacks its children vertically with <see cref="Spacing"/>
/// between them. Children are laid out top-to-bottom; their horizontal alignment
/// is governed by <see cref="HAlignChildren"/> (defaults to <see cref="HAlign.Stretch"/>).
/// </summary>
public sealed class Column : CodexWidget
{
    public System.Collections.Generic.List<CodexWidget> Children { get; } = new();
    public int Spacing { get; set; } = CodexDensity.RowGap;
    public Thickness Padding { get; set; }
    public HAlign HAlignChildren { get; set; } = HAlign.Stretch;

    public Column Add(CodexWidget c) { c.Parent = this; Children.Add(c); return this; }

    protected override Point MeasureCore(Point available)
    {
        int innerW = available.X - Padding.HorizontalSum();
        int innerH = available.Y - Padding.VerticalSum();
        int totalH = 0, maxW = 0;
        for (int i = 0; i < Children.Count; i++)
        {
            if (!Children[i].Visible) continue;
            var s = Children[i].Measure(new Point(innerW, innerH));
            totalH += s.Y;
            if (i < Children.Count - 1) totalH += Spacing;
            if (s.X > maxW) maxW = s.X;
        }
        return new Point(maxW + Padding.HorizontalSum(), totalH + Padding.VerticalSum());
    }

    protected override void ArrangeCore(Rectangle bounds)
    {
        int x = bounds.X + Padding.Left;
        int y = bounds.Y + Padding.Top;
        int innerW = bounds.Width - Padding.HorizontalSum();
        foreach (var c in Children)
        {
            if (!c.Visible) continue;
            int cw = HAlignChildren == HAlign.Stretch ? innerW : System.Math.Min(c.DesiredSize.X, innerW);
            int cx = HAlignChildren switch
            {
                HAlign.Center => x + (innerW - cw) / 2,
                HAlign.Right  => x + innerW - cw,
                _             => x,
            };
            c.Arrange(new Rectangle(cx, y, cw, c.DesiredSize.Y));
            y += c.DesiredSize.Y + Spacing;
        }
    }

    public override void Update(GameTime gt, CodexInput input)
    {
        foreach (var c in Children) if (c.Visible) c.Update(gt, input);
    }

    public override void Draw(SpriteBatch sb, GameTime gt)
    {
        foreach (var c in Children) if (c.Visible) c.Draw(sb, gt);
    }
}

/// <summary>
/// Container widget that stacks its children horizontally. Mirrors <see cref="Column"/>
/// along the perpendicular axis; vertical alignment via <see cref="VAlignChildren"/>.
/// </summary>
public sealed class Row : CodexWidget
{
    public System.Collections.Generic.List<CodexWidget> Children { get; } = new();
    public int Spacing { get; set; } = CodexDensity.ColGap;
    public Thickness Padding { get; set; }
    public VAlign VAlignChildren { get; set; } = VAlign.Top;

    public Row Add(CodexWidget c) { c.Parent = this; Children.Add(c); return this; }

    protected override Point MeasureCore(Point available)
    {
        int innerW = available.X - Padding.HorizontalSum();
        int innerH = available.Y - Padding.VerticalSum();
        int totalW = 0, maxH = 0;
        for (int i = 0; i < Children.Count; i++)
        {
            if (!Children[i].Visible) continue;
            var s = Children[i].Measure(new Point(innerW, innerH));
            totalW += s.X;
            if (i < Children.Count - 1) totalW += Spacing;
            if (s.Y > maxH) maxH = s.Y;
        }
        return new Point(totalW + Padding.HorizontalSum(), maxH + Padding.VerticalSum());
    }

    protected override void ArrangeCore(Rectangle bounds)
    {
        int x = bounds.X + Padding.Left;
        int y = bounds.Y + Padding.Top;
        int innerH = bounds.Height - Padding.VerticalSum();
        foreach (var c in Children)
        {
            if (!c.Visible) continue;
            int cy = VAlignChildren switch
            {
                VAlign.Middle  => y + (innerH - c.DesiredSize.Y) / 2,
                VAlign.Bottom  => y + innerH - c.DesiredSize.Y,
                VAlign.Stretch => y,
                _              => y,
            };
            int ch = VAlignChildren == VAlign.Stretch ? innerH : c.DesiredSize.Y;
            c.Arrange(new Rectangle(x, cy, c.DesiredSize.X, ch));
            x += c.DesiredSize.X + Spacing;
        }
    }

    public override void Update(GameTime gt, CodexInput input)
    {
        foreach (var c in Children) if (c.Visible) c.Update(gt, input);
    }

    public override void Draw(SpriteBatch sb, GameTime gt)
    {
        foreach (var c in Children) if (c.Visible) c.Draw(sb, gt);
    }
}

/// <summary>
/// Wrap-flow layout: lays children left-to-right, breaking to a new row when
/// the running width would exceed the container. Used for chip rows and the
/// review screen's starting-kit grid.
/// </summary>
public sealed class WrapRow : CodexWidget
{
    public System.Collections.Generic.List<CodexWidget> Children { get; } = new();
    public int HSpacing { get; set; } = CodexDensity.ColGap;
    public int VSpacing { get; set; } = CodexDensity.RowGap;

    public WrapRow Add(CodexWidget c) { c.Parent = this; Children.Add(c); return this; }

    protected override Point MeasureCore(Point available)
    {
        int innerW = available.X;
        int x = 0, y = 0, rowMaxH = 0, totalW = 0;
        foreach (var c in Children)
        {
            if (!c.Visible) continue;
            var s = c.Measure(new Point(innerW, available.Y));
            if (x > 0 && x + s.X > innerW) { x = 0; y += rowMaxH + VSpacing; rowMaxH = 0; }
            x += s.X + HSpacing;
            if (s.Y > rowMaxH) rowMaxH = s.Y;
            if (x > totalW) totalW = x;
        }
        y += rowMaxH;
        return new Point(System.Math.Min(totalW, innerW), y);
    }

    protected override void ArrangeCore(Rectangle bounds)
    {
        int x = bounds.X, y = bounds.Y, rowMaxH = 0;
        foreach (var c in Children)
        {
            if (!c.Visible) continue;
            if (x > bounds.X && x + c.DesiredSize.X > bounds.X + bounds.Width)
            {
                x = bounds.X;
                y += rowMaxH + VSpacing;
                rowMaxH = 0;
            }
            c.Arrange(new Rectangle(x, y, c.DesiredSize.X, c.DesiredSize.Y));
            x += c.DesiredSize.X + HSpacing;
            if (c.DesiredSize.Y > rowMaxH) rowMaxH = c.DesiredSize.Y;
        }
    }

    public override void Update(GameTime gt, CodexInput input)
    {
        foreach (var c in Children) if (c.Visible) c.Update(gt, input);
    }

    public override void Draw(SpriteBatch sb, GameTime gt)
    {
        foreach (var c in Children) if (c.Visible) c.Draw(sb, gt);
    }
}

/// <summary>
/// Fixed-column grid: lays children left-to-right then wraps. Each cell gets
/// (innerWidth - colGap*(columns-1)) / columns of horizontal space. Used for
/// the card grid on Clade / Species / Class / Background steps.
/// </summary>
public sealed class Grid : CodexWidget
{
    public System.Collections.Generic.List<CodexWidget> Children { get; } = new();
    public int Columns  { get; set; } = 2;
    public int HSpacing { get; set; } = CodexDensity.CardGap;
    public int VSpacing { get; set; } = CodexDensity.CardGap;

    public Grid Add(CodexWidget c) { c.Parent = this; Children.Add(c); return this; }

    protected override Point MeasureCore(Point available)
    {
        int cols = System.Math.Max(1, Columns);
        int cellW = (available.X - HSpacing * (cols - 1)) / cols;
        int rowH = 0, totalH = 0, col = 0;
        foreach (var c in Children)
        {
            if (!c.Visible) continue;
            var s = c.Measure(new Point(cellW, available.Y));
            if (s.Y > rowH) rowH = s.Y;
            col++;
            if (col >= cols) { totalH += rowH + VSpacing; rowH = 0; col = 0; }
        }
        if (col > 0) totalH += rowH;
        else if (totalH >= VSpacing) totalH -= VSpacing;
        return new Point(available.X, totalH);
    }

    protected override void ArrangeCore(Rectangle bounds)
    {
        int cols = System.Math.Max(1, Columns);
        int cellW = (bounds.Width - HSpacing * (cols - 1)) / cols;
        int x = bounds.X, y = bounds.Y, col = 0, rowH = 0;
        foreach (var c in Children)
        {
            if (!c.Visible) continue;
            int ch = c.DesiredSize.Y;
            c.Arrange(new Rectangle(x, y, cellW, ch));
            if (ch > rowH) rowH = ch;
            col++;
            if (col >= cols) { y += rowH + VSpacing; x = bounds.X; col = 0; rowH = 0; }
            else             { x += cellW + HSpacing; }
        }
    }

    public override void Update(GameTime gt, CodexInput input)
    {
        foreach (var c in Children) if (c.Visible) c.Update(gt, input);
    }

    public override void Draw(SpriteBatch sb, GameTime gt)
    {
        foreach (var c in Children) if (c.Visible) c.Draw(sb, gt);
    }
}

/// <summary>
/// Single-child decorator that adds breathing room. Combine with any widget
/// that doesn't expose its own padding field.
/// </summary>
public sealed class Padding : CodexWidget
{
    public CodexWidget? Child { get; set; }
    public Thickness Inset { get; set; }

    public Padding(CodexWidget child, Thickness inset) { Child = child; if (child is not null) child.Parent = this; Inset = inset; }

    protected override Point MeasureCore(Point available)
    {
        if (Child is null) return new Point(Inset.HorizontalSum(), Inset.VerticalSum());
        var inner = new Point(available.X - Inset.HorizontalSum(), available.Y - Inset.VerticalSum());
        var s = Child.Measure(inner);
        return new Point(s.X + Inset.HorizontalSum(), s.Y + Inset.VerticalSum());
    }

    protected override void ArrangeCore(Rectangle bounds)
    {
        Child?.Arrange(new Rectangle(
            bounds.X + Inset.Left,
            bounds.Y + Inset.Top,
            bounds.Width - Inset.HorizontalSum(),
            bounds.Height - Inset.VerticalSum()));
    }

    public override void Update(GameTime gt, CodexInput input) => Child?.Update(gt, input);
    public override void Draw(SpriteBatch sb, GameTime gt)     => Child?.Draw(sb, gt);
}

/// <summary>Box-model insets: independent left/top/right/bottom in pixels.</summary>
public readonly struct Thickness
{
    public readonly int Left, Top, Right, Bottom;
    public Thickness(int uniform) : this(uniform, uniform, uniform, uniform) { }
    public Thickness(int l, int t, int r, int b) { Left = l; Top = t; Right = r; Bottom = b; }
    public int HorizontalSum() => Left + Right;
    public int VerticalSum()   => Top + Bottom;
}
