package net.minecraft.world.item;

import com.mojang.authlib.GameProfile;

import net.minecraft.core.component.DataComponents;
import net.minecraft.world.entity.EntityTypes;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.IBlockData;
import net.minecraft.world.level.IMaterial;
import net.minecraft.world.level.saveddata.maps.MapId;
import net.minecraft.world.item.ItemWorldMap;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.item.component.TypedEntityData;
import net.minecraft.world.item.component.TooltipDisplay;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTTagLong;
import net.minecraft.nbt.NBTTagInt;
import net.minecraft.nbt.NBTTagByte;
import net.minecraft.nbt.NBTTagString;
import net.minecraft.nbt.GameProfileSerializer;
import net.minecraft.resources.MinecraftKey;

import com.bergerkiller.bukkit.common.nbt.CommonTagCompound;
import com.bergerkiller.generated.com.mojang.authlib.GameProfileHandle;
import com.bergerkiller.generated.net.minecraft.nbt.NBTTagCompoundHandle;
import com.bergerkiller.generated.net.minecraft.resources.MinecraftKeyHandle;
import com.bergerkiller.generated.net.minecraft.world.item.crafting.RecipesFurnaceHandle;
import com.bergerkiller.generated.net.minecraft.world.item.ItemStackHandle;
import com.bergerkiller.generated.net.minecraft.world.level.block.state.IBlockDataHandle;
import com.bergerkiller.bukkit.common.wrappers.ChatText;

class ItemStack {
    #bootstrap com.bergerkiller.bukkit.common.internal.CommonBootstrap.initServer();

    // Required macros to deal with all the NBT tag nonsense pre-1.20.5
#if version >= 1.20.5
    // None yet?
#else
  #if version >= 1.13
    #require net.minecraft.world.item.ItemStack public net.minecraft.nbt.NBTTagCompound getOrCreateTag();
  #else
    #require net.minecraft.world.item.ItemStack public net.minecraft.nbt.NBTTagCompound getOrCreateTag() {
        net.minecraft.nbt.NBTTagCompound tag = instance.getTag();
        if (tag == null) {
            tag = new net.minecraft.nbt.NBTTagCompound();
            instance.setTag(tag);
        }
        return tag;
    }
  #endif

    #require net.minecraft.world.item.ItemStack public void removeTagIfEmpty() {
        net.minecraft.nbt.NBTTagCompound tag = instance.getTag();
        if (tag != null && tag.isEmpty()) {
            instance.setTag((net.minecraft.nbt.NBTTagCompound) null);
        }
    }

    #require net.minecraft.world.item.ItemStack public net.minecraft.nbt.NBTTagCompound getDisplayTag() {
        net.minecraft.nbt.NBTTagCompound tag = instance.getTag();
        if (tag != null) {
            net.minecraft.nbt.NBTBase displayTag = tag.get("display");
            if (displayTag instanceof net.minecraft.nbt.NBTTagCompound) {
                return (net.minecraft.nbt.NBTTagCompound) displayTag;
            }
        }
        return null;
    }
#endif

#if version >= 1.17
    public optional static final (ItemStackHandle) ItemStack OPT_EMPTY_ITEM:EMPTY;
#elseif version >= 1.16
    public optional static final (ItemStackHandle) ItemStack OPT_EMPTY_ITEM:b;
#elseif version >= 1.11
    public optional static final (ItemStackHandle) ItemStack OPT_EMPTY_ITEM:a;
#else
    public optional static final (ItemStackHandle) ItemStack OPT_EMPTY_ITEM:###;
#endif

    private int amountField:count;

    <code>
    public static final ItemStackHandle EMPTY_ITEM;
    static {
        // On 1.11.2, an empty item constant is used. <= 1.9, null is used.
        if (T.OPT_EMPTY_ITEM.isAvailable()) {
            EMPTY_ITEM = T.OPT_EMPTY_ITEM.get();
        } else {
            EMPTY_ITEM = T.createHandle(null, true);
        }
    }
    </code>

    // available since MC 1.11
    public optional boolean isEmpty();

    public (Object) Item getItem();
    public (org.bukkit.Material) Item getTypeField:getItem();

    // Create an ItemStack with the given material type, and initial amount 1
    public static (ItemStackHandle) ItemStack newInstance(org.bukkit.Material type) {
        Item item = org.bukkit.craftbukkit.util.CraftMagicNumbers.getItem(type);
        if (item == null) {
            // This shouldn't be needed, but just in case, do a by-block lookup if not found
            Block block = org.bukkit.craftbukkit.util.CraftMagicNumbers.getBlock(type);
            if (block == null) {
                throw new IllegalArgumentException("Invalid item material type: " + type.name());
            }
#if forge
            return new ItemStack(block, 1);
#elseif version >= 1.13
            return new ItemStack(block, 1);
#elseif version >= 1.11.2
            return new ItemStack(Item.getItemOf(block), 1, 0, false);
#else
            return new ItemStack(block, 1, 0);
#endif
        } else {
#if version >= 1.13
            return new ItemStack((IMaterial) item, 1);
#else
            return new ItemStack(item, 1);
#endif
        }
    }

    public static (ItemStackHandle) ItemStack fromBlockData((IBlockDataHandle) IBlockData data, int amount) {
#if version >= 1.13
        return new ItemStack(data.getBlock(), amount);
#else
        Block block = data.getBlock();
        return new ItemStack(block, amount, data.getBlock().getDropData(data));
#endif
    }

    // This is done on Paper as a fix when calling ItemStack setType(), but is not
    // done on Spigot. This function is called in CommonItemStack setType() to work
    // around this problem.
    //
    // Basically, it ensures the components map is derived from the new item's base components.
    // Without doing this, it breaks serialization of data components after this point.
    public void refreshPatchMap() {
#if !paper && version >= 1.20.5
        net.minecraft.core.component.DataComponentPatch patch = instance.getComponentsPatch();

        net.minecraft.core.component.PatchedDataComponentMap newComponents;
        newComponents = new net.minecraft.core.component.PatchedDataComponentMap(instance.getItem().components());
        #require ItemStack final net.minecraft.core.component.PatchedDataComponentMap components;
        instance#components = newComponents;

        instance.applyComponents(patch);
#endif
    }

