using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using PhoenixLib.Logging;
using WingsAPI.Packets.Enums.Shells;
using WingsEmu.DTOs.BCards;
using WingsEmu.DTOs.Maps;
using WingsEmu.DTOs.Skills;
using WingsEmu.Game._enum;
using WingsEmu.Game._i18n;
using WingsEmu.Game.Battle;
using WingsEmu.Game.Buffs;
using WingsEmu.Game.Characters;
using WingsEmu.Game.Entities;
using WingsEmu.Game.Helpers.Damages;
using WingsEmu.Game.Maps;
using WingsEmu.Game.Mates;
using WingsEmu.Game.Networking;
using WingsEmu.Game.Networking.Broadcasting;
using WingsEmu.Game.RainbowBattle;
using WingsEmu.Game.Skills;
using WingsEmu.Packets.Enums;
using WingsEmu.Packets.Enums.Battle;
using WingsEmu.Packets.Enums.Character;
using WingsEmu.Packets.Enums.Chat;
using WingsEmu.Packets.ServerPackets.Battle;

namespace WingsEmu.Game.Extensions;

public static class BattleEntityExtension
{
    /// <summary>
    ///     Check if the BattleEntity is in the given Range.
    /// </summary>
    /// <param name="battleEntity">The BattleEntity from which position check</param>
    /// <param name="mapX">The X coordinate on the Map of the object to check.</param>
    /// <param name="mapY">The Y coordinate on the Map of the object to check.</param>
    /// <param name="distance">The maximum distance of the object to check.</param>
    /// <returns>True if the BattleEntity is in range, False if not.</returns>
    public static bool IsInRange(this IBattleEntity battleEntity, short mapX, short mapY, byte distance) => battleEntity.Position.GetDistance(mapX, mapY) <= distance;

    public static bool IsInvisible(this IBattleEntity seen)
    {
        if (seen is IPlayerEntity player && player.CheatComponent.IsInvisible)
        {
            return true;
        }

        return seen.BCardComponent.HasBCard(BCardType.SpecialActions, (byte)AdditionalTypes.SpecialActions.Hide)
            || seen.BCardComponent.HasBCard(BCardType.FalconSkill, (byte)AdditionalTypes.FalconSkill.Hide)
            || seen.BCardComponent.HasBCard(BCardType.FalconSkill, (byte)AdditionalTypes.FalconSkill.Ambush);
    }

    public static bool HasGodMode(this IBattleEntity entity)
    {
        if (entity is IPlayerEntity player && player.CheatComponent.HasGodMode)
        {
            return true;
        }

        if (entity.BCardComponent.HasBCard(BCardType.TimeCircleSkills, (byte)AdditionalTypes.TimeCircleSkills.DisableHPConsumption))
        {
            return true;
        }

        if (entity.BCardComponent.HasBCard(BCardType.NoDefeatAndNoDamage, (byte)AdditionalTypes.NoDefeatAndNoDamage.NeverReceiveDamage))
        {
            return true;
        }

        return entity.BCardComponent.HasBCard(BCardType.HideBarrelSkill, (byte)AdditionalTypes.HideBarrelSkill.NoHPConsumption);
    }

    public static int ApplyCooldownReduction(this IBattleEntity battleEntity, SkillInfo skill)
    {
        short cooldown = skill.Cooldown;
        if (battleEntity.NoCooldown())
        {
            return 0;
        }

        if (ResetByFairyWings(battleEntity, skill))
        {
            return 0;
        }

        (int firstData, int secondData) cooldownDecrease =
            battleEntity.BCardComponent.GetAllBCardsInformation(BCardType.Morale, (byte)AdditionalTypes.Morale.SkillCooldownDecreased, battleEntity.Level);
        (int firstData, int secondData) cooldownIncrease =
            battleEntity.BCardComponent.GetAllBCardsInformation(BCardType.Morale, (byte)AdditionalTypes.Morale.SkillCooldownIncreased, battleEntity.Level);
        double increaseMultiplier = cooldownIncrease.firstData * 0.01;
        double decreaseMultiplier = cooldownDecrease.firstData * 0.01;
        double newCooldown = cooldown * increaseMultiplier * decreaseMultiplier;
        cooldown = (short)newCooldown;

        return cooldown < 0 ? 0 : cooldown;
    }

    private static bool ResetByFairyWings(IBattleEntity battleEntity, SkillInfo skill)
    {
        if (!battleEntity.BCardComponent.HasBCard(BCardType.EffectSummon, (byte)AdditionalTypes.EffectSummon.CooldownResetChance))
        {
            return false;
        }

        if (skill.TargetAffectedEntities != TargetAffectedEntities.Enemies)
        {
            return false;
        }

        if (skill.CastId == 0)
        {
            return false;
        }

        if (battleEntity is not IPlayerEntity playerEntity)
        {
            return false;
        }

        int firstData = playerEntity.BCardComponent.GetAllBCardsInformation(BCardType.EffectSummon, (byte)AdditionalTypes.EffectSummon.CooldownResetChance, playerEntity.Level).firstData;
        if (StaticRandomGenerator.Instance.RandomNumber() > firstData)
        {
            return false;
        }

        if (!playerEntity.SkillComponent.SkillsResets.TryGetValue(skill.Vnum, out byte usages))
        {
            usages = 0;
        }

        if (playerEntity.SkillComponent.LastSkillVnum.HasValue && playerEntity.SkillComponent.LastSkillVnum.Value == skill.Vnum)
        {
            usages += 1;
        }
        else
        {
            playerEntity.SkillComponent.SkillsResets.Clear();
        }

        if (usages > 1)
        {
            return false;
        }

        playerEntity.SkillComponent.LastSkillVnum = skill.Vnum;
        playerEntity.SkillComponent.SkillsResets[skill.Vnum] = usages;
        playerEntity.BroadcastEffectInRange(EffectType.FairyResetBuff);

        return true;
    }

    private static bool NoCooldown(this IBattleEntity entity) => entity is IPlayerEntity character && character.CheatComponent.HasNoCooldown;

    public static IEnumerable<IBattleEntity> GetEnemiesInRange(this IBattleEntity sender, IBattleEntity caster, short range) => sender.Position.GetEnemiesInRange(caster, range);

    public static IEnumerable<IBattleEntity> GetAlliesInRange(this IBattleEntity sender, IBattleEntity caster, short range) => sender.Position.GetAlliesInRange(caster, range);

    public static List<Position> GetCellsInLine(this Position pos1, Position pos2)
    {
        var cells = new List<Position>();

        short x0 = pos1.X;
        short y0 = pos1.Y;

        short x1 = pos2.X;
        short y1 = pos2.Y;

        int dx = Math.Abs(x1 - x0);
        int sx = x0 < x1 ? 1 : -1;

        int dy = -Math.Abs(y1 - y0);
        int sy = y0 < y1 ? 1 : -1;

        int err = dx + dy;

        while (true)
        {
            cells.Add(new Position(x0, y0));
            if (x0 == x1 && y0 == y1)
            {
                break;
            }

            int e2 = 2 * err;
            if (e2 >= dy)
            {
                err += dy;
                x0 += (short)sx;
            }

            if (e2 > dx)
            {
                continue;
            }

            err += dx;
            y0 += (short)sy;
        }

        return cells;
    }

