using System.Collections.Generic;
using System.Linq;
using Sirenix.Utilities;
using UnityEngine;
using UnityEngine.UIElements;
using VOID.Generic.Objects.Abstract;
using VOID.Generic.Objects.Abstract.Events;
using VOID.Generic.Objects.Unit;
using VOID.Generic.Player;
using VOID.Generic.Turn;
using VOID.UserInterface.Game.Party;

namespace VOID.UserInterface.Game
{
    /// <summary>
    /// Manages TurnQueue elements for current player only:<br/>
    /// - Displays all participating <see cref="UnitObject"/> by their turn order with <see cref="UnitPortraitUI"/><br/>
    /// - Additional info when currently active unit is waiting for its turn<br/>
    /// </summary>
    public class TurnQueueUI : MonoBehaviour
    {
        public static TurnQueueUI current { get; private set; }

        // Visual Elements
        private VisualElement _rootElement;
        private VisualElement _currentQueueElement;
        private VisualElement _nextQueueElement;
        private VisualElement _waitingElement;
        
        // Portraits for units in current and next queue
        private readonly Dictionary<UnitObject, UnitPortraitUI> _currentQueuePortraits = new();
        private readonly Dictionary<UnitObject, UnitPortraitUI> _nextQueuePortraits = new();
        
        // References
        private UnitObject _activeUnit;
        private UnitObject _currentUnitTurn;
        private TurnManager _turnManager;
        
        private void Awake()
        {
            current = this;

            var uiDocument = gameObject.GetComponent<UIDocument>();
            var templateElement = uiDocument.rootVisualElement;

            _rootElement = templateElement.Q("root-turn-queue");
            _currentQueueElement = _rootElement.Q("current-queue");
            _nextQueueElement = _rootElement.Q("next-queue");
            _waitingElement = _rootElement.Q("waiting");
            
            HideTurnQueue();

            _rootElement.dataSource = this;
            _rootElement.pickingMode = PickingMode.Position;
        }

        private void Start()
        {
            PlayerController.current.OnActiveUnitChange += OnActiveUnitChange;
        }

        private void OnActiveUnitChange(UnitObject nextUnit)
        {
            var sameQueue = false;
            if (_activeUnit)
            {
                sameQueue = _activeUnit.basicState.turnManager == nextUnit.basicState.turnManager;
                _activeUnit.basicEvents.onTurnQueueEnter.RemoveListener(OnActiveUnitTurnQueueEnter);
                _activeUnit.basicEvents.onTurnQueueLeave.RemoveListener(OnActiveUnitTurnQueueLeave);
                _activeUnit.basicEvents.onTurnSelfStart.RemoveListener(OnActiveUnitTurnSelfStart);
                _activeUnit.basicEvents.onTurnSelfEnd.RemoveListener(OnActiveUnitTurnSelfEnd);
                _activeUnit.basicEvents.onTurnQueueSwap.RemoveListener(OnActiveUnitTurnQueueSwap);
            }
            nextUnit.basicEvents.onTurnQueueEnter.AddListener(OnActiveUnitTurnQueueEnter);
            nextUnit.basicEvents.onTurnQueueLeave.AddListener(OnActiveUnitTurnQueueLeave);
            nextUnit.basicEvents.onTurnSelfStart.AddListener(OnActiveUnitTurnSelfStart);
            nextUnit.basicEvents.onTurnSelfEnd.AddListener(OnActiveUnitTurnSelfEnd);
            nextUnit.basicEvents.onTurnQueueSwap.AddListener(OnActiveUnitTurnQueueSwap);

            if (!sameQueue)
            {
                HideTurnQueue();
                if (nextUnit.basicState.turnManager) ShowTurnQueue(nextUnit.basicState.turnManager);
            }
            _activeUnit = nextUnit;
            
            UpdateWaitingForTurn();
        }

        private void OnActiveUnitTurnQueueSwap(TurnEvent turnEvent)
        {
            _turnManager = turnEvent.turnManager;
            _turnManager.allUnitsInQueue.ForEach((unit, index) =>
            {
                if (_nextQueuePortraits.Keys.Contains(unit)) return;
                var nextUnit = _turnManager.allUnitsInQueue.ElementAtOrDefault(index + 1);
                AddPortrait(unit, false, nextUnit);
            });
            _turnManager.unitsInCurrentQueue.ForEach((unit, index) =>
            {
                if (_currentQueuePortraits.Keys.Contains(unit)) return;
                var nextUnit = _turnManager.unitsInCurrentQueue.ElementAtOrDefault(index + 1);
                AddPortrait(unit, true, nextUnit);
            });

            SetActiveTurn(_turnManager.unitsInCurrentQueue.First());
        }

        private void OnActiveUnitTurnQueueEnter(TurnEvent turnEvent)
        {
            ShowTurnQueue(turnEvent.turnManager);
            UpdateWaitingForTurn();
        }

        private void OnActiveUnitTurnQueueLeave(TurnEvent turnEvent)
        {
            HideTurnQueue();
            UpdateWaitingForTurn();
        }

        private void OnActiveUnitTurnSelfStart(TurnEvent turnEvent) => UpdateWaitingForTurn();
        
        private void OnActiveUnitTurnSelfEnd(TurnEvent turnEvent) => UpdateWaitingForTurn();
        
        private void OnUnitEnter(AbstractObject obj)
        {
            if (obj is not UnitObject unit) return;
            var beforeUnitInCurrent = _turnManager.unitsInCurrentQueue.ElementAtOrDefault(
                _turnManager.unitsInCurrentQueue.IndexOf(unit) + 1);
            var beforeUnitInNext = _turnManager.allUnitsInQueue.ElementAtOrDefault(
                _turnManager.allUnitsInQueue.IndexOf(unit) + 1);
            AddPortrait(unit, true, beforeUnitInCurrent);
            AddPortrait(unit, false, beforeUnitInNext);
        }

