using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Text;
using Voile.Rendering;

namespace Voile.UI;

/// <summary>
/// Base class for all UI elements. 
/// </summary>
public abstract class UIElement : IElement, IRenderableElement, IResizeableElement, IUpdatableElement, IAnchorableElement
{
    public bool Visible { get; set; } = true;
    public bool IgnoreInput { get; set; } = false;
    public Vector2 LocalPosition { get; set; } = Vector2.Zero;
    public Vector2 GlobalPosition => _parent?.GlobalPosition + LocalPosition ?? LocalPosition;

    public string StyleName => $"{StyleElementName ?? "UIElement"}{GetStyleVariantString()}{ConstructStyleModifiers(StyleModifiers)}";

    /// <summary>
    /// An element name for style.
    /// </summary>
    public virtual string? StyleElementName { get; }

    public string StyleVariant { get; set; } = string.Empty;

    /// <summary>
    /// List of style modifiers for this <see cref="UIElement"/>.
    /// </summary>
    public virtual string[]? StyleModifiers { get; }

    public ResourceRef<StyleSheet> StyleSheet => Parent?.StyleSheet ?? StyleSheetOverride;
    public ResourceRef<StyleSheet> StyleSheetOverride { get; set; } = ResourceRef<StyleSheet>.Empty();

    /// <summary>
    /// Parent <see cref="UIElement"/> of this element.
    /// </summary>
    public UIElement? Parent => _parent;

    public Rect Size
    {
        get => _size;
        set
        {
            float width = Math.Max(value.Width, MinimumSize.Width);
            float height = Math.Max(value.Height, MinimumSize.Height);

            bool changed =
                _size.Width != width ||
                _size.Height != height;

            if (!changed)
                return;

            _size.Width = width;
            _size.Height = height;

            MarkDirty(DirtyFlags.Layout);
        }
    }

    public Vector2 AnchorOffset { get; set; } = Vector2.Zero;
    public Anchor Anchor { get; set; } = Anchor.TopLeft;

    public abstract Rect MinimumSize { get; }
    public bool Dirty => _dirty != DirtyFlags.None || _pendingDirty != DirtyFlags.None;

    public bool ClipContents { get; set; } = true;

    public bool TryGetStyle(StyleSheet styleSheet, [NotNullWhen(true)] out Style? style)
    {
        return styleSheet.TryGet(StyleName, out style);
    }

    public virtual void MarkDirty(DirtyFlags flags = DirtyFlags.Layout) => _pendingDirty |= flags;

    /// <summary>
    /// Sets a parent element for this <see cref="UIElement"/>.
    /// </summary>
    /// <param name="parent">Element to parent this <see cref="UIElement"/> to.</param>

    public void SetParent(UIElement parent)
    {
        _parent = parent;
        MarkDirty(DirtyFlags.Layout);
    }

    public void Update(LayoutContext layoutContext)
    {
        _dirty |= _pendingDirty;
        _pendingDirty = DirtyFlags.None;

        if (_dirty == DirtyFlags.None)
            return;

        if (HasDirty(DirtyFlags.Layout))
            OnLayoutUpdate();

        if (HasDirty(DirtyFlags.Content))
            OnContentUpdate();

        if (HasDirty(DirtyFlags.Style))
            OnStyleUpdate();

        _dirty = DirtyFlags.None;

        OnUpdate(layoutContext);
    }


    public void Render(RenderSystem renderer, Style style)
    {
        RenderStyleBox(renderer, style);

        if (ClipContents)
        {
            renderer.BeginScissored(GlobalPosition, LayoutSize);
        }

        OnRender(renderer, style);

        if (ClipContents)
        {
            renderer.EndScissored();
        }
    }

    public void DrawSize(RenderSystem renderer)
    {
        renderer.SetTransform(GlobalPosition, Vector2.Zero);
        renderer.DrawRectangleOutline(new Vector2(Size.Width, Size.Height), Color.Red, 2.0f);
    }

    /// <summary>
    /// Determines if this <see cref="UIElement"/> contains a point within its confines.
    /// </summary>
    /// <param name="pointPosition">A global position of the point.</param>
    /// <returns>True if the point is inside the widget; otherwise, false.</returns>
    public bool ContainsPoint(Vector2 point)
    {
        return point.X >= GlobalPosition.X && point.Y >= GlobalPosition.Y &&
               point.X <= GlobalPosition.X + Size.Width &&
               point.Y <= GlobalPosition.Y + Size.Height;
    }