    public static void ChangeSize(this IBattleEntity battleEntity, byte size)
    {
        battleEntity.Size = size;
        battleEntity.MapInstance.Broadcast(battleEntity.GenerateScal());
    }

    public static int GetHpPercentage(this IBattleEntity target)
    {
        if (target.Hp == 0 || target.MaxHp == 0)
        {
            return 0;
        }

        int hpPercentage = (int)(target.Hp / (float)target.MaxHp * 100);
        return hpPercentage == 0 ? 1 : hpPercentage;
    }

    public static bool ShouldSendScal(this IBattleEntity battleEntity) => battleEntity.Size != 10;

    public static string GenerateScal(this IBattleEntity battleEntity) => $"char_sc {((byte)battleEntity.Type).ToString()} {battleEntity.Id.ToString()} {battleEntity.Size.ToString()}";

    public static string GenerateEffectPacket(this IBattleEntity battleEntity, int effectVnum) => $"eff {(byte)battleEntity.Type} {battleEntity.Id} {effectVnum}";

    public static string GenerateEffectPacket(this IBattleEntity battleEntity, EffectType effectType) => battleEntity.GenerateEffectPacket((int)effectType);

    public static void BroadcastEffectInRange(this IBattleEntity entity, EffectType effectType)
    {
        if (entity.IsInvisibleGm())
        {
            return;
        }

        entity.MapInstance.Broadcast(entity.GenerateEffectPacket((int)effectType), new RangeBroadcast(entity.PositionX, entity.PositionY));
    }

    public static void BroadcastEffectInRange(this IBattleEntity entity, int effectId)
    {
        if (entity.IsInvisibleGm())
        {
            return;
        }

        entity.MapInstance.Broadcast(entity.GenerateEffectPacket(effectId), new RangeBroadcast(entity.PositionX, entity.PositionY));
    }

    public static string GenerateMvPacket(this IBattleEntity entity, int speed = default)
        => $"mv {(byte)entity.Type} {entity.Id} {entity.PositionX} {entity.PositionY} {(speed == default ? entity.Speed : speed).ToString()}";

    public static string GenerateStPacket(this IBattleEntity target)
    {
        return
            $"st {(byte)target.Type} {target.Id} {target.Level} {(target is IPlayerEntity character ? character.HeroLevel : 0)} {target.HpPercentage} {target.MpPercentage} {target.Hp} {target.Mp}{target.BuffComponent.GetAllBuffs().Aggregate(string.Empty, (current, buff) => current + $" {buff.CardId}.{buff.CasterLevel}")}";
    }

    public static string GenerateTeleportPacket(this IBattleEntity entity, short x, short y) => $"tp {(byte)entity.Type} {entity.Id} {x} {y} 0";

    public static void TeleportOnMap(this IBattleEntity entity, short x, short y, bool teleportMates = false)
    {
        var newPosition = new Position(x, y);
        switch (entity)
        {
            case IPlayerEntity playerEntity:

                if (!teleportMates)
                {
                    entity.ChangePosition(newPosition);
                    entity.MapInstance.Broadcast(entity.GenerateTeleportPacket(x, y));
                    return;
                }

                IReadOnlyList<IMateEntity> nosMate = playerEntity.MateComponent.TeamMembers();
                foreach (IMateEntity mate in nosMate)
                {
                    mate.ChangePosition(newPosition);
                    playerEntity.MapInstance.Broadcast(mate.GenerateTeleportPacket(x, y));
                }

                break;
        }

        entity.ChangePosition(newPosition);
        entity.MapInstance.Broadcast(entity.GenerateTeleportPacket(x, y));
    }

    public static int GetMpPercentage(this IBattleEntity target)
    {
        if (target.Mp == 0 || target.MaxMp == 0)
        {
            return 0;
        }

        int mpPercentage = (int)(target.Mp / (float)target.MaxMp * 100);
        return mpPercentage == 0 ? 1 : mpPercentage;
    }

    public static bool IsPvpActivated(this IBattleEntity entity, IBattleEntity target)
    {
        if (!target.IsPlayer() && !target.IsMate())
        {
            return false;
        }

        if (entity.Id == target.Id && entity.Type == target.Type)
        {
            return false;
        }

        if (entity.BuffComponent.HasBuff((short)BuffVnums.PVP) && !entity.MapInstance.IsPvp)
        {
            // BIGU SWITCHU 2021 KEKW

            // 1 -> when target is Player (A) and A have PvP buff and entity isn't in the same group with A
            // 2 -> when target is Mate (B) and owner (C) have PvP buff and entity isn't in the same group with C
            // 3 -> when caster is Mate and target is Player (D) and Mate Owner (E) have PvP buff and D have PvP buff and E isn't in the same group with D
            // 4 => when caster is Mate (F - Owner) and target is Mate (G - Owner) and F and G have PvP buff and F isn't in the same group with G

            return entity switch
            {
                IPlayerEntity character when target is IPlayerEntity characterTarget =>
                    characterTarget.BuffComponent.HasBuff((short)BuffVnums.PVP) && character.IsInGroupOf(characterTarget) == false,

                IPlayerEntity character when target is IMateEntity mateTarget =>
                    mateTarget.Owner.Id != character.Id && mateTarget.Owner.BuffComponent.HasBuff((short)BuffVnums.PVP) && character.IsInGroupOf(mateTarget.Owner) == false,

                IMateEntity mate when target is IPlayerEntity character =>
                    mate.Owner.Id != character.Id && character.BuffComponent.HasBuff((short)BuffVnums.PVP) && mate.Owner.BuffComponent.HasBuff((short)BuffVnums.PVP)
                    && mate.Owner.IsInGroupOf(character) == false,

                IMateEntity mate when target is IMateEntity mateTarget =>
                    mate.Owner.Id != mateTarget.Owner.Id && mate.Owner.BuffComponent.HasBuff((short)BuffVnums.PVP) && mateTarget.Owner.BuffComponent.HasBuff((short)BuffVnums.PVP)
                    && mate.Owner.IsInGroupOf(mateTarget.Owner) == false,

                _ => false
            };
        }

        if (entity.MapInstance.MapInstanceType == MapInstanceType.RainbowBattle)
        {
            switch (entity)
            {
                case IPlayerEntity character when target is IPlayerEntity characterTarget:

                    RainbowBattleParty rainbowParty = character.RainbowBattleComponent.RainbowBattleParty;

                    return rainbowParty is { Started: true, FinishTime: null }
                        && !characterTarget.RainbowBattleComponent.IsFrozen && !character.RainbowBattleComponent.IsFrozen
                        && characterTarget.RainbowBattleComponent.Team != character.RainbowBattleComponent.Team;
                case IPlayerEntity character when target is IMateEntity mateTarget:

                    RainbowBattleParty rainbowPartyTwo = character.RainbowBattleComponent.RainbowBattleParty;

                    return rainbowPartyTwo is { Started: true, FinishTime: null }
                        && mateTarget.Owner.Id != character.Id && !mateTarget.Owner.RainbowBattleComponent.IsFrozen
                        && !character.RainbowBattleComponent.IsFrozen && mateTarget.Owner.RainbowBattleComponent.Team != character.RainbowBattleComponent.Team;
                case IMateEntity mate when target is IPlayerEntity character:

                    RainbowBattleParty rainbowPartyThree = mate.Owner.RainbowBattleComponent.RainbowBattleParty;

                    return rainbowPartyThree is { Started: true, FinishTime: null }
                        && !mate.Owner.RainbowBattleComponent.IsFrozen && !character.RainbowBattleComponent.IsFrozen
                        && mate.Owner.Id != character.Id && mate.Owner.RainbowBattleComponent.Team != character.RainbowBattleComponent.Team;
                case IMateEntity mate when target is IMateEntity mateTarget:

                    RainbowBattleParty rainbowPartyFour = mate.Owner.RainbowBattleComponent.RainbowBattleParty;

                    return rainbowPartyFour is { Started: true, FinishTime: null }
                        && !mate.Owner.RainbowBattleComponent.IsFrozen && !mateTarget.Owner.RainbowBattleComponent.IsFrozen
                        && mate.Owner.Id != mateTarget.Owner.Id && mate.Owner.RainbowBattleComponent.Team != mateTarget.Owner.RainbowBattleComponent.Team;
                default:
                    return false;
            }
        }

        switch (entity.MapInstance.IsPvp)
        {
            case false:
            case true when !target.IsInPvpZone():
                return false;
        }

        if (entity.MapInstance.HasMapFlag(MapFlags.ACT_4))
        {
            return entity.Faction != target.Faction;
        }

        return entity switch
        {
            IPlayerEntity character when target is IPlayerEntity characterTarget
                => !character.ArenaImmunity.HasValue && !characterTarget.ArenaImmunity.HasValue && character.IsInGroupOf(characterTarget) == false,

            IPlayerEntity character when target is IMateEntity mateTarget
                => mateTarget.Owner.IsInPvpZone() && !character.ArenaImmunity.HasValue && !mateTarget.Owner.ArenaImmunity.HasValue && mateTarget.Owner.Id != character.Id &&
                character.IsInGroupOf(mateTarget.Owner) == false,

            IMateEntity mate when target is IPlayerEntity character
                => mate.Owner.IsInPvpZone() && !character.ArenaImmunity.HasValue && !mate.Owner.ArenaImmunity.HasValue && mate.Owner.Id != character.Id && mate.Owner.IsInGroupOf(character) == false,

            IMateEntity mate when target is IMateEntity mateTarget
                => mateTarget.Owner.IsInPvpZone() && mate.Owner.IsInPvpZone() && !mateTarget.Owner.ArenaImmunity.HasValue && !mate.Owner.ArenaImmunity.HasValue &&
                mate.Owner.Id != mateTarget.Owner.Id && mate.Owner.IsInGroupOf(mateTarget.Owner) == false,
            _ => false
        };
    }

