package pers.neige.neigeitems.libs.bot.inker.bukkit.nbt;

import lombok.NonNull;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.Nullable;
import pers.neige.neigeitems.libs.bot.inker.bukkit.nbt.api.NbtComponentLike;
import pers.neige.neigeitems.libs.bot.inker.bukkit.nbt.internal.annotation.CbVersion;
import pers.neige.neigeitems.libs.bot.inker.bukkit.nbt.internal.invoke.InvokeUtil;
import pers.neige.neigeitems.ref.core.component.RefDataComponentHolder;
import pers.neige.neigeitems.ref.core.component.RefDataComponents;
import pers.neige.neigeitems.ref.nbt.*;
import pers.neige.neigeitems.ref.world.item.component.RefCustomData;

import java.util.Map;
import java.util.Objects;

public final class NbtItemStack implements Comparable<NbtItemStack> {
    /**
     * 1.13+ 版本起, damage字段存储于nbt当中.
     */
    private final static boolean NBT_DAMAGE = CbVersion.v1_13_R1.isSupport();
    /**
     * 1.20.5+ 版本起, Mojang献祭了自己的亲妈, 换来了物品格式的改动.
     */
    private final static boolean MOJANG_MOTHER_DEAD = CbVersion.v1_20_R4.isSupport();

    private final @NonNull ItemStack itemStack;
    private final @Nullable RefCraftItemStack craftItemStack;

    public NbtItemStack(@NonNull ItemStack itemStack) {
        if (itemStack instanceof RefCraftItemStack) {
            this.itemStack = itemStack;
            this.craftItemStack = (RefCraftItemStack) itemStack;
        } else {
            if (MOJANG_MOTHER_DEAD) {
                itemStack = ((RefBukkitItemStack) (Object) itemStack).craftDelegate;
                this.itemStack = itemStack;
                this.craftItemStack = (RefCraftItemStack) itemStack;
            } else {
                this.itemStack = itemStack;
                this.craftItemStack = null;
            }
        }
    }

    public @Nullable NbtComponentLike getDirectTag() {
        if (MOJANG_MOTHER_DEAD) {
            return getTag();
        } else {
            if (craftItemStack == null) {
                return new NbtBukkitItemComponent(itemStack);
            } else {
                return new NbtCompound(NbtUtils.getOrCreateTag(craftItemStack.handle));
            }
        }
    }

    public @Nullable NbtCompound getTag() {
        if (craftItemStack == null) {
            RefCraftMetaItem meta = (RefCraftMetaItem) (Object) InvokeUtil.getItemMeta(itemStack);
            if (meta == null) {
                return null;
            } else {
                if (MOJANG_MOTHER_DEAD) {
                    return new NbtCompound(InvokeUtil.getCustomTag(meta));
                } else {
                    NbtCompound compound = new NbtCompound();
                    meta.applyToItem(compound.delegate);
                    Map<String, RefNbtBase> tags = compound.delegate.tags;
                    for (Map.Entry<String, RefNbtBase> entry : tags.entrySet()) {
                        String key = entry.getKey();
                        RefNbtBase value = entry.getValue();
                        // applyToItem 直接把 unhandledTags 丢进去了, 这样是不行的, 必须 clone 一下
                        if (!RefCraftMetaItem.HANDLED_TAGS.contains(key)
                            && (value instanceof RefNbtTagCompound || value instanceof RefNbtTagList)) {
                            tags.put(key, value.rClone());
                        }
                    }
                    return compound.isEmpty() ? null : compound;
                }
            }
        } else {
            if (MOJANG_MOTHER_DEAD) {
                RefCustomData customData = ((RefDataComponentHolder) (Object) craftItemStack.handle).get(RefDataComponents.CUSTOM_DATA);
                return customData == null ? null : new NbtCompound(customData.getUnsafe());
            } else {
                RefNbtTagCompound tag = craftItemStack.handle.getTag();
                return tag == null ? null : new NbtCompound(tag);
            }
        }
    }

    public void setTag(@NonNull NbtCompound compound) {
        if (craftItemStack == null) {
            InvokeUtil.setItemMeta(itemStack, null);
            RefCraftItemStack craftItemStack = RefCraftItemStack.asCraftCopy(itemStack);
            if (MOJANG_MOTHER_DEAD) {
                craftItemStack.handle.set(RefDataComponents.CUSTOM_DATA, RefCustomData.of(compound.delegate));
            } else {
                craftItemStack.handle.setTag(compound.delegate);
            }
            InvokeUtil.setItemMeta(itemStack, craftItemStack.getItemMeta());
        } else {
            if (MOJANG_MOTHER_DEAD) {
                craftItemStack.handle.set(RefDataComponents.CUSTOM_DATA, RefCustomData.of(compound.delegate));
            } else {
                craftItemStack.handle.setTag(compound.delegate);
            }
        }
    }

