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

namespace Oxide.Plugins
{
    [Info("Quarry Computer", "Marte6", "1.0.0")]
    [Description("Quarry in a Computer with configurations and UI")]
    public class QuarryComputer : CovalencePlugin
    {
        [PluginReference]
        private Plugin ImageLibrary = null,
            LangAPI = null,
            Economics = null,
            ServerRewards = null,
            Notify = null;

        #region Constants
        private const ulong QuarrySkinId = 3341965622;

        private const string BaseItemName = "arcade.machine.chippy";
        private const string BaseDeployedItemName = "chippyarcademachine";
        private const string QuarryPrefab = "assets/content/structures/excavator/prefabs/excavatorsignalcomputer.prefab";
        private const string QuarryOutputStoragePrefab = "assets/prefabs/deployable/hot air balloon/subents/hab_storage.prefab";
        private const string QuarryFuelStoragePrefab = "assets/content/vehicles/boats/rowboat/subents/rowboat_storage.prefab";
        private const string StorageAdaptorPrefab = "assets/prefabs/deployable/playerioents/industrialadaptors/storageadaptor.deployed.prefab";
        private const string ButtonPrefab = "assets/prefabs/io/electric/switches/pressbutton/pressbutton.prefab";
        private const string GroundLossFx = "assets/prefabs/tools/pager/effects/beep.prefab";
        private const string QuarryTurnedOnFx = "assets/prefabs/npc/autoturret/effects/targetacquired.prefab";
        private const string QuarryUpgradedFx = "assets/bundled/prefabs/fx/build/promote_toptier.prefab";

        private const string UiBackground = "6iYcl0UppZxwaotNu6kghxTF7s8a4r4d";
        private const string UiMainPanel = "VsUNAYUweeIfW5qvG1zHcQ09hy06GGFi";
        private const string UiLeftPanel = "6k3PPUUOyd8A6RgqGBan9cMLn59GTzRS";
        private const string UiRightPanel = "o9C5OcIYWCoOsRpcULoyfKPGGxb8LAoJ";
        private const string UiPopupOverlayBlock = "Mu8AqzfN17Djw3TstzIsjnJqKi4W5n5j";
        private const string UiPopupShadow = "JVQZBnIR9vmF844dkfYsuOymwWBTCeJV";
        private const string UiPopupBorder = "tmZZ1WqIBuBlqvNeabQH6D6wvK5Gfz7u";
        private const string UiPopupBackground = "3kNfPEASr9RkcBTB9vtMLoxkpKfqc4FU";
        private const string UiButtonStart = "TB9vtMLoxk3kNfPEASr9RkcBpKfqc4FU";

        private const string CommandUiClose = "3bizteteO5ic8z82GJHeQx77hlDMLcI9";
        private const string CommandUiStart = "n4DHuNvEGFpIW2gCuZ4ZftJzQBLHpZUx";
        private const string CommandUiRemove = "fl6n4DHugCuZKCrLHpZUF4ZNvEGIW2px";
        private const string CommandUiConfirmRemove = "PTZdQzOalxnZFiwDkiKgMdk1wbuG6T64";
        private const string CommandUiCancelRemove = "09stuljgGWtJzQBvFevq2chgWvZNvodr";
        private const string CommandUiUpgrade = "mSjiZxaTD4hdq9hUHcLGdQviIPzfksgK";
        private const string CommandUiConfirmUpgrade = "gTpJiMwJn7JSbSWFr8fSjY4nHg72Fryl";
        private const string CommandUiCancelUpgrade = "B3xei8Zq8VVr4zptxJxYxEfW3sh40x82";

        private const Item.Flag ExtractedItemFlag = (Item.Flag)(1 << 78);
        #endregion

        #region Fields
        private static QuarryComputer _instance;
        private int _maxLevel;
        private readonly List<string> _permissions = new() { "quarrycomputer.give", "quarrycomputer.industrial", "quarrycomputer.vip", "quarrycomputer.norequirements", "quarrycomputer.admin" };
        #endregion

        #region Cache
        private readonly HashSet<ulong> _playersWithPopup = new();
        private readonly HashSet<ulong> _itemContainersFuel = new();
        private readonly HashSet<ulong> _itemContainersOutput = new();
        private readonly HashSet<ulong> _buttons = new();
        private readonly HashSet<ulong> _adaptors = new();
        private readonly Dictionary<ulong, QuarryComputerBehaviour> _openedQuarryInterfaces = new();
        #endregion

        #region Game Hooks
        private void OnServerInitialized()
        {
            _instance = this;

            if (ImageLibrary == null)
            {
                PrintError("ImageLibrary is not loaded!");
                PrintError("Plugin has been disabled!");
                Interface.Oxide.UnloadPlugin(Title);
                return;
            }

            var items = GetItemsForValidation();

            if (!ValidateItemsCounts())
            {
                PrintError("Invalid Configuration File!");
                PrintError("The maximum number of items per configuration must not exceed 12.");
                PrintError("Plugin has been disabled!");
                Interface.Oxide.UnloadPlugin(Title);
                return;
            }

            if (!ValidateConfigItems(items))
            {
                PrintError("Invalid Configuration File!");
                PrintError("Plugin has been disabled!");
                Interface.Oxide.UnloadPlugin(Title);
                return;
            }

            ImageLibrary?.Call("LoadImageList", Title, items, null);
            ImageLibrary?.Call("AddImage", "https://i.ibb.co/C22VZDj/money.png", "Economics");
            ImageLibrary?.Call("AddImage", "https://i.ibb.co/C22VZDj/money.png", "ServerRewards");

            RegisterPermissions();
            RegisterCommands();
            SetMaxLevel();
            LoadData();
            InitializeExistingQuarries();
        }

        private void OnServerSave() => SaveData();

        private void Unload()
        {
            DestroyAllUis();
            UnloadAllQuarryBehaviours();
            SaveData();
        }

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

            if (player == null || entity == null || entity.ShortPrefabName != BaseDeployedItemName || entity.skinID != QuarrySkinId)
                return;

            int playerQuarryLimit = _configs.Profiles.Default.QuarryLimit;

            if (permission.UserHasPermission(player.UserIDString, "quarrycomputer.vip"))
                playerQuarryLimit = _configs.Profiles.VIP.QuarryLimit;

            int level = RemoveItemData(player.GetActiveItem().uid.Value);

            if (!player.IsBuildingAuthed() || GetPlayerQuarryCount(player.userID) >= playerQuarryLimit)
            {
                entity?.Kill();

                NotifyPlayer(player.IPlayer, !player.IsBuildingAuthed() ? "NoBuildingAuth" : "QuarryLimitReached", 1);
                timer.Once(1, () => GiveQuarryItem(player, level));
                return;
            }

            if (!DeployQuarry(player, entity, level))
            {
                entity?.Kill();

                NotifyPlayer(player.IPlayer, "NoFundation", 1);
                timer.Once(1, () => GiveQuarryItem(player, level));
                return;
            }

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

        private object OnEntityKill(ExcavatorSignalComputer signalComputer)
        {
            if (signalComputer.OwnerID != 0)
            {
                RemoveQuarryData(signalComputer.OwnerID, signalComputer.net.ID.Value);

                if (_configs.DropItem)
                {
                    var quarry = signalComputer.GetComponent<QuarryComputerBehaviour>();
                    if (quarry != null)
                    {
                        var item = CreateQuarryItem(signalComputer.OwnerID, quarry.Level);
                        item.Drop(signalComputer.transform.position, new Vector3(0, 1, 0));
                        Effect.server.Run(GroundLossFx, signalComputer.transform.position);
                    }
                }
            }

            return null;
        }