    public static bool IsEnemyWith(this IBattleEntity entity, IBattleEntity target)
    {
        if (entity.IsSameEntity(target))
        {
            return false;
        }

        switch (target)
        {
            case IPlayerEntity playerEntity:
                if (playerEntity.IsSeal)
                {
                    return false;
                }

                break;
            case IMateEntity mateEntity:
                if (mateEntity.Owner.IsOnVehicle)
                {
                    return false;
                }

                break;
        }

        switch (entity)
        {
            case IPlayerEntity:
                if (target is not IMonsterEntity monsterEntitySummoner)
                {
                    return target.IsMonster() || entity.IsPvpActivated(target);
                }

                if (entity.Id == monsterEntitySummoner.SummonerId && entity.Type == monsterEntitySummoner.SummonerType && !monsterEntitySummoner.IsMateTrainer)
                {
                    return false;
                }

                if (monsterEntitySummoner.SummonerType is VisualType.Player && !monsterEntitySummoner.IsMateTrainer)
                {
                    if (!monsterEntitySummoner.MapInstance.IsPvp || !monsterEntitySummoner.SummonerId.HasValue)
                    {
                        return false;
                    }

                    IPlayerEntity player = monsterEntitySummoner.MapInstance.GetCharacterById(monsterEntitySummoner.SummonerId.Value);
                    return player is null || player.IsPvpActivated(entity);
                }

                if (monsterEntitySummoner.Faction == entity.Faction && monsterEntitySummoner.MapInstance.HasMapFlag(MapFlags.ACT_4))
                {
                    return false;
                }

                return target.IsMonster() || entity.IsPvpActivated(target);
            case IMateEntity mate:
                if (target is not IMonsterEntity monsterSummoner)
                {
                    return entity.IsPvpActivated(target);
                }

                if (monsterSummoner.IsMateTrainer)
                {
                    return true;
                }

                int mateOwnerId = mate.Owner.Id;
                if (monsterSummoner.SummonerType is not VisualType.Player)
                {
                    return true;
                }

                if (!monsterSummoner.SummonerId.HasValue)
                {
                    return true;
                }

                if (!monsterSummoner.MapInstance.IsPvp)
                {
                    return monsterSummoner.SummonerId.Value != mateOwnerId;
                }

                IPlayerEntity playerMate = monsterSummoner.MapInstance.GetCharacterById(monsterSummoner.SummonerId.Value);
                return playerMate is null || playerMate.IsPvpActivated(mate.Owner);
        }

        if (entity.IsNpc())
        {
            return !(target.IsNpc() || target.IsPlayer() || target.IsMate());
        }

        if (entity is not IMonsterEntity mob)
        {
            return false;
        }

        if (mob.MapInstance.HasMapFlag(MapFlags.ACT_4))
        {
            if (target.Faction == mob.Faction)
            {
                return false;
            }
        }

        if (mob.IsMateTrainer || !mob.SummonerId.HasValue || !mob.SummonerType.HasValue || mob.MonsterVNum == (short)MonsterVnum.ONYX_MONSTER)
        {
            return target.IsPlayer() || target.IsNpc() || target.IsMate() || target.CheckIfIsSummonedMonster(mob);
        }

        if (mob.SummonerId == target.Id && mob.SummonerType == target.Type)
        {
            return false;
        }

        if (target is IMonsterEntity monsterEntity)
        {
            if (mob.SummonerId == monsterEntity.SummonerId)
            {
                return false;
            }

            if (monsterEntity.IsMateTrainer)
            {
                return false;
            }

            if (monsterEntity.SummonerType is null or VisualType.Monster && mob.SummonerType is null or VisualType.Monster)
            {
                return false;
            }
        }

        IBattleEntity summoner = mob.MapInstance?.GetBattleEntity(mob.SummonerType.Value, mob.SummonerId.Value);
        if (summoner == null && mob.SummonerType.Value == VisualType.Player)
        {
            return false;
        }

        return summoner switch
        {
            IPlayerEntity character => character.IsEnemyWith(target),
            _ => target.IsPlayer() || target.IsMate() || target.IsMonster() || target.IsNpc()
        };
    }

    public static bool CanMonsterBeAttacked(this IBattleEntity entity, IMonsterEntity monsterEntity)
    {
        if (monsterEntity.SummonerType is not VisualType.Player)
        {
            return true;
        }

        if (monsterEntity.SummonerId is null)
        {
            return true;
        }

        IPlayerEntity summoner = monsterEntity.MapInstance.GetCharacterById(monsterEntity.SummonerId.Value);
        return summoner == null || summoner.IsEnemyWith(entity);
    }

