package net.minecraft.world.item.crafting;

import net.minecraft.core.NonNullList;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.RegistryAccess$Frozen;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.ItemStack;

import net.minecraft.world.item.crafting.Ingredient;

import com.bergerkiller.bukkit.common.inventory.CraftInputSlot;

import com.bergerkiller.generated.net.minecraft.world.item.crafting.RecipeManagerHandle;
import com.bergerkiller.generated.net.minecraft.world.item.crafting.RecipesFurnaceHandle;
import com.bergerkiller.generated.net.minecraft.world.item.crafting.RecipeHandle;
import com.bergerkiller.generated.net.minecraft.world.item.crafting.SmeltingRecipeHandle;
import com.bergerkiller.generated.net.minecraft.world.item.ItemStackHandle;

class Recipe {
#if version >= 1.15
    public (org.bukkit.inventory.ItemStack) ItemStack getOutput() {
        if (instance instanceof FireworkRocketRecipe) {
            // Special case (see getIngredients code)
            net.minecraft.world.item.ItemStack itemstack = new ItemStack(net.minecraft.world.item.Items.FIREWORK_ROCKET, 3);
            ItemStackHandle.T.setFireworksFlightDuration.invoke(itemstack, Integer.valueOf(3));
            return itemstack;
        } else {
  #if version >= 26.1
              //TODO: Recipes could change depending on inputs
              RecipeInput input;
              if (instance instanceof ImbueRecipe || instance instanceof DecoratedPotRecipe) {
                  // These recipes cannot operate with an empty input
                  return ItemStack.EMPTY;
              } else if (instance instanceof CraftingRecipe) {
                  input = CraftingInput.EMPTY;
              } else if (instance instanceof SingleItemRecipe) {
                  input = new SingleRecipeInput(ItemStack.EMPTY);
              } else if (instance instanceof SmithingRecipe) {
                  // record SmithingRecipeInput(ItemStack template, ItemStack base, ItemStack addition);
                  input = new SmithingRecipeInput(ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY);
              } else {
                  // Unknown recipe type. Fail.
                  throw new IllegalStateException("Unknown recipe type: " + instance.getClass().getName());
              }

              return instance.assemble((RecipeInput) input);
  #elseif version >= 1.21.2
            //TODO: Recipes could change depending on inputs
            RecipeInput input;
            if (instance instanceof TippedArrowRecipe || instance instanceof DecoratedPotRecipe) {
                // These recipes cannot operate with an empty input
                return ItemStack.EMPTY;
            } else if (instance instanceof CraftingRecipe) {
                input = CraftingInput.EMPTY;
            } else if (instance instanceof SingleItemRecipe) {
                input = new SingleRecipeInput(ItemStack.EMPTY);
            } else if (instance instanceof SmithingRecipe) {
                // record SmithingRecipeInput(ItemStack template, ItemStack base, ItemStack addition);
                input = new SmithingRecipeInput(ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY);
            } else {
                // Unknown recipe type. Fail.
                throw new IllegalStateException("Unknown recipe type: " + instance.getClass().getName());
            }

            RegistryAccess$Frozen registry = MinecraftServer.getServer().registryAccess();
            return instance.assemble((RecipeInput) input, registry);
  #elseif version >= 1.19.4
            //TODO: Technically could support world-specific recipes!
            RegistryAccess$Frozen registry = MinecraftServer.getServer().registryAccess();
            return instance.getResultItem(registry);
  #elseif version >= 1.18
            return instance.getResultItem();
  #else
            return instance.getResult();
  #endif
        }
    }
#elseif version >= 1.14
    public abstract (org.bukkit.inventory.ItemStack) ItemStack getOutput:c();
#elseif version >= 1.13
    public abstract (org.bukkit.inventory.ItemStack) ItemStack getOutput:d();
#else
    public (org.bukkit.inventory.ItemStack) ItemStack getOutput() {
        if (instance instanceof FireworkRocketRecipe) {
            // Special case (see getIngredients code)
            net.minecraft.world.item.ItemStack itemstack = new ItemStack(net.minecraft.world.item.Items.FIREWORKS, 3);
            ItemStackHandle.T.setFireworksFlightDuration.invoke(itemstack, Integer.valueOf(3));
            return itemstack;
        } else {
            return instance.b();
        }
    }
#endif

