// WingsEmu
// 
// Developed by NosWings Team

using System;
using System.Collections.Generic;
using System.Linq;
using PhoenixLib.Events;
using PhoenixLib.Logging;
using Plugin.CoreImpl.Maps.Systems;
using Plugin.CoreImpl.Pathfinding;
using WingsEmu.DTOs.Maps;
using WingsEmu.DTOs.ServerDatas;
using WingsEmu.Game;
using WingsEmu.Game._ECS;
using WingsEmu.Game._enum;
using WingsEmu.Game._i18n;
using WingsEmu.Game._ItemUsage.Configuration;
using WingsEmu.Game.Battle;
using WingsEmu.Game.Buffs;
using WingsEmu.Game.Characters;
using WingsEmu.Game.Configurations;
using WingsEmu.Game.Entities;
using WingsEmu.Game.Entities.Extensions;
using WingsEmu.Game.Extensions;
using WingsEmu.Game.Helpers.Damages;
using WingsEmu.Game.Items;
using WingsEmu.Game.Managers.StaticData;
using WingsEmu.Game.Maps;
using WingsEmu.Game.Mates;
using WingsEmu.Game.Miniland;
using WingsEmu.Game.Networking;
using WingsEmu.Game.Portals;
using WingsEmu.Game.Raids;
using WingsEmu.Game.RainbowBattle;
using WingsEmu.Game.Skills;
using WingsEmu.Packets.Enums;

namespace Plugin.CoreImpl.Maps
{
    public class MapInstance : SessionsContainer, IMapInstance
    {
        private static readonly TimeSpan _timeToIdleState = TimeSpan.FromSeconds(Convert.ToInt32(Environment.GetEnvironmentVariable("WINGSEMU_TICK_IDLE_DELAY") ?? "10"));

        private readonly BattleSystem _battleSystem;
        private readonly CharacterSystem _characterSystem;
        private readonly DropSystem _dropSystem;
        private readonly IEntityIdManager _entityIdManager;

        private readonly HashSet<MapFlags> _flags = new();
        private readonly IGameItemInstanceFactory _gameItemInstanceFactory;
        private readonly MateSystem _mateSystem;
        private readonly MonsterSystem _monsterSystem;
        private readonly NpcSystem _npcSystem;
        private readonly IPortalFactory _portalFactory;

        private readonly IRandomGenerator _randomGenerator;
        private readonly IMapSystem[] _systems;

        private readonly ITickManager _tickManager;
        private List<Position> _cells;

        private DateTime? _idleRequest;
        private bool _isTickRefreshRequested;