    private static bool CheckIfIsSummonedMonster(this IBattleEntity entity, IMonsterEntity monster)
    {
        if (entity is not IMonsterEntity monsterEntity)
        {
            return false;
        }

        if (monsterEntity.SummonerType is not VisualType.Player)
        {
            return false;
        }

        if (monster.SummonerType == monsterEntity.SummonerType && monsterEntity.SummonerId == monster.SummonerId)
        {
            return false;
        }

        IPlayerEntity player = monster.SummonerId.HasValue ? entity.MapInstance.GetCharacterById(monster.SummonerId.Value) : null;
        return player == null || player.IsEnemyWith(monster);
    }

    public static bool IsAllyWith(this IBattleEntity entity, IBattleEntity target)
    {
        if (target.IsMate())
        {
            if (target is IMateEntity mateEntity && mateEntity.Owner.IsOnVehicle)
            {
                return false;
            }
        }

        if (entity.IsPlayer())
        {
            if (target is IMonsterEntity monster)
            {
                if (monster.Faction == entity.Faction && monster.MapInstance != null && monster.MapInstance.HasMapFlag(MapFlags.ACT_4))
                {
                    return true;
                }
            }

            return !target.IsMonster() && (target.IsNpc() || entity.IsPvpActivated(target) == false);
        }

        if (entity.IsMate())
        {
            if (target is IMonsterEntity monster)
            {
                if (monster.Faction == entity.Faction && monster.MapInstance != null && monster.MapInstance.HasMapFlag(MapFlags.ACT_4))
                {
                    return true;
                }
            }

            return !target.IsMonster() && (target.IsNpc() || entity.IsPvpActivated(target) == false);
        }

        if (entity.IsMonster())
        {
            return target.IsMonster();
        }

        if (!entity.IsNpc())
        {
            return false;
        }

        if (target is not IMonsterEntity mons)
        {
            return true;
        }

        if (mons.IsMateTrainer)
        {
            return true;
        }

        return mons.SummonerType is VisualType.Player;
    }

    public static void BroadcastCastPacket(this IBattleEntity caster, IBattleEntity target, SkillInfo skillInfo)
    {
        caster.MapInstance.Broadcast(caster.GenerateCtPacket(target, skillInfo));
    }

    public static string GenerateCtPacket(this IBattleEntity caster, IBattleEntity target, SkillInfo skill) =>
        $"ct {(byte)caster.Type} {caster.Id} {(target == null ? 0 : (byte)target.Type)} {target?.Id ?? -1} {(skill.CastAnimation == 0 ? -1 : skill.CastAnimation)} {(skill.CastEffect == 0 ? -1 : skill.CastEffect)} {skill.Vnum}";

    public static string GenerateCleanSuPacket(this IBattleEntity entity, IBattleEntity target, int damage) =>
        $"su {(byte)entity.Type} {entity.Id} {(byte)target.Type} {target.Id} -1 0 -1 0 0 0 {(target.IsAlive() ? 1 : 0)} {target.HpPercentage} {damage} 0 0";

    public static string GenerateSuCapturePacket(this IBattleEntity caster, IBattleEntity target, SkillInfo skill, bool failed) =>
        $"su {(byte)caster.Type} {caster.Id} {(byte)target.Type} {target.Id} {skill.Vnum} {skill.Cooldown} {(failed ? 16 : 15)} -1 -1 -1 1 0 0 -1 0";

    public static string GenerateSuPacket(this IBattleEntity caster, IBattleEntity target, SkillInfo skill, int damages, SuPacketHitMode hitMode, Position position, bool isReflection, bool isFirst)
    {
        bool isArchMageSkill = skill.Vnum == (short)SkillsVnums.HOLY_EXPLOSION;
        return "su " +
            $"{(byte)caster.Type} " +
            $"{caster.Id} " +
            $"{(byte)target.Type} " +
            $"{target.Id} " +
            $"{(isReflection ? -1 : skill.Vnum)} " +
            $"{(isReflection ? 0 : skill.Cooldown)} " +
            $"{(isReflection || skill.Vnum == (short)SkillsVnums.SPY_OUT_SKILL ? -1 : skill.HitAnimation)} " +
            $"{(isReflection && isArchMageSkill ? 1047 : skill.HitEffect)} " +
            "0 " +
            "0 " +
            $"{(target.IsAlive() ? 1 : 0)} " +
            $"{target.HpPercentage} " +
            $"{damages} " +
            $"{(sbyte)hitMode} " +
            $"{(isReflection ? 1 : 0)}";
    }

    public static string GenerateSuDashPacket(this IBattleEntity caster, IBattleEntity target, SkillInfo skill, int damages, SuPacketHitMode hitMode, bool isReflection = false, bool isFirst = false)
    {
        if (isReflection)
        {
            return "su " +
                $"{(byte)caster.Type} " +
                $"{caster.Id} " +
                $"{(byte)target.Type} " +
                $"{target.Id} " +
                $"{(isFirst ? -1 : skill.Vnum)} " +
                "0 " +
                "-1 " +
                $"{(isFirst ? skill.HitEffect : -1)} " +
                "-1 " +
                "-1 " +
                $"{(target.IsAlive() ? 1 : 0)} " +
                $"{target.GetHpPercentage()} " +
                $"{damages} " +
                $"{(sbyte)hitMode} " +
                "1";
        }

        return "su " +
            $"{(byte)caster.Type} " +
            $"{caster.Id} " +
            $"{(byte)target.Type} " +
            $"{target.Id} " +
            $"{(!isFirst ? -1 : skill.Vnum)} " +
            $"{skill.Cooldown} " +
            $"{(isFirst ? skill.HitAnimation : -1)} " +
            $"{(isFirst ? skill.HitEffect : -1)} " +
            $"{(isFirst ? caster.PositionX : -1)} " +
            $"{(isFirst ? caster.PositionY : -1)} " +
            $"{(target.IsAlive() ? 1 : 0)} " +
            $"{target.GetHpPercentage()} " +
            $"{damages} " +
            $"{(sbyte)hitMode} " +
            $"{0}";
    }

    public static string GenerateIcon(this IBattleEntity entity, bool isItem, int vnum) => $"icon {(byte)entity.Type} {entity.Id} {(isItem ? 1 : 2)} {vnum}";

    public static void SendIconPacket(this IBattleEntity entity, bool isItem, int vnum)
    {
        switch (entity)
        {
            case IPlayerEntity character:
                character.Session.SendPacket(character.GenerateIcon(isItem, vnum));
                break;
            case IMateEntity mateEntity:
                mateEntity.Owner?.Session.SendPacket(mateEntity.GenerateIcon(isItem, vnum));
                break;
        }
    }

    public static void BroadcastCastNonTarget(this IBattleEntity caster, SkillInfo skillInfo)
    {
        caster.MapInstance.Broadcast(caster.GenerateCastNonTargetPacket(skillInfo));
    }

    public static void BroadcastNonTargetSkill(this IBattleEntity caster, Position position, SkillInfo skill)
    {
        caster.MapInstance.Broadcast(caster.GenerateNonTargetSkill(position, skill));
    }