#if version >= 1.20.5
    public (ChatText) IChatBaseComponent getCustomName() {
        return (IChatBaseComponent) instance.get(DataComponents.CUSTOM_NAME);
    }
#elseif version >= 1.13
    public (ChatText) IChatBaseComponent getCustomName() {
        // getHoverName() doesn't return null if none is set...
        // ChatSerializer fromJson returns null if parsing the string fails
        NBTTagCompound displayTag = instance#getDisplayTag();
        NBTBase nameTag;
        if (displayTag != null && (nameTag = displayTag.get("Name")) instanceof NBTTagString) {
            String jsonContent = ((NBTTagString) nameTag).getAsString();
            return net.minecraft.network.chat.IChatBaseComponent$ChatSerializer.fromJson(jsonContent);
        }
        return null;
    }
#else
    public (ChatText) String getCustomName() {
        // getName() doesn't return null if none is set...
        NBTTagCompound displayTag = instance#getDisplayTag();
        if (displayTag != null) {
            NBTBase nameTag;
            if ((nameTag = displayTag.get("Name")) instanceof NBTTagString) {
                return ((NBTTagString) nameTag).getAsString();
            }

  #if version >= 1.11
            NBTBase locName;
            if ((locName = displayTag.get("LocName")) instanceof NBTTagString) {
                String localeKey = ((NBTTagString) locName).getAsString();
                return net.minecraft.locale.LocaleI18n.get(localeKey);
            }
  #endif
        }
        return null;
   }
#endif

#if version >= 1.20.5
    public (ChatText) IChatBaseComponent getDisplayName() {
        IChatBaseComponent customName = instance.get(DataComponents.CUSTOM_NAME);
        if (customName == null) {
            customName = instance.getItem().getName(instance);
        }
        return customName;
    }
    public boolean hasCustomName() {
        return instance.has(DataComponents.CUSTOM_NAME);
    }
    public void setCustomName:setHoverName((ChatText) IChatBaseComponent name) {
        if (name != null) {
            instance.set(DataComponents.CUSTOM_NAME, name);
        } else {
            instance.remove(DataComponents.CUSTOM_NAME);
        }
    }
#elseif version >= 1.18
    public (ChatText) IChatBaseComponent getDisplayName:getHoverName();
    public boolean hasCustomName:hasCustomHoverName();
    public (void) ItemStack setCustomName:setHoverName((ChatText) IChatBaseComponent name);
#elseif version >= 1.13
    public (ChatText) IChatBaseComponent getDisplayName:getName();
    public boolean hasCustomName:hasName();
    public (void) ItemStack setCustomName:a((ChatText) IChatBaseComponent name);
#else
    public (ChatText) String getDisplayName:getName();
    public boolean hasCustomName:hasName();
    public void setCustomName((ChatText) String name) {
  #if version >= 1.11
        if (name != null) {
            instance.g(name);
        } else {
            instance.s();
        }
  #else
        if (name != null) {
            instance.c(name);
        } else {
            instance.r();
        }
  #endif
    }
#endif

#if version >= 1.21.5
    #require ItemStack public java.util.LinkedHashSet getHiddenToolTipComponents() {
        TooltipDisplay display = (TooltipDisplay) instance.get(DataComponents.TOOLTIP_DISPLAY);
        java.util.LinkedHashSet hiddenDataComponents = new java.util.LinkedHashSet();
        if (display.hideTooltip()) {
            hiddenDataComponents.addAll(display.hiddenComponents());
        }
        return hiddenDataComponents;
    }

    public void hideTooltip() {
        TooltipDisplay display = (TooltipDisplay) instance.get(DataComponents.TOOLTIP_DISPLAY);
        if (display == null) {
            instance.set(DataComponents.TOOLTIP_DISPLAY, new TooltipDisplay(true, new java.util.LinkedHashSet()));
        } else if (!display.hideTooltip()) {
            instance.set(DataComponents.TOOLTIP_DISPLAY, new TooltipDisplay(true, display.hiddenComponents()));
        }
    }

    public void hideAllAttributes() {
        java.util.LinkedHashSet hiddenDataComponents = instance#getHiddenToolTipComponents();
        java.util.Iterator allDataComponentsIter = net.minecraft.core.registries.BuiltInRegistries.DATA_COMPONENT_TYPE.iterator();
        while (allDataComponentsIter.hasNext()) {
            net.minecraft.core.component.DataComponentType type = (net.minecraft.core.component.DataComponentType) allDataComponentsIter.next();

            if (
                type == DataComponents.LORE ||
                type == DataComponents.CUSTOM_NAME ||
                type == DataComponents.BREAK_SOUND ||
                type == DataComponents.TOOLTIP_DISPLAY ||
                type == DataComponents.ITEM_MODEL ||
                type == DataComponents.DAMAGE ||
                type == DataComponents.MAX_STACK_SIZE ||
                type == DataComponents.MAX_DAMAGE ||
                type == DataComponents.RARITY ||
                type == DataComponents.TOOL ||
                type == DataComponents.WEAPON ||
                type == DataComponents.ENCHANTABLE ||
                type == DataComponents.REPAIRABLE
            ) {
                continue;
            }

            if (instance.has(type)) {
                hiddenDataComponents.add(type);
            }
        }

        instance.set(DataComponents.TOOLTIP_DISPLAY, new TooltipDisplay(false, hiddenDataComponents));
    }