        public MapInstance(Map map, MapInstanceType type, ITickManager tickManager, GameMinMaxConfiguration minMaxConfiguration,
            ISpyOutManager spyOutManager, ISkillsManager skillsManager, IGameLanguageService languageService, IMeditationManager meditationManager, IAsyncEventPipeline asyncEventPipeline,
            IRandomGenerator randomGenerator, IBCardEffectHandlerContainer bCardEffectHandlerContainer, IBuffFactory buffFactory,
            IPortalFactory portalFactory, IGameItemInstanceFactory gameItemInstanceFactory, ISpPartnerConfiguration spPartnerConfiguration, IMonsterTalkingConfig monsterTalkingConfig)
        {
            Id = Guid.NewGuid();

            Grid = map.Grid;
            Width = map.Width;
            Height = map.Height;
            MapId = map.MapId;
            MapVnum = map.MapVnum;
            MapNameId = map.MapNameId;
            Music = map.Music;

            foreach (MapFlags flag in map.Flags)
            {
                _flags.Add(flag);
            }

            IsPvp = HasMapFlag(MapFlags.HAS_PVP_ENABLED) || HasMapFlag(MapFlags.HAS_PVP_FACTION_ENABLED) || HasMapFlag(MapFlags.HAS_PVP_FAMILY_ENABLED);
            ShopAllowed = !HasMapFlag(MapFlags.HAS_USER_SHOPS_DISABLED) && !HasMapFlag(MapFlags.ACT_4) && !IsPvp && type == MapInstanceType.BaseMapInstance;

            MapInstanceType = type;

            IPathFinder pathfinder = new PathFinder(Grid, Width, Height);

            _tickManager = tickManager;
            _randomGenerator = randomGenerator;
            _portalFactory = portalFactory;
            _gameItemInstanceFactory = gameItemInstanceFactory;
            _npcSystem = new NpcSystem(bCardEffectHandlerContainer, this, asyncEventPipeline, randomGenerator, buffFactory, languageService, pathfinder, monsterTalkingConfig);
            _monsterSystem = new MonsterSystem(randomGenerator, bCardEffectHandlerContainer, asyncEventPipeline, this, buffFactory, languageService, pathfinder, monsterTalkingConfig);
            _characterSystem = new CharacterSystem(bCardEffectHandlerContainer, buffFactory, meditationManager, asyncEventPipeline, this, spyOutManager, randomGenerator, skillsManager,
                minMaxConfiguration, languageService);
            _mateSystem = new MateSystem(bCardEffectHandlerContainer, languageService, minMaxConfiguration, this, randomGenerator, buffFactory, languageService, spPartnerConfiguration);
            _battleSystem = new BattleSystem(asyncEventPipeline, this, bCardEffectHandlerContainer, languageService, buffFactory, randomGenerator);
            _dropSystem = new DropSystem(this, randomGenerator);
            _entityIdManager = new GenericEntityIdManager();

            _systems = new IMapSystem[]
            {
                _monsterSystem,
                _characterSystem,
                _npcSystem,
                _mateSystem,
                _dropSystem,
                _battleSystem
            };

            SetIdle();
        }


        public MapInstanceState State { get; private set; }

        public bool IsDance { get; set; }

        public short? MapMusic { get; set; }

        public bool IsPvp { get; set; }

        public byte MapIndexX { get; set; }

        public byte MapIndexY { get; set; }

        public Guid Id { get; }
        public MapInstanceType MapInstanceType { get; }

        public IReadOnlyList<byte> Grid { get; }
        public int Width { get; }
        public int Height { get; }
        public int MapId { get; }
        public int Music { get; }
        public int MapVnum { get; }
        public int MapNameId { get; }

        public bool ShopAllowed { get; }

        public bool AIDisabled { get; set; }

        public List<ITimeSpacePortalEntity> TimeSpacePortals { get; } = new();
        public List<MapDesignObject> MapDesignObjects { get; set; } = new();
        public List<IPortalEntity> Portals { get; } = new();

        public override void RegisterSession(IClientSession session)
        {
            base.RegisterSession(session);
            AddCharacter(session.PlayerEntity);
            foreach (IMateEntity activeMate in session.PlayerEntity.MateComponent.TeamMembers())
            {
                AddMate(activeMate);
            }

            ActivateMap();
        }

        public override void UnregisterSession(IClientSession session)
        {
            base.UnregisterSession(session);
            RemoveCharacter(session.PlayerEntity);
            foreach (IMateEntity activeMate in session.PlayerEntity.MateComponent.TeamMembers())
            {
                RemoveMate(activeMate);
            }

            if (State == MapInstanceState.Running && Sessions.Count == 0)
            {
                RequestIdleState();
            }
        }

