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

namespace Voile.UI;

public class UISystem : IUpdatableSystem, IRenderableSystem, IReloadableSystem
{
    public IReadOnlyList<IElement> Elements => _elements;

    public bool RenderDebugRects { get; set; }
    public Color DebugSizeRectColor { get; set; } = Color.Red;
    public Color DebugDirtyRectColor { get; set; } = new Color(1.0f, 1.0f, 0.0f, 0.5f);

    public UISystem(InputSystem inputSystem)
    {
        _input = inputSystem;
    }

    public UISystem(InputSystem inputSystem, List<UIElement> elements)
    {
        _input = inputSystem;
        _elements = elements;
    }

    public void SetStyleSheet(ResourceRef<StyleSheet> styleSheet)
    {
        _styleSheet = styleSheet;
    }

    public void AddElement(UIElement element)
    {
        element.StyleSheetOverride = _styleSheet;
        _elements.Add(element);
        _inputElementIndices.Add(element.GlobalPosition, _elements.Count - 1);
    }
    public void RemoveElement(UIElement element) => _elements.Remove(element);

    public void Update(double deltaTime)
    {
        float dt = (float)deltaTime;
        PropagateTick(_elements, dt);
        HandleInput((float)deltaTime);
    }

    public void Render(RenderSystem renderer)
    {
        foreach (var element in _elements)
        {
            if (element is not IUpdatableElement updatable) continue;
            updatable.Update();
        }


        foreach (var element in _elements)
        {
            if (element is IRenderableElement renderable)
            {
                var styleSheet = _styleSheet.Value;

                if (!styleSheet.TryGet(element.StyleName, out var style))
                {
                    style = new Style();
                }

                renderable.Render(renderer, style);
            }
        }

        if (!RenderDebugRects) return;

        foreach (var element in _elements)
        {
            if (element is not UIElement uiElement) continue;
            DrawDebugForElement(renderer, uiElement);
        }
    }

    public void Reload()
    {
        throw new NotImplementedException();
    }

    private void DrawDebugForElement(RenderSystem renderer, UIElement element)
    {
        var size = new Vector2(element.Size.Width, element.Size.Height);
        renderer.SetTransform(element.GlobalPosition, Vector2.Zero);
        renderer.DrawRectangleOutline(size, DebugSizeRectColor);

        if (element.Dirty)
        {
            renderer.DrawRectangle(size, DebugDirtyRectColor);
        }

        if (element is IParentableElement parentableElement)
        {
            foreach (var child in parentableElement.Children)
            {
                if (child is not UIElement childElement) continue;
                DrawDebugForElement(renderer, childElement);
            }
        }
    }

    private void HandleInput(float deltaTime)
    {
        int charPressed = _input.GetCharPressed();
        Vector2 mousePos = _input.GetMousePosition();
        Vector2 currentMousePosition = _input.GetMousePosition();
        bool inputHandled = false;

        // 1. Process Actions (Pressed or Held)
        foreach (var (actionName, mappings) in InputSystem.InputMappings)
        {
            foreach (var action in mappings)
            {
                bool isPressed = action.IsPressed(_input);
                bool isDown = action.IsDown(_input);

                if (isPressed)
                {
                    var context = new UIInputContext(action, mousePos, actionName, charPressed)
                    {
                        MouseDown = _input.IsMouseButtonDown(MouseButton.Left),
                        MouseReleased = _input.IsMouseButtonReleased(MouseButton.Left),
                        MousePressed = _input.IsMouseButtonPressed(MouseButton.Left),
                        DeltaTime = deltaTime
                    };

                    if (PropagateInput(_elements, context))
                    {
                        inputHandled = true;
                        break;
                    }
                }
            }
            if (inputHandled) break;
        }

        if (inputHandled)
        {
            _lastMousePosition = currentMousePosition;
            return;
        }

        if (charPressed != 0)
        {
            var context = new UIInputContext(new KeyInputAction(KeyboardKey.Null), mousePos, "", charPressed)
            {
                DeltaTime = deltaTime
            };
            if (PropagateInput(_elements, context))
            {
                _lastMousePosition = currentMousePosition;
                return;
            }
        }

        var frameContext = new UIInputContext(new MouseInputAction(MouseButton.Left), mousePos, "", charPressed)
        {
            MouseDown = _input.IsMouseButtonDown(MouseButton.Left),
            MouseReleased = _input.IsMouseButtonReleased(MouseButton.Left),
            MousePressed = _input.IsMouseButtonPressed(MouseButton.Left),
            DeltaTime = deltaTime
        };

        PropagateInput(_elements, frameContext);

        _lastMousePosition = currentMousePosition;
    }

    private void PropagateTick(List<UIElement> elements, float dt)
    {
        for (int i = 0; i < elements.Count; i++)
        {
            var element = elements[i];

            if (!element.Visible) continue;

            if (element is ITickableElement tickable)
            {
                tickable.Tick(dt, _input);
            }

            if (element is IParentableElement parentable)
            {
                PropagateTick(parentable.Children.ToList(), dt);
            }
        }
    }

    private bool PropagateInput(List<UIElement> elements, UIInputContext context)
    {
        for (int i = elements.Count - 1; i >= 0; i--)
        {
            var element = elements[i];

            if (element is IParentableElement parent)
            {
                if (PropagateInput(parent.Children.ToList(), context))
                    return true;
            }

            if (element is IInputElement input && !input.IgnoreInput)
            {
                input.Input(context);

                if (context.Handled)
                    return true;
            }
        }

        return false;
    }

    private ResourceRef<StyleSheet> _styleSheet;
    private List<UIElement> _elements = new();
    private InputSystem _input;

    private GridSet<int> _inputElementIndices = new();

    private Vector2 _lastMousePosition = Vector2.Zero;
}