/*
 *  Copyright 2025-2026 R3944Realms
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package top.r3944realms.eroticdungeongame.content.recipe;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import net.minecraft.core.NonNullList;
import net.minecraft.core.RegistryAccess;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.ShapedRecipe;
import net.minecraft.world.level.Level;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.crafting.IShapedRecipe;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.eroticdungeongame.EroticDungeon;
import top.r3944realms.eroticdungeongame.core.register.EDGRecipeSerializers;

import java.util.Map;
import java.util.Set;

public class DungeonShapedRecipe implements DungeonRecipe, IShapedRecipe<CraftingContainer> {
    static int MAX_WIDTH = 3;
    static int MAX_HEIGHT = 3;
    final int width;
    final int height;
    final NonNullList<Ingredient> recipeItems;
    final ItemStack result;
    private final ResourceLocation id;
    final String group;
    final DungeonCraftingBookCategory category;
    final boolean showNotification;
    public static void setCraftingSize(int width, int height) {
        if (MAX_WIDTH < width) {
            MAX_WIDTH = width;
        }

        if (MAX_HEIGHT < height) {
            MAX_HEIGHT = height;
        }

    }
    public DungeonShapedRecipe(ResourceLocation id, String group, DungeonCraftingBookCategory category, int width, int height, NonNullList<Ingredient> recipeItems, ItemStack result, boolean showNotification) {
        this.id = id;
        this.group = group;
        this.category = category;
        this.width = width;
        this.height = height;
        this.recipeItems = recipeItems;
        this.result = result;
        this.showNotification = showNotification;
    }

    public DungeonShapedRecipe(ResourceLocation id, String group, DungeonCraftingBookCategory category, int width, int height, NonNullList<Ingredient> recipeItems, ItemStack result) {
        this(id, group, category, width, height, recipeItems, result, true);
    }

    @Override
    public int getRecipeWidth() {
        return getWidth();
    }

    @Override
    public int getRecipeHeight() {
        return getHeight();
    }

    @Override
    public DungeonCraftingBookCategory category() {
        return category;
    }

    @Override
    public ItemStack getOutput() {
        return result;
    }

    @Override
    public boolean matches(@NotNull CraftingContainer inv, @NotNull Level level) {
        for(int i = 0; i <= inv.getWidth() - this.width; ++i) {
            for(int j = 0; j <= inv.getHeight() - this.height; ++j) {
                if (this.matches(inv, i, j, true)) {
                    return true;
                }

                if (this.matches(inv, i, j, false)) {
                    return true;
                }
            }
        }

        return false;
    }

    private boolean matches(@NotNull CraftingContainer craftingInventory, int width, int height, boolean mirrored) {
        for(int i = 0; i < craftingInventory.getWidth(); ++i) {
            for(int j = 0; j < craftingInventory.getHeight(); ++j) {
                int k = i - width;
                int l = j - height;
                Ingredient ingredient = Ingredient.EMPTY;
                if (k >= 0 && l >= 0 && k < this.width && l < this.height) {
                    if (mirrored) {
                        ingredient = this.recipeItems.get(this.width - k - 1 + l * this.width);
                    } else {
                        ingredient = this.recipeItems.get(k + l * this.width);
                    }
                }

                if (!ingredient.test(craftingInventory.getItem(i + j * craftingInventory.getWidth()))) {
                    return false;
                }
            }
        }

        return true;
    }

    @Override
    public @NotNull ItemStack assemble(@NotNull CraftingContainer craftingContainer, @NotNull RegistryAccess registryAccess) {
        return getResultItem(registryAccess).copy();
    }

    @Override
    public boolean canCraftInDimensions(int width, int height) {
        return width >= this.width && height >= this.height;
    }

    @Override
    public @NotNull ItemStack getResultItem(@Nullable RegistryAccess registryAccess) {
        return result;
    }

    @Override
    public @NotNull ResourceLocation getId() {
        return id;
    }

    @Override
    public @NotNull RecipeSerializer<?> getSerializer() {
        return EDGRecipeSerializers.SHAPED_DUNGEON_RECIPE.get();
    }

    @Override
    public boolean showNotification() {
        return showNotification;
    }

    @Override
    public @NotNull String getGroup() {
        return group;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    @Contract("null -> new")
    static String @NotNull [] shrink(String... toShrink) {
        if (toShrink ==null || toShrink.length == 0) {
            return new String[0];
        }
        int minStartIndex = Integer.MAX_VALUE;
        int maxEndIndex = 0;
        int firstNonEmptyIndex = 0;
        int trailingEmptyCount = 0;

        for (int i = 0; i < toShrink.length; ++i) {
            String line = toShrink[i];
            int lineStart = firstNonSpace(line);
            int lineEnd = lastNonSpace(line);

            if (lineStart < line.length()) {
                minStartIndex = Math.min(minStartIndex, lineStart);
                maxEndIndex = Math.max(maxEndIndex, lineEnd);
            }
            // 处理空行逻辑
            if (lineEnd < 0) { // 全空格行或空行
                if (firstNonEmptyIndex == i) {
                    firstNonEmptyIndex++;
                }
                trailingEmptyCount++;
            } else {
                trailingEmptyCount = 0; // 遇到非空行，重置尾随空行计数
            }
        }
        // 如果全是空行，返回空数组
        if (toShrink.length == trailingEmptyCount) {
            return new String[0];
        }
        // 创建结果数组，排除前导和尾随的空行
        int resultSize = toShrink.length - trailingEmptyCount - firstNonEmptyIndex;
        String[] result = new String[resultSize];

        for (int i = 0; i < resultSize; i++) {
            String original = toShrink[i + firstNonEmptyIndex];
            // 对每一行使用相同的截取范围
            result[i] = original.substring(minStartIndex, maxEndIndex + 1);
        }

        return result;
    }
    private static int firstNonSpace(@NotNull String entry) {
        for (int i = 0; i < entry.length(); i++) {
            if (entry.charAt(i) != ' ') {
                return i;
            }
        }
        return entry.length(); // 全是空格，返回长度
    }

    private static int lastNonSpace(@NotNull String entry) {
        for (int i = entry.length() - 1; i >= 0; i--) {
            if (entry.charAt(i) != ' ') {
                return i;
            }
        }
        return -1; // 全是空格或空字符串
    }

    @Override
    public @NotNull NonNullList<Ingredient> getIngredients() {
        return recipeItems;
    }

    public boolean isIncomplete() {
        NonNullList<Ingredient> ingredients = this.getIngredients();

        // 如果配料列表为空，则配方不完整
        if (ingredients.isEmpty()) {
            return true;
        }

        // 检查是否有任何配料为空或没有元素
        return ingredients.stream()
                .filter(ingredient -> !ingredient.isEmpty())
                .anyMatch(ForgeHooks::hasNoElements);
    }
    
    static @NotNull Map<String, Ingredient> keyFromJson(@NotNull JsonObject keyEntry) {
        Map<String, Ingredient> map = Maps.newHashMap();
        for (Map.Entry<String, JsonElement> entry : keyEntry.entrySet()) {
            if(entry.getKey().length() != 1) {
                throw new JsonSyntaxException("Invalid key entry: '" + entry.getKey() + "' is an invalid symbol (must be 1 character only).");
            }
            
            if(" ".equals(entry.getKey())) {
                throw new JsonSyntaxException("Invalid key entry: ' ' is a reserved symbol.");
            }
            
            map.put(entry.getKey(), Ingredient.fromJson(entry.getValue(), false));
        }
        map.put(" ", Ingredient.EMPTY);
        return map;
    }
    
    static String[] patternFormJson(JsonArray patternArr) {
        String[] astring = new String[patternArr.size()];
        if (astring.length > MAX_HEIGHT) {
            throw new JsonSyntaxException("Invalid pattern: too many rows, " + MAX_HEIGHT + " is maximum");
        } else if (astring.length == 0) {
            throw new JsonSyntaxException("Invalid pattern: empty pattern not allowed");
        } else { 
            for (int i = 0;i < astring.length; ++i) {
                String s = GsonHelper.convertToString(patternArr.get(i), "pattern[" + i + "]");
                if (s.length() > MAX_WIDTH) {
                    throw new JsonSyntaxException("Invalid pattern: too many columns, " + MAX_WIDTH + " is maximum");
                }
                if (i > 0 && astring[0].length() != s.length()) {
                    throw new JsonSyntaxException("Invalid pattern: each row must be the same width");
                }
                astring[i] = s;
            }
            return astring;
        }
    }
    
    static NonNullList<Ingredient> dissolvePattern(String[] patternArr, Map<String, Ingredient> keys, int patternWidth, int patternHeight) {
        NonNullList<Ingredient> ingredients = NonNullList.withSize(patternWidth * patternHeight, Ingredient.EMPTY);
        Set<String> set = Sets.newHashSet(keys.keySet());
        set.remove(" ");
        
        for (int i = 0; i < patternArr.length; ++i) {
            for (int j = 0; j < patternArr[i].length(); ++j) {
                String s = patternArr[i].substring(j, j + 1);
                Ingredient ingredient = keys.get(s);
                if (ingredient == null) {
                    throw new JsonSyntaxException("Pattern references symbol '" + s + "' but it's not defined in the key");
                }
                set.remove(s);
                ingredients.set(j + patternWidth * i, ingredient);
            }
        }
        if (!set.isEmpty()) {
            throw new JsonSyntaxException("Key defines symbols that aren't used in pattern: " + set);
        } else {
            return ingredients;
        }
    }
    
    
    public static class Serializer implements RecipeSerializer<DungeonShapedRecipe> {
        public static final ResourceLocation NAME = EroticDungeon.rl("crafting_shaped");
        @Override
        public @NotNull DungeonShapedRecipe fromJson(@NotNull ResourceLocation recipeId, @NotNull JsonObject json) {
            String s = GsonHelper.getAsString(json, "group", "");
            DungeonCraftingBookCategory dungeonCraftingBookCategory = DungeonCraftingBookCategory.CODEC.byName(GsonHelper.getAsString(json, "category", null) , DungeonCraftingBookCategory.MISC);
            Map<String, Ingredient> map = keyFromJson(GsonHelper.getAsJsonObject(json, "key"));
            String[] astring = DungeonShapedRecipe.shrink(patternFormJson(GsonHelper.getAsJsonArray(json, "pattern")));
            int width = astring[0].length();
            int height = astring.length;
            NonNullList<Ingredient> nonNullList = dissolvePattern(astring, map, width, height);
            ItemStack itemStack = ShapedRecipe.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result"));
            boolean flag =  GsonHelper.getAsBoolean(json, "show_notification", true);
            return new DungeonShapedRecipe(recipeId, s, dungeonCraftingBookCategory, width, height, nonNullList, itemStack, flag);
        }

        @Override
        public @Nullable DungeonShapedRecipe fromNetwork(@NotNull ResourceLocation recipeId, @NotNull FriendlyByteBuf buffer) {
            int i = buffer.readVarInt();
            int j = buffer.readVarInt();
            String s = buffer.readUtf();
            DungeonCraftingBookCategory craftingBookCategory = buffer.readEnum(DungeonCraftingBookCategory.class);
            NonNullList<Ingredient> nonnulllist = NonNullList.withSize(i * j, Ingredient.EMPTY);

            nonnulllist.replaceAll(ignored -> Ingredient.fromNetwork(buffer));

            ItemStack itemstack = buffer.readItem();
            boolean flag = buffer.readBoolean();
            return new DungeonShapedRecipe(recipeId, s, craftingBookCategory, i, j, nonnulllist, itemstack, flag);
        }

        @Override
        public void toNetwork(@NotNull FriendlyByteBuf buffer, @NotNull DungeonShapedRecipe recipe) {
            buffer.writeVarInt(recipe.width);
            buffer.writeVarInt(recipe.height);
            buffer.writeUtf(recipe.group);
            buffer.writeEnum(recipe.category);
            for (Ingredient recipeItem : recipe.recipeItems) {
                recipeItem.toNetwork(buffer);
            }
            buffer.writeItem(recipe.result);
            buffer.writeBoolean(recipe.showNotification);
        }
    }
}