        public IReadOnlyList<string> GetEntitiesOnMapPackets(bool onlyItemsAndPortals = false)
        {
            var packetCache = new List<string>();

            foreach (IPortalEntity portal in Portals)
            {
                packetCache.Add(portal.GenerateGp());
            }

            if (!onlyItemsAndPortals)
            {
                bool firstBossSent = false;
                foreach (IMonsterEntity monster in GetAliveMonsters())
                {
                    if (!monster.IsStillAlive)
                    {
                        continue;
                    }

                    packetCache.Add(monster.GenerateIn());
                    if (monster.IsBoss && monster.IsTarget)
                    {
                        packetCache.Add(monster.GenerateRaidBossPacket(firstBossSent));
                        firstBossSent = true;
                    }

                    TryAddScal(packetCache, monster);
                    packetCache.AddRange(monster.GenerateConstBuffEffects());
                }
            }

            foreach (MapItem mapItem in Drops)
            {
                packetCache.Add(mapItem.GenerateIn());
            }

            if (!onlyItemsAndPortals)
            {
                foreach (INpcEntity npc in GetAliveNpcs())
                {
                    if (!npc.IsStillAlive)
                    {
                        continue;
                    }

                    packetCache.Add(npc.GenerateIn());
                    if (npc.ShopNpc != null)
                    {
                        packetCache.Add($"shop 2 {npc.Id} 1 {(byte)npc.ShopNpc.MenuType} {npc.ShopNpc.ShopType} {npc.ShopNpc.Name}");
                    }

                    TryAddScal(packetCache, npc);
                    packetCache.AddRange(npc.GenerateConstBuffEffects());
                }

                foreach (INpcEntity npc in GetPassiveNpcs())
                {
                    if (!npc.IsStillAlive)
                    {
                        continue;
                    }

                    packetCache.Add(npc.GenerateIn());
                    if (npc.ShopNpc != null)
                    {
                        packetCache.Add($"shop 2 {npc.Id} 1 {(byte)npc.ShopNpc.MenuType} {npc.ShopNpc.ShopType} {npc.ShopNpc.Name}");
                    }

                    if (npc.TimeSpaceInfo != null)
                    {
                        packetCache.Add(npc.GenerateEffectGround(EffectType.BlueTimeSpace, npc.PositionX, npc.PositionY, false));
                    }

                    if (npc.RainbowFlag != null)
                    {
                        packetCache.Add(npc.GenerateFlagPacket());
                    }

                    TryAddScal(packetCache, npc);
                    packetCache.AddRange(npc.GenerateConstBuffEffects());
                }
            }

            return packetCache;
        }

        public IBattleEntity GetBattleEntity(VisualType type, long id)
        {
            switch (type)
            {
                case VisualType.Monster:
                    return GetMonsterById(id);
                case VisualType.Player:
                    return GetCharacterById(id);
                case VisualType.Npc:
                    if (id >= 1_000_000)
                    {
                        return GetMateById(id);
                    }

                    return GetNpcById(id);
                default:
                    return null;
            }
        }

        public bool HasMapFlag(MapFlags flags) => _flags.Contains(flags);

        public void LoadPortals(IEnumerable<PortalDTO> tmp = null)
        {
            if (tmp == null)
            {
                return;
            }

            foreach (PortalDTO portalInfo in tmp)
            {
                var sourcePos = new Position(portalInfo.SourceX, portalInfo.SourceY);
                var destPos = new Position(portalInfo.DestinationX, portalInfo.DestinationY);
                IPortalEntity portal = _portalFactory.CreatePortal((PortalType)portalInfo.Type, this, sourcePos, portalInfo.DestinationMapId, destPos, portalInfo.RaidType, portalInfo.MapNameId);
                Portals.Add(portal);
            }
        }

        public void Initialize(DateTime date)
        {
            State = MapInstanceState.Running;
            ProcessTick(date);
            State = MapInstanceState.Idle;
        }

        public void Destroy()
        {
            SetIdle();

            foreach (IClientSession session in Sessions.ToList())
            {
                session.ChangeToLastBaseMap();
            }

            foreach (IMapSystem t in _systems)
            {
                t.Clear();
            }
        }

        public Position GetRandomPosition()
        {
            if (_cells != null)
            {
                return _cells[_randomGenerator.RandomNumber(_cells.Count - 1)];
            }

            _cells = new List<Position>();
            for (short y = 0; y < Height; y++)
            {
                for (short x = 0; x < Width; x++)
                {
                    if (this.CanWalkAround(x, y))
                    {
                        _cells.Add(new Position(x, y));
                    }
                }
            }

            return _cells[_randomGenerator.RandomNumber(_cells.Count - 1)];
        }

