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.Events;
using VOID.Generic.Player;
using VOID.Generic.Player.Input;
using VOID.UserInterface.Game.Interfaces;
using VOID.UserInterface.Game.Util;

namespace VOID.UserInterface.Game
{
    public class EquipmentUI : MonoBehaviour, IHasWindow
    {
        public static EquipmentUI current {get; private set;}
        
        // WindowUI
        public WindowUI windowUI { get; private set; }
        
        // Visual Elements
        private VisualElement _rootElement;
        private Button[] _tabElements;
        private VisualElement[] _tabContentElements;
        
        // Visual Elements - Attributes values
        private Label _strengthLabel;
        private Label _dexterityLabel;
        private Label _intelligenceLabel;
        private Label _constitutionLabel;
        private Label _speedLabel;
        private Label _perceptionLabel;
        private Label _damageLabel;
        private Label _accuracyLabel;
        private Label _dodgeLabel;
        private Label _criticalChanceLabel;
        private Label _criticalMultiplierLabel;
        private Label _sightLabel;
        private Label _hearLabel;
        private Label _moveLabel;
        private Label _initiativeLabel;
        private Label _carryCapacityLabel;
        
        // Visual Elements - Resources values
        private Label _healthLabel;
        private Label _actionPointsLabel;
        private Label _movePointsLabel;
        
        // Visual Elements - Equipment Slots
        private readonly SlotUI _mainHandSlot = new();
        private readonly SlotUI _offHandSlot = new();
        private readonly SlotUI _helmetSlot = new();
        private readonly SlotUI _armorSlot = new();
        private readonly SlotUI _beltSlot = new();
        private readonly SlotUI _pantsSlot = new();
        private readonly SlotUI _bootsSlot = new();
        private readonly SlotUI _necklaceSlot = new();
        private readonly SlotUI _glovesSlot = new();
        private readonly SlotUI _ringOneSlot = new();
        private readonly SlotUI _ringTwoSlot = new();
        
        // References
        private UnitObject _unit;
        private UnitObjectEquipment _equipment;
        private PlayerController _playerController;
        private ShortcutsInputManager _shortcutsInputManager;

        // String formattings
        private const string F0 = "F0";
        private const string F1 = "F1";
        private const string F2 = "F2";
        private const string P0 = "P0";
        private const string P1 = "P1";
        private const string P2 = "P2";