#elseif version >= 1.20.5
    public void hideTooltip() {
  #if version >= 1.21.5
        instance.set(DataComponents.HIDE_TOOLTIP, new TooltipDisplay(true, ));
  #else
        instance.set(DataComponents.HIDE_TOOLTIP, net.minecraft.util.Unit.INSTANCE);
  #endif
    }

    public void hideAllAttributes() {
        // Hide additional tooltip
        {
            instance.set(DataComponents.HIDE_ADDITIONAL_TOOLTIP, net.minecraft.util.Unit.INSTANCE);
        }

        // Hide unbreakable
        {
            net.minecraft.world.item.component.Unbreakable unbreakable = (net.minecraft.world.item.component.Unbreakable) instance.get(DataComponents.UNBREAKABLE);
            if (unbreakable != null) {
                instance.set(DataComponents.UNBREAKABLE, unbreakable.withTooltip(false));
            }
        }

        // Hide enchantments
        {
            net.minecraft.world.item.enchantment.ItemEnchantments enchants = (net.minecraft.world.item.enchantment.ItemEnchantments) instance.get(DataComponents.ENCHANTMENTS);
            if (enchants == null) {
                enchants = net.minecraft.world.item.enchantment.ItemEnchantments.EMPTY;
            }
            instance.set(DataComponents.ENCHANTMENTS, enchants.withTooltip(false));
        }

        // Hide dyed color
        {
            net.minecraft.world.item.component.DyedItemColor dye = (net.minecraft.world.item.component.DyedItemColor) instance.get(DataComponents.DYED_COLOR);
            if (dye != null) {
                instance.set(DataComponents.DYED_COLOR, dye.withTooltip(false));
            }
        }

        // Hide armor trim
  #if version >= 1.21.2
        {
            net.minecraft.world.item.equipment.trim.ArmorTrim trim = (net.minecraft.world.item.equipment.trim.ArmorTrim) instance.get(DataComponents.TRIM);
            if (trim != null) {
                instance.set(DataComponents.TRIM, trim.withTooltip(false));
            }
        }
  #else
        {
            net.minecraft.world.item.armortrim.ArmorTrim trim = (net.minecraft.world.item.armortrim.ArmorTrim) instance.get(DataComponents.TRIM);
            if (trim != null) {
                instance.set(DataComponents.TRIM, trim.withTooltip(false));
            }
        }
  #endif

        // Hide item attributes in tooltip
        {
            net.minecraft.world.item.component.ItemAttributeModifiers modifiers = (net.minecraft.world.item.component.ItemAttributeModifiers) instance.get(DataComponents.ATTRIBUTE_MODIFIERS);
            if (modifiers == null) {
                modifiers = net.minecraft.world.item.component.ItemAttributeModifiers.EMPTY;
            }
            instance.set(DataComponents.ATTRIBUTE_MODIFIERS, modifiers.withTooltip(false));
        }
    }
#else
    #require ItemStack public void hideAllAttributesImpl() {
        // Use Bukkit ItemFlag API to hide all attributes in an easy way
        // This still works here
        org.bukkit.inventory.ItemStack itemStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(instance);
        org.bukkit.inventory.meta.ItemMeta meta = itemStack.getItemMeta();
        if (meta == null) {
            return; // Fail silently I guess?
        }
        meta.addItemFlags(org.bukkit.inventory.ItemFlag.values());
        itemStack.setItemMeta(meta);
    }

    public void hideAllAttributes() {
        instance#hideAllAttributesImpl();
    }

    public void hideTooltip() {
        // On this version we must hide all attributes
        instance#hideAllAttributesImpl();

        // Set custom name empty as well
        // Seems to be no way to truly hide it
  #if version >= 1.19
        instance.setHoverName(IChatBaseComponent.literal(""));
  #elseif version >= 1.18
        instance.setHoverName(new net.minecraft.network.chat.ChatComponentText(""));
  #elseif version >= 1.13
        instance.a(new net.minecraft.network.chat.ChatComponentText(""));
  #elseif version >= 1.11
        StringBuilder str = new StringBuilder(org.bukkit.ChatColor.RESET.toString());
        str.append((char) 0);
        instance.g(str.toString());
  #else
        StringBuilder str = new StringBuilder(org.bukkit.ChatColor.RESET.toString());
        str.append((char) 0);
        instance.c(str.toString());
  #endif
    }
#endif

    public void addGlint() {
#if version >= 1.20.5
        instance.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, Boolean.TRUE);
#else
        // Use Bukkit API to check for enchantments. If there are any, do nothing,
        // as glint already exists. If not, add a dummy enchantment and modify the
        // item flags to hide it in the tooltip.
        org.bukkit.inventory.ItemStack itemStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(instance);
        org.bukkit.inventory.meta.ItemMeta meta = itemStack.getItemMeta();
        if (meta == null) {
            return; // Fail silently I guess?
        }
        if (meta.hasEnchants()) {
            return; // No work needed
        }

        meta.addEnchant(org.bukkit.enchantments.Enchantment.ARROW_DAMAGE, 1, true);

        org.bukkit.inventory.ItemFlag[] flagsToAdd = new org.bukkit.inventory.ItemFlag[1];
        flagsToAdd[0] = org.bukkit.inventory.ItemFlag.HIDE_ENCHANTS;
        meta.addItemFlags(flagsToAdd);

        itemStack.setItemMeta(meta);
#endif
    }

#if version >= 1.20.5
    public (List<ChatText>) List<IChatBaseComponent> getLores() {
        net.minecraft.world.item.component.ItemLore lore;
        lore = (net.minecraft.world.item.component.ItemLore) instance.get(DataComponents.LORE);
        if (lore != null) {
            return lore.lines();
        } else {
            return java.util.Collections.emptyList();
        }
    }
    public void addLore((ChatText) IChatBaseComponent line) {
        net.minecraft.world.item.component.ItemLore lore;
        lore = (net.minecraft.world.item.component.ItemLore) instance.get(DataComponents.LORE);
        if (lore == null) {
            lore = net.minecraft.world.item.component.ItemLore.EMPTY;
        }
        lore = lore.withLineAdded(line);
        instance.set(DataComponents.LORE, lore);
    }
    public void clearLores() {
        instance.remove(DataComponents.LORE);
    }