        public MapItem PutItem(ushort amount, ref GameItemInstance inv, IClientSession session)
        {
            var possibilities = new List<Position>();

            for (short x = -1; x < 2; x++)
            {
                for (short y = -1; y < 2; y++)
                {
                    possibilities.Add(new Position(x, y));
                }
            }

            short mapX = 0;
            short mapY = 0;
            bool niceSpot = false;
            foreach (Position possibility in possibilities.OrderBy(s => _randomGenerator.RandomNumber()))
            {
                mapX = (short)(session.PlayerEntity.PositionX + possibility.X);
                mapY = (short)(session.PlayerEntity.PositionY + possibility.Y);
                if (this.IsBlockedZone(mapX, mapY))
                {
                    continue;
                }

                niceSpot = true;
                break;
            }

            if (!niceSpot)
            {
                return null;
            }

            if (amount <= 0 || amount > inv.Amount)
            {
                return null;
            }

            GameItemInstance newItemInstance = _gameItemInstanceFactory.DuplicateItem(inv);
            newItemInstance.Amount = amount;
            MapItem droppedItem = new CharacterMapItem(mapX, mapY, newItemInstance, this);
            AddDrop(droppedItem);
            return droppedItem;
        }

        public void DespawnMonster(IMonsterEntity monsterEntity)
        {
            monsterEntity.GenerateDeath();
            Broadcast(monsterEntity.GenerateOut());
        }

        public IReadOnlyList<IPlayerEntity> GetCharactersInRange(Position pos, short distance, Func<IPlayerEntity, bool> predicate) => _characterSystem.GetCharactersInRange(pos, distance, predicate);

        public IReadOnlyList<IPlayerEntity> GetCharactersInRange(Position position, short distance) => GetCharactersInRange(position, distance, null);

        public IReadOnlyList<IPlayerEntity> GetClosestCharactersInRange(Position pos, short distance) => _characterSystem.GetClosestCharactersInRange(pos, distance);

        public IReadOnlyList<IPlayerEntity> GetAliveCharactersInRange(Position pos, short distance, Func<IPlayerEntity, bool> predicate) =>
            _characterSystem.GetAliveCharactersInRange(pos, distance, predicate);

        public IReadOnlyList<IPlayerEntity> GetAliveCharactersInRange(Position position, short distance) => GetAliveCharactersInRange(position, distance, null);

        public IReadOnlyList<IBattleEntity> GetNonMonsterBattleEntities(Func<IBattleEntity, bool> predicate)
        {
            var battleEntitiesInRange = new List<IBattleEntity>();

            battleEntitiesInRange.AddRange(GetAliveCharacters(predicate));
            battleEntitiesInRange.AddRange(GetAliveMates(predicate));
            battleEntitiesInRange.AddRange(GetAliveNpcs(predicate));

            return battleEntitiesInRange;
        }

        public IReadOnlyList<IBattleEntity> GetBattleEntities(Func<IBattleEntity, bool> predicate)
        {
            var battleEntitiesInRange = new List<IBattleEntity>();

            battleEntitiesInRange.AddRange(GetAliveCharacters(predicate));
            battleEntitiesInRange.AddRange(GetAliveMates(predicate));
            battleEntitiesInRange.AddRange(GetAliveNpcs(predicate));
            battleEntitiesInRange.AddRange(GetAliveMonsters(predicate));

            return battleEntitiesInRange;
        }

        public IReadOnlyList<IBattleEntity> GetBattleEntitiesInRange(Position pos, short distance)
        {
            var battleEntitiesInRange = new List<IBattleEntity>();

            battleEntitiesInRange.AddRange(GetAliveCharactersInRange(pos, distance));
            battleEntitiesInRange.AddRange(GetAliveMatesInRange(pos, distance));
            battleEntitiesInRange.AddRange(GetAliveNpcsInRange(pos, distance));
            battleEntitiesInRange.AddRange(GetAliveMonstersInRange(pos, distance));

            return battleEntitiesInRange;
        }

