using System;
using JetBrains.Annotations;
using Sirenix.OdinInspector;
using UnityEngine;
using VOID.Generic.Dict;
using VOID.Generic.Objects.Abstract.Events;
using VOID.Generic.Objects.Container.Events;
using VOID.Generic.Objects.Item;
using VOID.Generic.Objects.Unit;
using VOID.Generic.Util;
using Object = UnityEngine.Object;

namespace VOID.Generic.Objects.Container
{
    [Serializable]
    public class ContainerObjectContent : ObjectComponent<ContainerObject>
    {
        #region Initialize

        protected override void SelfInitialize()
        {
            // Empty GameObject where all items from backpack are "attached"
            _content = new GameObject("_content");
            _content.transform.parent = parent.gameObject.transform;
            _content.transform.localPosition = Vector3.zero;
        }

        protected override void Initialize()
        {
            // Instantiate all prefabs
            for (var i = 0; i < _items.Length; i++)
            {
                if (!_items[i]) continue;

                var item = _items[i];

                _items[i] = null;
                
                // Selected item is ASSET (prefab), we need to instantiate it first
                if (!item.gameObject.scene.IsValid())
                    item = Object.Instantiate(item.gameObject).GetComponent<ItemObject>();

                // Instantiate prefab and get references to its GameObject and ItemObject
                MoveIn(i, item);
            }
            
            Resize();
        }

        #endregion
        
        private GameObject _content;

        // Items in content (list of ItemObjects and their GameObjects)
        [SerializeField, DisableInPlayMode] private ItemObject[] _items = Array.Empty<ItemObject>();
        public ItemObject[] items => _items;

        public void MoveIn(int itemIndex, ItemObject item, UnitObject byWho = null)
        {
            if (items.Length <= itemIndex)
                Resize(itemIndex);
            
            if (items[itemIndex])
            {
                MoveIn(item, byWho);
                return;
            }

            items[itemIndex] = item;

            var moveInEvent = new ContainerMoveInEvent
            {
                byWho = byWho,
                item = item,
                slotIndex = itemIndex,
                container = parent,
            };

            parent.containerEvents.onMoveIn.Invoke(moveInEvent);
            item.itemEvents.onMovedIntoContainer.Invoke(moveInEvent);

            var itemGameObject = item.gameObject;
            itemGameObject.transform.parent = _content.transform;
            itemGameObject.transform.localPosition = Vector3.zero;
            itemGameObject.transform.localRotation = new Quaternion();

            item.basicEvents.onRemove.AddListener(OnItemRemove);

            Resize();
        }

        public void MoveIn(ItemObject item, UnitObject byWho = null)
        {
            // Find first free slot in backpack
            var firstFreeIndex = Array.IndexOf(items, null);

            // No free slots, we need to resize array
            if (firstFreeIndex == -1)
            {
                firstFreeIndex = items.Length;
                Resize();
            }

            MoveIn(firstFreeIndex, item, byWho);
        }

        public void MoveOut(int itemIndex, UnitObject byWho = null)
        {
            var tempMoveOutItem = items[itemIndex];

            items[itemIndex].basicEvents.onRemove.RemoveListener(OnItemRemove);

            items[itemIndex] = null;

            var moveOutEvent = new ContainerMoveOutEvent
            {
                byWho = byWho,
                item = tempMoveOutItem,
                slotIndex = itemIndex,
                container = parent,
            };

            parent.containerEvents.onMoveOut.Invoke(moveOutEvent);
            tempMoveOutItem.itemEvents.onMovedOutOfContainer.Invoke(moveOutEvent);

            var tempItemGameObject = tempMoveOutItem.gameObject;
            tempItemGameObject.gameObject.transform.parent = null;
            tempItemGameObject.gameObject.transform.position = parent.transform.position;

            Resize();
        }

        public void MoveOut(ItemObject item, UnitObject byWho = null)
        {
            MoveOut(Array.IndexOf(items, item), byWho);
        }

        public void Swap(int firstItemIndex, int secondItemIndex, UnitObject byWho = null)
        {
            Resize(Math.Max(firstItemIndex, secondItemIndex));
            
            (items[firstItemIndex], items[secondItemIndex]) = (items[secondItemIndex], items[firstItemIndex]);

            var swapEvent = new ContainerSwapEvent
            {
                byWho = byWho,
                firstSlotIndex = firstItemIndex,
                firstItem = items[firstItemIndex],
                secondSlotIndex = secondItemIndex,
                secondItem = items[secondItemIndex],
            };

            parent.containerEvents.onSwap.Invoke(swapEvent);
            
            Resize();
        }

        public void Sort(Func<ItemObject, IComparable> valueGetter, SortDirectionType sortDirection, [CanBeNull] UnitObject byWho = null) {
            ItemObjectSortUtil.Sort(ref _items, valueGetter, sortDirection);

            var containerSortEvent = new ContainerSortEvent {
                container = parent,
                byUnit = byWho
            };

            parent.containerEvents.onSort.Invoke(containerSortEvent);

            Resize();
        }

        private void OnItemRemove(RemoveEvent removeEvent)
        {
            var removedItem = removeEvent.obj as ItemObject;
            MoveOut(removedItem);
        }

        public void Resize(int minSize = 0, int rowSize = 10)
        {
            var beforeSlotCount = items.Length;
            var lastItemIndex = 0;

            // Find last slotIndex with existing item (not null)
            for (var i = beforeSlotCount - 1; i >= 0; i--)
            {
                lastItemIndex = i;
                if (items[i] is not null) break;
            }

            // Count how many slots backpack will have after resize
            var afterSlotCount = Mathf.CeilToInt((Mathf.Max(lastItemIndex, minSize) + rowSize) / (float)rowSize) * rowSize;

            // Size not changed, do nothing
            if (beforeSlotCount == afterSlotCount) return;

            // Resize backpack's arrays
            var tempItems = items;
            Array.Resize(ref tempItems, afterSlotCount);
            _items = tempItems;

            var containerResizeEvent = new ContainerResizeEvent
            {
                container = parent,
                beforeSlotCount = beforeSlotCount,
                afterSlotCount = afterSlotCount,
                lastItemIndex = lastItemIndex,
            };
            
            parent.containerEvents.onResize.Invoke(containerResizeEvent);
        }
    }
}