#else
    public (List<ChatText>) List<IChatBaseComponent> getLores() {
        NBTTagCompound displayTag = instance#getDisplayTag();
        if (displayTag == null) {
            return java.util.Collections.emptyList();
        }

        NBTBase loreListBase = displayTag.get("Lore");
        if (!(loreListBase instanceof NBTTagList)) {
            return java.util.Collections.emptyList();
        }

        NBTTagList loreList = (NBTTagList) loreListBase;
        int loreCount = loreList.size();
        java.util.List result = new java.util.ArrayList(loreCount);
        for (int i = 0; i < loreCount; i++) {
            NBTBase loreLine = loreList.get(i);
            if (loreLine instanceof NBTTagString) {
                String line = ((NBTTagString) loreLine).getAsString();
                IChatBaseComponent lineComponent;
  #if version >= 1.20.5
                // Decode JSON into chat component (uses Minecraft's main server registry)
                lineComponent = net.minecraft.network.chat.IChatBaseComponent$ChatSerializer.fromJson(line,
                        net.minecraft.server.MinecraftServer.getDefaultRegistryAccess());
  #elseif version >= 1.14
                // Decode JSON into chat component
                lineComponent = net.minecraft.network.chat.IChatBaseComponent$ChatSerializer.fromJson(line);
  #else
                // Wrap String message into chat component
                // Has a lot of bukkit-esque crap in it, so we use our own ChatText for parsing it
                ChatText text = ChatText.fromMessage(line);
                lineComponent = (text == null) ? null : (IChatBaseComponent) text.getRawHandle();
  #endif
                if (lineComponent != null) {
                    result.add(lineComponent);
                }
            }
        }
        return java.util.Collections.unmodifiableList(result);
    }

    public void addLore((ChatText) IChatBaseComponent line) {
        // All this to retrieve the list of lore line tags
        NBTTagCompound tag = instance#getOrCreateTag();
        NBTBase displayBase = tag.get("display");
        if (!(displayBase instanceof NBTTagCompound)) {
            displayBase = new NBTTagCompound();
            tag.put("display", displayBase);
        }
        NBTTagCompound display = (NBTTagCompound) displayBase;
        NBTBase loreListBase = display.get("Lore");
        if (!(loreListBase instanceof NBTTagList)) {
            loreListBase = new NBTTagList();
            display.put("Lore", loreListBase);
        }
        NBTTagList loreList = (NBTTagList) loreListBase;

  #if version >= 1.14
        // Add a String entry to the list - Encode as JSON
        if (line != null) {
            String json = net.minecraft.network.chat.IChatBaseComponent$ChatSerializer.toJson(line);
            if (json != null) {
    #if version >= 1.15
                loreList.add((NBTBase) NBTTagString.valueOf(json));
    #else
                loreList.add((NBTBase) new NBTTagString(json));
    #endif
            }
        }
  #else
        // Add a String entry to the list - Encode as Bukkit legacy Message
        // Use ChatText for this as it contains a lot of hacky crap
        ChatText text = ChatText.fromComponent(line);
        if (text != null) {
            loreList.add((NBTBase) new NBTTagString(text.getMessage()));
        }
  #endif
    }

    public void clearLores() {
        NBTTagCompound tag = instance.getTag();
        if (tag != null) {
            NBTBase displayBase = tag.get("display");
            if (displayBase instanceof NBTTagCompound) {
                NBTTagCompound display = (NBTTagCompound) displayBase;
                display.remove("Lore");
                if (display.isEmpty()) {
                    tag.remove("display");
                    instance#removeTagIfEmpty();
                }
            }
        }
    }
#endif


#if version >= 1.18
    public int getDamageValue();
#elseif version >= 1.13
    public int getDamageValue:getDamage();
#else
    public int getDamageValue:getData();
#endif
    public void setDamageValue(int damage) {
#if version >= 1.20.5
        instance.setDamageValue(damage);
#elseif version >= 1.13
        // Only set when damage > 0 or a tag exists
        // This prevents creating a NBT tag for storing damage 0
        if (damage > 0 || instance.getTag() != null) {
  #if version >= 1.18
            instance.setDamageValue(damage);
  #else
            instance.setDamage(damage);
  #endif
        }
#else
        #require net.minecraft.world.item.ItemStack private int durabilityField:damage;
        instance#durabilityField = damage;
#endif
    }


    public boolean isUnbreakable() {
#if version >= 1.20.5
        return instance.has(DataComponents.UNBREAKABLE);
#else
        NBTTagCompound tag = instance.getTag();
        if (tag != null) {
            NBTBase unbreakable = tag.get("Unbreakable");
            if (unbreakable instanceof NBTTagByte) {
                return ((NBTTagByte) unbreakable).getAsByte() != 0;
            }
        }
        return false;
#endif
    }
    public void setUnbreakable(boolean unbreakable) {
#if version >= 1.21.5
        if (unbreakable) {
            instance.set(DataComponents.UNBREAKABLE, net.minecraft.util.Unit.INSTANCE);
        } else {
            instance.remove(DataComponents.UNBREAKABLE);
        }
#elseif version >= 1.20.5
        // Preserve tooltip yes/no by only checking it changed
        if (unbreakable != instance.has(DataComponents.UNBREAKABLE)) {
            if (unbreakable) {
                instance.set(DataComponents.UNBREAKABLE, new net.minecraft.world.item.component.Unbreakable(false));
            } else {
                instance.remove(DataComponents.UNBREAKABLE);
            }
        }
#else
        NBTTagCompound tag = instance#getOrCreateTag();
        if (unbreakable) {
            tag.putBoolean("Unbreakable", true);
        } else {
            tag.remove("Unbreakable");
            instance#removeTagIfEmpty();
        }
#endif
    }


#if version >= 1.20.5
    public int getRepairCost() {
        Integer cost = (Integer) instance.get(DataComponents.REPAIR_COST);
        return (cost != null) ? cost.intValue() : 0;
    }
    public void setRepairCost(int cost) {
        if (cost > 0) {
            instance.set(DataComponents.REPAIR_COST, Integer.valueOf(cost));
        } else {
            instance.remove(DataComponents.REPAIR_COST);
        }
    }