        public IReadOnlyList<IBattleEntity> GetNonMonsterBattleEntitiesInRange(Position pos, short distance) => GetNonMonsterBattleEntitiesInRange(pos, distance, null);

        public IReadOnlyList<IBattleEntity> GetNonMonsterBattleEntitiesInRange(Position pos, short distance, Func<IBattleEntity, bool> predicate)
        {
            var battleEntitiesInRange = new List<IBattleEntity>();

            battleEntitiesInRange.AddRange(GetAliveCharactersInRange(pos, distance, predicate));
            battleEntitiesInRange.AddRange(GetAliveMatesInRange(pos, distance, predicate));
            battleEntitiesInRange.AddRange(GetAliveNpcsInRange(pos, distance, predicate));

            return battleEntitiesInRange;
        }


        public IReadOnlyList<IBattleEntity> GetNonMonsterBattleEntities()
        {
            var battleEntitiesInRange = new List<IBattleEntity>();

            battleEntitiesInRange.AddRange(GetAliveCharacters());
            battleEntitiesInRange.AddRange(GetAliveMates());
            battleEntitiesInRange.AddRange(GetAliveNpcs());

            return battleEntitiesInRange;
        }


        public IReadOnlyList<IMonsterEntity> GetDeadMonsters() => _monsterSystem.GetDeadMonsters();

        public IReadOnlyList<IMonsterEntity> GetAliveMonsters(Func<IMonsterEntity, bool> predicate) => _monsterSystem.GetAliveMonsters(predicate);
        public IReadOnlyList<IMonsterEntity> GetAliveMonstersInRange(Position pos, short distance) => _monsterSystem.GetAliveMonstersInRange(pos, distance);
        public IReadOnlyList<IMonsterEntity> GetClosestMonstersInRange(Position pos, short distance) => _monsterSystem.GetClosestMonstersInRange(pos, distance);

        public IReadOnlyList<IBattleEntity> GetClosestBattleEntitiesInRange(Position pos, short distance)
        {
            var battleEntitiesInRange = new List<IBattleEntity>();

            battleEntitiesInRange.AddRange(GetClosestCharactersInRange(pos, distance));
            battleEntitiesInRange.AddRange(GetClosestMatesInRange(pos, distance));
            battleEntitiesInRange.AddRange(GetClosestNpcsInRange(pos, distance));
            battleEntitiesInRange.AddRange(GetClosestMonstersInRange(pos, distance));

            return battleEntitiesInRange;
        }

        public string Name => $"{nameof(MapInstance)}: {MapInstanceType.ToString()} - {MapId.ToString()}";

        public void ProcessTick(DateTime date)
        {
            try
            {
                if (State == MapInstanceState.Idle)
                {
                    return;
                }

                foreach (IMapSystem t in _systems)
                {
                    t.ProcessTick(date, _isTickRefreshRequested);
                    //_systemTicks.WithLabels(t.Name, Name).Observe(processingTime.ElapsedMilliseconds);
                }

                FlushPackets();

                if (_isTickRefreshRequested)
                {
                    _isTickRefreshRequested = false;
                }

                // idle state
                if (Sessions.Count > 0)
                {
                    //Log.Warn($"[TICK_MAP_INSTANCE] {Map.MapId} SessionCount > 0");
                    return;
                }

                _idleRequest ??= date;
                if (_idleRequest.HasValue && _idleRequest.Value + _timeToIdleState < date)
                {
                    SetIdle();
                }
            }
            catch (Exception e)
            {
                Log.Error($"TICK - MapId: {MapId.ToString()}", e);
            }
        }

        public IMonsterEntity GetMonsterById(long id) => _monsterSystem.GetMonsterById(id);
        public IMonsterEntity GetMonsterByUniqueId(Guid id) => _monsterSystem.GetMonsterByUniqueId(id);

