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
        {
            if (value.Width < MinimumSize.Width)
            {
                _size.Width = MinimumSize.Width;
            }

            if (value.Height < MinimumSize.Height)
            {
                _size.Height = MinimumSize.Height;
            }

            if (_size != value)
            {
                MarkDirty();
            }

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

    public abstract Rect MinimumSize { get; }
    public bool Dirty => _dirty;

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

    public virtual void MarkDirty()
    {
        if (Parent != null && !Parent.Dirty)
        {
            Parent.MarkDirty();
        }

        _dirty = true;
    }

    /// <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();
    }

    public void Update(float dt = 0.0f)
    {
        if (!_dirty) return;
        _dirty = false;

        if (Size == Rect.Zero)
            Size = MinimumSize;

        OnUpdate();

        if (_parent is not null && _parent.Size != Rect.Zero)
        {
            ApplyAnchor(_parent.GlobalPosition, _parent.Size);
        }
    }

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

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

    /// <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
            );
        }
    }

    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);
    }

    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 bool _dirty = true;
    private Rect _size = Rect.Zero;

    private UIElement? _parent;
}