#else
  #if version >= 1.18
    public int getRepairCost:getBaseRepairCost();
  #else
    public int getRepairCost();
  #endif
    public void setRepairCost(int cost);
#endif


    public int getMapColor() {
#if version >= 1.20.5
        net.minecraft.world.item.component.MapItemColor color;
        color = (net.minecraft.world.item.component.MapItemColor) instance.get(DataComponents.MAP_COLOR);
        return (color == null) ? -1 : color.rgb();
#else
        NBTTagCompound displayTag = instance#getDisplayTag();
        if (displayTag != null) {
            NBTBase color = displayTag.get("MapColor");
            if (color instanceof NBTTagInt) {
                return ((NBTTagInt) color).getAsInt();
            }
        }
        return -1;
#endif
    }
    public void setMapColor(int rgb) {
#if version >= 1.20.5
        if (rgb != -1) {
            instance.set(DataComponents.MAP_COLOR, new net.minecraft.world.item.component.MapItemColor(rgb));
        } else {
            instance.remove(DataComponents.MAP_COLOR);
        }
#else
        if (rgb != -1) {
            NBTTagCompound tag = instance#getOrCreateTag();
            NBTBase displayTag = tag.get("display");
            if (!(displayTag instanceof NBTTagCompound)) {
                displayTag = new NBTTagCompound();
                tag.put("display", displayTag);
            }
            ((NBTTagCompound) displayTag).putInt("MapColor", rgb);
        } else {
            NBTTagCompound tag = instance.getTag();
            if (tag != null) {
                NBTBase baseDisplayTag = tag.get("display");
                if (baseDisplayTag instanceof NBTTagCompound) {
                    NBTTagCompound displayTag = (NBTTagCompound) baseDisplayTag;
                    displayTag.remove("MapColor");
                    if (displayTag.isEmpty()) {
                        tag.remove("display");
                        instance#removeTagIfEmpty();
                    }
                }
            }
        }
#endif
    }


    public int getLeatherArmorColor() {
        int rgb = 5190175; // default brown
#if version >= 1.20.5
        net.minecraft.world.item.component.DyedItemColor color = (net.minecraft.world.item.component.DyedItemColor) instance.get(DataComponents.DYED_COLOR);
        if (color != null) {
            rgb = color.rgb();
        }
#else
        NBTTagCompound displayTag = instance#getDisplayTag();
        if (displayTag != null) {
            NBTBase color = displayTag.get("color");
            if (color instanceof NBTTagInt) {
                rgb = ((NBTTagInt) color).getAsInt();
            }
        }
#endif
        return rgb;
    }


    public int getPotionColor() {
#if version >= 1.20.5
        net.minecraft.world.item.alchemy.PotionContents potioncontents = (net.minecraft.world.item.alchemy.PotionContents) instance.get(DataComponents.POTION_CONTENTS);
        if (potioncontents != null) {
            return potioncontents.getColor() & 0x00FFFFFF; // Only keep RGB, omit alpha
        } else {
            return 0x385DC6;
        }
#else
        NBTTagCompound tag = instance.getTag();
        if (tag != null) {
            NBTBase potionColor = tag.get("CustomPotionColor");
            if (potionColor instanceof NBTTagInt) {
                return ((NBTTagInt) potionColor).getAsInt();
            }
        }

        //TODO: Base color based on potion type?
        org.bukkit.craftbukkit.inventory.CraftItemStack item = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(instance);
        int durability = (int) item.getDurability();
        return com.bergerkiller.bukkit.common.map.util.ModelInfoLookup.getPotionColor(durability);
#endif
    }


    public int getFireworksFlightDuration() {
#if version >= 1.20.5
        net.minecraft.world.item.component.Fireworks fireworks;
        fireworks = (net.minecraft.world.item.component.Fireworks) instance.get(DataComponents.FIREWORKS);
        return (fireworks != null) ? fireworks.flightDuration() : 0;
#else
        NBTTagCompound tag = instance.getTag();
        if (tag != null) {
            NBTBase fireworksTagBase = tag.get("Fireworks");
            if (fireworksTagBase instanceof NBTTagCompound) {
                NBTBase flightDurBase = ((NBTTagCompound) fireworksTagBase).get("Flight");
                if (flightDurBase instanceof NBTTagByte) {
                    return (int) ((NBTTagByte) flightDurBase).getAsByte() & 0XFF;
                }
            }
        }
        return 0;
#endif
    }
    public void setFireworksFlightDuration(int duration) {
#if version >= 1.20.5
        if (duration > 0) {
            net.minecraft.world.item.component.Fireworks fireworks;
            fireworks = (net.minecraft.world.item.component.Fireworks) instance.get(DataComponents.FIREWORKS);
            if (fireworks != null) {
                fireworks = new net.minecraft.world.item.component.Fireworks(duration, fireworks.explosions());
            } else {
                fireworks = new net.minecraft.world.item.component.Fireworks(duration, java.util.Collections.emptyList());
            }
            instance.set(DataComponents.FIREWORKS, fireworks);
        } else {
            instance.remove(DataComponents.FIREWORKS);
        }
#else
        if (duration > 0) {
            NBTTagCompound tag = instance#getOrCreateTag();
            NBTBase fireworksTagBase = tag.get("Fireworks");
            if (!(fireworksTagBase instanceof NBTTagCompound)) {
                fireworksTagBase = new NBTTagCompound();
                tag.put("Fireworks", fireworksTagBase);
            }
            ((NBTTagCompound) fireworksTagBase).putByte("Flight", (byte) duration);
        } else {
            NBTTagCompound tag = instance.getTag();
            if (tag != null) {
                NBTBase fireworksTagBase = tag.get("Fireworks");
                if (fireworksTagBase instanceof NBTTagCompound) {
                    tag.remove("Fireworks");
                    instance#removeTagIfEmpty();
                }
            }
        }
#endif
    }


    public (GameProfileHandle) GameProfile getSkullProfile() {
#if version >= 1.21.9
        net.minecraft.world.item.component.ResolvableProfile resolvableProfile;
        resolvableProfile = (net.minecraft.world.item.component.ResolvableProfile) instance.get(DataComponents.PROFILE);
        return (resolvableProfile == null) ? null : resolvableProfile.partialProfile();
#elseif version >= 1.20.5
        net.minecraft.world.item.component.ResolvableProfile resolvableProfile;
        resolvableProfile = (net.minecraft.world.item.component.ResolvableProfile) instance.get(DataComponents.PROFILE);
        return (resolvableProfile == null) ? null : resolvableProfile.gameProfile();
#else
        NBTTagCompound tag = instance.getTag();
        if (tag == null) {
            return null;
        }
        NBTBase serializedProfileBase = tag.get("SkullOwner");
        if (!(serializedProfileBase instanceof NBTTagCompound)) {
            return null;
        }
        NBTTagCompound serializedProfile = (NBTTagCompound) serializedProfileBase;
  #if version >= 1.18
        return GameProfileSerializer.readGameProfile(serializedProfile);
  #else
        return GameProfileSerializer.deserialize(serializedProfile);
  #endif
#endif
    }

    public void setSkullProfile((GameProfileHandle) GameProfile profile) {
#if version >= 1.21.9
        if (profile != null) {
            instance.set(DataComponents.PROFILE, net.minecraft.world.item.component.ResolvableProfile.createResolved(profile));
        } else {
            instance.remove(DataComponents.PROFILE);
        }
#elseif version >= 1.20.5
        if (profile != null) {
            instance.set(DataComponents.PROFILE, new net.minecraft.world.item.component.ResolvableProfile(profile));
        } else {
            instance.remove(DataComponents.PROFILE);
        }
#else
        if (profile != null) {
            NBTTagCompound tag = instance#getOrCreateTag();
  #if version >= 1.18
            NBTTagCompound serializedProfile = GameProfileSerializer.writeGameProfile(new NBTTagCompound(), profile);
  #else
            NBTTagCompound serializedProfile = GameProfileSerializer.serialize(new NBTTagCompound(), profile);
  #endif
            tag.put("SkullOwner", (NBTBase) serializedProfile);
    #if version < 1.13
            instance#durabilityField = 3;
    #endif
        } else {
            NBTTagCompound tag = instance.getTag();
            if (tag != null) {
                tag.remove("SkullOwner");
                instance#removeTagIfEmpty();
    #if version < 1.13
                instance#durabilityField = 0;
    #endif
            }
        }
#endif
    }

    // Sometimes we want read-only access to the tag. Frustrated since 1.21.9 when getUnsafe() was removed.
