using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.GameObjects.Components.Clothing;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.GameObjects.EntitySystems.EffectBlocker;
using Content.Shared.Preferences.Appearance;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines;
using static Content.Shared.GameObjects.Components.Inventory.SharedInventoryComponent.ClientInventoryMessage;

namespace Content.Client.GameObjects.Components.HUD.Inventory
{
    /// <summary>
    /// A character UI which shows items the user has equipped within his inventory
    /// </summary>
    [RegisterComponent]
    [ComponentReference(typeof(SharedInventoryComponent))]
    public class ClientInventoryComponent : SharedInventoryComponent, IEffectBlocker
    {
        private readonly Dictionary<Slots, IEntity> _slots = new();

        public IReadOnlyDictionary<Slots, IEntity> AllSlots => _slots;

        [ViewVariables] public InventoryInterfaceController InterfaceController { get; private set; } = default!;

        [ComponentDependency]
        private ISpriteComponent? _sprite;

        private bool _playerAttached = false;

        [ViewVariables]
        [DataField("speciesId")] public string? SpeciesId { get; set; }

        public override void OnRemove()
        {
            base.OnRemove();

            if (_playerAttached)
            {
                InterfaceController?.PlayerDetached();
            }
            InterfaceController?.Dispose();
        }

        public override void Initialize()
        {
            base.Initialize();

            var controllerType = ReflectionManager.LooseGetType(InventoryInstance.InterfaceControllerTypeName);
            var args = new object[] {this};
            InterfaceController = DynamicTypeFactory.CreateInstance<InventoryInterfaceController>(controllerType, args);
            InterfaceController.Initialize();

            if (_sprite != null)
            {
                foreach (var mask in InventoryInstance.SlotMasks.OrderBy(s => InventoryInstance.SlotDrawingOrder(s)))
                {
                    if (mask == Slots.NONE)
                    {
                        continue;
                    }

                    _sprite.LayerMapReserveBlank(mask);
                }
            }

            // Component state already came in but we couldn't set anything visually because, well, we didn't initialize yet.
            foreach (var (slot, entity) in _slots)
            {
                _setSlot(slot, entity);
            }
        }

        public override bool IsEquipped(IEntity item)
        {
            return item != null && _slots.Values.Any(e => e == item);
        }

        public override float WalkSpeedModifier
        {
            get
            {
                var mod = 1f;
                foreach (var slot in _slots.Values)
                {
                    if (slot != null)
                    {
                        foreach (var modifier in slot.GetAllComponents<IMoveSpeedModifier>())
                        {
                            mod *= modifier.WalkSpeedModifier;
                        }
                    }
                }

                return mod;
            }
        }

        public override float SprintSpeedModifier
        {
            get
            {
                var mod = 1f;
                foreach (var slot in _slots.Values)
                {
                    if (slot != null)
                    {
                        foreach (var modifier in slot.GetAllComponents<IMoveSpeedModifier>())
                        {
                            mod *= modifier.SprintSpeedModifier;
                        }
                    }
                }

                return mod;
            }
        }

        public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
        {
            base.HandleComponentState(curState, nextState);

            if (curState is not InventoryComponentState state)
                return;

            var doneSlots = new HashSet<Slots>();

            foreach (var (slot, entityUid) in state.Entities)
            {
                if (!Owner.EntityManager.TryGetEntity(entityUid, out var entity))
                {
                    continue;
                }
                if (!_slots.ContainsKey(slot) || _slots[slot] != entity)
                {
                    _slots[slot] = entity;
                    _setSlot(slot, entity);
                }
                doneSlots.Add(slot);
            }

            if (state.HoverEntity != null)
            {
                var (slot, (entityUid, fits)) = state.HoverEntity.Value;
                var entity = Owner.EntityManager.GetEntity(entityUid);

                InterfaceController?.HoverInSlot(slot, entity, fits);
            }

            foreach (var slot in _slots.Keys.ToList())
            {
                if (!doneSlots.Contains(slot))
                {
                    _clearSlot(slot);
                    _slots.Remove(slot);
                }
            }

            if (Owner.TryGetComponent(out MovementSpeedModifierComponent? mod))
            {
                mod.RefreshMovementSpeedModifiers();
            }
        }

        private void _setSlot(Slots slot, IEntity entity)
        {
            SetSlotVisuals(slot, entity);

            InterfaceController?.AddToSlot(slot, entity);
        }

        internal void SetSlotVisuals(Slots slot, IEntity entity)
        {
            if (_sprite == null)
            {
                return;
            }

            if (entity.TryGetComponent(out ClothingComponent? clothing))
            {
                var flag = SlotMasks[slot];
                var data = clothing.GetEquippedStateInfo(flag, SpeciesId);
                if (data != null)
                {
                    var (rsi, state) = data.Value;
                    _sprite.LayerSetVisible(slot, true);
                    _sprite.LayerSetState(slot, state, rsi);
                    _sprite.LayerSetAutoAnimated(slot, true);

                    if (slot == Slots.INNERCLOTHING && _sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out _))
                    {
                        _sprite.LayerSetState(HumanoidVisualLayers.StencilMask, clothing.FemaleMask switch
                        {
                            FemaleClothingMask.NoMask => "female_none",
                            FemaleClothingMask.UniformTop => "female_top",
                            _ => "female_full",
                        });
                    }

                    return;
                }
            }

            _sprite.LayerSetVisible(slot, false);
        }

        internal void ClearAllSlotVisuals()
        {
            if (_sprite == null)
                return;

            foreach (var slot in InventoryInstance.SlotMasks)
            {
                if (slot != Slots.NONE)
                {
                    _sprite.LayerSetVisible(slot, false);
                }
            }
        }

        private void _clearSlot(Slots slot)
        {
            InterfaceController?.RemoveFromSlot(slot);
            _sprite?.LayerSetVisible(slot, false);
        }

        public void SendEquipMessage(Slots slot)
        {
            var equipMessage = new ClientInventoryMessage(slot, ClientInventoryUpdate.Equip);
            SendNetworkMessage(equipMessage);
        }

        public void SendUseMessage(Slots slot)
        {
            var equipmessage = new ClientInventoryMessage(slot, ClientInventoryUpdate.Use);
            SendNetworkMessage(equipmessage);
        }

        public void SendHoverMessage(Slots slot)
        {
            SendNetworkMessage(new ClientInventoryMessage(slot, ClientInventoryUpdate.Hover));
        }

        public void SendOpenStorageUIMessage(Slots slot)
        {
            SendNetworkMessage(new OpenSlotStorageUIMessage(slot));
        }

        public override void HandleMessage(ComponentMessage message, IComponent? component)
        {
            base.HandleMessage(message, component);

            switch (message)
            {
                case PlayerAttachedMsg _:
                    InterfaceController.PlayerAttached();
                    _playerAttached = true;
                    break;

                case PlayerDetachedMsg _:
                    InterfaceController.PlayerDetached();
                    _playerAttached = false;
                    break;
            }
        }

        public bool TryGetSlot(Slots slot, [NotNullWhen(true)] out IEntity? item)
        {
            return _slots.TryGetValue(slot, out item);
        }

        public bool TryFindItemSlots(IEntity item, [NotNullWhen(true)] out Slots? slots)
        {
            slots = null;

            foreach (var (slot, entity) in _slots)
            {
                if (entity == item)
                {
                    slots = slot;
                    return true;
                }
            }

            return false;
        }

        bool IEffectBlocker.CanSlip()
        {
            return !TryGetSlot(Slots.SHOES, out var shoes) || shoes == null || EffectBlockerSystem.CanSlip(shoes);
        }
    }
}
