using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("IndustrialRecycler", "Marte6", "1.5.4")]
    [Description("Automate recycling by attaching storage and adapters to recyclers.")]
    class IndustrialRecycler : RustPlugin
    {
        #region PluginReferences
        [PluginReference]
        Plugin Friends;
        #endregion

        #region Fields
        private static IndustrialRecycler _instance;
        private Configuration _config;
        private const string RecyclerPrefabPath = "assets/bundled/prefabs/static/recycler_static.prefab";
        private const string InputStoragePrefabPath = "assets/content/vehicles/boats/rowboat/subents/rowboat_storage.prefab";
        private const string OutputStoragePrefabPath = "assets/prefabs/deployable/hot air balloon/subents/hab_storage.prefab";
        private const string AdapterPrefabPath = "assets/prefabs/deployable/playerioents/industrialadaptors/storageadaptor.deployed.prefab";
        private const string ItemName = "box.wooden.large";
        private const string PermissionUseRecycler = "industrialrecycler.give";
        private const string PermissionVip = "industrialrecycler.vip";
        private const string PermissionAdmin = "industrialrecycler.admin";
        private const string PermissionToggle = "industrialrecycler.toggle";
        private const string UiOverlayName = "IndustrialRecyclerMainPanel";
        #endregion

        #region Cache
        private readonly Dictionary<ulong, ulong> _recyclerOwners = new();
        private readonly HashSet<ulong> _recyclerItemContainers = new();
        private readonly HashSet<ulong> _inputItemContainers = new();
        private readonly HashSet<ulong> _storageAdapters = new();
        private readonly HashSet<ulong> _playersWithUi = new();
        #endregion

        #region Data
        private bool IsRecyclerIndustrial(Recycler recycler)
        {
            return recycler.GetComponentsInChildren<StorageContainer>().Any(sc => sc.PrefabName == InputStoragePrefabPath || sc.PrefabName == OutputStoragePrefabPath);
        }
        #endregion

        #region Game Hooks
        private void Init()
        {
            permission.RegisterPermission(PermissionUseRecycler, this);
            permission.RegisterPermission(PermissionVip, this);
            permission.RegisterPermission(PermissionAdmin, this);
            permission.RegisterPermission(PermissionToggle, this);

            CustomizeCommands();
        }

        private void CustomizeCommands()
        {
            foreach (var command in _config.RecyclerCommands)
            {
                cmd.AddChatCommand(command, this, nameof(CommandGetRecycler));
            }

            foreach (var command in _config.StandardRecyclerCommands)
            {
                cmd.AddChatCommand(command, this, nameof(CommandGetStandardRecycler));
            }

            cmd.AddChatCommand("upgraderecycler", this, nameof(CommandUpgradeRecycler));
            cmd.AddChatCommand("togglerecycler", this, nameof(ToggleRecyclerConfig));
            cmd.AddConsoleCommand("industrialrecycler.openinput", this, nameof(OpenInputStorage));
            cmd.AddConsoleCommand("industrialrecycler.openoutput", this, nameof(OpenOutputStorage));

            cmd.AddConsoleCommand("giveindustrialrecycler", this, nameof(ConsoleCommandGiveRecycler));
            cmd.AddConsoleCommand("givestandardrecycler", this, nameof(ConsoleCommandGiveStandardRecycler));
        }

        private void OnServerInitialized()
        {
            _instance = this;

            foreach (var recycler in UnityEngine.Object.FindObjectsOfType<Recycler>())
            {
                if (recycler.OwnerID != 0UL && IsRecyclerIndustrial(recycler))
                {
                    recycler.gameObject.AddComponent<RecyclerComponent>();
                }
            }
        }

        private void Unload()
        {
            foreach (var recyclerComponent in UnityEngine.Object.FindObjectsOfType<RecyclerComponent>())
            {
                UnityEngine.Object.Destroy(recyclerComponent);
            }

            foreach (var player in BasePlayer.activePlayerList)
            {
                CuiHelper.DestroyUi(player, UiOverlayName);
            }
        }

        private void OnItemAddedToContainer(ItemContainer container, Item item)
        {
            Recycler recycler = container.entityOwner as Recycler ?? container.entityOwner?.GetParentEntity() as Recycler;
            if (recycler == null)
                return;

            var recyclerComponent = recycler.GetComponent<RecyclerComponent>();
            if (recyclerComponent == null)
                return;

            bool isMainRecycler = container.entityOwner is Recycler;
            ulong containerUid = container.uid.Value;

            if (isMainRecycler && _recyclerItemContainers.Contains(containerUid))
            {
                ProcessRecyclerItems(recyclerComponent, item);
            }
            else if (!isMainRecycler && _inputItemContainers.Contains(containerUid))
            {
                recyclerComponent.HandleItemTransfers();
            }
        }

        private void OnEntityBuilt(Planner planner, GameObject entityObject)
        {
            var player = planner?.GetOwnerPlayer();
            var baseEntity = entityObject.GetComponent<BaseEntity>();

            if (player == null || baseEntity == null || baseEntity.ShortPrefabName != ItemName)
                return;

            if (baseEntity.skinID != _config.RecyclerSkinId && baseEntity.skinID != _config.StandardRecyclerSkinId)
                return;

            int maxRecyclers = _config.MaxRecyclersPerPlayer;

            if (permission.UserHasPermission(player.UserIDString, PermissionAdmin))
            {
                maxRecyclers = int.MaxValue;
            }
            else if (permission.UserHasPermission(player.UserIDString, PermissionVip))
            {
                maxRecyclers = _config.MaxRecyclersVip;
            }

            if (_recyclerOwners.Count(entry => entry.Value == player.userID) >= maxRecyclers)
            {
                player.ChatMessage(lang.GetMessage("RecyclerLimitReached", this, player.UserIDString));
                timer.Once(1, () => GiveRecyclerItem(player, baseEntity.skinID == _config.StandardRecyclerSkinId));
                NextTick(() => baseEntity?.Kill());
                return;
            }

            if (!InitializeRecyclerPlacement(player, baseEntity))
            {
                player.ChatMessage(lang.GetMessage("InvalidPlacement", this, player.UserIDString));
                timer.Once(1, () => GiveRecyclerItem(player, baseEntity.skinID == _config.StandardRecyclerSkinId));
            }
            else
            {
                player.ChatMessage(lang.GetMessage("RecyclerPlaced", this, player.UserIDString));
            }

            NextTick(() => baseEntity?.Kill());
        }

        private object CanPickupEntity(BasePlayer player, IndustrialStorageAdaptor adapter)
        {
            if (adapter?.net?.ID.Value != null && _storageAdapters.Contains(adapter.net.ID.Value))
            {
                if (!HasAccess(player, adapter))
                {
                    player.ChatMessage(lang.GetMessage("NoAccess", this, player.UserIDString));
                    return false;
                }

                adapter.GetParentEntity()?.GetParentEntity()?.Kill();
                GiveRecyclerItem(player, false);
                player.ChatMessage(lang.GetMessage("RecyclerPickedUp", this, player.UserIDString));
                return false;
            }

            return null;
        }

        private object OnEntityKill(Recycler recycler)
        {
            if (!IsRecyclerOwned(recycler))
                return null;

            UnregisterRecyclerOwnership(recycler);

            DropRecyclerItems(recycler);

            return null;
        }

        private bool IsRecyclerOwned(Recycler recycler)
        {
            return recycler.OwnerID != 0UL && _recyclerOwners.ContainsKey(recycler.net.ID.Value);
        }

        private void UnregisterRecyclerOwnership(Recycler recycler)
        {
            _recyclerOwners.Remove(recycler.net.ID.Value);
        }

        private object CanLootEntity(BasePlayer player, Recycler recycler)
        {
            if (recycler.OwnerID != 0UL && _recyclerOwners.ContainsKey(recycler.net.ID.Value) && !HasAccess(player, recycler))
            {
                player.ChatMessage(lang.GetMessage("NoAccess", this, player.UserIDString));
                return false;
            }
            return null;
        }

        private object CanLootEntity(BasePlayer player, StorageContainer storage)
        {
            Recycler recycler = storage.GetParentEntity() as Recycler;
            if (recycler != null && recycler.OwnerID != 0UL && _recyclerOwners.ContainsKey(recycler.net.ID.Value) && !HasAccess(player, recycler))
            {
                player.ChatMessage(lang.GetMessage("NoAccess", this, player.UserIDString));
                return false;
            }
            return null;
        }

        private void OnLootEntity(BasePlayer player, Recycler recycler)
        {
            if (recycler.OwnerID != 0UL && _recyclerOwners.ContainsKey(recycler.net.ID.Value) && IsRecyclerIndustrial(recycler))
            {
                _playersWithUi.Add(player.userID);

                string recyclerId = recycler.net.ID.Value.ToString();
                CreateUi(player, recyclerId);
            }
        }

        private void OnPlayerLootEnd(PlayerLoot playerLoot)
        {
            BasePlayer player = playerLoot.GetComponentInParent<BasePlayer>();
            if (player == null)
                return;

            if (_playersWithUi.Contains(player.userID))
            {
                _playersWithUi.Remove(player.userID);
                CuiHelper.DestroyUi(player, UiOverlayName);
            }
        }

        object OnHammerHit(BasePlayer player, HitInfo hitInfo)
        {
            Recycler recycler = hitInfo?.HitEntity as Recycler;
            if (recycler == null || recycler.OwnerID == 0)
                return null;

            if (!HasAccess(player, recycler))
            {
                player.ChatMessage(lang.GetMessage("NoAccess", this, player.UserIDString));
                return null;
            }

            bool isIndustrial = IsRecyclerIndustrial(recycler);

            DropRecyclerItems(recycler);

            recycler.Kill();

            GiveRecyclerItem(player, !isIndustrial);

            player.ChatMessage(lang.GetMessage("RecyclerPickedUp", this, player.UserIDString));
            return null;
        }

        #endregion

        #region Access Control
        private bool HasAccess(BasePlayer player, BaseEntity entity)
        {
            ulong ownerId = entity.OwnerID;
            if (ownerId == player.userID || permission.UserHasPermission(player.UserIDString, PermissionAdmin))
                return true;

            if (_config.AllowTeamAccess && player.currentTeam != 0)
            {
                var team = RelationshipManager.ServerInstance.FindTeam(player.currentTeam);
                if (team != null && team.members.Contains(ownerId))
                    return true;
            }

            if (_config.AllowFriendsAccess && Friends != null)
            {
                bool areFriends = (bool)(Friends.CallHook("AreFriends", ownerId, player.userID) ?? false);
                if (areFriends)
                    return true;
            }

            return false;
        }
        #endregion

        #region UI
        private void CreateUi(BasePlayer player, string recyclerId)
        {
            var container = new CuiElementContainer();
            container.Add(
                new CuiPanel
                {
                    CursorEnabled = true,
                    Image =
                    {
                        Color = "1 1 1 0.15",
                        Sprite = "assets/content/ui/ui.background.transparent.radial.psd",
                        Material = "assets/content/ui/uibackgroundblur.mat",
                    },
                    RectTransform =
                    {
                        AnchorMin = _config.AnchorMin,
                        AnchorMax = _config.AnchorMax,
                        OffsetMin = "341.64 405.595",
                        OffsetMax = "554.76 505.605",
                    },
                },
                "Overlay",
                UiOverlayName
            );

            container.Add(
                new CuiElement
                {
                    Name = "Text",
                    Parent = UiOverlayName,
                    Components =
                    {
                        new CuiTextComponent
                        {
                            Text = "INDUSTRIAL RECYCLER",
                            Font = "robotocondensed-bold.ttf",
                            FontSize = 17,
                            Align = TextAnchor.MiddleCenter,
                            Color = "1 1 1 1",
                        },
                        new CuiOutlineComponent { Color = "0 0 0 1", Distance = "1 1" },
                        new CuiRectTransformComponent
                        {
                            AnchorMin = "0.5 0.5",
                            AnchorMax = "0.5 0.5",
                            OffsetMin = "-86.493 0",
                            OffsetMax = "86.492 34.875",
                        },
                    },
                }
            );

            container.Add(
                new CuiButton
                {
                    Button =
                    {
                        Color = "0.3960785 0.4666667 0.2627451 1",
                        Command = $"industrialrecycler.openinput {recyclerId}",
                        Close = UiOverlayName,
                    },
                    Text =
                    {
                        Text = lang.GetMessage("InputButton", this, player.UserIDString),
                        Font = "robotocondensed-regular.ttf",
                        FontSize = 18,
                        Align = TextAnchor.MiddleCenter,
                        Color = "1 1 1 1",
                    },
                    RectTransform =
                    {
                        AnchorMin = "0.5 0.5",
                        AnchorMax = "0.5 0.5",
                        OffsetMin = "-104.206 -49.58",
                        OffsetMax = "-2.394 -17.22",
                    },
                },
                UiOverlayName,
                "Input"
            );

            container.Add(
                new CuiButton
                {
                    Button =
                    {
                        Color = "0.3960785 0.4666667 0.2627451 1",
                        Command = $"industrialrecycler.openoutput {recyclerId}",
                        Close = UiOverlayName,
                    },
                    Text =
                    {
                        Text = lang.GetMessage("OutputButton", this, player.UserIDString),
                        Font = "robotocondensed-regular.ttf",
                        FontSize = 18,
                        Align = TextAnchor.MiddleCenter,
                        Color = "1 1 1 1",
                    },
                    RectTransform =
                    {
                        AnchorMin = "0.5 0.5",
                        AnchorMax = "0.5 0.5",
                        OffsetMin = "1.994 -49.58",
                        OffsetMax = "103.806 -17.22",
                    },
                },
                UiOverlayName,
                "Output"
            );

            if (permission.UserHasPermission(player.UserIDString, PermissionToggle))
            {
                container.Add(
                    new CuiButton
                    {
                        Button =
                        {
                            Color = "0.3960785 0.7666667 0.2627451 1",
                            Command = $"chat.say /togglerecycler",
                            Close = "ToggleConfig",
                        },
                        Text =
                        {
                            Text = lang.GetMessage("ToggleConfig", this),
                            Font = "robotocondensed-regular.ttf",
                            FontSize = 14,
                            Align = TextAnchor.MiddleCenter,
                            Color = "1 1 1 1",
                        },
                        RectTransform =
                        {
                            AnchorMin = "1 1",
                            AnchorMax = "1 1",
                            OffsetMin = "-110 -20",
                            OffsetMax = "0 0",
                        },
                    },
                    UiOverlayName,
                    "ToggleConfig"
                );
            }

            CuiHelper.DestroyUi(player, UiOverlayName);
            CuiHelper.AddUi(player, container);
        }

        private void ConsoleCommandGiveRecycler(ConsoleSystem.Arg arg)
        {
            if (arg.Args.Length == 0)
            {
                return;
            }

            if (!ulong.TryParse(arg.Args[0], out ulong userId))
            {
                return;
            }

            var targetPlayer = BasePlayer.FindByID(userId);

            if (targetPlayer == null)
            {
                Puts("Player Not Found");
                return;
            }

            GiveRecyclerItem(targetPlayer, false);
        }

        private void ConsoleCommandGiveStandardRecycler(ConsoleSystem.Arg arg)
        {
            if (arg.Args.Length == 0)
            {
                return;
            }

            if (!ulong.TryParse(arg.Args[0], out ulong userId))
            {
                return;
            }

            var targetPlayer = BasePlayer.FindByID(userId);

            if (targetPlayer == null)
            {
                Puts("Player Not Found");
                return;
            }

            GiveRecyclerItem(targetPlayer, true);
        }

        private void OpenInputStorage(ConsoleSystem.Arg arg)
        {
            OpenStorage(arg, true);
        }

        private void OpenOutputStorage(ConsoleSystem.Arg arg)
        {
            OpenStorage(arg, false);
        }

        private void OpenStorage(ConsoleSystem.Arg arg, bool isInput)
        {
            var player = arg.Player();
            if (player == null)
            {
                return;
            }

            if (arg.Args.Length == 0)
            {
                return;
            }

            if (!uint.TryParse(arg.Args[0], out uint recyclerId))
            {
                return;
            }

            var networkableId = new NetworkableId(recyclerId);
            var recycler = BaseNetworkable.serverEntities.Find(networkableId) as Recycler;

            if (recycler != null && HasAccess(player, recycler))
            {
                var recyclerComponent = recycler.GetComponent<RecyclerComponent>();
                if (recyclerComponent != null)
                {
                    var storage = isInput ? recyclerComponent.InputStorage : recyclerComponent.OutputStorage;
                    player.inventory.loot.StartLootingEntity(storage, true);
                    player.inventory.loot.AddContainer(storage.inventory);
                    player.inventory.loot.SendImmediate();
                    player.ClientRPCPlayer(null, player, "RPC_OpenLootPanel", storage.panelName);
                }
            }
        }
        #endregion

        #region Main
        private void CommandGetRecycler(BasePlayer player, string command, string[] args)
        {
            if (player == null)
                return;

            if (!permission.UserHasPermission(player.UserIDString, PermissionUseRecycler))
            {
                player.ChatMessage(lang.GetMessage("NoPermission", this, player.UserIDString));
                return;
            }

            BasePlayer targetPlayer = player;

            if (args != null && args.Length > 0)
            {
                targetPlayer = BasePlayer.Find(args[0]);

                if (targetPlayer == null)
                {
                    player.ChatMessage(lang.GetMessage("PlayerNotFound", this, player.UserIDString));
                    return;
                }
            }

            if (GiveRecyclerItem(targetPlayer, false))
            {
                player.ChatMessage(lang.GetMessage("RecyclerGiven", this, player.UserIDString));
                if (targetPlayer != player)
                {
                    targetPlayer.ChatMessage(lang.GetMessage("RecyclerReceived", this, targetPlayer.UserIDString));
                }
            }
            else
            {
                player.ChatMessage(lang.GetMessage("RecyclerNotGiven", this, player.UserIDString));
                if (targetPlayer != player)
                {
                    targetPlayer.ChatMessage(lang.GetMessage("RecyclerNotReceived", this, targetPlayer.UserIDString));
                }
            }
        }

        private bool GiveRecyclerItem(BasePlayer player, bool isStandard)
        {
            ulong skinId = isStandard ? _config.StandardRecyclerSkinId : _config.RecyclerSkinId;
            var recyclerItem = ItemManager.CreateByName(ItemName, 1, skinId);
            if (recyclerItem == null)
                return false;

            recyclerItem.name = isStandard ? "Standard Recycler" : "Industrial Recycler";
            return player.inventory.GiveItem(recyclerItem);
        }

        private void CommandGetStandardRecycler(BasePlayer player, string command, string[] args)
        {
            if (player == null)
                return;

            if (!permission.UserHasPermission(player.UserIDString, PermissionUseRecycler))
            {
                player.ChatMessage(lang.GetMessage("NoPermission", this, player.UserIDString));
                return;
            }

            BasePlayer targetPlayer = player;

            if (args != null && args.Length > 0)
            {
                targetPlayer = BasePlayer.Find(args[0]);

                if (targetPlayer == null)
                {
                    player.ChatMessage(lang.GetMessage("PlayerNotFound", this, player.UserIDString));
                    return;
                }
            }

            if (GiveRecyclerItem(targetPlayer, true))
            {
                player.ChatMessage(lang.GetMessage("RecyclerGiven", this, player.UserIDString));
                if (targetPlayer != player)
                {
                    targetPlayer.ChatMessage(lang.GetMessage("RecyclerReceived", this, targetPlayer.UserIDString));
                }
            }
            else
            {
                player.ChatMessage(lang.GetMessage("RecyclerNotGiven", this, player.UserIDString));
                if (targetPlayer != player)
                {
                    targetPlayer.ChatMessage(lang.GetMessage("RecyclerNotReceived", this, targetPlayer.UserIDString));
                }
            }
        }

        private void CommandUpgradeRecycler(BasePlayer player, string command, string[] args)
        {
            if (!permission.UserHasPermission(player.UserIDString, PermissionUseRecycler))
            {
                player.ChatMessage(lang.GetMessage("NoPermission", this, player.UserIDString));
                return;
            }

            int maxRecyclers = _config.MaxRecyclersPerPlayer;

            if (permission.UserHasPermission(player.UserIDString, PermissionAdmin))
            {
                maxRecyclers = int.MaxValue;
            }
            else if (permission.UserHasPermission(player.UserIDString, PermissionVip))
            {
                maxRecyclers = _config.MaxRecyclersVip;
            }

            if (_recyclerOwners.Count(entry => entry.Value == player.userID) >= maxRecyclers)
            {
                player.ChatMessage(lang.GetMessage("RecyclerLimitReached", this, player.UserIDString));
                return;
            }

            if (!UpgradeToIndustrialRecycler(player))
            {
                player.ChatMessage(lang.GetMessage("NoRecyclerInView", this, player.UserIDString));
            }
            else
            {
                player.ChatMessage(lang.GetMessage("RecyclerUpgraded", this, player.UserIDString));
            }
        }

        private Recycler FindRecyclerInView(BasePlayer player)
        {
            if (Physics.Raycast(player.eyes.HeadRay(), out RaycastHit hit, 5f))
            {
                BaseEntity entity = hit.GetEntity();
                return entity as Recycler;
            }
            return null;
        }

        private Recycler SetupStandardRecycler(BasePlayer player, BaseEntity baseEntity, BuildingBlock foundation, int turn = 0)
        {
            var recycler = SpawnEntity(RecyclerPrefabPath, foundation, player, baseEntity.transform.position, baseEntity.transform.rotation * Quaternion.Euler(0, turn, 0)) as Recycler;

            if (recycler == null)
                return null;

            recycler.OwnerID = player.userID;
            _instance._recyclerOwners[recycler.net.ID.Value] = recycler.OwnerID;

            return recycler;
        }

        private bool InitializeRecyclerPlacement(BasePlayer player, BaseEntity baseEntity, int turn = 0)
        {
            var supportingFoundation = FindSupportingFoundation(baseEntity);
            if (supportingFoundation == null)
                return false;

            Recycler spawnedRecycler;

            if (baseEntity.skinID == _config.StandardRecyclerSkinId)
            {
                spawnedRecycler = SetupStandardRecycler(player, baseEntity, supportingFoundation, turn);
            }
            else if (baseEntity.skinID == _config.RecyclerSkinId)
            {
                spawnedRecycler = SpawnRecyclerWithComponents(player, baseEntity, supportingFoundation, turn);
            }
            else
            {
                return false;
            }

            if (spawnedRecycler != null && baseEntity.skinID == _config.RecyclerSkinId)
            {
                spawnedRecycler.gameObject.AddComponent<RecyclerComponent>();
            }

            return spawnedRecycler != null;
        }

        private bool InitializeUpgradeRecycler(BasePlayer player, Recycler recycler, int turn = 0)
        {
            var spawnedRecycler = UpgradeRecyclerWithComponents(player, recycler, turn);
            if (spawnedRecycler == null)
                return false;

            spawnedRecycler.gameObject.AddComponent<RecyclerComponent>();
            return true;
        }

        private BuildingBlock FindSupportingFoundation(BaseEntity baseEntity)
        {
            Vector3 origin = baseEntity.transform.position + Vector3.up * 0.1f;
            if (Physics.Raycast(origin, Vector3.down, out RaycastHit hitData, 4f, LayerMask.GetMask("Construction")))
            {
                return hitData.GetEntity() as BuildingBlock;
            }
            return null;
        }

        private Recycler SetupStorageAndAdapters(Recycler recycler, BasePlayer player)
        {
            Vector3 inputOffset,
                inputAdapterOffset,
                outputOffset,
                outputAdapterOffset;
            Quaternion inputRotation,
                outputRotation,
                inputAdapterRotation,
                outputAdapterRotation;

            if (_config.UseAlternativeAdapterConfig)
            {
                inputOffset = new Vector3(0.0f, 0.72f, 0.11f);
                inputAdapterOffset = new Vector3(0.32f, -0.6f, -0.03f);
                outputOffset = new Vector3(-0.4f, 0.8f, 0.2f);
                outputAdapterOffset = new Vector3(-0.07f, 0.535f, 0.397f);

                inputRotation = Quaternion.Euler(0, 180, 191);
                outputRotation = Quaternion.Euler(169, 90, 180);
                inputAdapterRotation = Quaternion.Euler(0, 0, 0);
                outputAdapterRotation = Quaternion.Euler(180, 90, 0);
            }
            else
            {
                inputOffset = new Vector3(-1.0f, 0.72f, 0.31f);
                inputAdapterOffset = new Vector3(0.00f, -0.15f, -0.25f);
                outputOffset = new Vector3(-0.9f, 0.72f, -0.09f);
                outputAdapterOffset = new Vector3(0.00f, 0.26f, -0.05f);

                inputRotation = Quaternion.Euler(90, 90, 180);
                outputRotation = Quaternion.Euler(180, 90, 180);
                inputAdapterRotation = Quaternion.Euler(180, 90, 270);
                outputAdapterRotation = Quaternion.Euler(0, 90, 0);
            }

            var inputStorage = SpawnEntity(InputStoragePrefabPath, recycler, player, inputOffset, inputRotation);
            SpawnEntity(AdapterPrefabPath, inputStorage, player, inputAdapterOffset, inputAdapterRotation);

            var outputStorage = SpawnEntity(OutputStoragePrefabPath, recycler, player, outputOffset, outputRotation);
            SpawnEntity(AdapterPrefabPath, outputStorage, player, outputAdapterOffset, outputAdapterRotation);

            return recycler;
        }

        private Recycler UpgradeRecyclerWithComponents(BasePlayer player, Recycler recycler, int turn = 0)
        {
            return SetupStorageAndAdapters(recycler, player);
        }

        private Recycler SpawnRecyclerWithComponents(BasePlayer player, BaseEntity baseEntity, BuildingBlock foundation, int turn = 0)
        {
            var recycler = SpawnEntity(RecyclerPrefabPath, foundation, player, baseEntity.transform.position, baseEntity.transform.rotation * Quaternion.Euler(0, turn, 0)) as Recycler;
            if (recycler == null)
                return null;

            return SetupStorageAndAdapters(recycler, player);
        }

        private BaseEntity SpawnEntity(string prefabPath, BaseEntity parent, BasePlayer player, Vector3 position, Quaternion rotation)
        {
            var entity = GameManager.server.CreateEntity(prefabPath, Vector3.zero, Quaternion.identity, true);
            if (entity == null)
                return null;

            entity.OwnerID = player.userID;

            if (parent != null)
            {
                entity.SetParent(parent, true);
            }

            if (parent is BuildingBlock)
            {
                entity.transform.position = position;
                entity.transform.rotation = rotation;
            }
            else
            {
                entity.transform.localPosition = position;
                entity.transform.localRotation = rotation;
            }

            entity.Spawn();
            entity.SendNetworkUpdateImmediate();
            return entity;
        }

        private void ProcessRecyclerItems(RecyclerComponent recyclerComponent, Item item)
        {
            if (item.position < 6)
            {
                recyclerComponent.Recycler.StartRecycling();
            }
            else
            {
                if (!item.MoveToContainer(recyclerComponent.OutputStorage.inventory))
                {
                    recyclerComponent.Recycler.StopRecycling();
                }
            }
        }

        private bool UpgradeToIndustrialRecycler(BasePlayer player)
        {
            if (Physics.Raycast(player.eyes.HeadRay(), out RaycastHit hit, 5f))
            {
                BaseEntity baseEntity = hit.GetEntity();
                if (baseEntity is Recycler recycler && recycler.OwnerID == player.userID && !IsRecyclerIndustrial(recycler))
                {
                    if (!InitializeUpgradeRecycler(player, recycler, 90))
                    {
                        player.ChatMessage(lang.GetMessage("InvalidPlacement", this, player.UserIDString));
                        timer.Once(1, () => GiveRecyclerItem(player, true));
                        return false;
                    }
                    else
                    {
                        player.ChatMessage(lang.GetMessage("RecyclerPlaced", this, player.UserIDString));
                    }

                    return true;
                }
            }
            return false;
        }

        private void ToggleRecyclerConfig(BasePlayer player, string command, string[] args)
        {
            if (!permission.UserHasPermission(player.UserIDString, PermissionToggle))
            {
                player.ChatMessage(lang.GetMessage("NoPermission", this, player.UserIDString));
                return;
            }

            Recycler recycler = FindRecyclerInView(player);
            if (recycler == null)
            {
                player.ChatMessage(lang.GetMessage("NoRecyclerInView", this, player.UserIDString));
                return;
            }

            var recyclerComponent = recycler.GetComponent<RecyclerComponent>();
            if (recyclerComponent == null)
            {
                player.ChatMessage(lang.GetMessage("RecyclerComponentNotFound", this, player.UserIDString));
                return;
            }

            recyclerComponent.ToggleConfig();
            player.ChatMessage(lang.GetMessage("RecyclerConfigToggled", this, player.UserIDString));
        }
        #endregion

        #region Item Dropping
        private void DropRecyclerItems(Recycler recycler)
        {
            if (recycler == null || recycler.inventory == null)
                return;

            List<Item> itemsToDrop;

            var recyclerComponent = recycler.GetComponent<RecyclerComponent>();
            if (recyclerComponent == null)
            {
                itemsToDrop = new List<Item>(recycler.inventory.itemList);
            }
            else
            {
                itemsToDrop = recyclerComponent.GatherAllItems();
            }

            Vector3 position = recycler.transform.position;
            Quaternion rotation = recycler.transform.rotation;

            foreach (var item in itemsToDrop)
            {
                item.RemoveFromContainer();
            }

            for (int i = 0; i < itemsToDrop.Count; i += 6)
            {
                var batch = itemsToDrop.Skip(i).Take(6).ToList();
                SpawnDroppedItemContainer(position, rotation, batch);
            }
        }

        private DroppedItemContainer SpawnDroppedItemContainer(Vector3 position, Quaternion rotation, List<Item> itemsToDrop)
        {
            var containerPrefabPath = "assets/prefabs/misc/item drop/item_drop.prefab";
            var droppedItemContainer = GameManager.server.CreateEntity(containerPrefabPath, position, rotation) as DroppedItemContainer;

            if (droppedItemContainer == null)
                return null;

            InitializeItemContainer(droppedItemContainer, itemsToDrop);
            droppedItemContainer.Spawn();

            return droppedItemContainer;
        }

        private void InitializeItemContainer(DroppedItemContainer container, List<Item> items)
        {
            container.inventory = new ItemContainer();
            container.inventory.ServerInitialize(null, items.Count);
            container.inventory.GiveUID();
            container.inventory.entityOwner = container;

            foreach (var item in items)
            {
                if (!item.MoveToContainer(container.inventory))
                    item.Remove();
            }
        }
        #endregion

        #region Configuration
        private class Configuration
        {
            [JsonProperty("Recycler Commands")]
            public List<string> RecyclerCommands { get; set; }

            [JsonProperty("Standard Recycler Commands")]
            public List<string> StandardRecyclerCommands { get; set; }

            [JsonProperty("Max Recyclers Per Player")]
            public int MaxRecyclersPerPlayer { get; set; }

            [JsonProperty("Max Recyclers Vip (industrialrecycler.vip)")]
            public int MaxRecyclersVip { get; set; }

            [JsonProperty("Recycler Item SkinId")]
            public ulong RecyclerSkinId { get; set; }

            [JsonProperty("Standard Recycler SkinId")]
            public ulong StandardRecyclerSkinId { get; set; }

            [JsonProperty("AnchorMin")]
            public string AnchorMin { get; set; }

            [JsonProperty("AnchorMax")]
            public string AnchorMax { get; set; }

            [JsonProperty("Alternative Adapter Configuration")]
            public bool UseAlternativeAdapterConfig { get; set; }

            [JsonProperty("Hide Storage Containers")]
            public bool HideStorageContainers { get; set; }

            [JsonProperty("Only owner can access recycler")]
            public bool OnlyOwnerAccess { get; set; }

            [JsonProperty("Allow team members to access recycler")]
            public bool AllowTeamAccess { get; set; }

            [JsonProperty("Allow friends to access recycler")]
            public bool AllowFriendsAccess { get; set; }

            [JsonProperty("Version")]
            public VersionNumber Version { get; set; }
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _config = Config.ReadObject<Configuration>() ?? throw new Exception("Failed to read configuration file.");

                bool configChanged = false;

                if (_config.Version == null || _config.Version < Version)
                {
                    PrintWarning("Updating configuration file to new version.");
                    _config.Version = Version;
                    _config.OnlyOwnerAccess = true;
                    _config.AllowTeamAccess = true;
                    _config.AllowFriendsAccess = true;
                    _config.StandardRecyclerSkinId = 3363257468;

                    configChanged = true;
                }

                if (string.IsNullOrEmpty(_config.AnchorMin))
                {
                    _config.AnchorMin = "0.5 0";
                    configChanged = true;
                }
                if (string.IsNullOrEmpty(_config.AnchorMax))
                {
                    _config.AnchorMax = "0.5 0";
                    configChanged = true;
                }

                if (_config.RecyclerCommands == null)
                {
                    _config.RecyclerCommands = new List<string> { "irecycler", "giverecycler" };
                    configChanged = true;
                }
                if (_config.StandardRecyclerCommands == null)
                {
                    _config.StandardRecyclerCommands = new List<string> { "getstandardrecycler" };
                    configChanged = true;
                }

                if (configChanged)
                {
                    PrintWarning("Configuration changes detected, saving changes.");
                    SaveConfig();
                }
            }
            catch (Exception ex)
            {
                PrintError($"There was an error in your configuration file: {ex.Message}. Default values will be used.");
                LoadDefaultConfig();
            }
        }

        protected override void LoadDefaultConfig()
        {
            _config = GetDefaultConfig();
            SaveConfig();
        }

        protected override void SaveConfig()
        {
            Config.WriteObject(_config, true);
        }

        private Configuration GetDefaultConfig()
        {
            return new Configuration
            {
                RecyclerCommands = new List<string> { "getrecycler", "irecycler", "giverecycler" },
                StandardRecyclerCommands = new List<string> { "getstandardrecycler" },
                MaxRecyclersPerPlayer = 1,
                MaxRecyclersVip = 2,
                RecyclerSkinId = 3357927081,
                StandardRecyclerSkinId = 3363257468,
                AnchorMin = "0.5 0",
                AnchorMax = "0.5 0",
                UseAlternativeAdapterConfig = false,
                HideStorageContainers = false,
                OnlyOwnerAccess = true,
                AllowTeamAccess = true,
                AllowFriendsAccess = true,
                Version = Version,
            };
        }
        #endregion

        #region Localization
        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(
                new Dictionary<string, string>
                {
                    ["NoPermission"] = "You do not have permission to use this command.",
                    ["RecyclerLimitReached"] = "You have reached the recycler limit.",
                    ["InvalidPlacement"] = "The recycler must be placed on a construction block.",
                    ["RecyclerPlaced"] = "Recycler placed successfully.",
                    ["RecyclerPickedUp"] = "Recycler picked up, item returned to inventory.",
                    ["RecyclerGiven"] = "Recycler given to your inventory.",
                    ["RecyclerReceived"] = "You have received a recycler.",
                    ["RecyclerNotGiven"] = "Failed to give recycler.",
                    ["RecyclerNotReceived"] = "Failed to receive recycler.",
                    ["NoRecyclerInView"] = "No recycler in sight to upgrade.",
                    ["RecyclerComponentNotFound"] = "Recycler component not found.",
                    ["RecyclerConfigToggled"] = "Recycler configuration toggled successfully.",
                    ["PlayerNotFound"] = "The specified player could not be found.",
                    ["ToggleConfig"] = "Toggle",
                    ["InputButton"] = "Input",
                    ["OutputButton"] = "Output",
                    ["NoAccess"] = "You do not have access to this recycler.",
                    ["StandardRecyclerGiven"] = "Standard recycler added to your inventory.",
                    ["StandardRecyclerNotGiven"] = "Failed to give standard recycler.",
                },
                this
            );

            lang.RegisterMessages(
                new Dictionary<string, string>
                {
                    ["NoPermission"] = "Você não tem permissão para usar este comando.",
                    ["RecyclerLimitReached"] = "Você atingiu o limite de recicladores.",
                    ["InvalidPlacement"] = "O reciclador deve ser colocado sobre um bloco de construção.",
                    ["RecyclerPlaced"] = "Reciclador colocado com sucesso.",
                    ["RecyclerPickedUp"] = "Reciclador recolhido, item devolvido ao inventário.",
                    ["RecyclerGiven"] = "Reciclador fornecido ao seu inventário.",
                    ["RecyclerReceived"] = "Você recebeu um reciclador.",
                    ["RecyclerNotGiven"] = "Falha ao fornecer o reciclador.",
                    ["RecyclerNotReceived"] = "Falha ao receber o reciclador.",
                    ["NoRecyclerInView"] = "Nenhum reciclador à vista para atualizar.",
                    ["RecyclerComponentNotFound"] = "Componente de reciclador não encontrado.",
                    ["RecyclerConfigToggled"] = "Configuração do reciclador alternada com sucesso.",
                    ["PlayerNotFound"] = "O jogador especificado não pôde ser encontrado.",
                    ["ToggleConfig"] = "Alterar",
                    ["InputButton"] = "Entrada",
                    ["OutputButton"] = "Saída",
                    ["NoAccess"] = "Você não tem acesso a este reciclador.",
                    ["StandardRecyclerGiven"] = "Reciclador padrão adicionado ao seu inventário.",
                    ["StandardRecyclerNotGiven"] = "Falha ao fornecer o reciclador padrão.",
                },
                this,
                "pt-BR"
            );

            lang.RegisterMessages(
                new Dictionary<string, string>
                {
                    ["NoPermission"] = "Sie haben keine Erlaubnis, diesen Befehl zu verwenden.",
                    ["RecyclerLimitReached"] = "Sie haben die Grenze für Recycler erreicht.",
                    ["InvalidPlacement"] = "Der Recycler muss auf einem Baublock platziert werden.",
                    ["RecyclerPlaced"] = "Recycler erfolgreich platziert.",
                    ["RecyclerPickedUp"] = "Recycler aufgenommen, Artikel in das Inventar zurückgegeben.",
                    ["RecyclerGiven"] = "Recycler wurde Ihrem Inventar hinzugefügt.",
                    ["RecyclerReceived"] = "Sie haben einen Recycler erhalten.",
                    ["RecyclerNotGiven"] = "Recycle fehlerhaft vorhanden.",
                    ["RecyclerNotReceived"] = "Falschen Empfang des Recyclers.",
                    ["NoRecyclerInView"] = "Kein Recycler in Sicht, um aufzurüsten.",
                    ["RecyclerComponentNotFound"] = "Recycler-Komponente nicht gefunden.",
                    ["RecyclerConfigToggled"] = "Recycler-Konfiguration erfolgreich umgeschaltet.",
                    ["PlayerNotFound"] = "Der angegebene Spieler konnte nicht gefunden werden.",
                    ["ToggleConfig"] = "Umschalten",
                    ["InputButton"] = "Eingang",
                    ["OutputButton"] = "Ausgang",
                    ["NoAccess"] = "Sie haben keinen Zugriff auf diesen Recycler.",
                    ["StandardRecyclerGiven"] = "Standard-Recycler wurde Ihrem Inventar hinzugefügt.",
                    ["StandardRecyclerNotGiven"] = "Standard-Recycler konnte nicht gegeben werden.",
                },
                this,
                "de"
            );

            lang.RegisterMessages(
                new Dictionary<string, string>
                {
                    ["NoPermission"] = "No tienes permiso para usar este comando.",
                    ["RecyclerLimitReached"] = "Has alcanzado el límite de recicladores.",
                    ["InvalidPlacement"] = "El reciclador debe colocarse en un bloque de construcción.",
                    ["RecyclerPlaced"] = "Reciclador colocado con éxito.",
                    ["RecyclerPickedUp"] = "Reciclador recogido, artículo devuelto al inventario.",
                    ["RecyclerGiven"] = "Reciclador dado a tu inventario.",
                    ["RecyclerReceived"] = "Has recibido un reciclador.",
                    ["RecyclerNotGiven"] = "Fallo al dar el reciclador.",
                    ["RecyclerNotReceived"] = "Fallo al recibir el reciclador.",
                    ["NoRecyclerInView"] = "No hay reciclador a la vista para mejorar.",
                    ["RecyclerComponentNotFound"] = "Componente de reciclador no encontrado.",
                    ["RecyclerConfigToggled"] = "Configuración del reciclador alternada con éxito.",
                    ["PlayerNotFound"] = "No se pudo encontrar al jugador especificado.",
                    ["ToggleConfig"] = "Alternar",
                    ["InputButton"] = "Entrada",
                    ["OutputButton"] = "Salida",
                    ["NoAccess"] = "No tienes acceso a este reciclador.",
                    ["StandardRecyclerGiven"] = "Reciclador estándar añadido a tu inventario.",
                    ["StandardRecyclerNotGiven"] = "Error al entregar el reciclador estándar.",
                },
                this,
                "es"
            );
        }
        #endregion

        #region Component
        private class RecyclerComponent : MonoBehaviour
        {
            public Recycler Recycler { get; private set; }
            public StorageContainer InputStorage { get; private set; }
            public StorageContainer OutputStorage { get; private set; }
            public int CurrentConfigState { get; private set; }

            private const int MaxStorageCapacity = 48;
            private const float TransferInterval = 10f;

            private enum RecyclerConfig
            {
                Default = 1,
                Alternative = 2,
            }

            private void Awake()
            {
                Recycler = GetComponent<Recycler>();
                if (Recycler == null || !_instance.IsRecyclerIndustrial(Recycler))
                {
                    enabled = false;
                    return;
                }

                CurrentConfigState = (int)RecyclerConfig.Default;
                InitializeRecyclerComponents();
            }

            private void InitializeRecyclerComponents()
            {
                foreach (MonoBehaviour mb in GetComponents<MonoBehaviour>())
                {
                    var type = mb.GetType();
                    if (type != typeof(Recycler) && type != typeof(RecyclerComponent) && type.Name != "Ground_Watch")
                    {
                        Destroy(mb);
                    }
                }

                if (!ValidateStorageContainers())
                {
                    enabled = false;
                    return;
                }

                SetupRecycler();

                RegisterEntities();
                InvokeRepeating(nameof(HandleItemTransfers), 0f, TransferInterval);
            }

            private bool ValidateStorageContainers()
            {
                InputStorage = Recycler.children.OfType<StorageContainer>().FirstOrDefault(c => c?.name == InputStoragePrefabPath);
                OutputStorage = Recycler.children.OfType<StorageContainer>().FirstOrDefault(c => c?.name == OutputStoragePrefabPath);
                return InputStorage != null && OutputStorage != null;
            }

            private void RegisterEntities()
            {
                _instance._inputItemContainers.Add(InputStorage.inventory.uid.Value);
                _instance._recyclerItemContainers.Add(Recycler.inventory.uid.Value);

                var inputAdapter = InputStorage.children.OfType<IndustrialStorageAdaptor>().FirstOrDefault();
                if (inputAdapter != null)
                    _instance._storageAdapters.Add(inputAdapter.net.ID.Value);

                var outputAdapter = OutputStorage.children.OfType<IndustrialStorageAdaptor>().FirstOrDefault();
                if (outputAdapter != null)
                    _instance._storageAdapters.Add(outputAdapter.net.ID.Value);
            }

            private void SetupRecycler()
            {
                if (Recycler == null || InputStorage == null || OutputStorage == null)
                {
                    return;
                }

                _instance._recyclerOwners[Recycler.net.ID.Value] = Recycler.OwnerID;
                InputStorage.inventory.capacity = MaxStorageCapacity;
                OutputStorage.inventory.capacity = MaxStorageCapacity;
            }

            public void HandleItemTransfers()
            {
                int availableRecyclerSlots = 6 - Recycler.inventory.itemList.Count;

                if (OutputStorage.inventory.IsFull())
                {
                    Recycler.StopRecycling();
                    return;
                }

                TransferItemsFromRecyclerToOutput(ref availableRecyclerSlots);
                TransferItemsFromInputToRecycler(ref availableRecyclerSlots);

                if (availableRecyclerSlots < 6 && Recycler.inventory.itemList.Count > 0)
                {
                    Recycler.StartRecycling();
                }
            }

            private void TransferItemsFromRecyclerToOutput(ref int availableRecyclerSlots)
            {
                for (int slot = 6; slot < Recycler.inventory.capacity; slot++)
                {
                    var itemInRecycler = Recycler.inventory.GetSlot(slot);
                    if (itemInRecycler != null && itemInRecycler.MoveToContainer(OutputStorage.inventory))
                    {
                        availableRecyclerSlots++;
                    }
                }
            }

            private void TransferItemsFromInputToRecycler(ref int availableRecyclerSlots)
            {
                var inputItems = new List<Item>(InputStorage.inventory.itemList);

                foreach (var item in inputItems)
                {
                    if (item.info.Blueprint != null)
                    {
                        MoveItemToRecycler(item, ref availableRecyclerSlots);
                    }
                    else
                    {
                        item.MoveToContainer(OutputStorage.inventory);
                    }
                }
            }

            private void MoveItemToRecycler(Item item, ref int availableRecyclerSlots)
            {
                if (availableRecyclerSlots <= 0)
                    return;

                for (int slot = 0; slot < 6; slot++)
                {
                    if (Recycler.inventory.GetSlot(slot) == null && item.MoveToContainer(Recycler.inventory, slot))
                    {
                        availableRecyclerSlots--;
                        break;
                    }
                }
            }

            public void ToggleConfig()
            {
                CancelInvoke(nameof(HandleItemTransfers));
                CurrentConfigState = (int)(CurrentConfigState == (int)RecyclerConfig.Default ? RecyclerConfig.Alternative : RecyclerConfig.Default);

                List<(string Shortname, int Amount, float ConditionNormalized)> ExtractItemDetails(ItemContainer container) =>
                    container?.itemList?.Select(item => (item.info.shortname, item.amount, item.conditionNormalized)).ToList() ?? new List<(string, int, float)>();

                var inputItemDetails = ExtractItemDetails(InputStorage.inventory);
                var outputItemDetails = ExtractItemDetails(OutputStorage.inventory);

                ClearExistingComponents();
                UpdateAdapterPositions();
                InitializeRecyclerComponents();

                if (InputStorage == null || OutputStorage == null || Recycler == null)
                {
                    return;
                }

                foreach (var (shortname, amount, condition) in inputItemDetails)
                    CreateAndMoveItem(shortname, amount, condition, InputStorage.inventory);

                foreach (var (shortname, amount, condition) in outputItemDetails)
                    CreateAndMoveItem(shortname, amount, condition, OutputStorage.inventory);
            }

            private void CreateAndMoveItem(string shortname, int amount, float conditionNormalized, ItemContainer container)
            {
                var item = ItemManager.CreateByName(shortname, amount);
                if (item != null)
                {
                    item.conditionNormalized = conditionNormalized;
                    if (!item.MoveToContainer(container))
                        item.Remove();
                }
            }

            private void UpdateAdapterPositions()
            {
                ClearExistingComponents();

                Vector3 inputOffset,
                    outputOffset,
                    inputAdapterOffset,
                    outputAdapterOffset;
                Quaternion inputRotation,
                    outputRotation,
                    inputAdapterRotation,
                    outputAdapterRotation;

                if (CurrentConfigState == (int)RecyclerConfig.Default)
                {
                    inputOffset = new Vector3(0.0f, 0.72f, 0.11f);
                    outputOffset = new Vector3(-0.4f, 0.8f, 0.2f);
                    inputAdapterOffset = new Vector3(0.32f, -0.6f, -0.03f);
                    outputAdapterOffset = new Vector3(-0.07f, 0.535f, 0.397f);

                    inputRotation = Quaternion.Euler(0, 180, 191);
                    outputRotation = Quaternion.Euler(169, 90, 180);
                    inputAdapterRotation = Quaternion.Euler(0, 0, 0);
                    outputAdapterRotation = Quaternion.Euler(180, 90, 0);
                }
                else
                {
                    if (_instance._config.HideStorageContainers)
                    {
                        inputOffset = new Vector3(0.0f, 0.72f, 0.11f);
                        outputOffset = new Vector3(-0.4f, 0.72f, 0.2f);
                        inputAdapterOffset = new Vector3(0.836f, -0.3301f, 0.18f);
                        outputAdapterOffset = new Vector3(-0.227f, 0.33f, 0.434f);

                        inputRotation = Quaternion.Euler(0, 0, 180);
                        outputRotation = Quaternion.Euler(180, 90, 180);
                        inputAdapterRotation = Quaternion.Euler(0, 0, 0);
                        outputAdapterRotation = Quaternion.Euler(180, 90, 0);
                    }
                    else
                    {
                        inputOffset = new Vector3(-1.0f, 0.72f, 0.31f);
                        outputOffset = new Vector3(-0.9f, 0.72f, -0.09f);
                        inputAdapterOffset = new Vector3(0.00f, -0.15f, -0.25f);
                        outputAdapterOffset = new Vector3(0.00f, 0.26f, -0.05f);

                        inputRotation = Quaternion.Euler(90, 90, 180);
                        outputRotation = Quaternion.Euler(180, 90, 180);
                        inputAdapterRotation = Quaternion.Euler(180, 90, 270);
                        outputAdapterRotation = Quaternion.Euler(0, 90, 0);
                    }
                }

                BasePlayer ownerPlayer = BasePlayer.FindByID(Recycler.OwnerID);
                if (ownerPlayer != null)
                {
                    var inputStorage = _instance.SpawnEntity(InputStoragePrefabPath, Recycler, ownerPlayer, inputOffset, inputRotation) as StorageContainer;
                    if (inputStorage == null)
                    {
                        return;
                    }
                    _instance.SpawnEntity(AdapterPrefabPath, inputStorage, ownerPlayer, inputAdapterOffset, inputAdapterRotation);

                    var outputStorage = _instance.SpawnEntity(OutputStoragePrefabPath, Recycler, ownerPlayer, outputOffset, outputRotation) as StorageContainer;
                    if (outputStorage == null)
                    {
                        return;
                    }
                    _instance.SpawnEntity(AdapterPrefabPath, outputStorage, ownerPlayer, outputAdapterOffset, outputAdapterRotation);
                }
            }

            private void ClearExistingComponents()
            {
                foreach (var child in Recycler.children.ToList())
                {
                    if (child is StorageContainer || child is IndustrialStorageAdaptor)
                    {
                        child.Kill();
                    }
                }
            }

            internal void DropItems()
            {
                _instance.DropRecyclerItems(Recycler);
            }

            public List<Item> GatherAllItems()
            {
                if (InputStorage == null || InputStorage.inventory == null)
                {
                    return new List<Item>();
                }
                if (OutputStorage == null || OutputStorage.inventory == null)
                {
                    return new List<Item>();
                }
                if (Recycler == null || Recycler.inventory == null)
                {
                    return new List<Item>();
                }

                return InputStorage.inventory.itemList.Concat(OutputStorage.inventory.itemList).Concat(Recycler.inventory.itemList).ToList();
            }

            private void OnDestroy()
            {
                CancelInvoke(nameof(HandleItemTransfers));
            }
        }
        #endregion
    }
}