#if version >= 1.21.9
    #require net.minecraft.world.item.component.CustomData private final readonly NBTTagCompound cmd_tag:tag;
#endif

#if version >= 1.21.2
    public boolean hasItemModelSet() {
        return instance.hasNonDefault(DataComponents.ITEM_MODEL);
    }

    public (MinecraftKeyHandle) MinecraftKey getItemModel() {
        return (MinecraftKey) instance.get(DataComponents.ITEM_MODEL);
    }

    public (MinecraftKeyHandle) MinecraftKey getItemModelIfSet() {
        if (instance.hasNonDefault(DataComponents.ITEM_MODEL)) {
            return (MinecraftKey) instance.get(DataComponents.ITEM_MODEL);
        } else {
            return null;
        }
    }

    public void setItemModel((MinecraftKeyHandle) MinecraftKey key) {
        if (key != null) {
            instance.set(DataComponents.ITEM_MODEL, key);
        } else {
            instance.remove(DataComponents.ITEM_MODEL);
        }
    }
#else
    public boolean hasItemModelSet() {
        return false;
    }

    public (MinecraftKeyHandle) MinecraftKey getItemModel() {
        return null;
    }

    public (MinecraftKeyHandle) MinecraftKey getItemModelIfSet() {
        return null;
    }

    public void setItemModel((MinecraftKeyHandle) MinecraftKey key) {
    }
#endif

#if version >= 1.21.4
    public boolean hasCustomModelData() {
        return instance.has(DataComponents.CUSTOM_MODEL_DATA);
    }

    public boolean hasCustomModelDataValue() {
        net.minecraft.world.item.component.CustomModelData cmd;
        cmd = (net.minecraft.world.item.component.CustomModelData) instance.get(DataComponents.CUSTOM_MODEL_DATA);
        return cmd != null && !cmd.floats().isEmpty();
    }

    public (com.bergerkiller.bukkit.common.wrappers.CustomModelData) net.minecraft.world.item.component.CustomModelData getCustomModelData() {
        net.minecraft.world.item.component.CustomModelData cmd;
        cmd = (net.minecraft.world.item.component.CustomModelData) instance.get(DataComponents.CUSTOM_MODEL_DATA);
        return (cmd != null) ? cmd : net.minecraft.world.item.component.CustomModelData.EMPTY;
    }

    public int getCustomModelDataValue() {
        net.minecraft.world.item.component.CustomModelData cmd;
        cmd = (net.minecraft.world.item.component.CustomModelData) instance.get(DataComponents.CUSTOM_MODEL_DATA);
        return (cmd == null || cmd.floats().isEmpty()) ? -1 : ((Float) cmd.floats().get(0)).intValue();
    }

    public void setCustomModelData((com.bergerkiller.bukkit.common.wrappers.CustomModelData) net.minecraft.world.item.component.CustomModelData cmd) {
        instance.set(DataComponents.CUSTOM_MODEL_DATA, cmd);
    }

    public void setCustomModelDataValue(int value) {
        instance.set(DataComponents.CUSTOM_MODEL_DATA, new net.minecraft.world.item.component.CustomModelData(
            java.util.Collections.singletonList(Float.valueOf((float) value)), /* floats */
            java.util.Collections.emptyList(), /* flags */
            java.util.Collections.emptyList(), /* strings */
            java.util.Collections.emptyList() /* colors */
        ));
    }

    public void clearCustomModelData() {
        instance.remove(DataComponents.CUSTOM_MODEL_DATA);
    }
