using KFCommonUtilityLib.Scripts.StaticManagers;
using System;
using System.Collections.Generic;
using UnityEngine;

namespace KFCommonUtilityLib.Scripts.Utilities
{
    public static class MultiActionUtils
    {
        public static readonly string[] ActionMetaNames = new string[]
        {
            "Meta0",
            "Meta1",
            "Meta2",
            "Meta3",
            "Meta4",
        };
        public static readonly string[] ActionSelectedAmmoNames = new string[]
        {
            "AmmoIndex0",
            "AmmoIndex1",
            "AmmoIndex2",
            "AmmoIndex3",
            "AmmoIndex4",
        };
        public static readonly int ExecutingActionIndexHash = Animator.StringToHash("ExecutingActionIndex");

        public static void SetMinEventArrays()
        {
            MinEvent.Start = new[]
            {
                MinEventTypes.onSelfPrimaryActionStart,
                MinEventTypes.onSelfSecondaryActionStart,
                MinEventTypes.onSelfAction2Start,
                MinEventTypes.onSelfPrimaryActionStart,
                MinEventTypes.onSelfPrimaryActionStart,
            };

            MinEvent.Update = new[]
            {
                MinEventTypes.onSelfPrimaryActionUpdate,
                MinEventTypes.onSelfSecondaryActionUpdate,
                MinEventTypes.onSelfAction2Update,
                MinEventTypes.onSelfPrimaryActionUpdate,
                MinEventTypes.onSelfPrimaryActionUpdate,
            };

            MinEvent.End = new[]
            {
                MinEventTypes.onSelfPrimaryActionEnd,
                MinEventTypes.onSelfSecondaryActionEnd,
                MinEventTypes.onSelfAction2End,
                MinEventTypes.onSelfPrimaryActionEnd,
                MinEventTypes.onSelfPrimaryActionEnd,
            };
        }

        //public static int GetActionIndexForItemValue(this ItemValue self)
        //{
        //    object val = self.GetMetadata(MultiActionMapping.STR_MULTI_ACTION_INDEX);
        //    if (val is int index)
        //        return index;
        //    return 0;
        //}

        public static string GetPropertyName(int index, string prop)
        {
            return $"Action{index}.{prop}";
        }

        public static void FixedItemReloadServer(int entityId, int actionIndex)
        {
            if (GameManager.Instance.World == null)
            {
                return;
            }

            FixedItemReloadClient(entityId, actionIndex);

            if (!SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer)
            {
                SingletonMonoBehaviour<ConnectionManager>.Instance.SendToServer(NetPackageManager.GetPackage<NetPackageFixedReload>().Setup(entityId, actionIndex), false);
                return;
            }
            SingletonMonoBehaviour<ConnectionManager>.Instance.SendPackage(NetPackageManager.GetPackage<NetPackageFixedReload>().Setup(entityId, actionIndex), false, -1, entityId);
        }

        public static void FixedItemReloadClient(int entityId, int actionIndex)
        {
            if (GameManager.Instance.World == null)
            {
                return;
            }
            EntityAlive entityAlive = (EntityAlive)GameManager.Instance.World.GetEntity(entityId);
            if (entityAlive != null && entityAlive.inventory.holdingItem.Actions[actionIndex] is ItemActionRanged actionRanged)
            {
                entityAlive.MinEventContext.ItemActionData = entityAlive.inventory.holdingItemData.actionData[actionIndex];
                actionRanged.ReloadGun(entityAlive.inventory.holdingItemData.actionData[actionIndex]);
            }
        }

        public static void CopyLauncherValueToProjectile(ItemValue launcherValue, ItemValue projectileValue, int index)
        {
            projectileValue.Meta = launcherValue.type;
            projectileValue.SelectedAmmoTypeIndex = (byte)index;
            projectileValue.UseTimes = launcherValue.UseTimes;
            projectileValue.Quality = launcherValue.Quality;
            projectileValue.Modifications = new ItemValue[launcherValue.Modifications.Length];
            Array.Copy(launcherValue.Modifications, projectileValue.Modifications, launcherValue.Modifications.Length);
            projectileValue.CosmeticMods = new ItemValue[launcherValue.CosmeticMods.Length];
            Array.Copy(launcherValue.CosmeticMods, projectileValue.CosmeticMods, launcherValue.CosmeticMods.Length);
        }

        public static void SetMinEventParamsActionData(EntityAlive entity, int actionIndex)
        {
            if (actionIndex >= 0 && actionIndex < entity.inventory?.holdingItemData?.actionData?.Count)
                entity.MinEventContext.ItemActionData = entity.inventory.holdingItemData.actionData[actionIndex];
        }

