using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements;
using VOID.Generic.Dict;
using VOID.Generic.Objects.Abstract.Events;
using VOID.Generic.Objects.Unit;
using VOID.Generic.Objects.Unit.Actions;
using VOID.Generic.Objects.Unit.Actions.Exceptions;
using VOID.Generic.Objects.Unit.Events;
using VOID.Generic.Player;
using VOID.Generic.Turn;

namespace VOID.UserInterface.Game
{
    /// <summary>
    /// Manages ActionBar's Turn elements for current player only:<br/>
    /// - Turn resources of <see cref="UnitObject"/><br/>
    /// - Button to start new <see cref="TurnManager"/><br/>
    /// - Buttons to <see cref="LeaveTurnAction"/> and <see cref="EndTurnAction"/> when in combat
    /// </summary>
    public class ActionBarTurnInfoUI : MonoBehaviour
    {
        public static ActionBarTurnInfoUI current { get; private set; }
        
        // ActionBar for this unit
        private UnitObject _unit;
        private UnitObjectActionBar _actionBar;
        
        // Visual Elements - Turn Info
        private VisualElement _turnInfoElement;
        private VisualElement _actionPointsElement;
        private readonly List<VisualElement> _actionPointsElements = new();
        private ProgressBar _moveBar;
        private Button _nextTurnButton;
        private Button _startTurnButton;
        private Button _endTurnButton;
        
        // References
        private PlayerController _playerController;
        
