using System.Numerics;
using Voile.Rendering;

namespace Voile.UI.Containers;

public enum FlexDirection
{
    Row,
    Column
}

public enum JustifyContent
{
    /// <summary>
    /// Align children to the start of the line.
    /// </summary>
    Start,
    /// <summary>
    /// Align children to the center of the line.
    /// </summary>
    Center,
    /// <summary>
    /// Align children to the end of the line.
    /// </summary>
    End,
    /// <summary>
    /// Equal space between items (no space at edges if multiple).
    /// </summary>
    SpaceBetween
}

public enum AlignItems
{
    /// <summary>
    /// Align to the top or left (cross-axis)
    /// </summary>
    Start,
    /// <summary>
    /// Center along the cross-axis.
    /// </summary>
    Center,
    /// <summary>
    /// Align to the bottom or right.
    /// </summary>
    End
}

/// <summary>
/// A flexible layout container that arranges its child elements in rows or columns, similar to CSS flexbox. Supports wrapping, alignment, spacing, and space distribution.
/// </summary>
public class FlexContainer : Container
{
    /// <summary>
    /// Layout direction to use with this <see cref="FlexContainer"/>.
    /// </summary>
    public FlexDirection Direction { get; set; } = FlexDirection.Row;
    /// <summary>
    /// Controls main-axis alignment of child elements. By default, children will be positioned at the start of a line. 
    /// </summary>
    public JustifyContent Justify { get; set; } = JustifyContent.Start;
    /// <summary>
    /// Controls cross-axis alignment (start, center, end).
    /// </summary>
    public AlignItems Align { get; set; } = AlignItems.Start;
    /// <summary>
    /// If true, children wrap onto multiple lines (rows or columns).
    /// </summary>
    public bool Wrap { get; set; } = false;
    /// <summary>
    /// Space between child elements.
    /// </summary>
    public float Gap { get; set; } = 16f;

    /// <summary>
    /// (Reserved) If true, distributes extra space among children (not implemented)
    /// </summary>
    public bool DistributeRemainingSpace { get; set; } = false;

    public FlexContainer() : base() { }

    public FlexContainer(Rect minimumSize, List<UIElement> children) : base(minimumSize, children) { }

    public override void Arrange()
    {
        float containerMainSize = (Direction == FlexDirection.Row) ? Size.Width : Size.Height;
        float mainPos = 0.0f;
        float crossPos = 0.0f;

        List<List<UIElement>> lines = new();
        List<UIElement> currentLine = new();
        float lineMainSum = 0f;
        float maxCross = 0f;

        foreach (var child in Children)
        {
            Vector2 childSize = GetChildSize(child);
            float mainSize = GetMainSize(childSize);
            float crossSize = GetCrossSize(childSize);

            bool wrapLine = Wrap && currentLine.Count > 0 && (lineMainSum + mainSize + Gap > containerMainSize);

            if (wrapLine)
            {
                lines.Add(currentLine);
                currentLine = new();
                lineMainSum = 0f;
                maxCross += crossSize + Gap;
            }

            currentLine.Add(child);
            lineMainSum += mainSize + (currentLine.Count > 1 ? Gap : 0);
        }

        if (currentLine.Count > 0)
        {
            lines.Add(currentLine);
        }

        float currentCross = crossPos;

        foreach (var line in lines)
        {
            float lineMainLength = line.Sum(child => GetMainSize(GetChildSize(child))) + Gap * (line.Count - 1);
            float justifyOffset = GetJustifyOffset(containerMainSize, lineMainLength, line.Count);

            float currentMain = mainPos + justifyOffset;
            float maxLineCross = line.Select(child => GetCrossSize(GetChildSize(child))).Max();

            foreach (var child in line)
            {
                Vector2 childSize = GetChildSize(child);

                float alignedCross = currentCross + GetAlignOffset(maxLineCross, GetCrossSize(childSize));

                Vector2 childPos = (Direction == FlexDirection.Row)
                    ? new Vector2(currentMain, alignedCross)
                    : new Vector2(alignedCross, currentMain);

                child.LocalPosition = childPos;

                currentMain += GetMainSize(childSize) + Gap;
            }

            currentCross += maxLineCross + Gap;
        }
    }

    private Vector2 GetChildSize(IElement child)
    {
        if (child is IResizeableElement resizeable)
            return new Vector2(resizeable.MinimumSize.Width, resizeable.MinimumSize.Height);
        return new Vector2(child.Size.Width, child.Size.Height);
    }

    private float GetMainSize(Vector2 size)
    {
        return Direction == FlexDirection.Row ? size.X : size.Y;
    }

    private float GetCrossSize(Vector2 size)
    {
        return Direction == FlexDirection.Row ? size.Y : size.X;
    }

    private float GetJustifyOffset(float containerSize, float totalMain, int itemCount)
    {
        return Justify switch
        {
            JustifyContent.Center => (containerSize - totalMain) / 2f,
            JustifyContent.End => containerSize - totalMain,
            JustifyContent.SpaceBetween => (itemCount > 1) ? 0 : (containerSize - totalMain) / 2f,
            _ => 0
        };
    }

    private float GetAlignOffset(float maxCross, float childCross)
    {
        return Align switch
        {
            AlignItems.Center => (maxCross - childCross) / 2f,
            AlignItems.End => maxCross - childCross,
            _ => 0f
        };
    }
}