        private void Awake()
        {
            current = this;

            var uiDocument = gameObject.GetComponent<UIDocument>();
            var templateElement = uiDocument.rootVisualElement;
            
            // Get all VisualElements
            _rootElement = templateElement.Q("root-equipment");
            windowUI = gameObject.GetComponent<WindowUI>();
            
            // Get all VisualElements - Attributes
            _strengthLabel = _rootElement.Q<Label>("strength");
            _dexterityLabel = _rootElement.Q<Label>("dexterity");
            _intelligenceLabel = _rootElement.Q<Label>("intelligence");
            _constitutionLabel = _rootElement.Q<Label>("constitution");
            _speedLabel = _rootElement.Q<Label>("speed");
            _perceptionLabel = _rootElement.Q<Label>("perception");
            _damageLabel = _rootElement.Q<Label>("damage");
            _accuracyLabel = _rootElement.Q<Label>("accuracy");
            _dodgeLabel = _rootElement.Q<Label>("dodge");
            _criticalChanceLabel = _rootElement.Q<Label>("critical-damage-chance");
            _criticalMultiplierLabel = _rootElement.Q<Label>("critical-damage-multiplier");
            _sightLabel = _rootElement.Q<Label>("sight");
            _hearLabel = _rootElement.Q<Label>("hear");
            _moveLabel = _rootElement.Q<Label>("move");
            _initiativeLabel = _rootElement.Q<Label>("initiative");
            
            // Get all VisualElements - Resources
            _healthLabel = _rootElement.Q<Label>("health");
            _actionPointsLabel = _rootElement.Q<Label>("action-points");
            _movePointsLabel = _rootElement.Q<Label>("move-points");
            
            // Get all VisualElements - Items
            _mainHandSlot.Init(_rootElement.Q("item-main-hand"), SlotType.EquipmentSlot, (int)EquipmentSlotType.MainHand);
            _offHandSlot.Init(_rootElement.Q("item-off-hand"), SlotType.EquipmentSlot, (int)EquipmentSlotType.OffHand);
            _helmetSlot.Init(_rootElement.Q("item-helmet"), SlotType.EquipmentSlot, (int)EquipmentSlotType.Helmet);
            _armorSlot.Init(_rootElement.Q("item-armor"), SlotType.EquipmentSlot, (int)EquipmentSlotType.Armor);
            _beltSlot.Init(_rootElement.Q("item-belt"), SlotType.EquipmentSlot, (int)EquipmentSlotType.Belt);
            _pantsSlot.Init(_rootElement.Q("item-pants"), SlotType.EquipmentSlot, (int)EquipmentSlotType.Pants);
            _bootsSlot.Init(_rootElement.Q("item-boots"), SlotType.EquipmentSlot, (int)EquipmentSlotType.Boots);
            _necklaceSlot.Init(_rootElement.Q("item-necklace"), SlotType.EquipmentSlot, (int)EquipmentSlotType.Necklace);
            _glovesSlot.Init(_rootElement.Q("item-gloves"), SlotType.EquipmentSlot, (int)EquipmentSlotType.Gloves);
            _ringOneSlot.Init(_rootElement.Q("item-ring-one"), SlotType.EquipmentSlot, (int)EquipmentSlotType.RingOne);
            _ringTwoSlot.Init(_rootElement.Q("item-ring-two"), SlotType.EquipmentSlot, (int)EquipmentSlotType.RingTwo);

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

        private void Start()
        {
            _playerController = PlayerController.current;
            _shortcutsInputManager = ShortcutsInputManager.current;
            
            // Toggle show/hide when pressing shortcut for backpack
            _shortcutsInputManager.equipment.performed += _ => windowUI.Toggle();
            windowUI.OnOpen += OnOpen;
            windowUI.OnClose += OnClose;
            
            windowUI.SetTitle("Equipment");

            // Hidden by default
            windowUI.Close();
        }

        public void Toggle() => windowUI.Toggle();

        public void Open() => windowUI.Open();

        public void Close() => windowUI.Close();
        
        private void OnOpen()
        {
            // References to active unit
            _unit = PlayerController.current.activeUnit;
            _equipment = _unit.unitEquipment;
            
            // Refresh UI when we change active unit
            _playerController.OnActiveUnitChange += OnActiveUnitChange;

            // Listen to all changes in equipment and update UI 
            _unit.unitEvents.onEquipmentEquip.AddListener(OnEquip);
            _unit.unitEvents.onEquipmentUnEquip.AddListener(OnUnEquip);
            _unit.unitEvents.onMainAttributeChange.AddListener(OnMainAttributeChange);
            _unit.unitEvents.onSubAttributeChange.AddListener(OnSubAttributeChange);
            _unit.unitEvents.onMaxResourceChange.AddListener(OnUnitResourceChange);
            _unit.unitEvents.onCurrentResourceChange.AddListener(OnUnitResourceChange);
            _unit.basicEvents.onMaxResourceChange.AddListener(OnBasicResourceChange);
            _unit.basicEvents.onCurrentResourceChange.AddListener(OnBasicResourceChange);
            
            // Update all values when opening
            UpdateAll();
        }

        private void OnClose()
        {
            // Stop listening 
            _playerController.OnActiveUnitChange -= OnActiveUnitChange;

            // Backpack closed, no need to listen for backpack changes
            if (_unit)
            {
                _unit.unitEvents.onEquipmentEquip.RemoveListener(OnEquip);
                _unit.unitEvents.onEquipmentUnEquip.RemoveListener(OnUnEquip);
                _unit.unitEvents.onMainAttributeChange.RemoveListener(OnMainAttributeChange);
                _unit.unitEvents.onSubAttributeChange.RemoveListener(OnSubAttributeChange);
                _unit.unitEvents.onMaxResourceChange.RemoveListener(OnUnitResourceChange);
                _unit.unitEvents.onCurrentResourceChange.RemoveListener(OnUnitResourceChange);
                _unit.basicEvents.onMaxResourceChange.RemoveListener(OnBasicResourceChange);
                _unit.basicEvents.onCurrentResourceChange.RemoveListener(OnBasicResourceChange);
            }

            // Clear variables
            _unit = null;
            _equipment = null;
        }

        private void OnActiveUnitChange(UnitObject unitObject)
        {
            windowUI.Close();
            windowUI.Open();
        }
        
        private void OnMainAttributeChange(MainAttributeChangeEvent mainAttributeChangeEvent)
        {
            switch (mainAttributeChangeEvent.type)
            {
                case MainAttributeType.Strength: OnStrengthChange(); break;
                case MainAttributeType.Dexterity: OnDexterityChange(); break;
                case MainAttributeType.Intelligence: OnIntelligenceChange(); break;
                case MainAttributeType.Constitution: OnConstitutionChange(); break;
                case MainAttributeType.Speed: OnSpeedChange(); break;
                case MainAttributeType.Perception: OnPerceptionChange(); break;
            }
        }
        
        private void OnSubAttributeChange(SubAttributeChangeEvent subAttributeChangeEvent)
        {
            switch (subAttributeChangeEvent.type)
            {
                case SubAttributeType.DamageMin:
                case SubAttributeType.DamageMax: OnDamageChange(); break;
                case SubAttributeType.CriticalDamageChance: OnCriticalDamageChanceChange(); break;
                case SubAttributeType.CriticalDamageMultiplier: OnCriticalDamageMultiplierChange(); break;
                case SubAttributeType.Accuracy: OnAccuracyChange(); break;
                case SubAttributeType.Dodge: OnDodgeChange(); break;
                case SubAttributeType.Sight: OnSightChange(); break;
                case SubAttributeType.Hear: OnHearChange(); break;
                case SubAttributeType.Move: OnMoveChange(); break;
                case SubAttributeType.Initiative: OnInitiativeChange(); break;
            }
        }
        
        private void OnBasicResourceChange(BasicResourceChangeEvent basicResourceChangeEvent)
        {
            switch (basicResourceChangeEvent.type)
            {
                case BasicResourceType.Health: OnHealthChange(); break;
            }
        }
        
        private void OnUnitResourceChange(UnitResourceChangeEvent unitResourceChangeEvent)
        {
            switch (unitResourceChangeEvent.type)
            {
                case UnitResourceType.MovePoints: OnMovePointsChange(); break;
                case UnitResourceType.ActionPoints: OnActionPointsChange(); break;
            }
        }

        private void OnEquip(EquipmentEquipEvent equipmentEquipEvent)
        {
            switch (equipmentEquipEvent.equipmentSlot)
            {
                case EquipmentSlotType.MainHand: OnMainHandChange(); break;
                case EquipmentSlotType.OffHand: OnOffHandChange(); break;
                case EquipmentSlotType.Helmet: OnHelmetChange(); break;
                case EquipmentSlotType.Armor: OnArmorChange(); break;
                case EquipmentSlotType.Pants: OnPantsChange(); break;
                case EquipmentSlotType.Belt: OnBeltChange(); break;
                case EquipmentSlotType.Boots: OnBootsChange(); break;
                case EquipmentSlotType.Gloves: OnGlovesChange(); break;
                case EquipmentSlotType.Necklace: OnNecklaceChange(); break;
                case EquipmentSlotType.RingOne: OnRingOneChange(); break;
                case EquipmentSlotType.RingTwo: OnRingTwoChange(); break;
                default: Debug.LogWarning("Unrecognized EquipmentSlot: " + equipmentEquipEvent.equipmentSlot); return;
            }
        }

        private void OnUnEquip(EquipmentUnEquipEvent equipmentUnEquipEvent)
        {
            switch (equipmentUnEquipEvent.equipmentSlot)
            {
                case EquipmentSlotType.MainHand: OnMainHandChange(); break;
                case EquipmentSlotType.OffHand: OnOffHandChange(); break;
                case EquipmentSlotType.Helmet: OnHelmetChange(); break;
                case EquipmentSlotType.Armor: OnArmorChange(); break;
                case EquipmentSlotType.Pants: OnPantsChange(); break;
                case EquipmentSlotType.Belt: OnBeltChange(); break;
                case EquipmentSlotType.Boots: OnBootsChange(); break;
                case EquipmentSlotType.Gloves: OnGlovesChange(); break;
                case EquipmentSlotType.Necklace: OnNecklaceChange(); break;
                case EquipmentSlotType.RingOne: OnRingOneChange(); break;
                case EquipmentSlotType.RingTwo: OnRingTwoChange(); break;
                default: Debug.LogWarning("Unrecognized EquipmentSlot: " + equipmentUnEquipEvent.equipmentSlot); return;
            }
        }

        private void UpdateAll()
        {
            OnStrengthChange();
            OnDexterityChange();
            OnIntelligenceChange();
            OnConstitutionChange();
            OnSpeedChange();
            OnPerceptionChange();
            
            OnDamageChange();
            OnAccuracyChange();
            OnDodgeChange();
            OnCriticalDamageChanceChange();
            OnCriticalDamageMultiplierChange();
            OnSightChange();
            OnHearChange();
            OnMoveChange();
            OnInitiativeChange();
            
            OnHealthChange();
            OnActionPointsChange();
            OnMovePointsChange();

            OnMainHandChange();
            OnOffHandChange();
            OnHelmetChange();
            OnArmorChange();
            OnPantsChange();
            OnBeltChange();
            OnBootsChange();
            OnGlovesChange();
            OnNecklaceChange();
            OnRingOneChange();
            OnRingTwoChange();
        }

        // On Change - MainAttributes
        private void OnStrengthChange() => _strengthLabel.text = $"{_unit.unitData.mainAttributes.strength.ToString(F0)}";
        private void OnDexterityChange() => _dexterityLabel.text = $"{_unit.unitData.mainAttributes.dexterity.ToString(F0)}";
        private void OnIntelligenceChange() => _intelligenceLabel.text = $"{_unit.unitData.mainAttributes.intelligence.ToString(F0)}";
        private void OnConstitutionChange() => _constitutionLabel.text = $"{_unit.unitData.mainAttributes.constitution.ToString(F0)}";
        private void OnSpeedChange() => _speedLabel.text = $"{_unit.unitData.mainAttributes.speed.ToString(F0)}";
        private void OnPerceptionChange() => _perceptionLabel.text = $"{_unit.unitData.mainAttributes.perception.ToString(F0)}";
        
        // On Change - SubAttributes
        private void OnDamageChange() => _damageLabel.text = $"{_unit.unitData.subAttributes.damageMin.ToString(F0)} - {_unit.unitData.subAttributes.damageMax.ToString(F0)}";
        private void OnAccuracyChange() => _accuracyLabel.text = $"{_unit.unitData.subAttributes.accuracy.ToString(P0)}";
        private void OnDodgeChange() => _dodgeLabel.text = $"{_unit.unitData.subAttributes.dodge.ToString(P0)}";
        private void OnCriticalDamageChanceChange() => _criticalChanceLabel.text = $"{_unit.unitData.subAttributes.criticalDamageChance.ToString(P0)}";
        private void OnCriticalDamageMultiplierChange() => _criticalMultiplierLabel.text = $"{_unit.unitData.subAttributes.criticalDamageMultiplier.ToString(P0)}";
        private void OnSightChange() => _sightLabel.text = $"{_unit.unitData.subAttributes.sight.ToString(F0)}";
        private void OnHearChange() => _hearLabel.text = $"{_unit.unitData.subAttributes.hear.ToString(F0)}";
        private void OnMoveChange() => _moveLabel.text = $"{_unit.unitData.subAttributes.move.ToString(F1)}m";
        private void OnInitiativeChange() => _initiativeLabel.text = $"{_unit.unitData.subAttributes.initiative.ToString(F0)}";
        
        // On Change - Resources
        private void OnHealthChange() => _healthLabel.text =
            $"{_unit.basicData.currentResources.health.ToString(F0)} / {_unit.basicData.maxResources.health.ToString(F0)}";
        private void OnActionPointsChange() => _actionPointsLabel.text =
            $"{_unit.unitData.currentResources.actionPoints.ToString(F0)} / {_unit.unitData.maxResources.actionPoints.ToString(F0)}";
        private void OnMovePointsChange() => _movePointsLabel.text =
            $"{_unit.unitData.currentResources.movePoints.ToString(F1)} / {_unit.unitData.maxResources.movePoints.ToString(F1)}";
        
        // On Change - Items
        private void OnMainHandChange() => _mainHandSlot.Set(_equipment.GetEquipped(EquipmentSlotType.MainHand));
        private void OnOffHandChange() => _offHandSlot.Set(_equipment.GetEquipped(EquipmentSlotType.OffHand));
        private void OnHelmetChange() => _helmetSlot.Set(_equipment.GetEquipped(EquipmentSlotType.Helmet));
        private void OnArmorChange() => _armorSlot.Set(_equipment.GetEquipped(EquipmentSlotType.Armor));
        private void OnPantsChange() => _pantsSlot.Set(_equipment.GetEquipped(EquipmentSlotType.Pants));
        private void OnBeltChange() => _beltSlot.Set(_equipment.GetEquipped(EquipmentSlotType.Belt));
        private void OnBootsChange() => _bootsSlot.Set(_equipment.GetEquipped(EquipmentSlotType.Boots));
        private void OnGlovesChange() => _glovesSlot.Set(_equipment.GetEquipped(EquipmentSlotType.Gloves));
        private void OnNecklaceChange() => _necklaceSlot.Set(_equipment.GetEquipped(EquipmentSlotType.Necklace));
        private void OnRingOneChange() => _ringOneSlot.Set(_equipment.GetEquipped(EquipmentSlotType.RingOne));
        private void OnRingTwoChange() => _ringTwoSlot.Set(_equipment.GetEquipped(EquipmentSlotType.RingTwo));
    }
}