    public @NonNull NbtCompound getOrCreateTag() {
        if (craftItemStack == null) {
            RefCraftMetaItem meta = (RefCraftMetaItem) (Object) InvokeUtil.getItemMeta(itemStack);
            if (meta == null) {
                return new NbtCompound();
            } else {
                if (MOJANG_MOTHER_DEAD) {
                    ItemMeta itemMeta = NbtUtils.getItemMeta(itemStack);
                    RefNbtTagCompound customTag = InvokeUtil.getCustomTag(meta);
                    if (customTag == null) {
                        customTag = new RefNbtTagCompound();
                        InvokeUtil.setCustomTag(itemMeta, customTag);
                    }
                    return new NbtCompound(customTag);
                } else {
                    NbtCompound compound = new NbtCompound();
                    meta.applyToItem(compound.delegate);
                    Map<String, RefNbtBase> tags = compound.delegate.tags;
                    for (Map.Entry<String, RefNbtBase> entry : tags.entrySet()) {
                        String key = entry.getKey();
                        RefNbtBase value = entry.getValue();
                        // applyToItem 直接把 unhandledTags 丢进去了, 这样是不行的, 必须 clone 一下
                        if (!RefCraftMetaItem.HANDLED_TAGS.contains(key)
                            && (value instanceof RefNbtTagCompound || value instanceof RefNbtTagList)) {
                            tags.put(key, value.rClone());
                        }
                    }
                    return compound;
                }
            }
        } else {
            if (MOJANG_MOTHER_DEAD) {
                RefCustomData customData = ((RefDataComponentHolder) (Object) craftItemStack.handle).get(RefDataComponents.CUSTOM_DATA);
                if (customData == null) {
                    customData = RefCustomData.of(new RefNbtTagCompound());
                    craftItemStack.handle.set(RefDataComponents.CUSTOM_DATA, customData);
                }
                return new NbtCompound(customData.getUnsafe());
            } else {
                return new NbtCompound(NbtUtils.getOrCreateTag(craftItemStack.handle));
            }
        }
    }

    public @NonNull ItemStack asBukkitCopy() {
        if (craftItemStack == null) {
            return itemStack.clone();
        } else {
            return RefCraftItemStack.asBukkitCopy(craftItemStack.handle);
        }
    }

    public @NonNull ItemStack asCraftCopy() {
        if (craftItemStack == null) {
            return RefCraftItemStack.asCraftCopy(itemStack);
        } else {
            return itemStack.clone();
        }
    }

    public @NonNull ItemStack asCopy() {
        if (craftItemStack == null) {
            return NbtUtils.bukkitCopy(itemStack);
        } else {
            return itemStack.clone();
        }
    }

    public boolean isBukkitItemStack() {
        return craftItemStack == null;
    }

    public boolean isCraftItemStack() {
        return craftItemStack != null;
    }

    public @NonNull ItemStack asItemStack() {
        return itemStack;
    }

    @Override
    public @NonNull NbtItemStack clone() {
        return new NbtItemStack(NbtUtils.asCopy(itemStack));
    }

    @Override
    public int compareTo(@NonNull NbtItemStack o) {
        int materialResult = Integer.compare(itemStack.getType().ordinal(), o.itemStack.getType().ordinal());
        if (materialResult != 0) return materialResult;
        int amountResult = Integer.compare(itemStack.getAmount(), o.itemStack.getAmount());
        if (amountResult != 0) return amountResult;
        if (NBT_DAMAGE) {
            int damageResult = Short.compare(itemStack.getDurability(), o.itemStack.getDurability());
            if (damageResult != 0) return damageResult;
        }
        NbtCompound nbt0 = getTag();
        NbtCompound nbt1 = o.getTag();
        if (nbt0 == null) {
            return nbt1 == null ? 0 : -1;
        } else if (nbt1 == null) return 1;
        return nbt0.compareTo(nbt1);
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof NbtItemStack)) return false;
        NbtItemStack that = (NbtItemStack) o;
        return Objects.equals(itemStack, that.itemStack);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(itemStack);
    }
}