        private ItemContainer.CanAcceptResult? CanAcceptItem(ItemContainer container, Item item)
        {
            if (_itemContainersFuel.Contains(container.uid.Value) && item.info.shortname != _configs.FuelConfiguration.Shortname)
                return ItemContainer.CanAcceptResult.CannotAccept;

            if (_itemContainersOutput.Contains(container.uid.Value) && !item.flags.HasFlag(ExtractedItemFlag))
                return ItemContainer.CanAcceptResult.CannotAccept;

            return null;
        }

        private object CanPickupEntity(BasePlayer player, IndustrialStorageAdaptor entity)
        {
            return _adaptors.Contains(entity.net.ID.Value) ? (object)false : null;
        }

        private object OnExcavatorSuppliesRequest(ExcavatorSignalComputer computer, BasePlayer player)
        {
            return _pluginData.QuarriesData.ContainsKey(computer.net.ID.Value) ? (object)false : null;
        }

        private object OnButtonPress(PressButton button, BasePlayer player)
        {
            if (_buttons.Contains(button.net.ID.Value))
            {
                var outputContainer = button.GetParentEntity();
                var quarry = outputContainer.GetComponentInParent<QuarryComputerBehaviour>();

                if (quarry.OwnerID != player.userID && !permission.UserHasPermission(player.UserIDString, "quarrycomputer.admin"))
                {
                    NotifyPlayer(player, "NotYours", 1);
                    return null;
                }

                _openedQuarryInterfaces.Add(player.userID, quarry);
                CreateUi(player, quarry);
            }

            return null;
        }
        #endregion

        #region Data
        private StoredData _pluginData;

        private class StoredData
        {
            [JsonProperty("Quarries")]
            public Dictionary<ulong, QuarriesData> QuarriesData = new();

            [JsonProperty("Items")]
            public Dictionary<ulong, ItemsData> ItemsData = new();
        }

        private class QuarriesData
        {
            [JsonProperty("OwnerId")]
            public ulong OwnerId { get; set; }

            [JsonProperty("Level")]
            public int Level { get; set; }
        }

        private class ItemsData
        {
            [JsonProperty("OwnerId")]
            public ulong OwnerId { get; set; }

            [JsonProperty("Level")]
            public int Level { get; set; }
        }

        private void SaveData()
        {
            if (_pluginData != null)
            {
                Interface.Oxide.DataFileSystem.WriteObject($"{Name}_Data", _pluginData, true);
            }
        }

        private void LoadData()
        {
            _pluginData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>($"{Name}_Data");
            if (_pluginData == null)
            {
                _pluginData = new StoredData();
                SaveData();
            }
        }

        private void AddQuarryData(ulong ownerId, ulong netId, int level = 1)
        {
            if (!_pluginData.QuarriesData.ContainsKey(netId))
            {
                _pluginData.QuarriesData[netId] = new QuarriesData { OwnerId = ownerId, Level = level };
            }
            else
            {
                var quarryData = _pluginData.QuarriesData[netId];
                quarryData.OwnerId = ownerId;
                quarryData.Level = level;
            }
        }

        private void UpdateQuarryLevelData(ulong netId, int newLevel)
        {
            if (_pluginData.QuarriesData.ContainsKey(netId))
            {
                var quarryData = _pluginData.QuarriesData[netId];
                quarryData.Level = newLevel;
            }
        }

        private int GetQuarryLevelData(ulong netId)
        {
            return _pluginData.QuarriesData.ContainsKey(netId) ? _pluginData.QuarriesData[netId].Level : 1;
        }

        private int GetPlayerQuarryCount(ulong ownerId)
        {
            return _pluginData.QuarriesData.Values.Count(quarry => quarry.OwnerId == ownerId);
        }

        private void RemoveQuarryData(ulong ownerId, ulong netId)
        {
            _pluginData.QuarriesData.Remove(netId);
        }

        private void AddItemData(ulong itemId, ulong userId, int level)
        {
            if (level > 1)
            {
                _pluginData.ItemsData[itemId] = new ItemsData { OwnerId = userId, Level = level };
            }
        }

        private int RemoveItemData(ulong itemId)
        {
            if (_pluginData.ItemsData.TryGetValue(itemId, out ItemsData data))
            {
                _pluginData.ItemsData.Remove(itemId);
                return data.Level;
            }

            return 1;
        }
        #endregion

        #region Configuration
        private Configuration _configs;

        private class Configuration
        {
            [JsonProperty("Use LangAPI Plugin")]
            public bool UseLangAPI { get; set; }

            [JsonProperty("Use Notify Plugin")]
            public bool UseNotify { get; set; }

            [JsonProperty("Give Quarry Command")]
            public string[] GiveQuarryCommand { get; set; }

            [JsonProperty("Drop Quarry Item on Ground Loss")]
            public bool DropItem { get; set; }

            [JsonProperty("Fuel Configuration")]
            public FuelConfiguration FuelConfiguration { get; set; }

            [JsonProperty("No Upgrades (Level 1)")]
            public NoUpgrades NoUpgrades { get; set; }

            [JsonProperty("Upgrades (Level 2+)")]
            public List<UpgradeOption> Upgrades { get; set; }

            [JsonProperty("Keep Upgrades On Remove Quarry")]
            public bool KeepUpgradesOnRemoval { get; set; }

            [JsonProperty("Profiles")]
            public UserProfiles Profiles { get; set; }

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

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

            [JsonProperty("Resource Extraction Interval In Seconds")]
            public int ResourceExtractionInterval { get; set; }

            [JsonProperty("Fuel Required Per Extraction")]
            public int FuelRequiredPerExtraction { get; set; }
        }

        private class NoUpgrades
        {
            [JsonProperty("Storage Slots")]
            public StorageSlots StorageSlots { get; set; }

            [JsonProperty("Resources Output Per Extraction")]
            public List<ResourceItem> ResourcesOutput { get; set; }
        }

        private class StorageSlots
        {
            [JsonProperty("Fuel")]
            public int FuelSlots { get; set; }

            [JsonProperty("Resources")]
            public int OutputSlots { get; set; }
        }

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

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

        private class UpgradeOption
        {
            [JsonProperty("Storage Slots")]
            public StorageSlots StorageSlots { get; set; }

            [JsonProperty("Resources Output Per Extraction")]
            public List<ResourceItem> ResourcesOutput { get; set; }

            [JsonProperty("Required Items To Upgrade")]
            public List<ResourceItem> UpgradeItems { get; set; }
        }

        private class UserProfiles
        {
            [JsonProperty("Default (No Permission)")]
            public UserProfile Default { get; set; }

            [JsonProperty("VIP (quarrycomputer.vip Permission)")]
            public UserProfile VIP { get; set; }
        }

        private class UserProfile
        {
            [JsonProperty("Quarry Limit")]
            public int QuarryLimit { get; set; }
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _configs = Config.ReadObject<Configuration>();
                if (_configs != null)
                {
                    if (_configs.Version < Version)
                        UpdateConfigValues();

                    SaveConfig();
                }
                else
                    throw new Exception();
            }
            catch
            {
                PrintError("There is an error in your configuration file. Default values will be used.");
                LoadDefaultConfig();
            }
        }

        protected override void LoadDefaultConfig() => _configs = CreateDefaultConfig();

        protected override void SaveConfig() => Config.WriteObject(_configs);

        private void UpdateConfigValues()
        {
            PrintWarning("Configuration change detected! Updating settings.");

            _configs.Version = Version;

            PrintWarning("Configuration update completed!");
        }

