using System;
using JetBrains.Annotations;
using UnityEngine;
using UnityEngine.UIElements;
using VOID.Generic.Dict;
using VOID.Generic.Objects.Item;
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 BackpackUI : MonoBehaviour, IHasWindow
    {
        public static BackpackUI current { get; private set; }

        // WindowUI
        public WindowUI windowUI { get; private set; }

        // Active Unit and his Backpack
        private UnitObject _unit;
        private UnitObjectBackpack _backpack;

        // Visual Elements
        private VisualElement _rootElement;
        private VisualElement _slotsElement;
        private SlotUI[] _slots;

        // References
        private ShortcutsInputManager _shortcutsInputManager;
        private PlayerController _playerController;

        // Sort buttons
        private Button _sortButton;

        private void Awake() {
            current = this;

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

            // Find all important elements
            _rootElement = templateElement.Q("root-backpack");
            _slotsElement = templateElement.Q("slots");
            windowUI = gameObject.GetComponent<WindowUI>();

            // Sort buttons
            _sortButton = templateElement.Q<Button>("Sort");

            // Clear placeholders
            _slotsElement.Clear();

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

        private void Start() {
            _shortcutsInputManager = ShortcutsInputManager.current;
            _playerController = PlayerController.current;

            // Toggle show/hide when pressing shortcut for backpack
            _shortcutsInputManager.inventory.performed += _ => windowUI.Toggle();
            windowUI.OnOpen += OnOpen;
            windowUI.OnClose += OnClose;

            // Sort buttons
            _sortButton.clicked += () => {
                var worldBound = _sortButton.worldBound;
                var positionToDisplayDropdown = new Vector2(worldBound.x, worldBound.y + worldBound.height);
                
                DropdownUI.current.Prepare(positionToDisplayDropdown);
                DropdownUI.current.AddOption("Name ↑", () => _backpack.Sort(item => item.basicData.finalName, SortDirectionType.Ascending));
                DropdownUI.current.AddOption("Name ↓", () => _backpack.Sort(item => item.basicData.finalName, SortDirectionType.Descending));
                DropdownUI.current.AddOption("Price ↑", () => _backpack.Sort(item => item.itemData.finalValue, SortDirectionType.Ascending));
                DropdownUI.current.AddOption("Price ↓", () => _backpack.Sort(item => item.itemData.finalValue, SortDirectionType.Descending));
                DropdownUI.current.AddOption("Weight ↑", () => _backpack.Sort(item => item.itemData.finalWeight, SortDirectionType.Ascending));
                DropdownUI.current.AddOption("Weight ↓", () => _backpack.Sort(item => item.itemData.finalWeight, SortDirectionType.Descending));
                DropdownUI.current.AddOption("Quality ↑", () => _backpack.Sort(item => item.itemData.quality.index, SortDirectionType.Ascending));
                DropdownUI.current.AddOption("Quality ↓", () => _backpack.Sort(item => item.itemData.quality.index, SortDirectionType.Descending));
                DropdownUI.current.Open();
            };

            windowUI.SetTitle("Backpack");

            // 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;
            _backpack = _unit.unitBackpack;

            // Refresh UI when we change active unit
            _playerController.OnActiveUnitChange += OnActiveUnitChange;

            // Initialize array with same amount of slots as backpack 
            _slots = new SlotUI[_backpack.items.Length];

            // Initialize all VisualElements
            for (var i = 0; i < _backpack.items.Length; i++)
                UpdateSlotUI(i, _backpack.items[i]);

            // Listen to all changes in backpack and update UI 
            _unit.unitEvents.onBackpackPick.AddListener(OnPick);
            _unit.unitEvents.onBackpackDrop.AddListener(OnDrop);
            _unit.unitEvents.onBackpackSwap.AddListener(OnSwap);
            _unit.unitEvents.onBackpackSort.AddListener(OnSort);
            _unit.unitEvents.onBackpackResize.AddListener(OnResize);
        }

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

            // Backpack closed, no need to listen for backpack changes
            if (_unit) {
                _unit.unitEvents.onBackpackPick.RemoveListener(OnPick);
                _unit.unitEvents.onBackpackDrop.RemoveListener(OnDrop);
                _unit.unitEvents.onBackpackSwap.RemoveListener(OnSwap);
                _unit.unitEvents.onBackpackSort.RemoveListener(OnSort);
                _unit.unitEvents.onBackpackResize.RemoveListener(OnResize);
            }

            // Remove all VisualElement slots
            if (_slots != null)
                for (var i = 0; i < _slots.Length; i++)
                    RemoveSlotUI(i);

            // Clear variables
            _slots = null;
            _unit = null;
            _backpack = null;
        }

        private void OnActiveUnitChange(UnitObject unitObject) {
            windowUI.Close();
            windowUI.Open();
        }

        private void OnPick(BackpackPickEvent backpackPickEvent) {
            UpdateSlotUI(backpackPickEvent.slotIndex, backpackPickEvent.item);
        }

        private void OnDrop(BackpackDropEvent backpackDropEvent) {
            UpdateSlotUI(backpackDropEvent.slotIndex, null);
        }

        private void OnSwap(BackpackSwapEvent backpackSwapEvent) {
            UpdateSlotUI(backpackSwapEvent.firstSlotIndex, backpackSwapEvent.firstItem);
            UpdateSlotUI(backpackSwapEvent.secondSlotIndex, backpackSwapEvent.secondItem);
        }

        private void OnSort(BackpackSortEvent backpackSortEvent) {
            var backpackItems = backpackSortEvent.unit.unitBackpack.items;
            for (var i = 0; i < backpackItems.Length; i++)
                UpdateSlotUI(i, backpackItems[i]);
        }

        private void OnResize(BackpackResizeEvent backpackResizeEvent) {
            // Before resizing - remove all unused slots 
            if (backpackResizeEvent.beforeSlotCount > backpackResizeEvent.afterSlotCount)
                for (var i = backpackResizeEvent.afterSlotCount; i < backpackResizeEvent.beforeSlotCount; i++)
                    RemoveSlotUI(i);

            Array.Resize(ref _slots, backpackResizeEvent.afterSlotCount);

            // After resizing - insert empty slots
            if (backpackResizeEvent.beforeSlotCount < backpackResizeEvent.afterSlotCount)
                for (var i = backpackResizeEvent.beforeSlotCount; i < backpackResizeEvent.afterSlotCount; i++)
                    UpdateSlotUI(i, null);
        }

        private void UpdateSlotUI(int slotIndex, [CanBeNull] ItemObject item) {
            // Create new VisualElement if not exists
            if (_slots[slotIndex] is null) {
                _slots[slotIndex] = new SlotUI();
                _slots[slotIndex].InitNew(_slotsElement, SlotType.BackpackSlot, slotIndex, "Slot-" + slotIndex);
            }

            // Put item's icon or else remain default
            _slots[slotIndex].Set(item);
        }

        private void RemoveSlotUI(int slotIndex) {
            _slots[slotIndex].Destroy();
        }
    }
}