#elseif version >= 1.20.5
    public boolean hasCustomModelData() {
        return instance.has(DataComponents.CUSTOM_MODEL_DATA);
    }

    public boolean hasCustomModelDataValue() {
        return instance.has(DataComponents.CUSTOM_MODEL_DATA);
    }

    public (com.bergerkiller.bukkit.common.wrappers.CustomModelData) net.minecraft.world.item.component.CustomModelData getCustomModelData() {
        net.minecraft.world.item.component.CustomModelData cmd;
        cmd = (net.minecraft.world.item.component.CustomModelData) instance.get(DataComponents.CUSTOM_MODEL_DATA);
        return (cmd != null) ? cmd : net.minecraft.world.item.component.CustomModelData.DEFAULT;
    }

    public int getCustomModelDataValue() {
        net.minecraft.world.item.component.CustomModelData cmd;
        cmd = (net.minecraft.world.item.component.CustomModelData) instance.get(DataComponents.CUSTOM_MODEL_DATA);
        return (cmd == null) ? -1 : cmd.value();
    }

    public void setCustomModelData((com.bergerkiller.bukkit.common.wrappers.CustomModelData) net.minecraft.world.item.component.CustomModelData cmd) {
        instance.set(DataComponents.CUSTOM_MODEL_DATA, cmd);
    }

    public void setCustomModelDataValue(int value) {
        instance.set(DataComponents.CUSTOM_MODEL_DATA, new net.minecraft.world.item.component.CustomModelData(value));
    }

    public void clearCustomModelData() {
        instance.remove(DataComponents.CUSTOM_MODEL_DATA);
    }
#else
    public boolean hasCustomModelData() {
        NBTTagCompound tag = instance.getTag();
        return tag != null && tag.get("CustomModelData") instanceof NBTTagInt;
    }

    public boolean hasCustomModelDataValue() {
        NBTTagCompound tag = instance.getTag();
        return tag != null && tag.get("CustomModelData") instanceof NBTTagInt;
    }

    public (com.bergerkiller.bukkit.common.wrappers.CustomModelData) net.minecraft.world.item.component.CustomModelData getCustomModelData() {
        NBTTagCompound tag = instance.getTag();
        if (tag != null) {
            NBTBase cmd = tag.get("CustomModelData");
            if (cmd instanceof NBTTagInt) {
                int value = ((NBTTagInt) cmd).getAsInt();
                return new net.minecraft.world.item.component.CustomModelData(value);
            }
        }
        return net.minecraft.world.item.component.CustomModelData.DEFAULT;
    }

    public int getCustomModelDataValue() {
        NBTTagCompound tag = instance.getTag();
        if (tag != null) {
            NBTBase cmd = tag.get("CustomModelData");
            if (cmd instanceof NBTTagInt) {
                return ((NBTTagInt) cmd).getAsInt();
            }
        }
        return -1;
    }

    public void setCustomModelData((com.bergerkiller.bukkit.common.wrappers.CustomModelData) net.minecraft.world.item.component.CustomModelData cmd) {
        NBTTagCompound tag = instance#getOrCreateTag();
        tag.putInt("CustomModelData", cmd.value());
    }

    public void setCustomModelDataValue(int value) {
        NBTTagCompound tag = instance#getOrCreateTag();
        tag.putInt("CustomModelData", value);
    }

    public void clearCustomModelData() {
        NBTTagCompound tag = instance.getTag();
        if (tag != null) {
            tag.remove("CustomModelData");
            instance#removeTagIfEmpty();
        }
    }
#endif

#if version >= 1.20.5
    public boolean hasCustomData() {
        return instance.has(DataComponents.CUSTOM_DATA);
    }

    public (CommonTagCompound) NBTTagCompound getCustomDataCopy() {
        CustomData customData = (CustomData) instance.get(DataComponents.CUSTOM_DATA);
        return (customData != null) ? customData.copyTag() : new NBTTagCompound();
    }

    public CommonTagCompound getCustomData() {
        CustomData customData = (CustomData) instance.get(DataComponents.CUSTOM_DATA);
        if (customData == null) {
            return CommonTagCompound.EMPTY;
        }

  #if version >= 1.21.9
        NBTTagCompound tag = customData#cmd_tag;
  #else
        NBTTagCompound tag = customData.getUnsafe();
  #endif
        return CommonTagCompound.createReadOnly(tag);
    }

    public void setCustomData((CommonTagCompound) NBTTagCompound tag) {
        if (tag == null || tag.isEmpty()) {
            instance.remove(DataComponents.CUSTOM_DATA);
        } else {
            instance.set(DataComponents.CUSTOM_DATA, (Object) CustomData.of(tag));
        }
    }

    public void updateCustomData((java.util.function.Consumer<CommonTagCompound>) java.util.function.Consumer<NBTTagCompound> consumer) {
        CustomData.update(DataComponents.CUSTOM_DATA, instance, consumer);
    }