        private Configuration CreateDefaultConfig()
        {
            return new Configuration
            {
                GiveQuarryCommand = new[] { "gq", "givequarry" },
                DropItem = true,
                UseLangAPI = true,
                UseNotify = true,
                FuelConfiguration = new FuelConfiguration
                {
                    Shortname = "charcoal",
                    ResourceExtractionInterval = 40,
                    FuelRequiredPerExtraction = 100,
                },
                NoUpgrades = new NoUpgrades
                {
                    StorageSlots = new StorageSlots { FuelSlots = 1, OutputSlots = 6 },
                    ResourcesOutput = new List<ResourceItem>
                    {
                        new ResourceItem { Shortname = "crude.oil", Amount = 4 },
                        new ResourceItem { Shortname = "stones", Amount = 35 },
                        new ResourceItem { Shortname = "metal.ore", Amount = 7 },
                        new ResourceItem { Shortname = "sulfur.ore", Amount = 7 },
                    },
                },
                Upgrades = new List<UpgradeOption>
                {
                    new UpgradeOption
                    {
                        StorageSlots = new StorageSlots { FuelSlots = 3, OutputSlots = 12 },
                        ResourcesOutput = new List<ResourceItem>
                        {
                            new ResourceItem { Shortname = "crude.oil", Amount = 6 },
                            new ResourceItem { Shortname = "stones", Amount = 50 },
                            new ResourceItem { Shortname = "metal.ore", Amount = 10 },
                            new ResourceItem { Shortname = "sulfur.ore", Amount = 10 },
                        },
                        UpgradeItems = new List<ResourceItem>
                        {
                            new ResourceItem { Shortname = "Economics", Amount = 25000 },
                            new ResourceItem { Shortname = "scrap", Amount = 1000 },
                            new ResourceItem { Shortname = "pumpkin", Amount = 100 },
                        },
                    },
                    new UpgradeOption
                    {
                        StorageSlots = new StorageSlots { FuelSlots = 6, OutputSlots = 24 },
                        ResourcesOutput = new List<ResourceItem>
                        {
                            new ResourceItem { Shortname = "crude.oil", Amount = 10 },
                            new ResourceItem { Shortname = "hq.metal.ore", Amount = 10 },
                            new ResourceItem { Shortname = "metal.ore", Amount = 30 },
                            new ResourceItem { Shortname = "stones", Amount = 100 },
                            new ResourceItem { Shortname = "sulfur.ore", Amount = 25 },
                        },
                        UpgradeItems = new List<ResourceItem>
                        {
                            new ResourceItem { Shortname = "scrap", Amount = 5000 },
                            new ResourceItem { Shortname = "pumpkin", Amount = 1000 },
                            new ResourceItem { Shortname = "cloth", Amount = 10000 },
                            new ResourceItem { Shortname = "red.berry", Amount = 1000 },
                            new ResourceItem { Shortname = "bone.fragments", Amount = 1000 },
                        },
                    },
                },
                KeepUpgradesOnRemoval = true,
                Profiles = new UserProfiles
                {
                    Default = new UserProfile { QuarryLimit = 1 },
                    VIP = new UserProfile { QuarryLimit = 3 },
                },
            };
        }
        #endregion

        #region Localization
        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(
                new Dictionary<string, string>
                {
                    ["NoPermission"] = "You don't have permission to use this command.",
                    ["NoBuildingAuth"] = "You must have building auth to use this.",
                    ["NoFundation"] = "Must be placed on a foundation.",
                    ["QuarryLimitReached"] = "Quarry Limit Reached.",
                    ["NotMiningQuarry"] = "This object is not a Mining Quarry.",
                    ["NotOwner"] = "This object belongs to another player.",
                    ["QuarryRemoved"] = "Mining Quarry removed.",
                    ["NoQuarriesFound"] = "No quarries found.",
                    ["QuarrySend"] = "You have sent a Mining Quarry to the player.",
                    ["QuarryReceived"] = "You have received a Mining Quarry.",
                    ["QuarryDropped"] = "No space in inventory! Quarry dropped on the ground.",
                    ["PlayerNotFound"] = "Player not found.",
                    ["TurnedOn"] = "Quarry turned on.",
                    ["AlreadyOn"] = "The Quarry is already on.",
                    ["NoFuel"] = "Not enough fuel to start extraction.",
                    ["NoSpace"] = "Not enough space in output for extracted resources.",
                    ["NotEmpty"] = "The Quarry still contains items. Empty it before removal.",
                    ["UpgradeRequirements"] = "You do not have the required items to upgrade.",
                    ["QuarryTurnedOn"] = "Quarry turned on.",
                    ["ExtractionIntervalText"] = "Extraction Interval: {0} seconds | Fuel per extraction: {1} {2}",
                    ["StartButton"] = "Start",
                    ["RemoveButton"] = "Remove",
                    ["UpgradeButton"] = "Upgrade",
                    ["UpgradeToLevel"] = "Upgrade to Level {0}",
                    ["StorageHeader"] = "Storage",
                    ["FuelSlots"] = "Fuel: {0} slots",
                    ["ResourceSlots"] = "Resource: {0} slots",
                    ["ExtractionHeader"] = "Extraction",
                    ["RequirementsHeader"] = "Requirements",
                    ["YesButton"] = "Yes",
                    ["NoButton"] = "No",
                    ["ConfirmRemove"] = "Remove the Quarry?",
                    ["ConfirmUpgrade"] = "Upgrade the Quarry?",
                    ["QuarryLevel"] = "Quarry Level {0}",
                },
                this
            );

            lang.RegisterMessages(
                new Dictionary<string, string>
                {
                    ["NoPermission"] = "Você não tem permissão.",
                    ["NoBuildingAuth"] = "Você precisa de permissão para construir.",
                    ["NoFundation"] = "Deve ser colocado em uma fundação.",
                    ["QuarryLimitReached"] = "Limite de Quarries alcançado.",
                    ["NotMiningQuarry"] = "Este não é uma Quarry.",
                    ["NotOwner"] = "Isto pertence a outro jogador.",
                    ["QuarryRemoved"] = "Quarry removida.",
                    ["NoQuarriesFound"] = "Nenhuma Quarry encontrada.",
                    ["QuarrySend"] = "Quarry enviada ao jogador.",
                    ["QuarryReceived"] = "Você recebeu uma Quarry.",
                    ["QuarryDropped"] = "Inventário cheio! Quarry no chão.",
                    ["PlayerNotFound"] = "Jogador não encontrado.",
                    ["TurnedOn"] = "Quarry ligada.",
                    ["AlreadyOn"] = "A Quarry já está ligada.",
                    ["NoFuel"] = "Combustível insuficiente.",
                    ["NoSpace"] = "Espaço insuficiente para recursos.",
                    ["NotEmpty"] = "Esvazie a Quarry antes de remover.",
                    ["UpgradeRequirements"] = "Itens insuficientes para atualização.",
                    ["QuarryTurnedOn"] = "Quarry ligada.",
                    ["ExtractionIntervalText"] = "Intervalo: {0}s | Combustível: {1} {2}",
                    ["StartButton"] = "Iniciar",
                    ["RemoveButton"] = "Remover",
                    ["UpgradeButton"] = "Atualizar",
                    ["UpgradeToLevel"] = "Atualizar para Nível {0}",
                    ["StorageHeader"] = "Armazém",
                    ["FuelSlots"] = "Fuel: {0} slots",
                    ["ResourceSlots"] = "Recursos: {0} slots",
                    ["ExtractionHeader"] = "Extração",
                    ["RequirementsHeader"] = "Requisitos",
                    ["YesButton"] = "Sim",
                    ["NoButton"] = "Não",
                    ["ConfirmRemove"] = "Remover a Quarry?",
                    ["ConfirmUpgrade"] = "Atualizar a Quarry?",
                    ["QuarryLevel"] = "Quarry Nível: {0}",
                },
                this,
                "pt-BR"
            );