        public static string GetPropertyOverrideForAction(this ItemValue self, string _propertyName, string _originalValue, int actionIndex)
        {
            if (self.Modifications.Length == 0 && self.CosmeticMods.Length == 0)
            {
                return _originalValue;
            }
            string text = "";
            ItemClass item = self.ItemClass;
            for (int i = 0; i < self.Modifications.Length; i++)
            {
                ItemValue itemValue = self.Modifications[i];
                if (itemValue != null)
                {
                    if (itemValue.ItemClass is ItemClassModifier mod && GetPropertyOverrideForMod(mod, _propertyName, item, ref text, actionIndex))
                    {
                        return text;
                    }
                }
            }
            text = "";
            for (int j = 0; j < self.CosmeticMods.Length; j++)
            {
                ItemValue itemValue2 = self.CosmeticMods[j];
                if (itemValue2 != null)
                {
                    if (itemValue2.ItemClass is ItemClassModifier cos && GetPropertyOverrideForMod(cos, _propertyName, item, ref text, actionIndex))
                    {
                        return text;
                    }
                }
            }
            return _originalValue;
        }

        public static IEnumerable<string> GetAllPropertyOverridesForAction(this ItemValue self, string _propertyName, int actionIndex)
        {
            if (self.Modifications.Length == 0 && self.CosmeticMods.Length == 0)
            {
                yield break;
            }

            string text = "";
            ItemClass item = self.ItemClass;
            for (int i = 0; i < self.Modifications.Length; i++)
            {
                ItemValue itemValue = self.Modifications[i];
                if (itemValue != null)
                {
                    if (itemValue.ItemClass is ItemClassModifier mod && GetPropertyOverrideForMod(mod, _propertyName, item, ref text, actionIndex))
                    {
                        yield return text;
                    }
                }
            }
            text = "";
            for (int j = 0; j < self.CosmeticMods.Length; j++)
            {
                ItemValue itemValue2 = self.CosmeticMods[j];
                if (itemValue2 != null)
                {
                    if (itemValue2.ItemClass is ItemClassModifier cos && GetPropertyOverrideForMod(cos, _propertyName, item, ref text, actionIndex))
                    {
                        yield return text;
                    }
                }
            }
        }

        public static bool GetPropertyOverrideForMod(ItemClassModifier mod, string _propertyName, ItemClass _item, ref string _value, int actionIndex)
        {
            //Log.Out($"Get property override for item {_item.Name} itemID{_item.Id} property {_propertyName} mod {mod.Name} modID {mod.Id} action {actionIndex} should exclude {MultiActionManager.ShouldExcludeMod(_item.Id, mod.Id, actionIndex)}");
            if (MultiActionManager.ShouldExcludeProperty(_item.Id, mod.Id, actionIndex))
                return false;
            string _itemName = _item.GetItemName();
            string itemNameWithActionIndex = $"{_itemName}_{actionIndex}";
            if (mod.PropertyOverrides.ContainsKey(itemNameWithActionIndex) && mod.PropertyOverrides[itemNameWithActionIndex].Values.ContainsKey(_propertyName))
            {
                _value = mod.PropertyOverrides[itemNameWithActionIndex].Values[_propertyName];
                return true;
            }
            if (mod.PropertyOverrides.ContainsKey(_itemName) && mod.PropertyOverrides[_itemName].Values.ContainsKey(_propertyName))
            {
                _value = mod.PropertyOverrides[_itemName].Values[_propertyName];
                return true;
            }
            itemNameWithActionIndex = $"*_{actionIndex}";
            if (mod.PropertyOverrides.ContainsKey(itemNameWithActionIndex) && mod.PropertyOverrides[itemNameWithActionIndex].Values.ContainsKey(_propertyName))
            {
                _value = mod.PropertyOverrides[itemNameWithActionIndex].Values[_propertyName];
                return true;
            }
            if (mod.PropertyOverrides.ContainsKey("*") && mod.PropertyOverrides["*"].Values.ContainsKey(_propertyName))
            {
                _value = mod.PropertyOverrides["*"].Values[_propertyName];
                return true;
            }
            return false;
        }

        public static int GetMode(this ItemValue self)
        {
            object mode = self.GetMetadata(MultiActionMapping.STR_MULTI_ACTION_INDEX);
            if (mode is int v)
            {
                return v;
            }
            return 0;
        }

        public static int GetActionIndexByEntityEventParams(EntityAlive entity)
        {
            return GetActionIndexByEventParams(entity?.MinEventContext ?? MinEventParams.CachedEventParam);
        }