#else
    public boolean hasCustomData:hasTag();

    public (CommonTagCompound) NBTTagCompound getCustomDataCopy() {
        NBTTagCompound tag = instance.getTag();
        if (tag == null) {
            return new NBTTagCompound();
        } else {
  #if version >= 1.18
            return (NBTTagCompound) tag.copy();
  #else
            return (NBTTagCompound) tag.clone();
  #endif
        }
    }

    public CommonTagCompound getCustomData() {
        NBTTagCompound tag = instance.getTag();
        return (tag != null) ? CommonTagCompound.createReadOnly(tag) : CommonTagCompound.EMPTY;
    }

    public void setCustomData((CommonTagCompound) NBTTagCompound tag) {
        boolean tagIsNullOrEmpty = tag == null || tag.isEmpty();
        if (tagIsNullOrEmpty) {
            instance.setTag((NBTTagCompound) null);
        } else {
  #if version >= 1.18
            instance.setTag((NBTTagCompound) tag.copy());
  #else
            instance.setTag((NBTTagCompound) tag.clone());
  #endif
        }
    }

    public void updateCustomData((java.util.function.Consumer<CommonTagCompound>) java.util.function.Consumer<NBTTagCompound> consumer) {
        NBTTagCompound tag = instance.getTag();
        if (tag == null) {
            tag = new NBTTagCompound();
        } else {
  #if version >= 1.18
            tag = (NBTTagCompound) tag.copy();
  #else
            tag = (NBTTagCompound) tag.clone();
  #endif
        }

        consumer.accept(tag);

        boolean tagIsEmpty = tag.isEmpty();
        instance.setTag(tagIsEmpty ? null : tag);
    }
#endif

    // No longer supported as of 1.20.5
    //public (CommonTagCompound) NBTTagCompound saveToNBT:save((CommonTagCompound) NBTTagCompound compound);

#if version >= 1.18
    public (ItemStackHandle) ItemStack cloneItemStack:copy();
#else
    public (ItemStackHandle) ItemStack cloneItemStack();
#endif

#if version >= 1.18
    public (ItemStackHandle) ItemStack cloneAndSubtract:split(int n);
#elseif version >= 1.8.8
    public (ItemStackHandle) ItemStack cloneAndSubtract(int n);
#else
    public (ItemStackHandle) ItemStack cloneAndSubtract:a(int n);
#endif

    public org.bukkit.inventory.ItemStack toBukkit() {
        return org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(instance);
    }

    public boolean isMapItem() {
        return instance.getItem() instanceof ItemWorldMap;
    }

    public int getMapId() {
        return instance#getItemStackMapId();
    }

    public void setMapId(int mapId) {
#if version >= 1.20.5
        instance.set(DataComponents.MAP_ID, (Object) new MapId(mapId));
#elseif version >= 1.13
        instance.getOrCreateTag().putInt("map", mapId);
#else
        instance.setData(mapId);
#endif
    }

    public String getPaintingName() {
#if version >= 1.21.9
        TypedEntityData entityData = (TypedEntityData) instance.get(DataComponents.ENTITY_DATA);
        if (entityData == null) return null;
        NBTTagCompound tag = entityData.getUnsafe();
#elseif version >= 1.20.5
        CustomData cmd = (CustomData) instance.get(DataComponents.ENTITY_DATA);
        if (cmd == null) return null;
        NBTTagCompound tag = cmd.getUnsafe();
#else
        NBTTagCompound fullTag = instance.getTag();
        if (fullTag == null) return null;
        NBTBase tagBase = fullTag.get("EntityTag");
        if (!(tagBase instanceof NBTTagCompound)) return null;
        NBTTagCompound tag = (NBTTagCompound) tagBase;
#endif
        NBTBase tagVariant = tag.get("variant");
        if (!(tagVariant instanceof NBTTagString)) return null;
        return ((NBTTagString) tagVariant).getAsString();
    }

    public void setPaintingName(String name) {
#if version >= 1.21.9
        if (name == null) {
            instance.remove(DataComponents.ENTITY_DATA);
        } else {
            NBTTagCompound tag = new NBTTagCompound();
            tag.putString("variant", name);
            TypedEntityData entityData = TypedEntityData.of(EntityTypes.PAINTING, tag);
            instance.set(DataComponents.ENTITY_DATA, entityData);
        }
#elseif version >= 1.20.5
        if (name == null) {
            instance.remove(DataComponents.ENTITY_DATA);
        } else {
            NBTTagCompound tag = new NBTTagCompound();
            tag.putString("id", EntityTypes.getKey(EntityTypes.PAINTING).toString());
            tag.putString("variant", name);
            CustomData entityData = CustomData.of(tag);
            instance.set(DataComponents.ENTITY_DATA, entityData);
        }
#else
        if (name == null) {
            NBTTagCompound tag = instance.getTag();
            if (tag == null) return;
            NBTBase entityTagBase = tag.get("EntityTag");
            if (!(entityTagBase instanceof NBTTagCompound)) return;
            NBTTagCompound entityTag = (NBTTagCompound) entityTagBase;
            entityTag.remove("variant");
            if (entityTag.isEmpty()) {
                tag.remove("EntityTag");
            }
            instance#removeTagIfEmpty();
        } else {
            NBTTagCompound tag = instance#getOrCreateTag();
            NBTBase entityTagBase = tag.get("EntityTag");
            if (!(entityTagBase instanceof NBTTagCompound)) {
                entityTagBase = new NBTTagCompound();
                tag.put("EntityTag", entityTagBase);
            }
            NBTTagCompound entityTag = (NBTTagCompound) entityTagBase;
            entityTag.putString("variant", name);
        }
#endif
    }

    public java.util.UUID getMapDisplayDynamicOnlyUUID() {
        if (!(instance.getItem() instanceof ItemWorldMap)) {
            return null;
        }
        return instance#getItemStackMapDisplayUUID();
    }

    public java.util.UUID getMapDisplayUUID() {
        if (!(instance.getItem() instanceof ItemWorldMap)) {
            return null;
        }
        java.util.UUID mapDisplayUUID = instance#getItemStackMapDisplayUUID();
        if (mapDisplayUUID != null) {
            return mapDisplayUUID;
        }
        int mapId = instance#getItemStackMapId();
        if (mapId != -1) {
            return new java.util.UUID(0L, (long) mapId);
        }
        return null;
    }

    <code>
    public static ItemStackHandle fromBukkit(org.bukkit.inventory.ItemStack itemStack) {
        if (itemStack == null) {
            return null;
        } else {
            return createHandle(com.bergerkiller.bukkit.common.conversion.type.HandleConversion.toItemStackHandle(itemStack));
        }
    }
    </code>
}