        public IReadOnlyList<IMonsterEntity> GetAliveMonsters() => _monsterSystem.GetAliveMonsters();

        public void AddMonster(IMonsterEntity entity)
        {
            _monsterSystem.AddMonster(entity);
        }

        public void RemoveMonster(IMonsterEntity entity)
        {
            _monsterSystem.RemoveMonster(entity);
        }

        public void AddEntityToTargets(IMonsterEntity monsterEntity, IBattleEntity target)
        {
            _monsterSystem.AddEntityToTargets(monsterEntity, target);
        }

        public void MonsterRefreshTarget(IMonsterEntity target, IBattleEntity caster, in DateTime time, bool isByAttacking = false)
        {
            _monsterSystem.MonsterRefreshTarget(target, caster, time, isByAttacking);
        }

        public void ForgetAll(IMonsterEntity monsterEntity, in DateTime time, bool clearDamagers = true)
        {
            _monsterSystem.ForgetAll(monsterEntity, time, clearDamagers);
        }

        public void RemoveTarget(IMonsterEntity monsterEntity, IBattleEntity entity, bool checkIfPlayer = false)
        {
            _monsterSystem.RemoveTarget(monsterEntity, entity, checkIfPlayer);
        }

        public void ActivateMode(IMonsterEntity monsterEntity)
        {
            _monsterSystem.ActivateMode(monsterEntity);
        }

        public void DeactivateMode(IMonsterEntity monsterEntity)
        {
            _monsterSystem.DeactivateMode(monsterEntity);
        }

        public void IncreaseMonsterDeathsOnMap()
        {
            _monsterSystem.IncreaseMonsterDeathsOnMap();
        }

        public long MonsterDeathsOnMap() => _monsterSystem.MonsterDeathsOnMap();
        public byte CurrentVessels() => _monsterSystem.CurrentVessels();
        public bool IsSummonLimitReached(int? summonerId, SummonType? summonSummonType) => _monsterSystem.IsSummonLimitReached(summonerId, summonSummonType);

        public IReadOnlyList<INpcEntity> GetAliveNpcs() => _npcSystem.GetAliveNpcs();
        public IReadOnlyList<INpcEntity> GetAliveNpcs(Func<INpcEntity, bool> predicate) => _npcSystem.GetAliveNpcs(predicate);

        public IReadOnlyList<INpcEntity> GetPassiveNpcs() => _npcSystem.GetPassiveNpcs();

        public IReadOnlyList<INpcEntity> GetAliveNpcsInRange(Position pos, short distance) => _npcSystem.GetAliveNpcsInRange(pos, distance);
        public IReadOnlyList<INpcEntity> GetClosestNpcsInRange(Position pos, short distance) => _npcSystem.GetClosestNpcsInRange(pos, distance);

        public void NpcRefreshTarget(INpcEntity npcEntity, IBattleEntity target)
        {
            _npcSystem.NpcRefreshTarget(npcEntity, target);
        }

        public IReadOnlyList<INpcEntity> GetAliveNpcsInRange(Position pos, short distance, Func<INpcEntity, bool> predicate) => _npcSystem.GetAliveNpcsInRange(pos, distance, predicate);


        public INpcEntity GetNpcById(long id) => _npcSystem.GetNpcById(id);

        public void AddNpc(INpcEntity entity)
        {
            _npcSystem.AddNpc(entity);
        }

        public void RemoveNpc(INpcEntity entity)
        {
            _npcSystem.RemoveNpc(entity);
        }

        public IPlayerEntity GetCharacterById(long id) => _characterSystem.GetCharacterById(id);

        public IReadOnlyList<IPlayerEntity> GetCharacters() => _characterSystem.GetCharacters();

        public IReadOnlyList<IPlayerEntity> GetCharacters(Func<IPlayerEntity, bool> predicate) => _characterSystem.GetCharacters(predicate);

        public IReadOnlyList<IPlayerEntity> GetAliveCharacters() => _characterSystem.GetAliveCharacters();