        private void Awake()
        {
            current = this;

            var uiDocument = gameObject.GetComponent<UIDocument>();
            var templateElement = uiDocument.rootVisualElement;
            
            // Find all important elements
            _turnInfoElement = templateElement.Q("turn-info");
            _actionPointsElement = templateElement.Q("action-points");
            _moveBar = templateElement.Q<ProgressBar>("move-bar");
            _nextTurnButton = templateElement.Q<Button>("next-turn");
            _startTurnButton = templateElement.Q<Button>("start-turn");
            _endTurnButton = templateElement.Q<Button>("end-turn");
            
            // Remove placeholders from action points
            _actionPointsElement.Clear();

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

        private void Start()
        {
            _playerController = PlayerController.current;
            _playerController.OnActiveUnitChange += OnActiveUnitChange;
            _nextTurnButton.clicked += NextTurn;
            _startTurnButton.clicked += StartTurn;
            _endTurnButton.clicked += EndTurn;
        }

        /// <summary>
        /// Player order active unit to end its turn.
        /// </summary>
        private void NextTurn()
        {
            if (!_unit) return;

            _unit.OrderStop();
            _unit.Order(new EndTurnAction(_unit));
        }

        /// <summary>
        /// Starts new turn manager - also nearby units will also join
        /// </summary>
        private void StartTurn()
        {
            if (!_unit) return;
            if (_unit.basicState.turnManager) return;
            TurnManager.BuildGameObject(_unit);
        }

        /// <summary>
        /// Player order active unit to try end turn.<br/>
        /// - If combat is FORCED then try to escape (if possible)
        /// - If combat is NOT FORCED then end whole turn manager
        /// </summary>
        private void EndTurn()
        {
            if (!_unit) return;
            if (!_unit.basicState.turnManager) return;

            // if fight is forced when try leave with current unit
            // else end whole turn combat
            if (_unit.basicState.turnManager.isForcedFight)
                _unit.OrderInstantly(new LeaveTurnAction(_unit));
            else
                _unit.basicState.turnManager.EndTurnManager();
        }

        /// <summary>
        /// Depending on unit turn state it hide or show player's buttons for turn.
        /// </summary>
        private void UpdateTurnButtonsVisibility()
        {
            if (_unit.basicState.turnManager)
            {
                _nextTurnButton.style.display = DisplayStyle.Flex;
                _endTurnButton.style.display = DisplayStyle.Flex;
                _startTurnButton.style.display = DisplayStyle.None;
            }
            else
            {
                _nextTurnButton.style.display = DisplayStyle.None;
                _endTurnButton.style.display = DisplayStyle.None;
                _startTurnButton.style.display = DisplayStyle.Flex;
            }
        }

        /// <summary>
        /// 1. Stops listening to changes for previous unit
        /// 2. Start listening to changes in new active unit
        /// 3. Updates whole UI
        /// </summary>
        private void OnActiveUnitChange(UnitObject unit)
        {
            if (_unit)
            {
                _unit.basicEvents.onTurnQueueEnter.RemoveListener(OnTurnEnter);
                _unit.basicEvents.onTurnQueueLeave.RemoveListener(OnTurnLeave);
                _unit.basicEvents.onTurnSelfStart.RemoveListener(OnTurnSelfStart);
                _unit.basicEvents.onTurnSelfEnd.RemoveListener(OnTurnSelfEnd);
                _unit.unitEvents.onCurrentResourceChange.RemoveListener(OnResourceChange);
                _unit.unitEvents.onMaxResourceChange.RemoveListener(OnResourceChange);
            }

            _unit = unit;
            UpdateActionPoints();
            UpdateMovePoints();
            UpdateTurnButtonsVisibility();
            UpdateWaitingForTurn();
            
            _unit.basicEvents.onTurnQueueEnter.AddListener(OnTurnEnter);
            _unit.basicEvents.onTurnQueueLeave.AddListener(OnTurnLeave);
            _unit.basicEvents.onTurnSelfStart.AddListener(OnTurnSelfStart);
            _unit.basicEvents.onTurnSelfEnd.AddListener(OnTurnSelfEnd);
            _unit.unitEvents.onCurrentResourceChange.AddListener(OnResourceChange);
            _unit.unitEvents.onMaxResourceChange.AddListener(OnResourceChange);
        }

        private void OnTurnEnter(TurnEvent turnEvent)
        {
            UpdateTurnButtonsVisibility();
            UpdateWaitingForTurn();
        }

        private void OnTurnLeave(TurnEvent turnEvent)
        {
            UpdateTurnButtonsVisibility();
            UpdateWaitingForTurn();
        }
        
        private void OnTurnSelfStart(TurnEvent turnEvent)
        {
            UpdateWaitingForTurn();
        }
        
        private void OnTurnSelfEnd(TurnEvent turnEvent)
        {
            UpdateWaitingForTurn();
        }

        /// <summary>
        /// Makes ActionBar's turn elements inactive if currently active unit is waiting for its turn.
        /// </summary>
        private void UpdateWaitingForTurn()
        {
            var isWaiting = _unit.basicState.turnManager && _unit.basicState.turnManager.currentUnitTurn != _unit;
            _turnInfoElement.EnableInClassList("inactive", isWaiting);
        }
        
        private void OnResourceChange(UnitResourceChangeEvent resourceChangeEvent)
        {
            switch (resourceChangeEvent.type)
            {
                case UnitResourceType.ActionPoints:
                    UpdateActionPoints();
                    break;
                case UnitResourceType.MovePoints:
                    UpdateMovePoints();
                    break;
                default: return;
            }
        }

        /// <summary>
        /// Updates current and max action points to correspond these from UnitData.
        /// </summary>
        private void UpdateActionPoints()
        {
            var maxActionPoints = (int)_unit.unitData.maxResources.actionPoints;
            var currentActionPoints = (int)_unit.unitData.currentResources.actionPoints;

            // Add/Remove visual elements if needed
            for (var i = 0; i < maxActionPoints - _actionPointsElements.Count;) AddActionPointElement();
            for (var i = 0; i < _actionPointsElements.Count - maxActionPoints;) RemoveActionPointElement();

            // Activate/Disable action points
            for (var i = 0; i < _actionPointsElements.Count; i++)
            {
                var actionPointElement = _actionPointsElements[i];
                actionPointElement.EnableInClassList("active-action-point", i < currentActionPoints);
            }
        }

        /// <summary>
        /// Adds new action point element to UI.
        /// </summary>
        private void AddActionPointElement()
        {
            var actionPointElement = new VisualElement();
            actionPointElement.AddToClassList("action-point");
            _actionPointsElement.Add(actionPointElement);
            _actionPointsElements.Add(actionPointElement);
        }

        /// <summary>
        /// Removes one action point element from UI.
        /// </summary>
        private void RemoveActionPointElement()
        {
            _actionPointsElement.Remove(_actionPointsElements.Last());
            _actionPointsElements.Remove(_actionPointsElements.Last());
        }
        
        /// <summary>
        /// Updates current and max move points to correspond these from UnitData.
        /// </summary>
        private void UpdateMovePoints()
        {
            _moveBar.highValue = _unit.unitData.maxResources.movePoints;
            _moveBar.value = _unit.unitData.currentResources.movePoints;
            _moveBar.title = $"{_moveBar.value:F1} / {_moveBar.highValue:F1}";
        }
    }
}