    public static string GenerateNonTargetSkill(this IBattleEntity caster, Position position, SkillInfo skill) =>
        $"bs 1 {caster.Id} {position.X} {position.Y} {skill.Vnum} {skill.Cooldown} {skill.HitAnimation} {skill.HitEffect} 0 0 1 1 0 0 0";

    public static string GenerateCastNonTargetPacket(this IBattleEntity caster, SkillInfo skill) => $"ct_n 1 {caster.Id} 3 -1 {skill.CastAnimation} {skill.CastEffect} {skill.Vnum}";

    public static void BroadcastSuPacket(this IBattleEntity caster, IBattleEntity target, SkillInfo skill, int damages, SuPacketHitMode hitMode, bool isReflection = false, bool isFirst = false)
    {
        if (skill.AttackType == AttackType.Dash)
        {
            caster.MapInstance.Broadcast(caster.GenerateSuDashPacket(target, skill, damages, hitMode, isReflection, isFirst));
            return;
        }

        caster.MapInstance.Broadcast(caster.GenerateSuPacket(target, skill, damages, hitMode, target.Position, isReflection, isFirst));
    }

    public static void BroadcastCleanSuPacket(this IBattleEntity caster, IBattleEntity target, int damage) => caster.MapInstance.Broadcast(caster.GenerateCleanSuPacket(target, damage));

    public static bool CharacterCanCastOrCancel(this IBattleEntity entity, CharacterSkill skill, IGameLanguageService gameLanguage, SkillInfo skillInfo, bool removeAmmo)
    {
        var character = (IPlayerEntity)entity;

        if (character.Mp < skillInfo.ManaCost)
        {
            character.Session.SendCancelPacket(CancelType.NotInCombatMode);
            character.Session.SendSound(SoundType.NO_MANA);
            character.Session.SendChatMessage(gameLanguage.GetLanguage(GameDialogKey.INFORMATION_CHATMESSAGE_NOT_ENOUGH_MP, character.Session.UserLanguage), ChatMessageColorType.Yellow);
            return false;
        }

        if (!character.WeaponLoaded(skill, gameLanguage, removeAmmo))
        {
            character.Session.SendDebugMessage("[BATTLE_DEBUG] No Weapon loaded");
            character.Session.SendCancelPacket(CancelType.NotInCombatMode);
            return false;
        }

        if (character.SkillCanBeUsed(skill))
        {
            return true;
        }

        character.Session.SendDebugMessage("[BATTLE_DEBUG] Skill under cooldown");
        character.Session.SendCancelPacket(CancelType.NotInCombatMode);
        return false;
    }

    public static bool CharacterCanCastPartnerSkill(this IBattleEntity entity, IBattleEntitySkill skill, SkillInfo skillInfo)
    {
        var mateEntity = (IMateEntity)entity;
        return mateEntity.Mp >= skill.Skill.MpCost && mateEntity.SkillCanBeUsed(skill, DateTime.UtcNow);
    }

    public static void SendTargetConstBuffEffect(this IClientSession session, IBattleEntity battleEntity, Buff buff, int time)
    {
        session.SendPacket(battleEntity.GenerateConstBuffEffect(buff, time));
    }

    public static void SendTargetConstBuffEffects(this IClientSession session, IBattleEntity battleEntity)
    {
        session.SendPackets(battleEntity.GenerateConstBuffEffects());
    }

    public static void BroadcastConstBuffEffect(this IBattleEntity battleEntity, Buff buff, int time)
    {
        battleEntity.MapInstance?.Broadcast(battleEntity.GenerateConstBuffEffect(buff, time));
    }

    public static void BroadcastConstBuffEffects(this IBattleEntity battleEntity)
    {
        battleEntity.MapInstance?.Broadcast(battleEntity.GenerateConstBuffEffects());
    }

    public static IEnumerable<string> GenerateConstBuffEffects(this IBattleEntity battleEntity)
    {
        return battleEntity.BuffComponent.GetAllBuffs(b => b.IsConstEffect).Select(buff => battleEntity.GenerateConstBuffEffect(buff, (int)buff.Duration.TotalMilliseconds));
    }

    public static string GenerateConstBuffEffect(this IBattleEntity battleEntity, Buff buff, int time) =>
        $"bf_e {((byte)battleEntity.Type).ToString()} {battleEntity.Id.ToString()} {buff.CardId.ToString()} {time.ToString()}";

    public static async Task RemoveNegativeBuffs(this IBattleEntity entity, int level)
    {
        IReadOnlyList<Buff> buffs = entity.BuffComponent.GetAllBuffs(x => x.Level <= level && x.BuffGroup == BuffGroup.Bad && x.IsNormal());
        await entity.RemoveBuffAsync(false, buffs.ToArray());
    }

    public static async Task RemovePositiveBuffs(this IBattleEntity entity, int level)
    {
        IReadOnlyList<Buff> buffs = entity.BuffComponent.GetAllBuffs(x => x.Level <= level && x.BuffGroup == BuffGroup.Good && x.IsNormal());
        await entity.RemoveBuffAsync(false, buffs.ToArray());
    }

    public static async Task RemoveNeutralBuffs(this IBattleEntity entity, int level)
    {
        IReadOnlyList<Buff> buffs = entity.BuffComponent.GetAllBuffs(x => x.Level <= level && x.BuffGroup == BuffGroup.Neutral && x.IsNormal());
        await entity.RemoveBuffAsync(false, buffs.ToArray());
    }

    public static async Task RemoveInvisibility(this IBattleEntity damager)
    {
        if (!damager.BuffComponent.HasAnyBuff())
        {
            return;
        }

        if (damager is IPlayerEntity { Invisible: false })
        {
            return;
        }

        if (!damager.BCardComponent.HasBCard(BCardType.SpecialActions, (byte)AdditionalTypes.SpecialActions.Hide))
        {
            return;
        }

        if (damager is not IPlayerEntity charDamager)
        {
            return;
        }

        IReadOnlyList<Buff> buffs = charDamager.BuffComponent.GetAllBuffs(b => b.BCards.Any(t => t.Type == (short)BCardType.SpecialActions && t.SubType == (byte)AdditionalTypes.SpecialActions.Hide));
        foreach (Buff buff in buffs)
        {
            await damager.RemoveBuffAsync(false, buff);
            charDamager.Session.UpdateVisibility();
        }

        foreach (IMateEntity mateEntity in charDamager.MateComponent.TeamMembers())
        {
            if (!mateEntity.BuffComponent.HasAnyBuff())
            {
                continue;
            }

            IReadOnlyList<Buff> mateBuffs =
                mateEntity.BuffComponent.GetAllBuffs(b => b.BCards.Any(t => t.Type == (short)BCardType.SpecialActions && t.SubType == (byte)AdditionalTypes.SpecialActions.Hide));
            foreach (Buff mateBuff in mateBuffs)
            {
                await mateEntity.RemoveBuffAsync(false, mateBuff);
            }
        }
    }

