using System.Numerics;
using Voile.Rendering;

namespace Voile.UI.Containers;

/// <summary>
/// A base class for all UI containers, used to position and rendering child <see cref="IElement">s.
/// </summary>
public abstract class Container : UIElement, IParentableElement
{
    /// <inheritdoc />
    public IReadOnlyList<UIElement> Children => _children;

    /// <summary>
    /// Specifies if this <see cref="Container"/>'s minimum size will be confined to contents.
    /// </summary>
    public bool ConfineToContents { get; set; } = false;

    public override string? StyleElementName => nameof(Container);

    public override Rect MinimumSize => _minimumSize;

    public Container()
    {
        MarkDirty();
    }

    public Container(Rect minimumSize)
    {
        _minimumSize = minimumSize;

        MarkDirty();
    }

    public Container(Rect minimumSize, List<UIElement> children)
    {
        _minimumSize = minimumSize;
        _children = children;

        MarkDirty();
    }

    protected override void OnUpdate()
    {
        foreach (var child in _children)
        {
            if (child is not IUpdatableElement updatable) continue;

            updatable.Update();

            if (child is IAnchorableElement anchorable)
            {
                anchorable.ApplyAnchor(GlobalPosition, Size);
            }
        }

        Arrange();

        if (ConfineToContents)
        {
            RecalculateSizes();
        }
    }

    public override void MarkDirty()
    {
        base.MarkDirty();

        foreach (var child in _children)
        {
            if (child is not IUpdatableElement updatable) continue;

            updatable.MarkDirty();
        }
    }

    /// <summary>
    /// Called when this <see cref="Container"/> has to rearrange its children.
    /// </summary>
    public abstract void Arrange();

    /// <summary>
    /// Recalculates sizes for this <see cref="Container"/>.
    /// This is done automatically if <see cref="ConfineToContents"/> is true.
    /// </summary>
    public void RecalculateSizes()
    {
        float minX = float.MaxValue;
        float minY = float.MaxValue;
        float maxX = float.MinValue;
        float maxY = float.MinValue;

        foreach (var child in Children)
        {
            var pos = child.GlobalPosition;
            var size = child.Size;

            minX = MathF.Min(minX, pos.X);
            minY = MathF.Min(minY, pos.Y);
            maxX = MathF.Max(maxX, pos.X + size.Width);
            maxY = MathF.Max(maxY, pos.Y + size.Height);
        }

        float padding = 0f;

        float occupiedWidth = (maxX - minX) + padding * 2;
        float occupiedHeight = (maxY - minY) + padding * 2;

        float finalWidth = MathF.Max(occupiedWidth, _minimumSize.Width);
        float finalHeight = MathF.Max(occupiedHeight, _minimumSize.Height);

        var finalSize = new Rect(finalWidth, finalHeight);

        if (finalSize != Size)
        {
            Size = finalSize;
        }

        if (_minimumSize > Size)
        {
            Size = _minimumSize;
        }
    }

    public void AddChild(UIElement child)
    {
        // child.StyleSheetOverride = StyleSheet;
        _children.Add(child);
        child.SetParent(this);

        MarkDirty();
        Update();
    }

    public void RemoveChild(UIElement child)
    {
        _children.Remove(child);

        MarkDirty();
        Update();
    }

    protected override void OnRender(RenderSystem renderer, Style style)
    {
        foreach (var child in Children)
        {
            if (child is not IRenderableElement renderable) continue;

            if (!child.TryGetStyle(StyleSheet.Value, out var childStyle))
            {
                childStyle = new Style();
            }

            renderable.Render(renderer, childStyle);
        }
    }

    private List<UIElement> _children = new();
    private Rect _minimumSize = Rect.Zero;
}