        public static int GetActionIndexByEventParams(MinEventParams pars)
        {
            if (pars?.ItemActionData == null)
                return 0;
            return pars.ItemActionData.indexInEntityOfAction;
        }

        public static int GetActionIndexByMetaData(this ItemValue self)
        {
            int mode = self.GetMode();
            MultiActionIndice indice = MultiActionManager.GetActionIndiceForItemID(self.type);
            return indice.GetActionIndexForMode(mode);
        }

        public static int GetSelectedAmmoIndexByMode(this ItemValue self, int mode)
        {
            MultiActionIndice indice = MultiActionManager.GetActionIndiceForItemID(self.type);
            int metaIndex = indice.GetMetaIndexForMode(mode);
            if (metaIndex >= 0)
            {
                object ammoIndex = self.GetMetadata(ActionSelectedAmmoNames[metaIndex]);
                if (ammoIndex is int)
                {
                    return (int)ammoIndex;
                }
            }
            return self.SelectedAmmoTypeIndex;
        }

        public static int GetMetaByMode(this ItemValue self, int mode)
        {
            MultiActionIndice indice = MultiActionManager.GetActionIndiceForItemID(self.type);
            int metaIndex = indice.GetMetaIndexForMode(mode);
            if (metaIndex >= 0)
            {
                object meta = self.GetMetadata(ActionMetaNames[metaIndex]);
                if (meta is int)
                {
                    return (int)meta;
                }
            }
            return self.Meta;
        }

        public static int GetSelectedAmmoIndexByActionIndex(this ItemValue self, int actionIndex)
        {
            MultiActionIndice indice = MultiActionManager.GetActionIndiceForItemID(self.type);
            int mode = indice.GetModeForAction(actionIndex);
            if (mode < 0)
                return self.SelectedAmmoTypeIndex;
            int metaIndex = indice.GetMetaIndexForMode(mode);
            if (metaIndex >= 0)
            {
                object ammoIndex = self.GetMetadata(ActionSelectedAmmoNames[metaIndex]);
                if (ammoIndex is int)
                {
                    return (int)ammoIndex;
                }
            }
            return self.SelectedAmmoTypeIndex;
        }

        public static int GetMetaByActionIndex(this ItemValue self, int actionIndex)
        {
            MultiActionIndice indice = MultiActionManager.GetActionIndiceForItemID(self.type);
            int mode = indice.GetModeForAction(actionIndex);
            if (mode < 0)
                return self.Meta;
            int metaIndex = indice.GetMetaIndexForMode(mode);
            if (metaIndex >= 0)
            {
                object meta = self.GetMetadata(ActionMetaNames[metaIndex]);
                if (meta is int)
                {
                    //Log.Out($"GetMetaByActionIndex: mode: {mode}, action: {metaIndex}, meta: {(int)meta}\n{StackTraceUtility.ExtractStackTrace()}");
                    return (int)meta;
                }
            }

            return self.Meta;
        }

        public static void SetMinEventParamsByEntityInventory(EntityAlive entity)
        {
            if (entity != null && entity.MinEventContext != null)
            {
                entity.MinEventContext.ItemActionData = entity.inventory?.holdingItemData?.actionData[MultiActionManager.GetActionIndexForEntity(entity)];
            }
        }

        public static bool MultiActionRemoveAmmoFromItemStack(ItemStack stack, List<ItemStack> result)
        {
            ItemValue itemValue = stack.itemValue;
            object mode = itemValue.GetMetadata(MultiActionMapping.STR_MULTI_ACTION_INDEX);
            if (mode is false || mode is null)
            {
                return false;
            }
            MultiActionIndice indices = MultiActionManager.GetActionIndiceForItemID(itemValue.type);
            ItemClass item = ItemClass.GetForId(itemValue.type);
            for (int i = 0; i < MultiActionIndice.MAX_ACTION_COUNT; i++)
            {
                int metaIndex = indices.GetMetaIndexForMode(i);
                if (metaIndex < 0)
                {
                    break;
                }

                int actionIndex = indices.GetActionIndexForMode(i);
                if (item.Actions[actionIndex] is ItemActionRanged ranged && !(ranged is ItemActionTextureBlock))
                {
                    object meta = itemValue.GetMetadata(MultiActionUtils.ActionMetaNames[metaIndex]);
                    object ammoIndex = itemValue.GetMetadata(MultiActionUtils.ActionSelectedAmmoNames[metaIndex]);
                    if (meta is int && ammoIndex is int && (int)meta > 0)
                    {
                        itemValue.SetMetadata(MultiActionUtils.ActionMetaNames[metaIndex], 0, TypedMetadataValue.TypeTag.Integer);
                        ItemStack ammoStack = new ItemStack(ItemClass.GetItem(ranged.MagazineItemNames[(int)ammoIndex]), (int)meta);
                        result.Add(ammoStack);
                        Log.Out($"Remove ammo: metadata {MultiActionUtils.ActionMetaNames[metaIndex]}, meta {(int)meta}, left {itemValue.GetMetadata(MultiActionUtils.ActionMetaNames[metaIndex])}");
                    }
                }
            }
            itemValue.Meta = 0;
            return true;
        }