    public static bool CanPerformMove(this IBattleEntity entity)
    {
        switch (entity)
        {
            case IPlayerEntity playerEntity:
                RainbowBattleParty rainbowBattleParty = playerEntity.RainbowBattleComponent.RainbowBattleParty;
                if (rainbowBattleParty != null)
                {
                    if (!playerEntity.RainbowBattleComponent.RainbowBattleParty.Started)
                    {
                        return false;
                    }

                    if (playerEntity.RainbowBattleComponent.IsFrozen)
                    {
                        return false;
                    }
                }

                break;
            case IMateEntity mateEntity:
                if (mateEntity.MapInstance is { MapInstanceType: MapInstanceType.RainbowBattle })
                {
                    return false;
                }

                break;
        }

        return !entity.BCardComponent.HasBCard(BCardType.Move, (byte)AdditionalTypes.Move.MovementImpossible)
            && !entity.BuffComponent.HasBuff((int)BuffVnums.ETERNAL_ICE) && entity.IsAlive();
    }

    public static bool CanPerformAttack(this IBattleEntity entity)
    {
        if (entity.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.NoAttack))
        {
            return false;
        }

        if (entity.BuffComponent.HasBuff((int)BuffVnums.ETERNAL_ICE))
        {
            return false;
        }

        if (!entity.IsAlive())
        {
            return false;
        }

        switch (entity)
        {
            case IPlayerEntity playerEntity:
                if (playerEntity.IsSeal)
                {
                    return false;
                }

                if (playerEntity.RainbowBattleComponent.IsFrozen)
                {
                    return false;
                }

                break;
            case IMonsterEntity mapMonster:
                switch (mapMonster.AttackType)
                {
                    case AttackType.Melee:
                        if (mapMonster.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MeleeDisabled))
                        {
                            return false;
                        }

                        break;
                    case AttackType.Ranged:
                        if (mapMonster.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.RangedDisabled))
                        {
                            return false;
                        }

