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

namespace Voile.UI.Widgets;

public class InputField : Widget, ITickableElement
{
    enum ActionType { None, Backspace, Left, Right }

    public override Rect MinimumSize => _placeholderSize + _padding;
    public override string? StyleElementName => nameof(InputField);

    public override string[]? StyleModifiers =>
    [
        _isFocused ? "Focused" : string.Empty
    ];

    /// <summary>
    /// Current text value of this input field.
    /// </summary>
    public string Value
    {
        get => _input;
        set
        {
            _input = value ?? string.Empty;
            _cursor = Math.Clamp(_cursor, 0, _input.Length);

            MarkDirty(DirtyFlags.Content);
        }
    }

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

    public bool IsFocused => _isFocused;

    public FontSet FontSet { get; set; } = new();

    public InputField(string initialText, FontSet fontSet)
    {
        _input = initialText ?? string.Empty;
        FontSet = fontSet;

        MarkDirty();
    }

    public void Tick(float dt, InputSystem input)
    {
        if (!_isFocused)
            return;

        HandleRepeatingActions(dt, input);
    }

    protected override void OnInput(UIInputContext context)
    {
        HandleFocus(context);

        if (!_isFocused)
        {
            _lastActiveAction = ActionType.None;
            _repeatTimer = 0.0f;
            return;
        }

        HandleTextInput(context);
        ClampCursor();
    }


    private void HandleFocus(UIInputContext context)
    {
        if (!context.MousePressed)
            return;

        bool isInside = ContainsPoint(context.MousePosition);

        _isFocused = isInside;

        if (isInside)
            context.SetHandled();
    }

    private void HandleTextInput(UIInputContext context)
    {
        if (!context.HasCharInput)
            return;

        char c = (char)context.CharPressed;

        if (!char.IsControl(c))
        {
            _input = _input.Insert(_cursor, c.ToString());
            _cursor++;

            MarkDirty(DirtyFlags.Content);
        }

        context.SetHandled();
    }

    private void HandleRepeatingActions(float dt, InputSystem input)
    {
        ActionType currentAction = ActionType.None;

        if (input.IsKeyboardKeyDown(KeyboardKey.Backspace)) currentAction = ActionType.Backspace;
        else if (input.IsKeyboardKeyDown(KeyboardKey.Left)) currentAction = ActionType.Left;
        else if (input.IsKeyboardKeyDown(KeyboardKey.Right)) currentAction = ActionType.Right;

        if (currentAction == ActionType.None)
        {
            _lastActiveAction = ActionType.None;
            _repeatTimer = 0.0f;
            return;
        }

        bool shouldExecute = false;

        if (_lastActiveAction != currentAction)
        {
            shouldExecute = true;
            _repeatTimer = 0.0f;
            _lastActiveAction = currentAction;
        }
        else
        {
            _repeatTimer += dt;

            if (_repeatTimer >= INITIAL_DELAY)
            {
                shouldExecute = true;
                _repeatTimer = INITIAL_DELAY - REPEAT_RATE;
            }
        }

        if (shouldExecute)
        {
            ExecuteAction(currentAction);
        }
    }

    private void ExecuteAction(ActionType action)
    {
        switch (action)
        {
            case ActionType.Backspace:
                if (_cursor > 0 && _input.Length > 0)
                {
                    _input = _input.Remove(_cursor - 1, 1);
                    _cursor--;
                    MarkDirty(DirtyFlags.Content);
                }
                break;

            case ActionType.Left:
                if (_cursor > 0)
                {
                    _cursor--;
                    MarkDirty(DirtyFlags.Content);
                }
                break;

            case ActionType.Right:
                if (_cursor < _input.Length)
                {
                    _cursor++;
                    MarkDirty(DirtyFlags.Content);
                }
                break;
        }
    }

    private void ClampCursor()
    {
        _cursor = Math.Clamp(_cursor, 0, _input.Length);
    }

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

        var textColor = style.TextColor ?? Color.Black;
        var caretColor = style.BorderColor ?? Color.Black;

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

        renderer.SetTransform(pos, Vector2.Zero);

        string text = string.IsNullOrEmpty(_input)
            ? PlaceholderText
            : _input;

        // TODO: use a placeholder color from the style instead of making it lighter than the original text color.
        var placeholderColor = textColor.Lightened(0.5f);

        var color = string.IsNullOrEmpty(_input)
            ? placeholderColor
            : textColor;

        if (_font.HasValue)
            renderer.DrawText(_font, text, color);

        if (_isFocused && _blink)
        {
            float caretX = GetCaretX(_cursor);

            renderer.SetTransform(
                new Vector2(GlobalPosition.X + _padding.Left + caretX,
                            GlobalPosition.Y + _padding.Top),
                Vector2.Zero);

            float h = Math.Max(_textSize.Height, _placeholderSize.Height);
            renderer.DrawRectangle(new Vector2(1, h), caretColor);
        }
    }

    protected override void OnUpdate(LayoutContext layoutContext)
    {
        if (!_font.HasValue)
            ResolveFont();

        if (!_font.HasValue)
            return;

        var font = _font.Value;

        _textSize = font.Measure(_input);

        if (_input.Length == 0)
            _placeholderSize = font.Measure(PlaceholderText);

        var content = Rect.MaxWidth(_textSize, _placeholderSize);

        float width = content.Width + _padding.Left + _padding.Right;
        float height = content.Height + _padding.Top + _padding.Bottom;

        if (LayoutSize.Width == width && LayoutSize.Height == height)
            return;

        LayoutSize = new Rect(width, height);

        MarkDirty(DirtyFlags.Layout);
    }

    private float GetCaretX(int index)
    {
        var span = _input.AsSpan(0, index);

        if (!_font.HasValue)
            return 0.0f;

        return _font.Value.Measure(span).Width;
    }

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

        var text = string.IsNullOrEmpty(_input) ? PlaceholderText : _input;

        foreach (var c in text)
        {
            if (FontSet.TryGetFontFor(c, out var f))
                _font = f;
        }
    }

    private float _repeatTimer = 0.0f;
    private ActionType _lastActiveAction = ActionType.None;
    private const float INITIAL_DELAY = 0.5f;
    private const float REPEAT_RATE = 0.05f;

    private string _input = string.Empty;
    private bool _isFocused;

    private int _cursor;

    private bool _blink = true;

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

    private Size _padding = Voile.Size.Zero;
    private Rect _textSize = Rect.Zero;
    private Rect _placeholderSize = Rect.Zero;
}