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

namespace Voile.UI.Widgets;

public class InputField : Widget, ITickableElement
{
    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();
        }
    }

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

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

        HandleRepeatingActions(dt, input);
    }

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

        if (!_isFocused)
        {
            _lastActiveAction = string.Empty;
            _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();
        }

        context.SetHandled();
    }

    private void HandleRepeatingActions(float dt, InputSystem input)
    {
        string currentAction = string.Empty;

        if (input.IsKeyboardKeyDown(KeyboardKey.Backspace)) currentAction = "backspace";
        else if (input.IsKeyboardKeyDown(KeyboardKey.Left)) currentAction = "left";
        else if (input.IsKeyboardKeyDown(KeyboardKey.Right)) currentAction = "right";

        if (string.IsNullOrEmpty(currentAction))
        {
            _lastActiveAction = string.Empty;
            _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(string actionName)
    {
        switch (actionName)
        {
            case "backspace":
                if (_cursor > 0 && _input.Length > 0)
                {
                    _input = _input.Remove(_cursor - 1, 1);
                    _cursor--;
                    MarkDirty();
                }
                break;

            case "left":
                if (_cursor > 0)
                {
                    _cursor--;
                    MarkDirty();
                }
                break;

            case "right":
                if (_cursor < _input.Length)
                {
                    _cursor++;
                    MarkDirty();
                }
                break;
        }
    }

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

    protected override void OnRender(RenderSystem renderer, Style style)
    {
        if (_padding != style.Padding)
        {
            MarkDirty();
        }

        _padding = style.Padding ?? Voile.Size.Zero;

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

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

        // Placeholder
        // TODO: use a color from the style instead of making it less transparent.
        if (string.IsNullOrEmpty(_input) && !string.IsNullOrEmpty(PlaceholderText))
        {
            var placeholderColor = textColor.Lightened(0.5f);

            if (_suitableFont.HasValue)
            {
                renderer.DrawText(_suitableFont, PlaceholderText, placeholderColor);
            }
        }
        else
        {
            if (_suitableFont.HasValue)
            {
                renderer.DrawText(_suitableFont, _input, textColor);
            }
        }

        // Caret
        if (_isFocused && _blink)
        {
            var caretX = MeasureTextWidth(_input.Substring(0, _cursor));

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

            var caretHeight = Math.Max(_placeholderSize.Height, _textSize.Height);

            renderer.DrawRectangle(
                new Vector2(1, caretHeight),
                caretColor
            );
        }
    }

    protected override void OnUpdate()
    {
        ResourceRef<Font> fontRef = ResourceRef<Font>.Empty();

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

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

        if (fontRef.HasValue)
        {
            _suitableFont = fontRef;
            var font = _suitableFont.Value;

            _textSize = font.Measure(_input);
            _placeholderSize = font.Measure(PlaceholderText);
        }

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

        Size = _padding + size;
    }

    private float MeasureTextWidth(string text)
    {
        if (!_suitableFont.HasValue)
        {
            return 0.0f;
        }

        return _suitableFont.Value.Measure(text).Width;
    }

    private float _repeatTimer = 0.0f;
    private string _lastActiveAction = string.Empty;
    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> _suitableFont = ResourceRef<Font>.Empty();

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