                        break;
                    case AttackType.Magical:
                        if (mapMonster.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MagicDisabled))
                        {
                            return false;
                        }

                        break;
                }

                break;
            case IMateEntity mateEntity:
                if (mateEntity.MapInstance is { MapInstanceType: MapInstanceType.RainbowBattle })
                {
                    return false;
                }

                switch (mateEntity.AttackType)
                {
                    case AttackType.Melee:
                        if (mateEntity.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MeleeDisabled))
                        {
                            return false;
                        }

                        break;
                    case AttackType.Ranged:
                        if (mateEntity.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.RangedDisabled))
                        {
                            return false;
                        }

                        break;
                    case AttackType.Magical:
                        if (mateEntity.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MagicDisabled))
                        {
                            return false;
                        }

                        break;
                }

                break;
            case INpcEntity mapNpc:
                switch (mapNpc.AttackType)
                {
                    case AttackType.Melee:
                        if (mapNpc.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MeleeDisabled))
                        {
                            return false;
                        }

                        break;
                    case AttackType.Ranged:
                        if (mapNpc.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.RangedDisabled))
                        {
                            return false;
                        }

                        break;
                    case AttackType.Magical:
                        if (mapNpc.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MagicDisabled))
                        {
                            return false;
                        }

                        break;
                }

                break;
        }

        return true;
    }

    public static bool CanPerformAttack(this IPlayerEntity playerEntity, SkillInfo skillInfo)
    {
        return skillInfo.AttackType switch
        {
            AttackType.Melee => !playerEntity.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MeleeDisabled),
            AttackType.Ranged => !playerEntity.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.RangedDisabled),
            AttackType.Magical => !playerEntity.BCardComponent.HasBCard(BCardType.SpecialAttack, (byte)AdditionalTypes.SpecialAttack.MagicDisabled),
            _ => true
        };
    }

    public static void ApplyAttackBCard(this IBattleEntity attacker, IBattleEntity defender, SkillInfo skillInfo, IBCardEffectHandlerContainer bCardHandler)
    {
        EquipmentType eqType = skillInfo.IsUsingSecondWeapon ? EquipmentType.MainWeapon : EquipmentType.SecondaryWeapon;
        bool isPlayer = attacker.IsPlayer();

        if (isPlayer)
        {
            IReadOnlyList<BCardDTO> shellBCards = attacker.BCardComponent.GetShellTriggers(!skillInfo.IsUsingSecondWeapon);
            foreach (BCardDTO shellBCard in shellBCards)
            {
                bCardHandler.Execute(defender, attacker, shellBCard);
            }

            byte executeMainOrSecond = skillInfo.IsUsingSecondWeapon
                ? (byte)AdditionalTypes.SpecialEffects2.SecondaryWeaponCausingChance
                : (byte)AdditionalTypes.SpecialEffects2.MainWeaponCausingChance;

            foreach ((int _, BCardDTO bCard) in attacker.BCardComponent.GetBuffBCards(x => x.Item2.Type == (short)BCardType.SpecialEffects2 && x.Item2.SubType == executeMainOrSecond))
            {
                bCardHandler.Execute(defender, attacker, bCard);
            }

            IReadOnlyDictionary<EquipmentType, List<BCardDTO>> dictionary = attacker.BCardComponent.GetEquipmentBCards();

            foreach (KeyValuePair<EquipmentType, List<BCardDTO>> bCardDictionary in dictionary)
            {
                EquipmentType equipmentType = bCardDictionary.Key;
                if (equipmentType == eqType)
                {
                    continue;
                }

                if (equipmentType != EquipmentType.MainWeapon && equipmentType != EquipmentType.SecondaryWeapon)
                {
                    continue;
                }

                if (bCardDictionary.Value == null)
                {
                    continue;
                }

                foreach (BCardDTO bCard in bCardDictionary.Value)
                {
                    var castType = (CastType)bCard.CastType;
                    switch (castType)
                    {
                        case CastType.ATTACK:
                            bCardHandler.Execute(defender, attacker, bCard);
                            break;
                        case CastType.SELF:
                        case CastType.DEFENSE:
                            bCardHandler.Execute(attacker, attacker, bCard);
                            break;
                    }
                }
            }

            return;
        }

        if (attacker.IsMate())
        {
            IReadOnlyDictionary<EquipmentType, List<BCardDTO>> mateEq = attacker.BCardComponent.GetEquipmentBCards();

            foreach (KeyValuePair<EquipmentType, List<BCardDTO>> bCardDictionary in mateEq)
            {
                EquipmentType equipmentType = bCardDictionary.Key;
                if (equipmentType == eqType)
                {
                    continue;
                }

                if (equipmentType != EquipmentType.MainWeapon && equipmentType != EquipmentType.SecondaryWeapon)
                {
                    continue;
                }

                if (bCardDictionary.Value == null)
                {
                    continue;
                }

                foreach (BCardDTO bCard in bCardDictionary.Value)
                {
                    var castType = (CastType)bCard.CastType;
                    switch (castType)
                    {
                        case CastType.ATTACK:
                            bCardHandler.Execute(defender, attacker, bCard);
                            break;
                        case CastType.SELF:
                        case CastType.DEFENSE:
                            bCardHandler.Execute(attacker, attacker, bCard);
                            break;
                    }
                }
            }
        }

        IReadOnlyList<BCardDTO> attackerBCards = attacker.BCardComponent.GetTriggerBCards(BCardTriggerType.ATTACK);

        if (!attackerBCards.Any())
        {
            return;
        }

        if (attacker.BCardComponent.HasBCard(BCardType.Mode, (byte)AdditionalTypes.Mode.DirectDamage))
        {
            return;
        }

        foreach (BCardDTO bCard in attackerBCards.ToList())
        {
            switch ((CastType)bCard.CastType)
            {
                case CastType.ATTACK: // during attacking on enemy (the target)
                    bCardHandler.Execute(defender, attacker, bCard);
                    break;
                case CastType.DEFENSE: // during attacking on self
                    bCardHandler.Execute(attacker, attacker, bCard);
                    break;
                default:
                    continue;
            }
        }
    }

    public static void ApplyDefenderBCard(this IBattleEntity defender, IBattleEntity attacker, SkillInfo skillInfo, IBCardEffectHandlerContainer bCardHandler)
    {
        bool isPlayer = defender.IsPlayer();

        if (isPlayer)
        {
            IReadOnlyDictionary<EquipmentType, List<BCardDTO>> dictionary = defender.BCardComponent.GetEquipmentBCards();

            foreach ((EquipmentType equipmentType, List<BCardDTO> value) in dictionary)
            {
                if (equipmentType is EquipmentType.MainWeapon or EquipmentType.SecondaryWeapon)
                {
                    continue;
                }

                if (value == null)
                {
                    continue;
                }

                foreach (BCardDTO bCard in value)
                {
                    var castType = (CastType)bCard.CastType;
                    switch (castType)
                    {
                        case CastType.ATTACK:
                            bCardHandler.Execute(attacker, defender, bCard, skillInfo);
                            break;
                        case CastType.SELF:
                        case CastType.DEFENSE:
                            bCardHandler.Execute(defender, defender, bCard, skillInfo);
                            break;
                    }
                }
            }

            return;
        }

        IReadOnlyList<BCardDTO> defenderBCards = defender.BCardComponent.GetTriggerBCards(BCardTriggerType.DEFENSE);

        if (!defenderBCards.Any())
        {
            return;
        }

        foreach (BCardDTO bCard in defenderBCards.ToList())
        {
            switch ((CastType)bCard.CastType)
            {
                case CastType.ATTACK: // on getting hit on enemy (the attacker)
                    bCardHandler.Execute(attacker, defender, bCard);
                    break;
                case CastType.DEFENSE: // on getting hit on self
                    bCardHandler.Execute(defender, defender, bCard);
                    break;
            }
        }
    }


    public static void InitializeBCards(this IBattleEntity entity)
    {
        var attackBCards = new List<BCardDTO>();
        var defenseBCards = new List<BCardDTO>();

        IReadOnlyList<BCardDTO> entityBCards = entity switch
        {
            IMonsterEntity mapMonster => mapMonster.BCards,
            IMateEntity mateEntity => mateEntity.BCards,
            INpcEntity mapNpc => mapNpc.BCards,
            _ => null
        };

        if (entityBCards == null)
        {
            Log.Warn($"Couldn't initialize BCards of this BattleEntity -> VisualType: '{entity.Type.ToString()}' Id: '{entity.Id.ToString()}'");
            return;
        }

        foreach (BCardDTO bCard in entityBCards)
        {
            switch (bCard.NpcTriggerType)
            {
                case BCardNpcTriggerType.ON_ATTACK:
                    if ((CastType)bCard.CastType == CastType.SELF)
                    {
                        entity.BCardComponent.AddBCard(bCard);
                    }
                    else
                    {
                        attackBCards.Add(bCard);
                    }

                    break;
                case BCardNpcTriggerType.ON_DEFENSE:
                    if ((CastType)bCard.CastType == CastType.SELF)
                    {
                        entity.BCardComponent.AddBCard(bCard);
                    }
                    else
                    {
                        defenseBCards.Add(bCard);
                    }

                    break;
                default:
                    entity.BCardComponent.AddBCard(bCard);
                    break;
            }
        }

        entity.BCardComponent.AddTriggerBCards(BCardTriggerType.ATTACK, attackBCards);
        entity.BCardComponent.AddTriggerBCards(BCardTriggerType.DEFENSE, defenseBCards);

        if (entity is not IMonsterEntity monsterEntity)
        {
            return;
        }

        monsterEntity.RefreshStats();
    }

    public static bool IsSameEntity(this IBattleEntity entity, IBattleEntity target) => entity != null && target != null && entity.Id == target.Id && entity.Type == target.Type;

    public static void SetSkillCooldown(this IBattleEntity caster, SkillInfo skill)
    {
        short cooldownReduction = skill.Cooldown;
        List<IBattleEntitySkill> skills = caster.Skills;
        IBattleEntitySkill battleEntitySkill;

        if (caster.IsMonster() || caster.IsNpc())
        {
            battleEntitySkill = skills.FirstOrDefault(x => x.Skill.Id == skill.Vnum);
        }
        else
        {
            if (caster.IsPlayer())
            {
                battleEntitySkill = skills.FirstOrDefault(x => x.Skill.CastId == skill.CastId && x.Skill.SkillType == SkillType.NormalPlayerSkill);
            }
            else
            {
                battleEntitySkill = skills.FirstOrDefault(x => x.Skill.CastId == skill.CastId);
            }
        }

        if (battleEntitySkill == null)
        {
            return;
        }

        DateTime time = DateTime.UtcNow.AddMilliseconds(cooldownReduction * 100);
        battleEntitySkill.LastUse = time;

        switch (caster)
        {
            case IPlayerEntity character:
                character.AddSkillCooldown(time, skill.CastId);
                break;
            case IMateEntity mateEntity:
                if (skill.Vnum == 0)
                {
                    return;
                }

                mateEntity.Owner?.AddMateSkillCooldown(time, skill.CastId, mateEntity.MateType);
                break;
        }
    }

    public static string GenerateDiePacket(this IBattleEntity entity) => $"die {(byte)entity.Type} {entity.Id} {(byte)entity.Type} {entity.Id}";
    public static void BroadcastDie(this IBattleEntity entity) => entity.MapInstance.Broadcast(entity.GenerateDiePacket());
    public static string GeneratePushPacket(this IBattleEntity entity, short x, short y, int value) => $"guri 3 {(byte)entity.Type} {entity.Id} {x} {y} 3 {value} 2 -1";

    public static string GenerateDashGuriPacket(this IBattleEntity entity, short x, short y, int value)
        => $"guri 3 {(byte)entity.Type} {entity.Id} {x} {y} 3 2 2 {value}";

    public static async Task RemoveSacrifice(this IBattleEntity caster, IBattleEntity target, ISacrificeManager sacrificeManager, IGameLanguageService gameLanguage)
    {
        Buff sacrifice = caster.BuffComponent.GetBuff((short)BuffVnums.SPIRIT_OF_SACRIFICE);
        await caster.RemoveBuffAsync(false, sacrifice);
        Buff nobleGesture = target.BuffComponent.GetBuff((short)BuffVnums.NOBLE_GESTURE);
        await target.RemoveBuffAsync(false, nobleGesture);
        string message;
        if (caster is IPlayerEntity character)
        {
            message = gameLanguage.GetLanguage(GameDialogKey.SKILL_SHOUTMESSAGE_SACRIFICE_REMOVED, character.Session.UserLanguage);
            character.Session.SendMsg(message, MsgMessageType.SmallMiddle);
        }

        if (target is IPlayerEntity targetCharacter)
        {
            message = gameLanguage.GetLanguage(GameDialogKey.SKILL_SHOUTMESSAGE_SACRIFICE_REMOVED, targetCharacter.Session.UserLanguage);
            targetCharacter.Session.SendMsg(message, MsgMessageType.SmallMiddle);
        }
    }

    public static bool CanBeInterrupted(this IPlayerEntity character, SkillInfo skillInfo)
    {
        if (character.Class != ClassType.Magician && character.Class != ClassType.Adventurer)
        {
            return false;
        }

        if (character.GetMaxWeaponShellValue(ShellEffectType.AntiMagicDisorder) != 0)
        {
            return false;
        }

        if (skillInfo.AttackType != AttackType.Magical || skillInfo.IsUsingSecondWeapon)
        {
            return false;
        }

        return !character.SkillComponent.IsSkillInterrupted;
    }

    public static string GenerateShadowFigurePacket(this IBattleEntity entity, int firstValue, int secondValue) => $"guri 0 {(byte)entity.Type} {entity.Id} {firstValue} {secondValue}";

    public static void BroadcastShadowFigurePacket(this IBattleEntity entity, int firstValue, int secondValue) =>
        entity.MapInstance?.Broadcast(entity.GenerateShadowFigurePacket(firstValue, secondValue));

    public static void ShadowAppears(this IBattleEntity battleEntity, bool broadcastZeroValue, Buff buff = null)
    {
        if (buff != null && broadcastZeroValue && buff.BCards.Any(x => x.Type == (short)BCardType.SpecialEffects && x.SubType == (byte)AdditionalTypes.SpecialEffects.ShadowAppears))
        {
            battleEntity.BroadcastShadowFigurePacket(0, 0);
        }

        if (!battleEntity.BuffComponent.HasAnyBuff())
        {
            return;
        }

        if (!battleEntity.BCardComponent.HasBCard(BCardType.SpecialEffects, (byte)AdditionalTypes.SpecialEffects.ShadowAppears))
        {
            battleEntity.BroadcastShadowFigurePacket(0, 0);
            return;
        }

        (int firstData, int secondData) = battleEntity.BCardComponent.GetAllBCardsInformation(BCardType.SpecialEffects,
            (byte)AdditionalTypes.SpecialEffects.ShadowAppears, battleEntity.Level);
        if (firstData == 0 && secondData == 0)
        {
            battleEntity.BroadcastShadowFigurePacket(0, 0);
            return;
        }

        battleEntity.BroadcastShadowFigurePacket(!broadcastZeroValue ? firstData : 0, secondData);
    }

    public static DateTime GenerateSkillCastTime(this IBattleEntity entity, SkillInfo skillInfo, bool isPartnerSkill = false)
    {
        DateTime time = DateTime.UtcNow.AddMilliseconds(GenerateSkillCastTimeNumber(entity, skillInfo, isPartnerSkill));
        return time;
    }

    public static short GenerateSkillCastTimeNumber(this IBattleEntity entity, SkillInfo skillInfo, bool isPartnerSkill = false)
    {
        short milliSeconds = skillInfo.CastTime;
        int toAddMilliSeconds = skillInfo.AttackType switch
        {
            AttackType.Melee => (short)entity.BCardComponent.GetAllBCardsInformation(BCardType.JumpBackPush, (byte)AdditionalTypes.JumpBackPush.MeleeDurationIncreased, entity.Level).firstData,
            AttackType.Ranged => (short)entity.BCardComponent.GetAllBCardsInformation(BCardType.JumpBackPush, (byte)AdditionalTypes.JumpBackPush.RangedDurationIncreased, entity.Level).firstData,
            AttackType.Magical => (short)entity.BCardComponent.GetAllBCardsInformation(BCardType.JumpBackPush, (byte)AdditionalTypes.JumpBackPush.MagicalDurationIncreased, entity.Level).firstData,
            _ => 0
        };

        toAddMilliSeconds -= skillInfo.AttackType switch
        {
            AttackType.Melee => (short)entity.BCardComponent.GetAllBCardsInformation(BCardType.JumpBackPush, (byte)AdditionalTypes.JumpBackPush.MeleeDurationDecreased, entity.Level).firstData,
            AttackType.Ranged => (short)entity.BCardComponent.GetAllBCardsInformation(BCardType.JumpBackPush, (byte)AdditionalTypes.JumpBackPush.RangedDurationDecreased, entity.Level).firstData,
            AttackType.Magical => (short)entity.BCardComponent.GetAllBCardsInformation(BCardType.JumpBackPush, (byte)AdditionalTypes.JumpBackPush.MagicalDurationDecreased, entity.Level).firstData,
            _ => 0
        };

        int toIncrease = entity.BCardComponent.GetAllBCardsInformation(BCardType.Casting, (byte)AdditionalTypes.Casting.EffectDurationIncreased, entity.Level).firstData;
        int toDecrease = entity.BCardComponent.GetAllBCardsInformation(BCardType.Casting, (byte)AdditionalTypes.Casting.EffectDurationDecreased, entity.Level).firstData;

        toAddMilliSeconds += (short)((toAddMilliSeconds == 0 ? milliSeconds * 10 : toAddMilliSeconds) * toIncrease * 0.01);
        toAddMilliSeconds -= (short)((toAddMilliSeconds == 0 ? milliSeconds * 10 : toAddMilliSeconds) * toDecrease * 0.01);

        milliSeconds += (short)(toAddMilliSeconds / 10);

        if (isPartnerSkill)
        {
            milliSeconds = (short)((milliSeconds - 1) * 2);
        }

        return (short)(milliSeconds * 100);
    }

    public static int CalculateManaUsage(this IBattleEntity entity, int mana, SkillDTO skill = null)
    {
        double manaMultiplier = 1;

        if (skill is { AttackType: AttackType.Magical })
        {
            manaMultiplier -= entity.BCardComponent.GetAllBCardsInformation(BCardType.Casting,
                (byte)AdditionalTypes.Casting.ManaForSkillsDecreased, entity.Level).firstData * 0.01;

            manaMultiplier += entity.BCardComponent.GetAllBCardsInformation(BCardType.Casting,
                (byte)AdditionalTypes.Casting.ManaForSkillsIncreased, entity.Level).firstData * 0.01;
        }

        if (entity is IPlayerEntity playerEntity)
        {
            manaMultiplier -= playerEntity.GetJewelsCellonsValue(CellonType.MpConsumption) * 0.01;
            manaMultiplier -= playerEntity.GetMaxWeaponShellValue(ShellEffectType.ReducedMPConsume) * 0.01;
        }

        return (int)(mana * manaMultiplier);
    }

    public static void RemoveEntityMp(this IBattleEntity battleEntity, short mana, SkillDTO skill = null)
    {
        int mp = battleEntity.CalculateManaUsage(mana, skill);

        battleEntity.Mp = battleEntity.Mp - mp < 0 ? 0 : battleEntity.Mp - mp;
    }

    public static bool IsMateTrainer(this IBattleEntity entity) => entity is IMonsterEntity { IsMateTrainer: true };

    public static bool IsInvisibleGm(this IBattleEntity entity) => entity is IPlayerEntity player && player.CheatComponent.IsInvisible;
}