            lang.RegisterMessages(
                new Dictionary<string, string>
                {
                    ["NoPermission"] = "Du hast keine Berechtigung.",
                    ["NoBuildingAuth"] = "Du benötigst eine Baugenehmigung.",
                    ["NoFundation"] = "Muss auf einem Fundament platziert werden.",
                    ["QuarryLimitReached"] = "Steinbruchgrenze Erreicht",
                    ["NotMiningQuarry"] = "Dies ist keine Quarry.",
                    ["NotOwner"] = "Dies gehört einem anderen Spieler.",
                    ["QuarryRemoved"] = "Quarry entfernt.",
                    ["NoQuarriesFound"] = "Keine Quarry gefunden.",
                    ["QuarrySend"] = "Quarry an den Spieler gesendet.",
                    ["QuarryReceived"] = "Du hast eine Quarry erhalten.",
                    ["QuarryDropped"] = "Inventar voll! Quarry auf dem Boden.",
                    ["PlayerNotFound"] = "Spieler nicht gefunden.",
                    ["TurnedOn"] = "Quarry eingeschaltet.",
                    ["AlreadyOn"] = "Die Quarry ist bereits eingeschaltet.",
                    ["NoFuel"] = "Nicht genug Treibstoff.",
                    ["NoSpace"] = "Nicht genug Platz für Ressourcen.",
                    ["NotEmpty"] = "Entleere die Quarry, bevor du sie entfernst.",
                    ["UpgradeRequirements"] = "Du hast nicht die notwendigen Gegenstände zum Aufrüsten.",
                    ["QuarryTurnedOn"] = "Quarry eingeschaltet.",
                    ["ExtractionIntervalText"] = "Intervall: {0}s | Treibstoff: {1} {2}",
                    ["StartButton"] = "Starten",
                    ["RemoveButton"] = "Entfernen",
                    ["UpgradeButton"] = "Aufrüsten",
                    ["UpgradeToLevel"] = "Aufrüsten auf Stufe {0}",
                    ["StorageHeader"] = "Lager",
                    ["FuelSlots"] = "Treibstoff: {0} Plätze",
                    ["ResourceSlots"] = "Ressourcen: {0} Plätze",
                    ["ExtractionHeader"] = "Extraktion",
                    ["RequirementsHeader"] = "Anforderungen",
                    ["YesButton"] = "Ja",
                    ["NoButton"] = "Nein",
                    ["ConfirmRemove"] = "Quarry entfernen?",
                    ["ConfirmUpgrade"] = "Quarry aufrüsten?",
                    ["QuarryLevel"] = "Stufe: {0}",
                },
                this,
                "de"
            );
        }

        public string GetItemDisplayName(BasePlayer player, string itemShortName)
        {
            var itemDefinition = ItemManager.FindItemDefinition(itemShortName);
            var displayName = itemDefinition.displayName.translated;

            if (_configs.UseLangAPI && LangAPI != null && LangAPI.Call<bool>("IsDefaultDisplayName", displayName))
            {
                var displayNameTranslated = LangAPI.Call<string>("GetItemDisplayName", itemShortName, displayName, player.UserIDString);
                if (!string.IsNullOrEmpty(displayNameTranslated))
                    return displayNameTranslated;
            }

            return displayName;
        }
        #endregion

        #region UiCommands
        [Command(CommandUiStart)]
        private void UiStartCommand(IPlayer player)
        {
            var basePlayer = player.Object as BasePlayer;
            if (!_openedQuarryInterfaces.TryGetValue(basePlayer.userID, out var quarry))
                return;

            if (!quarry.HasFuel())
            {
                DestroyUi(basePlayer);
                NotifyPlayer(player, "NoFuel", 1);
                return;
            }

            if (!quarry.HasSpace())
            {
                DestroyUi(basePlayer);
                NotifyPlayer(player, "NoSpace", 1);
                return;
            }

            if (quarry.TurnOn())
            {
                NotifyPlayer(player, "TurnedOn", 0);
                Effect.server.Run(QuarryTurnedOnFx, basePlayer.GetNetworkPosition());
            }
            else
            {
                DestroyUi(basePlayer);
                NotifyPlayer(player, "AlreadyOn", 0);
            }

            CuiHelper.DestroyUi(basePlayer, UiButtonStart);
        }

        [Command(CommandUiClose)]
        private void UiCloseCommand(IPlayer player)
        {
            var basePlayer = player.Object as BasePlayer;
            DestroyUi(basePlayer);
        }

        [Command(CommandUiRemove)]
        private void UiRemoveCommand(IPlayer player)
        {
            var basePlayer = player.Object as BasePlayer;
            ToggleConfirmationPopup(basePlayer, Msg(basePlayer.UserIDString, "ConfirmRemove"), CommandUiConfirmRemove, CommandUiCancelRemove);
        }

        [Command(CommandUiConfirmRemove)]
        private void UiConfirmRemoveCommand(IPlayer player)
        {
            var basePlayer = player.Object as BasePlayer;
            if (!_openedQuarryInterfaces.TryGetValue(basePlayer.userID, out var quarry))
                return;

            if (!quarry.IsEmpty())
            {
                DestroyUi(basePlayer);
                NotifyPlayer(basePlayer, "NotEmpty", 1);
                return;
            }

            RemoveQuarryData(quarry.SignalComputer.OwnerID, quarry.SignalComputer.net.ID.Value);

            Unsubscribe(nameof(OnEntityKill));
            quarry.SignalComputer.Kill();
            Subscribe(nameof(OnEntityKill));

            GiveQuarryItem(basePlayer, quarry.Level);
            NotifyPlayer(basePlayer, "QuarryRemoved", 0);
            DestroyUi(basePlayer);
        }

        [Command(CommandUiCancelRemove)]
        private void UiCancelRemoveCommand(IPlayer player)
        {
            var basePlayer = player.Object as BasePlayer;
            ClosePopup(basePlayer);
        }

        [Command(CommandUiUpgrade)]
        private void UiUpgradeCommand(IPlayer player)
        {
            var basePlayer = player.Object as BasePlayer;
            ToggleConfirmationPopup(basePlayer, Msg(basePlayer.UserIDString, "ConfirmUpgrade"), CommandUiConfirmUpgrade, CommandUiCancelUpgrade);
        }

        [Command(CommandUiConfirmUpgrade)]
        private void UiConfirmUpgradeCommand(IPlayer player)
        {
            var basePlayer = player.Object as BasePlayer;
            if (!_openedQuarryInterfaces.TryGetValue(basePlayer.userID, out var quarry))
                return;

            if (!permission.UserHasPermission(basePlayer.UserIDString, "quarrycomputer.norequirements"))
            {
                if (quarry.UpgradeRequirements != null && !CheckAndRemoveUpgradeItems(basePlayer, quarry.UpgradeRequirements.UpgradeItems))
                {
                    NotifyPlayer(player, "UpgradeRequirements", 1);
                    ClosePopup(basePlayer);
                    return;
                }
            }

            quarry.Upgrade();
            Effect.server.Run(QuarryUpgradedFx, basePlayer.GetNetworkPosition());

            DestroyUi(basePlayer);
        }

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

            int economicsValue = ExtractAndRemoveValue(requiredItemMap, "Economics");
            int serverRewardsValue = ExtractAndRemoveValue(requiredItemMap, "ServerRewards");

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

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

                if (!CheckBalance(player, economicsValue, serverRewardsValue))
                    return false;

                if (!WithdrawFunds(player, economicsValue, serverRewardsValue))
                    return false;

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

        private int ExtractAndRemoveValue(Dictionary<string, int> itemMap, string key)
        {
            if (itemMap.Remove(key, out var value))
                return value;

            return 0;
        }

        private bool CheckBalance(BasePlayer player, int economicsValue, int serverRewardsValue)
        {
            return (economicsValue == 0 || CheckEconomicsBalance(player, economicsValue)) && (serverRewardsValue == 0 || CheckServerRewardsBalance(player, serverRewardsValue));
        }

        private bool WithdrawFunds(BasePlayer player, int economicsValue, int serverRewardsValue)
        {
            return (economicsValue == 0 || WithdrawEconomicsFunds(player, economicsValue)) && (serverRewardsValue == 0 || WithdrawServerRewardsFunds(player, serverRewardsValue));
        }

        private bool CheckEconomicsBalance(BasePlayer player, int economicsValue)
        {
            if (Economics == null)
                return true;

            double balance = Convert.ToDouble(Economics?.CallHook("Balance", player.userID));
            if (balance < economicsValue)
            {
                return false;
            }
            return true;
        }

        private bool CheckServerRewardsBalance(BasePlayer player, int serverRewardsValue)
        {
            if (ServerRewards == null)
                return true;

            double balance = Convert.ToDouble(ServerRewards?.CallHook("CheckPoints", player.userID));
            if (balance < serverRewardsValue)
            {
                return false;
            }
            return true;
        }

        private bool WithdrawEconomicsFunds(BasePlayer player, int economicsValue)
        {
            if (Economics == null)
                return true;

            if (!Convert.ToBoolean(Economics?.CallHook("Withdraw", player.userID, (double)economicsValue)))
            {
                return false;
            }
            return true;
        }

        private bool WithdrawServerRewardsFunds(BasePlayer player, int serverRewardsValue)
        {
            if (ServerRewards == null)
                return true;

            if (!Convert.ToBoolean(ServerRewards?.CallHook("TakePoints", player.userID, serverRewardsValue)))
            {
                return false;
            }
            return true;
        }

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

        [Command(CommandUiCancelUpgrade)]
        private void UiCancelUpgradeCommand(IPlayer player)
        {
            var basePlayer = player.Object as BasePlayer;
            ClosePopup(basePlayer);
        }
        #endregion

        #region Commands
        private void GiveQuarryCommand(IPlayer iPlayer, string command, string[] args)
        {
            var player = iPlayer?.Object as BasePlayer;
            if (player == null || args == null || args.Length < 1)
                return;

            if (!iPlayer.HasPermission("quarrycomputer.give"))
            {
                NotifyPlayer(iPlayer, "NoPermission", 1);
                return;
            }

            var targetPlayer = BasePlayer.Find(args[0]);
            if (targetPlayer == null)
            {
                NotifyPlayer(iPlayer, "PlayerNotFound", 1);
                return;
            }

            if (GiveQuarryItem(targetPlayer, 1))
            {
                NotifyPlayer(iPlayer, "QuarrySend", 0);
                NotifyPlayer(targetPlayer, "QuarryReceived", 0);
            }
            else
            {
                NotifyPlayer(iPlayer, "QuarrySend", 0);
                NotifyPlayer(targetPlayer, "QuarryReceived", 0);
                NotifyPlayer(targetPlayer, "QuarryDropped", 1);
            }
        }
        #endregion

        #region UI Handling
        private void CreateUi(BasePlayer player, QuarryComputerBehaviour quarry)
        {
            var fuelName = GetItemDisplayName(player, _configs.FuelConfiguration.Shortname);
            var container = new CuiElementContainer();
            AddBackground(container);
            AddMainPanel(player, container, fuelName);
            AddLeftPanel(player, container, quarry);

            if (quarry.UpgradeRequirements != null)
                AddRightPanel(player, container, quarry);

            CuiHelper.AddUi(player, container);
        }

        private void AddBackground(CuiElementContainer container)
        {
            AddPanel(container, "0 0", "1 1", UiBackground, "Overlay", "0 0 0 0.9", true);
        }

        private void AddMainPanel(BasePlayer player, CuiElementContainer container, string fuelName)
        {
            string resourceExtractionInterval = Msg(
                player.UserIDString,
                "ExtractionIntervalText",
                _configs.FuelConfiguration.ResourceExtractionInterval,
                _configs.FuelConfiguration.FuelRequiredPerExtraction,
                fuelName
            );

            AddPanel(container, "0.235 0.18", "0.765 0.784", UiMainPanel, UiBackground, "0.6 0.6 0.6 0.95", true);
            AddInnerSquare(container, UiMainPanel, "0 0.9570", "1 1", "0.39 0.39 0.39 1");
            AddText(container, UiMainPanel, "0.02 0.96", "1 1", resourceExtractionInterval, 13, TextAnchor.MiddleLeft);
            AddCloseButton(container, UiMainPanel);
        }

        private void AddLeftPanel(BasePlayer player, CuiElementContainer container, QuarryComputerBehaviour quarry)
        {
            var outputSlots = quarry.OutputContainer.inventory.capacity;
            var fuelSlots = quarry.FuelContainer.inventory.capacity;

            var parent = UiLeftPanel;
            AddPanel(container, "0 0", "0.5 0.94", parent, UiMainPanel, "0 0 0 0");
            AddPanelStructure(container, parent, Msg(player.UserIDString, "QuarryLevel", quarry.Level), "0 0 1 1");

            AddText(container, parent, "0.38 0.679", "1 1", Msg(player.UserIDString, "StorageHeader"), 13, TextAnchor.MiddleLeft);
            AddText(container, parent, "0.09 0.590", "1 1", Msg(player.UserIDString, "FuelSlots", fuelSlots), 13, TextAnchor.MiddleLeft);
            AddText(container, parent, "0.45 0.590", "1 1", Msg(player.UserIDString, "ResourceSlots", outputSlots), 13, TextAnchor.MiddleLeft);

            AddHorizontalLine(container, parent, "0.06 0.769", "0.89 0.77");

            AddText(container, parent, "0.34 0.490", "1 1", Msg(player.UserIDString, "ExtractionHeader"), 13, TextAnchor.MiddleLeft);

            AddDictionaryWithSpacing(container, parent, quarry.ResourcesOutput, 0.18f, 0.725f, 0.065f);

            AddButton(container, parent, Msg(player.UserIDString, "StartButton"), "", "0.15", "0.11", "0.45", "0.16", "0.6 0.6 0.6 0.95", "0.4 0.4 0.4 1");
            if (!quarry.IsOn)
                AddButton(container, parent, Msg(player.UserIDString, "StartButton"), CommandUiStart, "0.15", "0.11", "0.45", "0.16", "0.6 0.6 0.6 0.95", "0 1 0 1", buttonName: UiButtonStart);

            AddButton(container, parent, Msg(player.UserIDString, "RemoveButton"), CommandUiRemove, "0.50", "0.11", "0.80", "0.16", "0.6 0.6 0.6 0.95", "1 0 0 1");
        }

        private void AddRightPanel(BasePlayer player, CuiElementContainer container, QuarryComputerBehaviour quarry)
        {
            var parent = UiRightPanel;
            AddPanel(container, "0.5 0", "1 0.94", parent, UiMainPanel, "0 0 0 0");

            string upgradeTitle = Msg(player.UserIDString, "UpgradeToLevel", quarry.Level + 1);
            AddPanelStructure(container, parent, upgradeTitle, "0.5 0 0 1");

            AddText(container, parent, "0.38 0.679", "1 1", Msg(player.UserIDString, "StorageHeader"), 13, TextAnchor.MiddleLeft);
            AddText(container, parent, "0.09 0.590", "1 1", Msg(player.UserIDString, "FuelSlots", quarry.UpgradeRequirements.StorageSlots.FuelSlots), 13, TextAnchor.MiddleLeft);
            AddText(container, parent, "0.45 0.590", "1 1", Msg(player.UserIDString, "ResourceSlots", quarry.UpgradeRequirements.StorageSlots.OutputSlots), 13, TextAnchor.MiddleLeft);

            AddHorizontalLine(container, parent, "0.06 0.769", "0.89 0.77");

            AddText(container, parent, "0.34 0.490", "1 1", Msg(player.UserIDString, "ExtractionHeader"), 13, TextAnchor.MiddleLeft);

            AddDictionaryWithSpacing(container, parent, quarry.UpgradeRequirements.ResourcesOutput, 0.18f, 0.725f, 0.065f);

            AddHorizontalLine(container, parent, "0.06 0.464", "0.89 0.465");

            AddText(container, parent, "0.34 0.0", "1 0.88", Msg(player.UserIDString, "RequirementsHeader"), 13, TextAnchor.MiddleLeft);

            AddDictionaryWithSpacing(container, parent, quarry.UpgradeRequirements.UpgradeItems, 0.18f, 0.419f, 0.065f);

            AddButton(container, parent, Msg(player.UserIDString, "UpgradeButton"), CommandUiUpgrade, "0.35", "0.11", "0.65", "0.16", "0.6 0.6 0.6 0.95", "0 1 0 1");
        }

        private void AddPanelStructure(CuiElementContainer container, string parent, string title, string titleColor)
        {
            AddInnerSquare(container, parent, "0.10 0.05", "0.95 0.90", "0 0 0 0.97");
            AddInnerSquare(container, parent, "0.054 0.1033", "0.895 0.945", "0.8 0.8 0.8 1");
            AddInnerSquare(container, parent, "0.06 0.108", "0.89 0.94", titleColor);
            AddHorizontalLine(container, parent, "0.06 0.865", "0.89 0.865");

            AddText(container, parent, "0.1 0.84", "0.84 0.98", title, 15, TextAnchor.MiddleCenter);
        }

        private void AddDictionaryWithSpacing(CuiElementContainer container, string parent, List<ResourceItem> items, float initialAnchorX, float initialAnchorY, float spacing)
        {
            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.07f;
                float imageAnchorMinX = anchorX - (imageSize / 2);
                float imageAnchorMaxX = anchorX + (imageSize / 2);
                float imageAnchorMinY = anchorY + 0.04f - (imageSize / 2);
                float imageAnchorMaxY = anchorY + 0.02f + (imageSize / 2);

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

        private void AddHorizontalLine(CuiElementContainer container, string parent, string anchorMin, string anchorMax)
        {
            AddInnerSquare(container, parent, anchorMin, anchorMax, "0.8 0.8 0.8 1");
        }

        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 AddCloseButton(CuiElementContainer container, string parent)
        {
            container.Add(
                new CuiButton
                {
                    Button =
                    {
                        Color = "1 0 0 1",
                        Command = CommandUiClose,
                        Close = UiBackground,
                    },
                    RectTransform = { AnchorMin = "0.965 0.957", AnchorMax = "1 1" },
                    Text =
                    {
                        Text = "✖",
                        FontSize = 13,
                        Align = TextAnchor.MiddleCenter,
                        Color = "1 1 1 0.5",
                    },
                },
                parent
            );
        }

        private void DestroyUi(BasePlayer player)
        {
            if (player == null)
                return;

            CuiHelper.DestroyUi(player, UiBackground);
            CuiHelper.DestroyUi(player, UiMainPanel);
            CuiHelper.DestroyUi(player, UiLeftPanel);
            CuiHelper.DestroyUi(player, UiRightPanel);
            CuiHelper.DestroyUi(player, UiPopupOverlayBlock);
            CuiHelper.DestroyUi(player, UiPopupShadow);
            CuiHelper.DestroyUi(player, UiPopupBorder);
            CuiHelper.DestroyUi(player, UiPopupBackground);

            _openedQuarryInterfaces.Remove(player.userID);
            _playersWithPopup.Remove(player.userID);
        }

        private void DestroyAllUis()
        {
            foreach (BasePlayer player in BasePlayer.activePlayerList)
                DestroyUi(player);
        }

        private void ToggleConfirmationPopup(BasePlayer player, string message, string confirmCommand, string cancelCommand)
        {
            if (player != null && !_playersWithPopup.Contains(player.userID))
            {
                _playersWithPopup.Add(player.userID);
                ShowConfirmationPopup(player, message, confirmCommand, cancelCommand);
            }
        }

        private void ShowConfirmationPopup(BasePlayer player, string message, string confirmCommand, string cancelCommand)
        {
            var container = new CuiElementContainer();
            AddPanel(container, "0 0", "1 1", UiPopupOverlayBlock, "Overlay", "0 0 0 0.9", true);
            AddPanel(container, "0.4 0.4", "0.6 0.6", UiPopupShadow, UiPopupOverlayBlock, "0 0 0 0.85");
            AddPanel(container, "0.0 0.09", "0.95 1.0", UiPopupBorder, UiPopupShadow, "1 1 1 1");
            AddPanel(container, "0.01 0.01", "0.991 0.985", UiPopupBackground, UiPopupBorder, "0 0 0 1");
            AddText(container, UiPopupBackground, "0 0", "1 1", message, 14, TextAnchor.MiddleCenter);
            AddButton(container, UiPopupBackground, Msg(player.UserIDString, "YesButton"), confirmCommand, "0.25", "0.05", "0.45", "0.20", "0.6 0.6 0.6 0.95", "0 1 0 1");
            AddButton(container, UiPopupBackground, Msg(player.UserIDString, "NoButton"), cancelCommand, "0.55", "0.05", "0.75", "0.20", "0.6 0.6 0.6 0.95", "1 0 0 1");
            CuiHelper.AddUi(player, container);
        }

        private void ClosePopup(BasePlayer player)
        {
            if (player != null)
            {
                CuiHelper.DestroyUi(player, UiPopupOverlayBlock);
                CuiHelper.DestroyUi(player, UiPopupShadow);
                CuiHelper.DestroyUi(player, UiPopupBorder);
                CuiHelper.DestroyUi(player, UiPopupBackground);

                _playersWithPopup.Remove(player.userID);
            }
        }
        #endregion

        #region UI Helpers
        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 AddInnerSquare(CuiElementContainer container, string parent, string anchorMin, string anchorMax, string color)
        {
            container.Add(new CuiPanel { Image = { Color = color }, RectTransform = { AnchorMin = anchorMin, AnchorMax = anchorMax } }, parent);
        }

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

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

        #region Quarry Management Methods
        private bool GiveQuarryItem(BasePlayer player, int level)
        {
            var item = CreateQuarryItem(player.userID, level);

            if (player.inventory.GiveItem(item))
                return true;

            item.Drop(player.inventory.containerMain.dropPosition, player.inventory.containerMain.dropVelocity);
            return false;
        }

        private Item CreateQuarryItem(ulong userId, int level)
        {
            var item = ItemManager.CreateByName(BaseItemName, 1, QuarrySkinId);
            item.name = "Quarry Computer";

            AddItemData(item.uid.Value, userId, level);

            return item;
        }

        private bool DeployQuarry(BasePlayer player, BaseEntity entity, int level)
        {
            if (!TryGetGroundEntity(entity, out var groundEntity))
            {
                return false;
            }

            if (groundEntity is not BuildingBlock)
            {
                return false;
            }

            var quarry = CreateAndPositionQuarry(player, entity, groundEntity);
            ConfigureQuarry(player, quarry, level);

            return true;
        }

        private bool TryGetGroundEntity(BaseEntity entity, out BaseEntity groundEntity)
        {
            Vector3 origin = entity.transform.position + Vector3.up * 0.1f;
            Ray ray = new Ray(origin, Vector3.down);
            if (Physics.Raycast(ray, out RaycastHit hitInfo, 4f, LayerMask.GetMask("Construction")))
            {
                groundEntity = hitInfo.GetEntity();
                return true;
            }

            groundEntity = null;
            return false;
        }

        private BaseEntity CreateAndPositionQuarry(BasePlayer player, BaseEntity entity, BaseEntity groundEntity)
        {
            var quarry = GameManager.server.CreateEntity(QuarryPrefab);
            quarry.OwnerID = player.userID;
            quarry.SetParent(groundEntity);
            quarry.transform.position = entity.transform.position;
            quarry.transform.rotation = entity.transform.rotation * Quaternion.Euler(0, 90, 0);
            quarry.Spawn();
            quarry.SendNetworkUpdateImmediate();
            return quarry;
        }

        private void ConfigureQuarry(BasePlayer player, BaseEntity quarry, int level)
        {
            AttachContainer(quarry, QuarryOutputStoragePrefab, new Vector3(0.05f, 0.75f, -0.21f), new Vector3(0, 180, 0), false, player);
            AttachContainer(quarry, QuarryFuelStoragePrefab, new Vector3(0.05f, 0.75f, 0.32f), new Vector3(90, 0, 0), true, player);

            AddQuarryData(player.userID, quarry.net.ID.Value, level);
            quarry.gameObject.AddComponent<QuarryComputerBehaviour>();
        }

        private void AttachContainer(BaseEntity entity, string prefab, Vector3 localPosition, Vector3 localEulerAngles, bool isFuelContainer, BasePlayer player)
        {
            var container = GameManager.server.CreateEntity(prefab) as StorageContainer;
            container.SetParent(entity);
            container.OwnerID = player.userID;
            container.transform.localPosition = localPosition;
            container.transform.localRotation = Quaternion.Euler(localEulerAngles);
            container.Spawn();
            container.SendNetworkUpdateImmediate();

            if (permission.UserHasPermission(player.UserIDString, "quarrycomputer.industrial"))
            {
                var adaptor = GameManager.server.CreateEntity(StorageAdaptorPrefab, container.transform.position, container.transform.rotation) as IndustrialStorageAdaptor;
                adaptor.SetParent(container, true, true);
                adaptor.OwnerID = player.userID;
                adaptor.transform.localPosition = isFuelContainer ? new Vector3(0.00f, -0.06f, -0.26f) : new Vector3(0.00f, 0.26f, 0.05f);
                adaptor.transform.rotation = container.transform.rotation * Quaternion.Euler(isFuelContainer ? new Vector3(0, 270, 90) : new Vector3(0, 90, 0));
                adaptor.Spawn();
                adaptor.SendNetworkUpdateImmediate();
            }

            if (isFuelContainer)
                return;

            var button = GameManager.server.CreateEntity(ButtonPrefab) as PressButton;
            button.SetParent(container, true, true);
            button.OwnerID = player.userID;
            button.transform.localPosition = new Vector3(0.40f, 1.60f, -0.13f);
            button.transform.rotation = container.transform.rotation * Quaternion.Euler(new Vector3(10, 90, 180));
            button.Spawn();
            button.SendNetworkUpdateImmediate();
        }

        private void HandleFuelConsumption(Item fuel, int requiredFuelAmount)
        {
            if (fuel.amount > requiredFuelAmount)
                SafelyRemoveItem(fuel.SplitItem(requiredFuelAmount));
            else
                SafelyRemoveItem(fuel);
        }

        public static class DictionaryPool<TKey, TValue>
        {
            private static readonly Stack<Dictionary<TKey, TValue>> _pool = new();

            public static Dictionary<TKey, TValue> Get()
            {
                lock (_pool)
                {
                    return _pool.Count > 0 ? _pool.Pop() : new Dictionary<TKey, TValue>();
                }
            }

            public static void Free(Dictionary<TKey, TValue> dictionary)
            {
                dictionary.Clear();
                lock (_pool)
                {
                    _pool.Push(dictionary);
                }
            }
        }

        private bool CanTransferAllResources(StorageContainer outputContainer, List<ResourceItem> resources)
        {
            Dictionary<string, int> stackableItems = DictionaryPool<string, int>.Get();
            int emptySlots = outputContainer.inventory.capacity - outputContainer.inventory.itemList.Count;

            foreach (var item in outputContainer.inventory.itemList)
            {
                if (!stackableItems.ContainsKey(item.info.shortname))
                    stackableItems[item.info.shortname] = 0;

                stackableItems[item.info.shortname] += item.MaxStackable() - item.amount;
            }

            foreach (var resource in resources)
            {
                var itemDef = ItemManager.FindItemDefinition(resource.Shortname);
                if (itemDef == null)
                {
                    DictionaryPool<string, int>.Free(stackableItems);
                    return false;
                }

                int remainingAmount = resource.Amount;
                if (stackableItems.TryGetValue(resource.Shortname, out int currentStackableSpace))
                    remainingAmount -= currentStackableSpace;

                while (remainingAmount > 0 && emptySlots > 0)
                {
                    remainingAmount -= itemDef.stackable;
                    emptySlots--;
                }

                if (remainingAmount > 0)
                {
                    DictionaryPool<string, int>.Free(stackableItems);
                    return false;
                }
            }

            DictionaryPool<string, int>.Free(stackableItems);
            return true;
        }

        private bool TransferResourcesIfPossible(StorageContainer outputContainer, List<ResourceItem> resources)
        {
            if (!CanTransferAllResources(outputContainer, resources))
                return false;

            foreach (var resource in resources)
            {
                var item = ItemManager.CreateByName(resource.Shortname, resource.Amount, 0uL);
                item.SetFlag(ExtractedItemFlag, true);

                if (!item.MoveToContainer(outputContainer.inventory))
                {
                    item.Remove();
                    return false;
                }

                item.SetFlag(ExtractedItemFlag, false);
            }

            return true;
        }

        private void SafelyRemoveItem(Item item)
        {
            item?.RemoveFromWorld();
            item?.RemoveFromContainer();
            item?.Remove();
        }
        #endregion

        #region Utility Methods
        private bool ValidateItemsCounts()
        {
            if (_configs.NoUpgrades.ResourcesOutput.Count > 12)
            {
                return false;
            }

            foreach (var upgrade in _configs.Upgrades)
            {
                if (upgrade.ResourcesOutput.Count > 12)
                {
                    return false;
                }
                if (upgrade.UpgradeItems.Count > 12)
                {
                    return false;
                }
            }

            return true;
        }

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

            foreach (var item in _configs.NoUpgrades.ResourcesOutput)
                itemIcons.Add(new KeyValuePair<string, ulong>(item.Shortname, 0));

            foreach (var upgrade in _configs.Upgrades)
            {
                foreach (var item in upgrade.ResourcesOutput)
                    itemIcons.Add(new KeyValuePair<string, ulong>(item.Shortname, 0));

                foreach (var item in upgrade.UpgradeItems)
                {
                    if (item.Shortname != "Economics" && item.Shortname != "ServerRewards")
                        itemIcons.Add(new KeyValuePair<string, ulong>(item.Shortname, 0));
                }
            }

            itemIcons.Add(new KeyValuePair<string, ulong>(_configs.FuelConfiguration.Shortname, 0));

            return itemIcons;
        }

        private bool ValidateConfigItems(List<KeyValuePair<string, ulong>> items)
        {
            foreach (var item in items)
            {
                if (ItemManager.FindItemDefinition(item.Key) == null)
                {
                    PrintError($"Item '{item.Key}' does not exist.");
                    return false;
                }
            }

            return true;
        }

        private string Msg(string userId, string key, params object[] obj)
        {
            return string.Format(lang.GetMessage(key, this, userId), obj);
        }

        private void NotifyPlayer(IPlayer player, string key, int type, params object[] obj)
        {
            var userIdString = player.Id;
            if (_configs.UseNotify && Notify != null)
                Interface.Oxide.CallHook("SendNotify", userIdString, type, Msg(userIdString, key, obj));
            else
                player.Message(Msg(player.Id, key, obj));
        }

        private void NotifyPlayer(BasePlayer player, string key, int type, params object[] obj)
        {
            var userIdString = player.UserIDString;
            if (_configs.UseNotify && Notify != null)
                Interface.Oxide.CallHook("SendNotify", userIdString, type, Msg(userIdString, key, obj));
            else
                player.ChatMessage(Msg(userIdString, key, obj));
        }

        private void RegisterPermissions()
        {
            foreach (string permissionName in _permissions)
                permission.RegisterPermission(permissionName, this);
        }

        private void RegisterCommands()
        {
            AddCovalenceCommand(_configs.GiveQuarryCommand, nameof(GiveQuarryCommand));
        }

        private void InitializeExistingQuarries()
        {
            var existingNetIds = new HashSet<ulong>();

            foreach (var signalComputer in UnityEngine.Object.FindObjectsOfType<ExcavatorSignalComputer>())
            {
                if (signalComputer.OwnerID != 0)
                {
                    ulong netId = signalComputer.net.ID.Value;
                    ulong ownerId = signalComputer.OwnerID;
                    int level = GetQuarryLevelData(netId);

                    AddQuarryData(ownerId, netId, level);
                    existingNetIds.Add(netId);

                    signalComputer.gameObject.AddComponent<QuarryComputerBehaviour>();
                }
            }

            RemoveMissingQuarries(existingNetIds);
        }

        private void RemoveMissingQuarries(HashSet<ulong> existingNetIds)
        {
            var quarriesToRemove = _pluginData.QuarriesData.Keys.Where(id => !existingNetIds.Contains(id)).ToList();

            foreach (ulong quarryId in quarriesToRemove)
            {
                _pluginData.QuarriesData.Remove(quarryId);
            }
        }

        private void UnloadAllQuarryBehaviours()
        {
            var behaviours = UnityEngine.Object.FindObjectsOfType<QuarryComputerBehaviour>();
            foreach (var behaviour in behaviours)
            {
                behaviour.Unload();
                UnityEngine.Object.Destroy(behaviour);
            }
        }

        private void SetMaxLevel() => _maxLevel = _configs.Upgrades.Count + 1;
        #endregion

        #region Quarry Behaviour Class
        private class QuarryComputerBehaviour : MonoBehaviour
        {
            public ExcavatorSignalComputer SignalComputer { get; private set; }
            public StorageContainer OutputContainer { get; private set; }
            public StorageContainer FuelContainer { get; private set; }
            public List<ResourceItem> ResourcesOutput { get; private set; }
            public UpgradeOption UpgradeRequirements { get; private set; }
            public int Level { get; private set; }
            public ulong OwnerID { get; private set; }
            public bool IsOn { get; private set; } = true;

            private void Awake()
            {
                InitializeSignalComputer();
                UpdateQuarryOwnerId();
                InitializeContainers();
                UpdateQuarryLevel();
                UpdateQuarryResourcesOutput();
                UpdateQuarryCapacity();
                UpdateUpgradeRequirements();
                ScheduleRoutineTasks();
            }

            public bool TurnOn()
            {
                if (!IsInvoking(nameof(PerformResourceExtraction)))
                {
                    IsOn = true;
                    InvokeRepeating(nameof(PerformResourceExtraction), 0f, _instance._configs.FuelConfiguration.ResourceExtractionInterval);
                    return true;
                }

                return false;
            }

            private void InitializeSignalComputer()
            {
                SignalComputer = GetComponent<ExcavatorSignalComputer>();
                SignalComputer.SetFlag(BaseEntity.Flags.Reserved8, false);
                SignalComputer.chargePower = 0f;
            }

            private void UpdateQuarryOwnerId() => OwnerID = SignalComputer.OwnerID;

            private void InitializeContainers()
            {
                foreach (var container in SignalComputer.children.Cast<StorageContainer>())
                {
                    if (container.name == QuarryOutputStoragePrefab || container.name == QuarryFuelStoragePrefab)
                    {
                        if (container.name == QuarryOutputStoragePrefab)
                        {
                            OutputContainer = container;
                            _instance._itemContainersOutput.Add(container.inventory.uid.Value);
                        }
                        else
                        {
                            FuelContainer = container;
                            _instance._itemContainersFuel.Add(container.inventory.uid.Value);
                        }

                        foreach (var entity in container.children)
                        {
                            if (entity is PressButton)
                                _instance._buttons.Add(entity.net.ID.Value);

                            if (entity is IndustrialStorageAdaptor)
                                _instance._adaptors.Add(entity.net.ID.Value);
                        }
                    }
                }
            }

            public void Upgrade()
            {
                var newLevel = Level + 1;
                if (newLevel <= _instance._maxLevel)
                    Level = newLevel;

                _instance.UpdateQuarryLevelData(SignalComputer.net.ID.Value, newLevel);
                UpdateQuarryResourcesOutput();
                UpdateQuarryCapacity();
                UpdateUpgradeRequirements();
            }

            private void UpdateQuarryLevel() => Level = _instance.GetQuarryLevelData(SignalComputer.net.ID.Value);

            private void UpdateQuarryResourcesOutput()
            {
                ResourcesOutput = Level == 1 ? _instance._configs.NoUpgrades.ResourcesOutput : _instance._configs.Upgrades[Level - 2].ResourcesOutput;
            }

            private void UpdateQuarryCapacity()
            {
                if (Level == 1)
                {
                    OutputContainer.inventory.capacity = _instance._configs.NoUpgrades.StorageSlots.OutputSlots;
                    FuelContainer.inventory.capacity = _instance._configs.NoUpgrades.StorageSlots.FuelSlots;
                }
                else
                {
                    OutputContainer.inventory.capacity = _instance._configs.Upgrades[Level - 2].StorageSlots.OutputSlots;
                    FuelContainer.inventory.capacity = _instance._configs.Upgrades[Level - 2].StorageSlots.FuelSlots;
                }
            }

            private void UpdateUpgradeRequirements() => UpgradeRequirements = _instance._configs.Upgrades.ElementAtOrDefault(Level - 1);

            private void ScheduleRoutineTasks()
            {
                InvokeRepeating(nameof(PerformResourceExtraction), 0f, _instance._configs.FuelConfiguration.ResourceExtractionInterval);
            }

            public bool HasFuel()
            {
                var fuelItem = FuelContainer.inventory.itemList.Find(item => item.info.shortname == _instance._configs.FuelConfiguration.Shortname);
                int fuelRequired = _instance._configs.FuelConfiguration.FuelRequiredPerExtraction;

                return fuelItem != null && fuelItem.amount >= fuelRequired;
            }

            public bool HasSpace()
            {
                return _instance.CanTransferAllResources(OutputContainer, ResourcesOutput);
            }

            public bool IsEmpty() => FuelContainer.inventory.itemList.Count == 0 && OutputContainer.inventory.itemList.Count == 0;

            private void PerformResourceExtraction()
            {
                var fuelItem = FuelContainer.inventory.itemList.Find(item => item.info.shortname == _instance._configs.FuelConfiguration.Shortname);
                int fuelRequired = _instance._configs.FuelConfiguration.FuelRequiredPerExtraction;

                if (!HasSufficientFuel(fuelItem, fuelRequired))
                {
                    DeactivateQuarry();
                    return;
                }

                if (!_instance.TransferResourcesIfPossible(OutputContainer, ResourcesOutput))
                {
                    DeactivateQuarry();
                    return;
                }

                _instance.HandleFuelConsumption(fuelItem, fuelRequired);
                ActivateQuarry();
            }

            private bool HasSufficientFuel(Item fuelItem, int fuelRequired) => fuelItem != null && fuelItem.amount >= fuelRequired;

            private void ActivateQuarry()
            {
                SetSignalComputerState(true);
                IsOn = true;
            }

            private void DeactivateQuarry()
            {
                SetSignalComputerState(false);
                IsOn = false;
                CancelInvoke(nameof(PerformResourceExtraction));
            }

            private void SetSignalComputerState(bool isActive)
            {
                SignalComputer.SetFlag(BaseEntity.Flags.Reserved8, isActive);
                SignalComputer.chargePower = isActive ? 1 : 0;
            }

            private void OnDestroy()
            {
                CancelInvoke(nameof(PerformResourceExtraction));
            }

            public void Unload()
            {
                SignalComputer.SetFlag(BaseEntity.Flags.Reserved8, false);
                SignalComputer.chargePower = 0f;
            }
        }
        #endregion
    }
}