using System;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using Sirenix.Utilities;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UIElements;

namespace VOID.UserInterface
{
    [DefaultExecutionOrder(-1)]
    public class GameUI : MonoBehaviour
    {
        public static GameUI current { get; private set; }

        [Title("Generic Templates:")]
        public VisualTreeAsset slotTemplate => _slotTemplate;
        public VisualTreeAsset statusTemplate => _statusTemplate;
        public VisualTreeAsset unitPortraitTemplate => _unitPortraitTemplate;
        public VisualTreeAsset levelUpOptionTemplate => _levelUpOptionTemplate;
        public VisualTreeAsset profileTemplate => _profileTemplate;
        [SerializeField, Required] private VisualTreeAsset _slotTemplate;
        [SerializeField, Required] private VisualTreeAsset _statusTemplate;
        [SerializeField, Required] private VisualTreeAsset _unitPortraitTemplate;
        [SerializeField, Required] private VisualTreeAsset _levelUpOptionTemplate;
        [SerializeField, Required] private VisualTreeAsset _profileTemplate;
        
        private VisualElement _rootVisualElement;
        public IPanel runtimePanel { get; private set; }
        public PanelSettings panelSettings { get; private set; }
        public Vector2 cursorPositionOnUI { get; private set; }
        public bool isCursorOverUI { get; private set; }
        public float scale { get; private set; }
        
        // Visual elements under cursor + event when Visual element under cursor changes
        public readonly List<VisualElement> elementsUnderCursor = new();
        public event Action ElementsUnderCursorChanged;
        
        private void Awake()
        {
            current = this;
            var uiDocument = gameObject.GetComponent<UIDocument>();
            _rootVisualElement = uiDocument.rootVisualElement;
            panelSettings = uiDocument.panelSettings;
            runtimePanel = _rootVisualElement.panel;
            CalculateScale();
        }

        public VisualElement PickTopElementUI(List<VisualElement> cachedElements = null)
        {
            return (cachedElements ?? elementsUnderCursor).FirstOrDefault();
        }

        public T PickComponentUI<T>(List<VisualElement> cachedElements = null)
        {
            return (cachedElements ?? elementsUnderCursor).Select(el => el.dataSource).FilterCast<T>().FirstOrDefault();
        }

        public T PickTopComponentUI<T>(List<VisualElement> cachedElements = null)
        {
            return PickTopComponentUI(cachedElements) is T dataSource ? dataSource : default;
        }

        public object PickTopComponentUI(List<VisualElement> cachedElements = null)
        {
            return PickTopElementUI(cachedElements)?.dataSource;
        }

        private void CalculateScale()
        {
            var screenSize = new Vector2(Screen.width, Screen.height);
            var referenceScreenSize = panelSettings.referenceResolution;
            var proportions = referenceScreenSize / screenSize;
            scale = proportions.x * panelSettings.match + proportions.y * (1f - panelSettings.match);
        }
    
        private void Update()
        {
            FindCursorPositionOnUI();
            FindElementsUnderCursor();
            
            isCursorOverUI = !elementsUnderCursor.IsNullOrEmpty();
        }

        private void FindCursorPositionOnUI()
        {
            var mousePos = Mouse.current.position.ReadValue();
            mousePos.y = Screen.height - mousePos.y;
            cursorPositionOnUI = RuntimePanelUtils.ScreenToPanel(runtimePanel, mousePos);
        }

        private void FindElementsUnderCursor()
        {
            var previousTopElement = elementsUnderCursor.FirstOrDefault();
            var currentTopElement = _rootVisualElement.panel.Pick(cursorPositionOnUI);
            
            elementsUnderCursor.Clear();
            
            // Find all "touchable" elements in UI under cursor - those with PickingMode.Position
            // Move through parents (in case when position absolute is used)
            var element = currentTopElement; 
            while (element != null)
            {
                if (element.pickingMode is PickingMode.Position) elementsUnderCursor.Add(element);
                element = element.parent;
            }
            
            if (previousTopElement != currentTopElement) ElementsUnderCursorChanged?.Invoke();
        }
    }
}