using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UIElements;
using PlayerInputManager = VOID.Generic.Player.Input.PlayerInputManager;
using ShortcutsInputManager = VOID.Generic.Player.Input.ShortcutsInputManager;

namespace VOID.UserInterface.Game.Util
{
    [Serializable]
    public class WindowUI : MonoBehaviour
    {
        public static List<WindowUI> openWindows = new();
        
        // UI Visual Elements
        private VisualElement _parentElement;
        private VisualElement _rootElement;
        private Button _windowCloseButton;
        private VisualElement _headerElement;
        private Label _titleLabel;
        
        // Events
        public event Action OnClose;
        public event Action OnOpen;
        
        // References
        private PlayerInputManager _playerInputManager;
        private GameUI _gameUI;
        
        // Toggle
        private bool _isOpen;
        private bool _isOverHeader;
        private bool _isDragging;

        // Offset - relative position from window position to cursor
        private Vector2 _draggingOffset;
        
        private void Awake()
        {
            var uiDocument = gameObject.GetComponent<UIDocument>();
            var templateElement = uiDocument.rootVisualElement;
            
            // Find all visual elements
            _rootElement = templateElement.Q("root-window"); 
            _parentElement = _rootElement.parent.parent; 
            _headerElement = _rootElement.Q("header");
            _titleLabel = _headerElement.Q<Label>("title");
            _windowCloseButton = _rootElement.Q<Button>("close");
            
            // Close backpack when "x" pressed
            _windowCloseButton.clicked += Close;
            
            // Make window active if pressed
            _parentElement.RegisterCallback(new EventCallback<MouseDownEvent>(_ => SetActiveWindow()), TrickleDown.TrickleDown);
        }

        private void Start()
        {
            _gameUI = GameUI.current;
            _playerInputManager = PlayerInputManager.current;
            _playerInputManager.OnDraggingPrepare += PrepareDragging;
            _playerInputManager.OnDraggingStart += StartDragging;
            _playerInputManager.OnDraggingEnd += EndDragging;
        }

        public void SetTitle(string title)
        {
            _titleLabel.text = title;
        }

        public void Toggle()
        {
            if (_isOpen)
            {
                Close();
            }
            else
            {
                Open();
            }
        }

        public void Close()
        {
            // Hide element
            _parentElement.style.display = DisplayStyle.None;
            
            // Reset element position styles
            _parentElement.style.right = StyleKeyword.Null;
            _parentElement.style.top = StyleKeyword.Null;
            _parentElement.style.bottom = StyleKeyword.Null;
            _parentElement.style.left = StyleKeyword.Null;
            
            _isOpen = false;
            OnClose?.Invoke();
            openWindows.Remove(this);
        }
        
        public void Open()
        {
            // Show Element
            _parentElement.style.display = DisplayStyle.Flex;
            
            _isOpen = true;
            OnOpen?.Invoke();
            openWindows.Add(this);
            SetActiveWindow();
        }

        private void Update()
        {
            if (!_isDragging) return;
            
            var mousePos = _gameUI.cursorPositionOnUI;
            _parentElement.style.top = new StyleLength(mousePos.y - Mathf.Abs(_draggingOffset.y));
            _parentElement.style.left = new StyleLength(mousePos.x - Mathf.Abs(_draggingOffset.x));
        }

        private void PrepareDragging()
        {
            // Check if dragging started at window's title header...
            _isOverHeader = GameUI.current.elementsUnderCursor.Contains(_headerElement);
            
            // ...Only dragging by TITLE element allows to drag whole window
            if (!_isOverHeader) return;
            
            // Calculate offset from mouse position to window position
            var windowPos = new Vector2(_parentElement.worldBound.x,_parentElement.worldBound.y);
            var mousePos = _gameUI.cursorPositionOnUI;
            _draggingOffset = windowPos - mousePos;
        }
        
        private void StartDragging()
        {
            if (!_isOverHeader) return;
                
            _isDragging = true;
            _parentElement.style.right = new StyleLength(StyleKeyword.Auto);
            _parentElement.style.bottom = new StyleLength(StyleKeyword.Auto);
        }

        public void EndDragging()
        {
            _isDragging = false;
            _isOverHeader = false;
        }
        
        public void SetActiveWindow()
        {
            openWindows.Remove(this);
            openWindows = openWindows.Prepend(this).ToList();
            UpdateDocumentUIOrder();
        }
        
        /// <summary>
        /// Calculating sorting order for all windows (in which order they will be rendered)
        /// </summary>
        public static void UpdateDocumentUIOrder()
        {
            openWindows.ForEach(window =>
            {
                var sortingOrder = openWindows.Count - openWindows.IndexOf(window);
                window.GetComponent<UIDocument>().sortingOrder = sortingOrder;
            });
        }
    }
}