        private void OnUnitLeave(AbstractObject obj)
        {
            if (obj is not UnitObject unit) return;
            RemovePortrait(unit);
        }

        private void OnNextTurn()
        {
            if (_currentUnitTurn)
            {
                _currentQueuePortraits.GetValueOrDefault(_currentUnitTurn)?.Destroy();
                _currentQueuePortraits.Remove(_currentUnitTurn);
            }
            _currentUnitTurn = _turnManager.currentUnitTurn;
            SetActiveTurn(_currentUnitTurn);
        }

        private void OnNextQueue()
        {
            _currentUnitTurn = null;
            _currentQueuePortraits.Values.ForEach(portrait => portrait.Destroy());
            _currentQueuePortraits.Clear();
            _turnManager.unitsInCurrentQueue.ForEach(unit => AddPortrait(unit));
        }
        
        private void ShowTurnQueue(TurnManager turnManager)
        {
            HideTurnQueue();
            _turnManager = turnManager;
            _rootElement.style.display = DisplayStyle.Flex;
            _turnManager.unitsInCurrentQueue.ForEach(unit => AddPortrait(unit));
            _turnManager.allUnitsInQueue.ForEach(unit => AddPortrait(unit, false));
            if (_turnManager.currentUnitTurn) SetActiveTurn(_turnManager.currentUnitTurn);
            _turnManager.OnNextTurn += OnNextTurn;
            _turnManager.OnNextQueue += OnNextQueue;
            _turnManager.OnObjectEnter += OnUnitEnter;
            _turnManager.OnObjectLeave += OnUnitLeave;
            _turnManager.OnNextQueueOrderChange += OnNextQueueOrderChange;
            _turnManager.OnCurrentQueueOrderChange += OnCurrentQueueOrderChange;
        }
        
        private void HideTurnQueue()
        {
            _rootElement.style.display = DisplayStyle.None;
            _currentQueuePortraits.Values.ForEach(unitPortrait => unitPortrait.Destroy());
            _currentQueuePortraits.Clear();
            _currentQueueElement.Clear();
            _nextQueuePortraits.Values.ForEach(unitPortrait => unitPortrait.Destroy());
            _nextQueuePortraits.Clear();
            _nextQueueElement.Clear();
            if (!_turnManager) return;
            _turnManager.OnNextTurn -= OnNextTurn;
            _turnManager.OnNextQueue -= OnNextQueue;
            _turnManager.OnObjectEnter -= OnUnitEnter;
            _turnManager.OnObjectLeave -= OnUnitLeave;
            _turnManager.OnNextQueueOrderChange -= OnNextQueueOrderChange;
            _turnManager.OnCurrentQueueOrderChange -= OnCurrentQueueOrderChange;
            _currentUnitTurn = null;
            _turnManager = null;
        }

        private void AddPortrait(UnitObject unit, bool putIntoCurrent = true, UnitObject putBefore = null)
        {
            var element = putIntoCurrent ? _currentQueueElement : _nextQueueElement;
            var dict = putIntoCurrent ? _currentQueuePortraits : _nextQueuePortraits;
            var unitPortrait = new UnitPortraitUI();
            unitPortrait.InitNew(element);
            unitPortrait.SetUnit(unit);
            if (putBefore)
            {
                var behindElement = dict.GetValueOrDefault(putBefore).templateElement;
                unitPortrait.templateElement.PlaceBehind(behindElement);
            }
            dict.Add(unit, unitPortrait);
        }

        private void RemovePortrait(UnitObject unit)
        {
            _currentQueuePortraits.GetValueOrDefault(unit)?.Destroy();
            _nextQueuePortraits.GetValueOrDefault(unit)?.Destroy();
            _currentQueuePortraits.Remove(unit);
            _nextQueuePortraits.Remove(unit);
        }

        private void MovePortrait(UnitObject unit, bool putIntoCurrent = true, UnitObject putBefore = null)
        {
            var dict = putIntoCurrent ? _currentQueuePortraits : _nextQueuePortraits;
            if (putBefore)
            {
                var behindElement = dict.GetValueOrDefault(putBefore).templateElement;
                dict.GetValueOrDefault(unit).templateElement.PlaceBehind(behindElement);
            }
            else
            {
                dict.GetValueOrDefault(unit).templateElement.BringToFront();
            }
        }

        private void OnNextQueueOrderChange(UnitObject unit, int newIndex)
        {
            MovePortrait(unit, false, _turnManager.allUnitsInQueue.ElementAtOrDefault(newIndex + 1));
        }
        
        private void OnCurrentQueueOrderChange(UnitObject unit, int newIndex)
        {
            MovePortrait(unit, true, _turnManager.unitsInCurrentQueue.ElementAtOrDefault(newIndex + 1));
        }

        private void SetActiveTurn(UnitObject unit)
        {
            _currentQueueElement.Q(className: "active-turn")?.RemoveFromClassList("active-turn");
            _currentQueuePortraits.GetValueOrDefault(unit)?.templateElement.AddToClassList("active-turn");
        }
        
        /// <summary>
        /// Makes TurnQueue's waiting element visible if it is not active unit's turn.
        /// </summary>
        private void UpdateWaitingForTurn()
        {
            var isWaiting = _activeUnit.basicState.turnManager && _activeUnit.basicState.turnManager.currentUnitTurn != _activeUnit;
            _waitingElement.style.display = isWaiting ? DisplayStyle.Flex : DisplayStyle.None;
        }
    }
}