        public IReadOnlyList<IPlayerEntity> GetAliveCharacters(Func<IPlayerEntity, bool> predicate) => _characterSystem.GetAliveCharacters(predicate);

        public void AddCharacter(IPlayerEntity character)
        {
            _characterSystem.AddCharacter(character);
        }

        public void RemoveCharacter(IPlayerEntity entity)
        {
            _characterSystem.RemoveCharacter(entity);
        }

        public IReadOnlyList<IMateEntity> GetAliveMates() => _mateSystem.GetAliveMates();
        public IReadOnlyList<IMateEntity> GetAliveMates(Func<IMateEntity, bool> predicate) => _mateSystem.GetAliveMates(predicate);
        public IReadOnlyList<IMateEntity> GetAliveMatesInRange(Position position, short range) => _mateSystem.GetAliveMatesInRange(position, range);
        public IReadOnlyList<IMateEntity> GetClosestMatesInRange(Position position, short range) => _mateSystem.GetClosestMatesInRange(position, range);

        public IReadOnlyList<IMateEntity> GetAliveMatesInRange(Position position, short range, Func<IBattleEntity, bool> predicate) => _mateSystem.GetAliveMatesInRange(position, range, predicate);
        public IMateEntity GetMateById(long mateId) => _mateSystem.GetMateById(mateId);

        public void AddMate(IMateEntity entity)
        {
            _mateSystem.AddMate(entity);
        }

        public void RemoveMate(IMateEntity entity)
        {
            _mateSystem.RemoveMate(entity);
        }

        public IReadOnlyList<MapItem> Drops => _dropSystem.Drops;

        public void AddDrop(MapItem item)
        {
            _dropSystem.AddDrop(item);
        }

        public bool RemoveDrop(long dropId) => _dropSystem.RemoveDrop(dropId);

        public bool HasDrop(long dropId) => _dropSystem.HasDrop(dropId);

        public MapItem GetDrop(long dropId) => _dropSystem.GetDrop(dropId);

        public void AddCastHitRequest(HitProcessable hitProcessable)
        {
            _battleSystem.AddCastHitRequest(hitProcessable);
        }

        public void AddCastBuffRequest(BuffProcessable buffProcessable)
        {
            _battleSystem.AddCastBuffRequest(buffProcessable);
        }

        public void AddHitRequest(HitRequest hitRequest)
        {
            _battleSystem.AddHitRequest(hitRequest);
        }

        public void AddBuffRequest(BuffRequest buffRequest)
        {
            _battleSystem.AddBuffRequest(buffRequest);
        }

        public int GenerateEntityId() => _entityIdManager.GenerateEntityId();

        private static void TryAddScal(ICollection<string> list, IBattleEntity battleEntity)
        {
            if (!battleEntity.ShouldSendScal())
            {
                return;
            }

            list.Add(battleEntity.GenerateScal());
        }

        public void SetInactive()
        {
            SetIdle();
        }

        private void RequestIdleState()
        {
            _idleRequest = DateTime.UtcNow;
            // Log.Warn($"[TICK_IDLE] Requested by: {Map.MapId.ToString()}");
        }

        private void ActivateMap()
        {
            if (State == MapInstanceState.Running)
            {
                _idleRequest = null;
                return;
            }

            // Log.Warn($"[TICK_ACTIVATE] Activation of map: {Map.MapId.ToString()}");
            State = MapInstanceState.Running;
            _isTickRefreshRequested = true;
            _tickManager.AddProcessable(this);
        }

        private void SetIdle()
        {
            if (State == MapInstanceState.Idle)
            {
                return;
            }

            // Log.Warn($"[TICK_IDLE] {Map.MapId.ToString()} is now in idle mode");
            State = MapInstanceState.Idle;
            _idleRequest = null;
            _tickManager.RemoveProcessable(this);
            foreach (IMapSystem t in _systems)
            {
                t.PutIdleState();
            }
        }
    }
}