using System;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using Sirenix.Utilities;
using UnityEngine;
using UnityEngine.UIElements;
using VOID.Generic.Objects.Unit;
using VOID.Generic.Objects.Unit.Actions;
using VOID.Generic.Objects.Usable;
using VOID.Generic.Player.CursorOperation;
using VOID.Generic.Player.Input;
using VOID.Generic.Player.Util;
using VOID.UserInterface.Game;
using VOID.UserInterface.Game.AlertUI;
using VOID.UserInterface.Game.Util;
using VOID.ScriptableObjects.Abilities;
using PlayerInputManager = VOID.Generic.Player.Input.PlayerInputManager;

namespace VOID.Generic.Player
{
    /// <summary>
    /// Manages ALL inputs from player and <see cref="UnitObject"/> which player can control.
    /// </summary>
    [DefaultExecutionOrder(-1)]
    public class PlayerController : MonoBehaviour
    {
        public static PlayerController current { get; private set; }

        [Title("Unit currently controlled by player")]
        public UnitObject activeUnit { get; private set; }

        [Title("Units which player can control")]
        [field: SerializeField] public UnitObject mainUnit { get; private set; }
        [field: SerializeField] public UnitObject subUnit { get; private set; }

        // Event
        public event Action<UnitObject> OnActiveUnitChange;
        public event Action<UnitObject> OnMainUnitChange;
        public event Action<UnitObject> OnSubUnitChange;

        // References
        private AlertUI _alertUI;
        private ActionBarSlotsUI _actionBarSlotsUI;
        private PlayerInputManager _playerInputManager;
        private ShortcutsInputManager _shortcutsInputManager;

        private readonly List<AbstractCursorOperation> _playerOperations = new();

        private void Awake()
        {
            current = this;

            // Get all classes that inherits from "AbstractCursorOperation"
            // BUT! In that order where children will be before parents
            // So when we can choose first matching OrdersList for that specific type
            AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(assembly => assembly.GetTypes())
                .Where(type => type.IsSubclassOf(typeof(AbstractCursorOperation)) && type.IsClass)
                .Select(Activator.CreateInstance)
                .Cast<AbstractCursorOperation>()
                .ForEach(ordersList =>
                {
                    var insertIndex = _playerOperations.FindIndex(otherOrderList =>
                        ordersList.ForType() != null && otherOrderList.ForType() != null && ordersList.ForType().IsSubclassOf(otherOrderList.ForType()));
                    if (insertIndex == -1) insertIndex = _playerOperations.Count;
                    _playerOperations.Insert(insertIndex, ordersList);
                });
        }

        private void Start()
        {
            // references
            _playerInputManager = PlayerInputManager.current;
            _shortcutsInputManager = ShortcutsInputManager.current;
            _alertUI = AlertUI.current;
            _actionBarSlotsUI = ActionBarSlotsUI.current;

            // left and right mouse buttons events
            _playerInputManager.OnDefaultUseUI += OnPlayerDefaultUseOnUI;
            _playerInputManager.OnDefaultAlternateUseUI += OnPlayerDefaultAlternateUseOnUI;
            _playerInputManager.OnDefaultUse += OnPlayerDefaultUseOnScene;
            _playerInputManager.OnContextUse += OnPlayerContextUse;
            
            // Bind ESC
            _shortcutsInputManager.menu.performed += _ => OnESC();

            // Bind events for action bar slot shortcuts
            _shortcutsInputManager.actionBarSlot1.performed += _ => OnActionBarUseShortcut(0);
            _shortcutsInputManager.actionBarSlot2.performed += _ => OnActionBarUseShortcut(1);
            _shortcutsInputManager.actionBarSlot3.performed += _ => OnActionBarUseShortcut(2);
            _shortcutsInputManager.actionBarSlot4.performed += _ => OnActionBarUseShortcut(3);
            _shortcutsInputManager.actionBarSlot5.performed += _ => OnActionBarUseShortcut(4);
            _shortcutsInputManager.actionBarSlot6.performed += _ => OnActionBarUseShortcut(5);
            _shortcutsInputManager.actionBarSlot7.performed += _ => OnActionBarUseShortcut(6);
            _shortcutsInputManager.actionBarSlot8.performed += _ => OnActionBarUseShortcut(7);
            _shortcutsInputManager.actionBarSlot9.performed += _ => OnActionBarUseShortcut(8);
            _shortcutsInputManager.actionBarSlot10.performed += _ => OnActionBarUseShortcut(9);
            _shortcutsInputManager.actionBarSlot11.performed += _ => OnActionBarUseShortcut(10);
            _shortcutsInputManager.actionBarSlot12.performed += _ => OnActionBarUseShortcut(11);
            _shortcutsInputManager.actionBarSlot13.performed += _ => OnActionBarUseShortcut(12);
            _shortcutsInputManager.actionBarSlot14.performed += _ => OnActionBarUseShortcut(13);
            _shortcutsInputManager.actionBarSlot15.performed += _ => OnActionBarUseShortcut(14);
            _shortcutsInputManager.actionBarSlot16.performed += _ => OnActionBarUseShortcut(15);
            _shortcutsInputManager.actionBarSlot17.performed += _ => OnActionBarUseShortcut(16);
            _shortcutsInputManager.actionBarSlot18.performed += _ => OnActionBarUseShortcut(17);
            _shortcutsInputManager.actionBarSlot19.performed += _ => OnActionBarUseShortcut(18);
            _shortcutsInputManager.actionBarSlot20.performed += _ => OnActionBarUseShortcut(19);
            _shortcutsInputManager.actionBarSlot21.performed += _ => OnActionBarUseShortcut(20);
            _shortcutsInputManager.actionBarSlot22.performed += _ => OnActionBarUseShortcut(21);
            _shortcutsInputManager.actionBarSlot23.performed += _ => OnActionBarUseShortcut(22);
            _shortcutsInputManager.actionBarSlot24.performed += _ => OnActionBarUseShortcut(23);
            _shortcutsInputManager.actionBarSlot25.performed += _ => OnActionBarUseShortcut(24);
        }