    public abstract (List<CraftInputSlot>) List<Ingredient> getIngredients() {
        // Firework recipe must be handled by BKCommonLib for it to work at all
        // It's a special case as people frequently want this to work as part of automation
        if (instance instanceof FireworkRocketRecipe) {
            // Fireworks are a 'complex' recipe with many possible options
            // To sort of work properly rather than not at all, default to
            // crafting a flight 3 firework:
            // Shapeless(1 paper + 3 gunpowder) -> Flight 3 firework
  #if version >= 1.18
            Ingredient paper = Ingredient.of(new net.minecraft.world.level.ItemLike[] { net.minecraft.world.item.Items.PAPER });
            Ingredient gunpowder = Ingredient.of(new net.minecraft.world.level.ItemLike[] { net.minecraft.world.item.Items.GUNPOWDER });
  #elseif version >= 1.12
            Ingredient paper = Ingredient.a(new net.minecraft.world.level.ItemLike[] { net.minecraft.world.item.Items.PAPER });
            Ingredient gunpowder = Ingredient.a(new net.minecraft.world.level.ItemLike[] { net.minecraft.world.item.Items.GUNPOWDER });
  #else
            Ingredient paper = new Ingredient(net.minecraft.world.item.Items.PAPER);
            Ingredient gunpowder = new Ingredient(net.minecraft.world.item.Items.GUNPOWDER);
  #endif

            java.util.List ingredients = new java.util.ArrayList(4);
            ingredients.add(paper);
            ingredients.add(gunpowder);
            ingredients.add(gunpowder);
            ingredients.add(gunpowder);
            return ingredients;
        }

#if version >= 1.12
        if (instance instanceof ShapedRecipe) {
            ShapedRecipe s = (ShapedRecipe) instance;
  #select version >=
  #case 1.21.2:   java.util.List optionalIngredients = s.getIngredients();
                  java.util.List ingredients = new java.util.ArrayList(optionalIngredients.size());
                  for (java.util.Iterator iter = optionalIngredients.iterator(); iter.hasNext();) {
                      java.util.Optional opt = (java.util.Optional) iter.next();
                      if (opt.isPresent()) {
                          ingredients.add(opt.get());
                      }
                  }
                  return ingredients;
  #case 1.18:     return s.getIngredients();
  #case 1.14:     return s.a();
  #case 1.13:     return s.e();
  #case 1.12.1:   return s.d();
  #case else:     #require net.minecraft.world.item.crafting.ShapedRecipe private final NonNullList<Ingredient> items;
                  return instance#items;
  #endselect

        } else if (instance instanceof ShapelessRecipe) {
            ShapelessRecipe s = (ShapelessRecipe) instance;
  #select version >=
  #case 1.21.2:   #require net.minecraft.world.item.crafting.ShapelessRecipe final java.util.List<Ingredient> shapelessIngredients:ingredients;
                  return s#shapelessIngredients;
  #case 1.18:     return s.getIngredients();
  #case 1.14:     return s.a();
  #case 1.13:     return s.e();
  #case 1.12.1:   return s.d();
  #case else:     #require net.minecraft.world.item.crafting.ShapelessRecipe private final NonNullList<Ingredient> ingredients;
                  return instance#ingredients;
  #endselect

        } else {
            return null;
        }
#else
        // < 1.12
        java.util.List ingredients = instance.getIngredients();

        // Some recipes are messed up and the ingredients have item counts on them
        // But these counts are not used in practise
        // To fix, we got to clone the item and set count to 1.
        boolean clonedList = false;
        int targetIndex = 0;
        for (java.util.Iterator iter = ingredients.iterator(); iter.hasNext();) {
            ItemStack item = (ItemStack) iter.next();
  #if version >= 1.11
            if (item.getCount() > 1) {
  #else
            if (item.count > 1) {
  #endif
                if (!clonedList) {
                    ingredients = new java.util.ArrayList(ingredients); // Clone list to avoid modifying original
                    clonedList = true;
                }
                item = item.cloneItemStack();
  #if version >= 1.11
                item.setCount(1);
  #else
                item.count = 1;
  #endif
                ingredients.set(targetIndex, item);
            }
            targetIndex++;
        }

        return ingredients;
#endif
    }
}

// >= MC 1.12
optional class Ingredient {
#if version >= 1.21.2
    public (List<org.bukkit.inventory.ItemStack>) List<ItemStack> getChoices() {
        if (instance.isExact()) {
  #if exists net.minecraft.world.item.crafting.Ingredient public java.util.Set<net.minecraft.world.item.ItemStack> itemStacks();
            java.util.Set itemStacks = instance.itemStacks();
            if (itemStacks.isEmpty()) {
                return java.util.Collections.emptyList();
            } else {
                return new java.util.ArrayList(itemStacks);
            }
  #else
            return (List) instance.itemStacks();
  #endif
        }

        // Only stores the Item material to match. Create itemstacks out of them (count 1)
  #if version >= 1.21.4
        #require Ingredient private final net.minecraft.core.HolderSet<net.minecraft.world.item.Item> values;
        net.minecraft.core.HolderSet items = instance#values;
  #else
        java.util.List items = instance.items();
  #endif
        java.util.List itemStacks = new java.util.ArrayList(items.size());
        for (java.util.Iterator iter = items.iterator(); iter.hasNext();) {
            net.minecraft.core.Holder itemHolder = (net.minecraft.core.Holder) iter.next();
            ItemStack item = new ItemStack(itemHolder);
            itemStacks.add(item);
        }
        return itemStacks;
    }
#elseif version >= 1.18
    public (List<org.bukkit.inventory.ItemStack>) ItemStack[] getChoices:getItems();
#elseif version >= 1.17
    public (List<org.bukkit.inventory.ItemStack>) ItemStack[] getChoices:a();
#elseif version >= 1.12
    public (List<org.bukkit.inventory.ItemStack>) ItemStack[] getChoices() {
  #if version >= 1.13
        instance.buildChoices();
  #endif
        return instance.choices;
    }
#else
    public (List<org.bukkit.inventory.ItemStack>) List<ItemStack> getChoices() {
        return java.util.Collections.singletonList(instance);
    }
#endif

    public void setChoices((List<org.bukkit.inventory.ItemStack>) List<ItemStack> choices) {
#if version >= 1.21.2
  #if exists net.minecraft.world.item.crafting.Ingredient private java.util.Set<net.minecraft.world.item.ItemStack> itemStacks;
        // Paper
        #require net.minecraft.world.item.crafting.Ingredient private Set<ItemStack> choicesRecipeItemStack:itemStacks;
        choices = new HashSet(choices); // Safe copy / avoid conversion stuff sticking around
        instance#choicesRecipeItemStack = choices;
  #else
        #require net.minecraft.world.item.crafting.Ingredient private List<ItemStack> choicesRecipeItemStack:itemStacks;
        choices = new ArrayList(choices); // Safe copy / avoid conversion stuff sticking around
        instance#choicesRecipeItemStack = choices;
  #endif
#elseif version >= 1.12

  #if version >= 1.17
        #require net.minecraft.world.item.crafting.Ingredient private ItemStack[] choicesRecipeItemStack:itemStacks;
  #elseif version >= 1.12
        #require net.minecraft.world.item.crafting.Ingredient public final ItemStack[] choicesRecipeItemStack:choices;
  #endif
        ItemStack[] choicesArr = (ItemStack[]) choices.toArray(new ItemStack[0]);
        instance#choicesRecipeItemStack = choicesArr;
#else
        throw new UnsupportedOperationException("Ingredient class does not exist before Minecraft 1.12, use an ItemStack directly");
#endif
    }

    <code>
    // Note: only 1.12+
    public static Object createRawRecipeItemStack(List<org.bukkit.inventory.ItemStack> choices) {
        Object raw = T.newInstanceNull();
        T.setChoices.invoke(raw, choices);
        return raw;
    }
    </code>
}

// <= MC 1.12.2
optional class RecipesFurnace {
    public (Map<org.bukkit.inventory.ItemStack, org.bukkit.inventory.ItemStack>) Map<ItemStack, ItemStack> recipes;

    public static (RecipesFurnaceHandle) RecipesFurnace getInstance();

    public (ItemStackHandle) ItemStack getResult((ItemStackHandle) ItemStack itemstack);

}

// >= MC 1.13
optional class SmeltingRecipe extends Recipe {

#if version >= 1.21.2
    public (CraftInputSlot) Ingredient getIngredient:input();
#else
    public (CraftInputSlot) Ingredient getIngredient() {
  #if version >= 1.14
        #require net.minecraft.world.item.crafting.AbstractCookingRecipe protected final Ingredient ingredient;
  #elseif version >= 1.13
        #require net.minecraft.world.item.crafting.SmeltingRecipe private final Ingredient ingredient;
  #endif
        return instance#ingredient;
    }
#endif

    public static (Iterable<SmeltingRecipeHandle>) Iterable<SmeltingRecipe> getRecipes() {
#if version >= 1.18
        java.util.Iterator allRecipes = MinecraftServer.getServer().getRecipeManager().getRecipes().iterator();
#else
        java.util.Iterator allRecipes = MinecraftServer.getServer().getCraftingManager().b().iterator();
#endif
        java.util.List filteredResult = new java.util.ArrayList();
        while (allRecipes.hasNext()) {
            Object recipe = allRecipes.next();
#if version >= 1.20.2
            recipe = ((net.minecraft.world.item.crafting.RecipeHolder) recipe).value();
#endif
            if (recipe instanceof SmeltingRecipe) {
                filteredResult.add(recipe);
            }
        }
        return filteredResult;
    }
}

class RecipeManager {
    public static (Iterable<RecipeHandle>) Iterable<Recipe> getRecipes() {
#if version >= 1.13
  #if version >= 1.18
        java.util.Iterator allRecipes = MinecraftServer.getServer().getRecipeManager().getRecipes().iterator();
  #else
        java.util.Iterator allRecipes = MinecraftServer.getServer().getCraftingManager().b().iterator();
  #endif
        java.util.List filteredResult = new java.util.ArrayList();

  #if version >= 1.19.4 && version < 26.1
        RegistryAccess$Frozen registry = MinecraftServer.getServer().registryAccess();
  #endif
  #if version >= 1.21.2
        RecipeInput emptyCraftingInput = CraftingInput.EMPTY;
  #endif

        while (allRecipes.hasNext()) {
            Object recipe = allRecipes.next();
  #if version >= 1.20.2
            recipe = ((net.minecraft.world.item.crafting.RecipeHolder) recipe).value();
  #endif

            // Recipe must be a crafting recipe. This can be shaped, shapeless or complex
            // This removes furnace recipes, stone cutting, etc. Which is not intended to be 'seen' here.
            // TODO: In future, we probably do want to have an api accessible for such recipes?
  #if version >= 1.14
            if (!(recipe instanceof CraftingRecipe)) {
                continue;
            }
  #else
            if (!(recipe instanceof ShapedRecipe || recipe instanceof ShapelessRecipe || recipe instanceof CustomRecipe)) {
                continue;
            }
  #endif

            // Since Minecraft 1.17 the fireworks recipe exists twice
            // One is shapeless, the other is complex
            // Correct for this and only keep the complex one!
  #if version >= 26.1
            if (recipe instanceof ShapelessRecipe && ((Recipe) recipe).assemble(emptyCraftingInput).getItem() == Items.FIREWORK_ROCKET) {
                continue;
            }
  #elseif version >= 1.21.2
            if (recipe instanceof ShapelessRecipe && ((Recipe) recipe).assemble(emptyCraftingInput, registry).getItem() == Items.FIREWORK_ROCKET) {
                continue;
            }
  #elseif version >= 1.19.4
            if (!(recipe instanceof FireworkRocketRecipe) && ((Recipe) recipe).getResultItem(registry).getItem() == Items.FIREWORK_ROCKET) {
                continue;
            }
  #elseif version >= 1.18
            if (!(recipe instanceof FireworkRocketRecipe) && ((Recipe) recipe).getResultItem().getItem() == Items.FIREWORK_ROCKET) {
                continue;
            }
  #elseif version >= 1.17
            if (!(recipe instanceof FireworkRocketRecipe) && ((Recipe) recipe).getResult().getItem() == Items.FIREWORK_ROCKET) {
                continue;
            }
  #endif

            filteredResult.add(recipe);
        }
        return filteredResult;
#elseif version >= 1.12
        return RecipeManager.recipes;
#else
        return RecipeManager.getInstance().recipes;
#endif
    }
}