        public static readonly ItemActionData[] DummyActionDatas = new ItemActionData[]
        {
            new ItemActionData(null, 0),
            new ItemActionData(null, 3),
            new ItemActionData(null, 4)
        };

        public static int GetMultiActionInitialMetaData(this ItemClass itemClass, ItemValue itemValue)
        {
            MultiActionIndice indice = MultiActionManager.GetActionIndiceForItemID(itemClass.Id);
            if (indice.modeCount <= 1)
            {
                return itemClass.GetInitialMetadata(itemValue);
            }

            var prevItemValue = MinEventParams.CachedEventParam.ItemValue;
            var prevActionData = MinEventParams.CachedEventParam.ItemActionData;
            MinEventParams.CachedEventParam.ItemValue = itemValue;
            itemValue.SetMetadata(MultiActionMapping.STR_MULTI_ACTION_INDEX, 0, TypedMetadataValue.TypeTag.Integer);
            int ret = 0;
            for (int i = 0; i < indice.modeCount; i++)
            {
                MinEventParams.CachedEventParam.ItemActionData = DummyActionDatas[i];

                ItemAction itemAction = itemClass.Actions[indice.GetActionIndexForMode(i)];
                int meta = itemAction.GetInitialMeta(itemValue);
                if (i == 0)
                {
                    ret = meta;
                }
                else if (itemAction.Properties.Contains("ActionUnlocked") && !itemAction.Properties.GetBool("ActionUnlocked"))
                {
                    meta = 0;
                }
                if (indice.GetMetaIndexForMode(i) == indice.GetActionIndexForMode(i))
                    itemValue.SetMetadata(MultiActionUtils.ActionMetaNames[indice.GetMetaIndexForMode(i)], meta, TypedMetadataValue.TypeTag.Integer);
            }
            MinEventParams.CachedEventParam.ItemValue = prevItemValue;
            MinEventParams.CachedEventParam.ItemActionData = prevActionData;
            return ret;
        }

        public static void SetCachedEventParamsDummyAction(ItemStack itemStack)
        {
            ItemClass itemClass = itemStack?.itemValue?.ItemClass;
            if (itemClass != null)
            {
                MinEventParams.CachedEventParam.ItemActionData = MultiActionUtils.DummyActionDatas[itemStack.itemValue.GetMode()];
                MinEventParams.CachedEventParam.ItemValue = itemStack.itemValue;
                MinEventParams.CachedEventParam.Seed = itemStack.itemValue.Seed;
            }
        }

        public static string GetDisplayTypeForAction(ItemStack itemStack)
        {
            return GetDisplayTypeForAction(itemStack?.itemValue);
        }

        public static string GetDisplayTypeForAction(ItemValue itemValue)
        {
            if (itemValue == null || itemValue.IsEmpty())
            {
                return "";
            }
            if (itemValue.ItemClass.Actions[itemValue.GetActionIndexByMetaData()] is IModuleContainerFor<ActionModuleMultiActionFix> module)
            {
                return module.Instance.GetDisplayType(itemValue);
            }
            return itemValue.ItemClass.DisplayType;
        }

        public static bool CanCompare(ItemValue itemValue1, ItemValue itemValue2)
        {
            if (itemValue1 == null || itemValue2 == null || itemValue1.IsEmpty() || itemValue2.IsEmpty())
            {
                return false;
            }

            string displayType1 = itemValue1.ItemClass.IsBlock() ? Block.list[itemValue1.ItemClass.Id].DisplayType : GetDisplayTypeForAction(itemValue1);
            string displayType2 = itemValue2.ItemClass.IsBlock() ? Block.list[itemValue2.ItemClass.Id].DisplayType : GetDisplayTypeForAction(itemValue2);
            ItemDisplayEntry displayStatsForTag = UIDisplayInfoManager.Current.GetDisplayStatsForTag(displayType1);
            ItemDisplayEntry displayStatsForTag2 = UIDisplayInfoManager.Current.GetDisplayStatsForTag(displayType2);
            return displayStatsForTag != null && displayStatsForTag2 != null && displayStatsForTag.DisplayGroup == displayStatsForTag2.DisplayGroup;
        }
    }
}