        public void SetMainUnit(UnitObject mainUnit)
        {
            this.mainUnit = mainUnit;
            OnMainUnitChange?.Invoke(mainUnit);
        }

        public void SetSubUnit(UnitObject subUnit)
        {
            this.subUnit = subUnit;
            OnSubUnitChange?.Invoke(subUnit);
        }

        public void SetMainUnitActive() => SetUnitActive(mainUnit);

        public void SetSubUnitActive() => SetUnitActive(subUnit);

        private void SetUnitActive(UnitObject unit)
        {
            if (activeUnit) _alertUI.StopListenFor(activeUnit);
            
            activeUnit = unit;
            _alertUI.ListenFor(activeUnit);
            OnActiveUnitChange?.Invoke(activeUnit);
        }

        #region CURSOR - DEFAULT & CONTEXT

        /// <summary>
        /// Shortcuts from player to use <see cref="UsableObject"/> or <see cref="BasicAbility"/>
        /// from <see cref="UnitObjectActionBar"/> in currently <see cref="activeUnit"/>.
        /// </summary>
        /// <param name="index">Index of slot to use</param>
        private void OnActionBarUseShortcut(int index)
        {
            var slotUI = _actionBarSlotsUI.slotsUI[index];

            if (slotUI == null) return;
            if (!slotUI.item && !slotUI.ability) return;

            activeUnit.OrderStop();
            if (slotUI.item) activeUnit.Order(new UseAction(activeUnit, slotUI.item));
            if (slotUI.ability) activeUnit.Order(new CastingAction(activeUnit, slotUI.ability));
        }

        private void OnPlayerDefaultAlternateUseOnUI(Vector2 cachedPosition, List<VisualElement> cachedElements)
        {
            HandlePlayerDefaultUseOnUI(cachedElements, true);
        }

        private void OnPlayerDefaultUseOnUI(Vector2 cachedPosition, List<VisualElement> cachedElements)
        {
            HandlePlayerDefaultUseOnUI(cachedElements);
        }

        private void HandlePlayerDefaultUseOnUI(List<VisualElement> cachedElements, bool isAlternate = false)
        {
            var select = SelectUtil.SelectFromUI(cachedElements);
            
            // For QoL ActionBar slots can be used with single click
            if (select.actionBarSlotsUI) isAlternate = false;

            HandlePlayerDefaultUse(select, isAlternate);
        }

        private void OnPlayerDefaultUseOnScene()
        {
            HandlePlayerDefaultUse(SelectUtil.SelectFromScene());
        }

        /// <summary>
        /// Handle default use from both SCENE and UI
        /// </summary>
        private void HandlePlayerDefaultUse(SelectUtilResult select, bool isAlternate = false)
        {
            if (!activeUnit || !activeUnit.unitState.isControllable) return;
            if (!select.hit) return;
            var operation = _playerOperations.FirstOrDefault(operation => operation.Check(select));
            operation?.DoDefault(select, isAlternate);
            if (operation == null) Debug.LogError($"No Cursor Operation defined for: {select.selectObject.GetType().GetNiceName()}");
        }

        private void OnPlayerContextUse()
        {
            if (!activeUnit || !activeUnit.unitState.isControllable) return;
            var select = SelectUtil.Select();
            if (!select.hit) return;
            var operation = _playerOperations.FirstOrDefault(operation => operation.Check(select));
            operation?.DoContext(select);
            if (operation == null) Debug.LogError($"No Cursor Operation defined for: {select.selectObject.GetType().GetNiceName()}");
        }

        #endregion

        #region ESC

        private void OnESC()
        {
            if (TryStopCasting()) return;
            if (TryCloseWindowUI()) return;
            if (TryTogglePauseMenu()) return;
        }

        private bool TryStopCasting()
        {
            if (!activeUnit) return false;
            if (activeUnit.unitActionQueue.actionCurrent is not CastingAction) return false;
            
            activeUnit.OrderStop();
            return true;
        }
        
        private bool TryCloseWindowUI()
        {
            if (WindowUI.openWindows.IsNullOrEmpty()) return false;
            
            WindowUI.openWindows.First().Close();
            return true;
        }
        
        private bool TryTogglePauseMenu()
        {
            PauseMenuUI.current.Toggle();
            return true;
        }

        #endregion
    }
}