    /// <summary>
    /// Applies this <see cref="UIElement"/> anchor.
    /// </summary>
    public virtual void ApplyAnchor(Vector2 parentPosition, Rect parentRect)
    {
        LocalPosition = Anchor.Calculate(parentPosition, parentRect, Size) + new Vector2(AnchorOffset.X, AnchorOffset.Y);
    }

    /// <summary>
    /// Helper method for determining if this <see cref="UIElement"/> has a specific dirty flag.
    /// </summary>
    /// <param name="flags"></param>
    /// <returns></returns>
    public bool HasDirty(DirtyFlags flags) =>
    (_dirty & flags) != 0 || (_pendingDirty & flags) != 0;

    /// <summary>
    /// The layout-computed size of this UI element that doesn't trigger
    /// dirty state propagation.
    /// </summary>
    /// <remarks>
    /// This property is intended to be used exclusively by the layout system
    /// (e.g. containers during <c>RecalculateSizes</c>).
    ///
    /// Unlike the <see cref="Size"/> property setter, this property does not call
    /// <c>MarkDirty</c>, and therefore will not trigger layout invalidation or
    /// parent propagation.
    /// </remarks>
    protected Rect LayoutSize
    {
        get => _size; set => _size = value;
    }

    protected abstract void OnRender(RenderSystem renderer, Style style);

    protected virtual void OnLayoutUpdate() { }
    protected virtual void OnContentUpdate() { }
    protected virtual void OnStyleUpdate() { }
    protected abstract void OnUpdate(LayoutContext layoutContext);

    /// <summary>
    /// Renders a stylebox from a given style.
    /// </summary>
    /// <param name="renderer"></param>
    /// <param name="style"></param>
    protected void RenderStyleBox(RenderSystem renderer, Style style)
    {
        var backgroundColor = style.BackgroundColor ?? Color.Transparent;
        var borderColor = style.BorderColor ?? Color.Transparent;
        var borderSize = style.BorderSize;

        var borderLeft = borderSize?.Left ?? 0;
        var borderRight = borderSize?.Right ?? 0;
        var borderTop = borderSize?.Top ?? 0;
        var borderBottom = borderSize?.Bottom ?? 0;

        renderer.SetTransform(GlobalPosition, Vector2.Zero);

        renderer.DrawRectangle(new Vector2(Size.Width, Size.Height), backgroundColor);

        if (borderLeft > 0)
        {
            renderer.SetTransform(GlobalPosition, Vector2.Zero);
            renderer.DrawRectangle(
                new Vector2(borderLeft, Size.Height),
                borderColor
            );
        }

        if (borderTop > 0)
        {
            renderer.SetTransform(GlobalPosition, Vector2.Zero);
            renderer.DrawRectangle(
                new Vector2(Size.Width, borderTop),
                borderColor
            );
        }

        if (borderRight > 0)
        {
            var rightX = GlobalPosition.X + Size.Width - borderRight;
            renderer.SetTransform(new Vector2(rightX, GlobalPosition.Y), Vector2.Zero);
            renderer.DrawRectangle(
                new Vector2(borderRight, Size.Height),
                borderColor
            );
        }

        if (borderBottom > 0)
        {
            var bottomY = GlobalPosition.Y + Size.Height - borderBottom;
            renderer.SetTransform(new Vector2(GlobalPosition.X, bottomY), Vector2.Zero);
            renderer.DrawRectangle(
                new Vector2(Size.Width, borderBottom),
                borderColor
            );
        }
    }

    private string ConstructStyleModifiers(string[]? modifiers)
    {
        if (modifiers == null)
        {
            return string.Empty;
        }

        var sb = new StringBuilder();
        foreach (var modifier in modifiers)
        {
            if (string.IsNullOrEmpty(modifier)) continue;
            sb.Append($".{modifier}");
        }

        return sb.ToString();
    }

    private string GetStyleVariantString()
    {
        if (string.IsNullOrEmpty(StyleVariant))
            return string.Empty;

        return $".{StyleVariant}";
    }

    private DirtyFlags _dirty = DirtyFlags.Layout;
    private DirtyFlags _pendingDirty = DirtyFlags.None;
    private Rect _size = Rect.Zero;

    private UIElement? _parent;
}
