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

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

        #region Fields
        private static IndustrialRecycler _instance;
        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 PermissionUseRecycler = "industrialrecycler.give";
        private const string PermissionVip1 = "industrialrecycler.vip";
        private const string PermissionVip2 = "industrialrecycler.vip2";
        private const string PermissionAdmin = "industrialrecycler.admin";
        private const string PermissionBuyStandard = "industrialrecycler.buystandard";
        private const string PermissionBuyIndustrial = "industrialrecycler.buyindustrial";
        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(PermissionVip1, this);
            permission.RegisterPermission(PermissionVip2, this);
            permission.RegisterPermission(PermissionAdmin, this);
            permission.RegisterPermission(PermissionToggle, this);
            permission.RegisterPermission(PermissionBuyStandard, this);
            permission.RegisterPermission(PermissionBuyIndustrial, 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));
            }

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

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

            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));
            cmd.AddConsoleCommand("buystandard.confirm", this, nameof(ConfirmPurchaseStandard));
            cmd.AddConsoleCommand("buyindustrial.confirm", this, nameof(ConfirmPurchaseIndustrial));
            cmd.AddConsoleCommand("buyrecycler.close", this, nameof(ClosePurchaseUI));
        }

        private void OnServerInitialized()
        {
            _instance = this;

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

            var items = GetItemsForImagePreload();
            ImageLibrary?.Call("LoadImageList", Title, items, null);
        }

        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);
                CuiHelper.DestroyUi(player, "PurchaseUIScreen");
            }
        }

        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();
            if (player == null)
                return;

            var baseEntity = entityObject.GetComponent<BaseEntity>();
            var activeItem = player.GetActiveItem();
            if (baseEntity == null || activeItem == null || activeItem.info.shortname != _config.BaseItem)
                return;

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

            bool isIndustrial = baseEntity.skinID == _config.IndustrialRecyclerSkinId;
            int maxRecyclers = isIndustrial ? _config.IndustrialMaxRecyclers : _config.StandardMaxRecyclers;

            if (permission.UserHasPermission(player.UserIDString, PermissionAdmin))
                maxRecyclers = int.MaxValue;
            else if (permission.UserHasPermission(player.UserIDString, PermissionVip2))
                maxRecyclers = isIndustrial ? _config.Vip2IndustrialMaxRecyclers : _config.Vip2StandardMaxRecyclers;
            else if (permission.UserHasPermission(player.UserIDString, PermissionVip1))
                maxRecyclers = isIndustrial ? _config.Vip1IndustrialMaxRecyclers : _config.Vip1StandardMaxRecyclers;

            int currentCount = _recyclerOwners.Count(entry =>
            {
                var recycler = BaseNetworkable.serverEntities.Find(new NetworkableId(entry.Key)) as Recycler;
                return recycler?.OwnerID == player.userID && (isIndustrial ? recycler.GetComponent<RecyclerComponent>() != null : recycler.GetComponent<RecyclerComponent>() == null);
            });

            if (currentCount >= maxRecyclers)
            {
                player.ChatMessage(lang.GetMessage("RecyclerLimitReached", this, player.UserIDString));
                timer.Once(1, () => GiveRecyclerItem(player, !isIndustrial));
                NextTick(() => baseEntity?.Kill());
                return;
            }

            if (!InitializeRecyclerPlacement(player, baseEntity))
            {
                player.ChatMessage(lang.GetMessage("InvalidPlacement", this, player.UserIDString));
                timer.Once(1, () => GiveRecyclerItem(player, !isIndustrial));
            }
            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;
        }

        private List<KeyValuePair<string, ulong>> GetItemsForImagePreload()
        {
            var itemIcons = new List<KeyValuePair<string, ulong>>();

            foreach (var item in _config.PurchaseIndustrialCost)
                itemIcons.Add(new KeyValuePair<string, ulong>(item.Shortname, 0));

            foreach (var item in _config.PurchaseStandardCost)
                itemIcons.Add(new KeyValuePair<string, ulong>(item.Shortname, 0));

            return itemIcons;
        }
        #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);
                }
            }
        }

        private void CommandOpenStandardPurchaseUI(BasePlayer player)
        {
            if (permission.UserHasPermission(player.UserIDString, PermissionBuyStandard))
            {
                CreatePurchaseUi(player, "standard");
            }
            else
            {
                player.ChatMessage(lang.GetMessage("NoPermission", this, player.UserIDString));
                return;
            }
        }

        private void CommandOpenIndustrialPurchaseUI(BasePlayer player)
        {
            if (permission.UserHasPermission(player.UserIDString, PermissionBuyIndustrial))
            {
                CreatePurchaseUi(player, "industrial");
            }
            else
            {
                player.ChatMessage(lang.GetMessage("NoPermission", this, player.UserIDString));
                return;
            }
        }

        #endregion

        #region UI
        private void CreatePurchaseUi(BasePlayer player, string type)
        {
            var container = new CuiElementContainer();
            var parent = "PurchaseUIScreen";

            AddPanel(container, "0.3 0.3", "0.7 0.7", parent, "Overlay", "0 0 0 0.8", true);
            if (type == "standard")
            {
                AddText(container, parent, "0.1 0.9", "0.9 1", lang.GetMessage("PurchaseRecyclerTitle", this, player.UserIDString), 20, TextAnchor.MiddleCenter, "1 1 1 1");
                AddText(container, parent, "0.05 0.75", "0.9 1", lang.GetMessage("PurchaseCosts", this, player.UserIDString), 16, TextAnchor.MiddleLeft, "1 1 1 1");

                float initialY = 0.85f;
                AddDictionaryWithSpacing(container, parent, _config.PurchaseStandardCost, 0.10f, initialY, 0.15f, "1 1 1 1");

                AddButton(container, parent, lang.GetMessage("ConfirmPurchase", this, player.UserIDString), "buystandard.confirm", "0.25", "0.1", "0.45", "0.18", "0.4 0.6 0.4 0.95", "1 1 1 1");
            }
            else if (type == "industrial")
            {
                AddText(container, parent, "0.1 0.9", "0.9 1", lang.GetMessage("PurchaseIndustrialRecyclerTitle", this, player.UserIDString), 20, TextAnchor.MiddleCenter, "1 1 1 1");
                AddText(container, parent, "0.05 0.75", "0.9 1", lang.GetMessage("PurchaseCosts", this, player.UserIDString), 16, TextAnchor.MiddleLeft, "1 1 1 1");

                float initialY = 0.85f;
                AddDictionaryWithSpacing(container, parent, _config.PurchaseIndustrialCost, 0.10f, initialY, 0.15f, "1 1 1 1");

                AddButton(container, parent, lang.GetMessage("ConfirmPurchase", this, player.UserIDString), "buyindustrial.confirm", "0.25", "0.1", "0.45", "0.18", "0.4 0.6 0.4 0.95", "1 1 1 1");
            }

            AddButton(container, parent, lang.GetMessage("Close", this, player.UserIDString), "buyrecycler.close", "0.55", "0.1", "0.75", "0.18", "1 0 0 1", "1 1 1 1");
            CuiHelper.AddUi(player, container);
        }

        private void AddDictionaryWithSpacing(CuiElementContainer container, string parent, List<ResourceItem> items, float initialAnchorX, float initialAnchorY, float spacing, string FontColor)
        {
            float anchorX = initialAnchorX;

            int i = 0;
            foreach (var item in items)
            {
                i++;
                float anchorY = initialAnchorY - (spacing * i);

                if (i >= 5 && i < 9)
                {
                    anchorY = initialAnchorY - (spacing * (i - 4));
                    anchorX = initialAnchorX + 0.23f;
                }

                if (i >= 9)
                {
                    anchorY = initialAnchorY - (spacing * (i - 8));
                    anchorX = initialAnchorX + 0.47f;
                }

                float imageSize = 0.10f;
                float imageAnchorMinX = anchorX - (imageSize / 2);
                float imageAnchorMaxX = anchorX + (imageSize / 2);
                float imageAnchorMinY = anchorY - 0.01f - (imageSize / 2);
                float imageAnchorMaxY = anchorY + 0.05f + (imageSize / 2);

                AddImage(container, parent, item.Shortname, $"{imageAnchorMinX} {imageAnchorMinY}", $"{imageAnchorMaxX} {imageAnchorMaxY}", "ResourceImage");
                AddText(container, parent, $"{anchorX + 0.08f} {anchorY}", $"{anchorX + 0.05f + 0.8f} {anchorY + 0.06}", item.Amount.ToString(), 12, TextAnchor.MiddleLeft, FontColor);
            }
        }

        private static void AddPanel(CuiElementContainer container, string anchorMin, string anchorMax, string panelName, string parent, string color, bool isCursorEnabled = false)
        {
            var panel = new CuiPanel
            {
                Image = { Color = color },
                RectTransform = { AnchorMin = anchorMin, AnchorMax = anchorMax },
                CursorEnabled = isCursorEnabled,
            };

            container.Add(panel, parent, panelName);
        }

        private static void AddText(CuiElementContainer container, string parent, string anchorMin, string anchorMax, string text, int fontSize, TextAnchor alignment, string textColor)
        {
            container.Add(
                new CuiLabel
                {
                    Text =
                    {
                        Text = text,
                        FontSize = fontSize,
                        Align = alignment,
                        Color = textColor,
                    },
                    RectTransform = { AnchorMin = anchorMin, AnchorMax = anchorMax },
                },
                parent
            );
        }

        private void AddImage(CuiElementContainer container, string parent, string resourceName, string anchorMin, string anchorMax, string imageName)
        {
            string imageUrl = ImageLibrary?.Call("GetImage", resourceName) as string;
            if (string.IsNullOrEmpty(imageUrl))
            {
                PrintWarning($"Failed to get image for item: {resourceName}");
                return;
            }

            container.Add(
                new CuiElement
                {
                    Name = imageName,
                    Parent = parent,
                    Components =
                    {
                        new CuiRawImageComponent { Png = imageUrl },
                        new CuiRectTransformComponent { AnchorMin = anchorMin, AnchorMax = anchorMax },
                    },
                }
            );
        }

        private void AddButton(
            CuiElementContainer container,
            string parent,
            string text,
            string command,
            string anchorMinX,
            string anchorMinY,
            string anchorMaxX,
            string anchorMaxY,
            string bgColor,
            string textColor
        )
        {
            container.Add(
                new CuiButton
                {
                    Button = { Color = bgColor, Command = command },
                    RectTransform = { AnchorMin = $"{anchorMinX} {anchorMinY}", AnchorMax = $"{anchorMaxX} {anchorMaxY}" },
                    Text =
                    {
                        Text = text,
                        FontSize = 14,
                        Align = TextAnchor.MiddleCenter,
                        Color = textColor,
                    },
                },
                parent
            );
        }

        private void ConfirmPurchaseStandard(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();

            int maxRecyclers = GetMaxRecyclers(player, isIndustrial: false);
            int currentRecyclers = CountPlayerRecyclers(player, isIndustrial: false);

            if (currentRecyclers >= maxRecyclers)
            {
                player.ChatMessage(lang.GetMessage("RecyclerLimitReached", this, player.UserIDString));
                return;
            }

            if (CheckAndRemoveBuyItems(player, _config.PurchaseStandardCost))
            {
                GiveRecyclerItem(player, true);
                player.ChatMessage(lang.GetMessage("PurchaseSuccessStandard", this, player.UserIDString));
            }
            else
            {
                player.ChatMessage(lang.GetMessage("PurchaseFailed", this, player.UserIDString));
            }
        }

        private void ConfirmPurchaseIndustrial(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();

            int maxRecyclers = GetMaxRecyclers(player, isIndustrial: true);
            int currentRecyclers = CountPlayerRecyclers(player, isIndustrial: true);

            if (currentRecyclers >= maxRecyclers)
            {
                player.ChatMessage(lang.GetMessage("RecyclerLimitReached", this, player.UserIDString));
                return;
            }

            if (CheckAndRemoveBuyItems(player, _config.PurchaseIndustrialCost))
            {
                GiveRecyclerItem(player, false);
                player.ChatMessage(lang.GetMessage("PurchaseSuccessIndustrial", this, player.UserIDString));
            }
            else
            {
                player.ChatMessage(lang.GetMessage("PurchaseFailed", this, player.UserIDString));
            }
        }

        private void ClosePurchaseUI(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            CuiHelper.DestroyUi(player, "PurchaseUIScreen");
        }
        #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.IndustrialRecyclerSkinId;
            var recyclerItem = ItemManager.CreateByName(_config.BaseItem, 1, skinId);
            if (recyclerItem == null)
                return false;

            recyclerItem.name = isStandard ? "Standard Recycler" : "Industrial Recycler";

            if (!player.inventory.GiveItem(recyclerItem))
            {
                recyclerItem.Drop(player.transform.position + Vector3.up, Vector3.zero);
                player.ChatMessage(lang.GetMessage("InventoryFull", this, player.UserIDString));
                return false;
            }

            player.ChatMessage(lang.GetMessage(isStandard ? "StandardRecyclerReceived" : "IndustrialRecyclerReceived", this, player.UserIDString));
            return true;
        }

        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;
                }
            }

            bool wasGiven = GiveRecyclerItem(targetPlayer, true);

            if (wasGiven)
            {
                player.ChatMessage(lang.GetMessage("RecyclerGiven", this, player.UserIDString));
            }
            else
            {
                player.ChatMessage(lang.GetMessage("RecyclerNotGiven", this, player.UserIDString));
            }

            if (targetPlayer != player)
            {
                if (wasGiven)
                {
                    targetPlayer.ChatMessage(lang.GetMessage("RecyclerReceived", this, targetPlayer.UserIDString));
                }
                else
                {
                    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.IndustrialMaxRecyclers;

            if (permission.UserHasPermission(player.UserIDString, PermissionAdmin))
            {
                maxRecyclers = int.MaxValue;
            }
            else if (permission.UserHasPermission(player.UserIDString, PermissionVip2))
            {
                maxRecyclers = _config.Vip2IndustrialMaxRecyclers;
            }
            else if (permission.UserHasPermission(player.UserIDString, PermissionVip1))
            {
                maxRecyclers = _config.Vip1IndustrialMaxRecyclers;
            }

            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 int GetMaxRecyclers(BasePlayer player, bool isIndustrial)
        {
            int maxRecyclers = isIndustrial ? _config.IndustrialMaxRecyclers : _config.StandardMaxRecyclers;

            if (permission.UserHasPermission(player.UserIDString, PermissionAdmin))
                maxRecyclers = int.MaxValue;
            else if (permission.UserHasPermission(player.UserIDString, PermissionVip2))
                maxRecyclers = isIndustrial ? _config.Vip2IndustrialMaxRecyclers : _config.Vip2StandardMaxRecyclers;
            else if (permission.UserHasPermission(player.UserIDString, PermissionVip1))
                maxRecyclers = isIndustrial ? _config.Vip1IndustrialMaxRecyclers : _config.Vip1StandardMaxRecyclers;

            return maxRecyclers;
        }

        private int CountPlayerRecyclers(BasePlayer player, bool isIndustrial)
        {
            return _recyclerOwners.Count(entry =>
            {
                var recycler = BaseNetworkable.serverEntities.Find(new NetworkableId(entry.Key)) as Recycler;
                return recycler?.OwnerID == player.userID && (isIndustrial ? recycler.GetComponent<RecyclerComponent>() != null : recycler.GetComponent<RecyclerComponent>() == null);
            });
        }

        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, BaseEntity 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 Tugboat IsPlacingOnTugboat(BaseEntity baseEntity)
        {
            Vector3 origin = baseEntity.transform.position + new Vector3(0f, 0.1f, 0f);
            if (Physics.Raycast(origin, Vector3.down, out RaycastHit hitData, 1f, Layers.Mask.Vehicle_Detailed | Layers.Mask.Vehicle_Large))
            {
                var hitEntity = hitData.GetEntity();
                if (hitEntity is Tugboat)
                {
                    return hitEntity as Tugboat;
                }
            }

            return null;
        }

        private bool InitializeRecyclerPlacement(BasePlayer player, BaseEntity baseEntity, int turn = 0)
        {
            if (IsPlacingOnTugboat(baseEntity) is Tugboat tugboat)
            {
                BaseEntity recycler = null;

                if (baseEntity.skinID == _config.StandardRecyclerSkinId)
                {
                    recycler = SetupStandardRecycler(player, baseEntity, tugboat, turn);
                }
                else if (baseEntity.skinID == _config.IndustrialRecyclerSkinId)
                {
                    recycler = SpawnRecyclerWithComponents(player, baseEntity, tugboat, turn);
                }

                if (recycler != null)
                {
                    baseEntity.Kill();
                    return true;
                }

                return false;
            }

            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.IndustrialRecyclerSkinId)
            {
                spawnedRecycler = SpawnRecyclerWithComponents(player, baseEntity, supportingFoundation, turn);
            }
            else
            {
                return false;
            }

            if (spawnedRecycler != null && baseEntity.skinID == _config.IndustrialRecyclerSkinId)
            {
                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, BaseEntity foundation, int turn = 0)
        {
            if (foundation is Tugboat)
                return null;

            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 || parent is Tugboat)
            {
                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));
        }

        private bool CheckAndRemoveBuyItems(BasePlayer player, List<ResourceItem> requiredItems)
        {
            var requiredItemMap = requiredItems.ToDictionary(item => item.Shortname, item => item.Amount);

            var allItems = Pool.Get<List<Item>>();
            try
            {
                player.inventory.GetAllItems(allItems);

                if (!HasAllRequiredItems(allItems, requiredItemMap))
                    return false;

                RemoveItems(allItems, player, requiredItemMap);
                return true;
            }
            finally
            {
                Pool.FreeList(ref allItems);
            }
        }

        private bool HasAllRequiredItems(List<Item> allItems, Dictionary<string, int> requiredItemMap)
        {
            var playerItemMap = allItems.GroupBy(item => item.info.shortname).ToDictionary(g => g.Key, g => g.Sum(item => item.amount));

            foreach (var requirement in requiredItemMap)
                if (!playerItemMap.TryGetValue(requirement.Key, out int count) || count < requirement.Value)
                    return false;

            return true;
        }

        private void RemoveItems(List<Item> allItems, BasePlayer player, Dictionary<string, int> requiredItemMap)
        {
            foreach (var requirement in requiredItemMap)
            {
                int amountToRemove = requirement.Value;

                foreach (var item in allItems.Where(item => item.info.shortname == requirement.Key))
                {
                    if (amountToRemove <= 0)
                        break;

                    if (item.amount <= amountToRemove)
                    {
                        amountToRemove -= item.amount;
                        player.inventory.Take(null, item.info.itemid, item.amount);
                        item.Remove();
                    }
                    else
                    {
                        item.amount -= amountToRemove;
                        item.MarkDirty();
                        amountToRemove = 0;
                    }
                }
            }
        }
        #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 Configuration _config;

        private class Configuration
        {
            [JsonProperty(PropertyName = "Recycler Commands", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> RecyclerCommands { get; set; } = new List<string> { "industrialrecycler", "giveindustrialrecycler" };

            [JsonProperty(PropertyName = "Standard Recycler Commands", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> StandardRecyclerCommands { get; set; } = new List<string> { "standardrecycler", "givestandardrecycler" };

            [JsonProperty(PropertyName = "Standard Recycler Purchase Commands", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> StandardPurchaseCommands { get; set; } = new List<string> { "buyrecycler" };

            [JsonProperty(PropertyName = "Industrial Recycler Purchase Commands", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> IndustrialPurchaseCommands { get; set; } = new List<string> { "buyindustrialrecycler" };

            [JsonProperty(PropertyName = "BaseItem")]
            public string BaseItem { get; set; } = "box.wooden.large";

            [JsonProperty(PropertyName = "Standard MaxRecyclers")]
            public int StandardMaxRecyclers { get; set; } = 1;

            [JsonProperty(PropertyName = "Vip1 Standard MaxRecyclers (industrialrecycler.vip)")]
            public int Vip1StandardMaxRecyclers { get; set; } = 2;

            [JsonProperty(PropertyName = "Vip2 Standard MaxRecyclers (industrialrecycler.vip2)")]
            public int Vip2StandardMaxRecyclers { get; set; } = 3;

            [JsonProperty(PropertyName = "Industrial MaxRecyclers")]
            public int IndustrialMaxRecyclers { get; set; } = 1;

            [JsonProperty(PropertyName = "Vip1 Industrial MaxRecyclers (industrialrecycler.vip)")]
            public int Vip1IndustrialMaxRecyclers { get; set; } = 2;

            [JsonProperty(PropertyName = "Vip2 Industrial MaxRecyclers (industrialrecycler.vip2)")]
            public int Vip2IndustrialMaxRecyclers { get; set; } = 3;

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

            [JsonProperty(PropertyName = "Industrial Recycler Item SkinId")]
            public ulong IndustrialRecyclerSkinId { get; set; } = 3373542609;

            [JsonProperty(PropertyName = "AnchorMin")]
            public string AnchorMin { get; set; } = "0.5 0";

            [JsonProperty(PropertyName = "AnchorMax")]
            public string AnchorMax { get; set; } = "0.5 0";

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

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

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

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

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

            [JsonProperty(PropertyName = "Standard Recycler Purchase Cost", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<ResourceItem> PurchaseStandardCost { get; set; } =
                new List<ResourceItem>
                {
                    new ResourceItem { Shortname = "scrap", Amount = 1000 },
                    new ResourceItem { Shortname = "sheetmetal", Amount = 50 },
                    new ResourceItem { Shortname = "metal.refined", Amount = 100 },
                    new ResourceItem { Shortname = "metal.fragments", Amount = 500 },
                };

            [JsonProperty(PropertyName = "Industrial Recycler Purchase Cost", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<ResourceItem> PurchaseIndustrialCost { get; set; } =
                new List<ResourceItem>
                {
                    new ResourceItem { Shortname = "scrap", Amount = 2000 },
                    new ResourceItem { Shortname = "sheetmetal", Amount = 100 },
                    new ResourceItem { Shortname = "metal.refined", Amount = 200 },
                    new ResourceItem { Shortname = "metal.fragments", Amount = 1000 },
                };

            [JsonProperty(PropertyName = "Version")]
            public VersionNumber Version { get; set; } = new VersionNumber(1, 5, 4);
        }

        private class ResourceItem
        {
            [JsonProperty(PropertyName = "Shortname")]
            public string Shortname { get; set; }

            [JsonProperty(PropertyName = "Amount")]
            public int Amount { get; set; }
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _config = Config.ReadObject<Configuration>();
                bool configChanged = false;

                if (_config == null)
                    LoadDefaultConfig();

                if (_config.Version == null || _config.Version < Version)
                {
                    PrintWarning("Updating configuration file to new version.");
                    _config.Version = Version;
                    configChanged = true;
                }

                if (configChanged)
                {
                    PrintWarning("Configuration changes detected, saving changes.");
                    SaveConfig();
                }
            }
            catch (Exception ex)
            {
                PrintError($"Error reading config: {ex.Message}");
                LoadDefaultConfig();
            }
            SaveConfig();
        }

        protected override void LoadDefaultConfig()
        {
            PrintWarning("Creating a new configuration file");
            _config = new Configuration();
            _config.Version = Version;
        }

        protected override void SaveConfig() => Config.WriteObject(_config, true);
        #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.",
                    ["RecyclerUpgraded"] = "Recycler successfully upgraded to industrial.",
                    ["InventoryFull"] = "Your inventory is full! The recycler has been dropped on the ground.",
                    ["StandardRecyclerReceived"] = "You have received a standard recycler!",
                    ["IndustrialRecyclerReceived"] = "You have received an industrial recycler!",
                    ["PurchaseRecyclerTitle"] = "Purchase Recycler",
                    ["PurchaseIndustrialRecyclerTitle"] = "Purchase Industrial Recycler",
                    ["PurchaseCosts"] = "Purchase Costs:",
                    ["ConfirmPurchase"] = "Confirm Purchase",
                    ["Close"] = "Close",
                    ["PurchaseSuccessStandard"] = "You have purchased a Recycler!",
                    ["PurchaseSuccessIndustrial"] = "You have purchased an Industrial Recycler!",
                    ["PurchaseFailed"] = "You do not have the required items to purchase this 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.",
                    ["RecyclerUpgraded"] = "Reciclador atualizado com sucesso para industrial.",
                    ["InventoryFull"] = "Seu inventário está cheio! O reciclador foi dropado no chão.",
                    ["StandardRecyclerReceived"] = "Você recebeu um reciclador padrão!",
                    ["IndustrialRecyclerReceived"] = "Você recebeu um reciclador industrial!",
                    ["PurchaseRecyclerTitle"] = "Comprar Reciclador",
                    ["PurchaseIndustrialRecyclerTitle"] = "Comprar Reciclador Industrial",
                    ["PurchaseCosts"] = "Custos de Compra:",
                    ["ConfirmPurchase"] = "Confirmar Compra",
                    ["Close"] = "Fechar",
                    ["PurchaseSuccessStandard"] = "Você comprou um Reciclador!",
                    ["PurchaseSuccessIndustrial"] = "Você comprou um Reciclador Industrial!",
                    ["PurchaseFailed"] = "Você não tem os itens necessários para comprar este Reciclador.",
                },
                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.",
                    ["RecyclerUpgraded"] = "Recycler erfolgreich zu industriellem Recycler aufgerüstet.",
                    ["InventoryFull"] = "Dein Inventar ist voll! Der Recycler wurde auf den Boden geworfen.",
                    ["StandardRecyclerReceived"] = "Du hast einen Standard-Recycler erhalten!",
                    ["IndustrialRecyclerReceived"] = "Du hast einen industriellen Recycler erhalten!",
                    ["PurchaseRecyclerTitle"] = "Recycler kaufen",
                    ["PurchaseIndustrialRecyclerTitle"] = "Industriellen Recycler kaufen",
                    ["PurchaseCosts"] = "Kaufkosten:",
                    ["ConfirmPurchase"] = "Kauf bestätigen",
                    ["Close"] = "Schließen",
                    ["PurchaseSuccessStandard"] = "Sie haben einen Recycler gekauft!",
                    ["PurchaseSuccessIndustrial"] = "Sie haben einen Industriellen Recycler gekauft!",
                    ["PurchaseFailed"] = "Sie haben nicht die erforderlichen Gegenstände, um diesen Recycler zu kaufen.",
                },
                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.",
                    ["RecyclerUpgraded"] = "Reciclador mejorado con éxito a industrial.",
                    ["InventoryFull"] = "¡Tu inventario está lleno! El reciclador se ha dejado caer al suelo.",
                    ["StandardRecyclerReceived"] = "¡Has recibido un reciclador estándar!",
                    ["IndustrialRecyclerReceived"] = "¡Has recibido un reciclador industrial!",
                    ["PurchaseRecyclerTitle"] = "Comprar Reciclador",
                    ["PurchaseIndustrialRecyclerTitle"] = "Comprar Reciclador Industrial",
                    ["PurchaseCosts"] = "Costos de Compra:",
                    ["ConfirmPurchase"] = "Confirmar Compra",
                    ["Close"] = "Cerrar",
                    ["PurchaseSuccessStandard"] = "¡Has comprado un Reciclador!",
                    ["PurchaseSuccessIndustrial"] = "¡Has comprado un Reciclador Industrial!",
                    ["PurchaseFailed"] = "No tienes los objetos necesarios para comprar este Reciclador.",
                },
                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
    }
}