using System.Numerics;
using Voile.Rendering;
using Voile.Resources;

namespace Voile.UI.Widgets;

/// <summary>
/// A clickable button with a label.
/// </summary>
public class Button : Widget
{
    public enum ButtonState
    {
        Disabled,
        Hovered,
        Pressed,
        Focused,
        Normal
    }

    public string Text
    {
        get => _text; set
        {
            _text = value;
            MarkDirty(DirtyFlags.Content);
        }
    }

    public ButtonState CurrentState { get; private set; } = ButtonState.Normal;

    public override Rect MinimumSize => Padding + _textSize;

    /// <summary>
    /// <see cref="FontSet"/> to use with this button.
    /// </summary>
    public FontSet FontSet { get; set; } = new();
    public Size Padding
    {
        get => _padding; set
        {
            _padding = value;
        }
    }

    public override string? StyleElementName => nameof(Button);
    public override string[]? StyleModifiers =>
    [
        CurrentState.ToString()
    ];

    public Button(string text, ResourceRef<Font> fontOverride, Action pressedAction)
    {
        _text = text;
        _pressedAction = pressedAction;

        FontSet.AddFont(fontOverride);

        MarkDirty();
    }

    public Button(string text, FontSet fontSet)
    {
        _text = text;

        FontSet = fontSet;

        MarkDirty();
    }

    public Button(string text, FontSet fontSet, Action pressedAction)
    {
        _text = text;
        _pressedAction = pressedAction;

        FontSet = fontSet;

        MarkDirty();
    }

    protected override void OnRender(RenderSystem renderer, Style style)
    {
        _padding = style.Padding ?? Voile.Size.Zero;

        var color = style.TextColor ?? Color.Black;

        var pos = new Vector2(
            GlobalPosition.X + _padding.Left,
            GlobalPosition.Y + _padding.Top);

        renderer.SetTransform(pos, Vector2.Zero);

        if (_font.HasValue)
        {
            renderer.DrawText(_font, _text, color);
        }
    }

    protected virtual void Pressed() { }

    protected override void OnInput(UIInputContext action)
    {
        bool isHovering = ContainsPoint(action.MousePosition);

        if (action.MousePressed && isHovering)
        {
            _isHeldDown = true;
            CurrentState = ButtonState.Pressed;
        }
        else if (action.MouseReleased)
        {
            if (_isHeldDown && isHovering)
            {
                _pressedAction?.Invoke();
                Pressed();
            }

            _isHeldDown = false;
            CurrentState = isHovering ? ButtonState.Hovered : ButtonState.Normal;
        }
        else
        {
            if (_isHeldDown)
            {
                CurrentState = ButtonState.Pressed; // keep showing as pressed
            }
            else if (isHovering)
            {
                CurrentState = ButtonState.Hovered;
            }
            else
            {
                CurrentState = ButtonState.Normal;
            }
        }
    }

    protected override void OnUpdate(LayoutContext layoutContext)
    {
        ResolveFont();

        if (!_font.HasValue)
            return;

        var font = _font.Value;

        var newSize = font.Measure(_text);
        _textSize = newSize;

        var final = _textSize + _padding;

        if (_cachedSize.Width == final.Width &&
            _cachedSize.Height == final.Height)
            return;

        _cachedSize = final;

        Size = final;

        MarkDirty(DirtyFlags.Layout);
    }

    private void ResolveFont()
    {
        if (_font.HasValue)
            return;

        var text = _text;

        for (int i = 0; i < text.Length; i++)
        {
            if (FontSet.TryGetFontFor(text[i], out var f))
            {
                _font = f;
                break;
            }
        }
    }

    private Action? _pressedAction;
    private Rect _cachedSize = Rect.Zero;
    private string _text = "Hello, World!";
    private Rect _textSize = Rect.Zero;

    private ResourceRef<Font> _font = ResourceRef<Font>.Empty();

    private Size _padding;

    private bool _isHeldDown;
}