import os

from anvil.api.blocks.blocks import Block
from anvil.api.core.enums import ItemCategory, RecipeUnlockContext, SmeltingTags
from anvil.api.core.types import Identifier
from anvil.api.items.items import Item
from anvil.lib.config import CONFIG
from anvil.lib.schemas import (
    AddonObject,
    JsonSchemes,
    MinecraftBlockDescriptor,
    MinecraftItemDescriptor,
)
from anvil.lib.translator import AnvilTranslator


class CraftingItemCatalog(AddonObject):
    _instance = None
    _extension = ".json"
    _path = os.path.join(CONFIG.BP_PATH, "item_catalog")

    def __new__(cls, *Parameters, **kwParameters):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self) -> None:
        if getattr(self, "_initialized", False):
            return
        super().__init__("crafting_item_catalog")
        self.content(JsonSchemes.crafting_items_catalog())
        self._initialized = True

    def add_group(
        self,
        category: ItemCategory,
        group_name: str,
        group_icon: MinecraftItemDescriptor | MinecraftBlockDescriptor,
        items_list: list[MinecraftItemDescriptor | MinecraftBlockDescriptor] = [],
    ) -> "CraftingItemCatalog":
        """
        Adds a group to an existing category.

        Parameters:
            category (ItemCategory): The category to update.
            group_name (str): The group name to add.
            group_icon (MinecraftItemDescriptor | MinecraftBlockDescriptor): The item to use as the icon for the group.
            items_list (list[MinecraftItemDescriptor | MinecraftBlockDescriptor], optional): List of items to add to the group. Defaults to an empty list.
        """
        localized_key = f"tag.{CONFIG.NAMESPACE}:{group_name.replace(" ", "_").lower()}"
        AnvilTranslator().add_localization_entry(localized_key, group_name)

        group = {
            "group_identifier": {
                "icon": group_icon.identifier,
                "name": localized_key,
            },
            "items": [item.identifier for item in items_list],
        }

        for item in items_list:
            item: Item | Block = item.server.description.menu_category(
                category=category, group=localized_key
            )

        for cat in self._content["minecraft:crafting_items_catalog"]["categories"]:
            if cat.get("category_name") == str(category):
                if "groups" not in cat:
                    cat["groups"] = []
                for existing_group in cat["groups"]:
                    if existing_group["group_identifier"]["name"] == localized_key:
                        existing_group.setdefault(
                            "items", existing_group.get("items", [])
                        ).extend([item.identifier for item in items_list])
                    continue
                else:
                    cat["groups"].append(group)
                break
        else:
            self._content["minecraft:crafting_items_catalog"]["categories"].append(
                {
                    "category_name": str(category),
                    "groups": [group],
                }
            )
        return self

    def add_loose_items(
        self,
        category_name: ItemCategory,
        items: list[MinecraftItemDescriptor | MinecraftBlockDescriptor],
    ) -> "CraftingItemCatalog":
        """Adds loose items to a category.

        Parameters:
            category_name (ItemCategory): The category name to update.
            items (list[MinecraftItemDescriptor | MinecraftBlockDescriptor]): List of items to add to the category.

        Returns:
            CraftingItemCatalog: The current instance of CraftingItemCatalog.
        """

        for item in items:
            item: Item | Block = item.server.description.menu_category(
                category=category_name
            )

        for cat in self._content["minecraft:crafting_items_catalog"]["categories"]:
            if cat.get("category_name") == str(category_name):
                cat.setdefault("loose_items", []).extend(items)
                break
        else:
            self._content["minecraft:crafting_items_catalog"]["categories"].append(
                {
                    "category_name": str(category_name),
                    "loose_items": items,
                }
            )
        return self

    def queue(self):
        return super().queue()


class _BaseRecipe(AddonObject):
    _extension = ".recipe.json"
    _path = os.path.join(CONFIG.BP_PATH, "recipes")
    _object_type = "Base Recipe"
    _type = "minecraft:recipe_furnace"

    def __init__(self, name: str):
        super().__init__(name)

    def priority(self, priority: int):
        self._content[self._type]["priority"] = priority
        return self

    def unlock_context(self, unlock_context: RecipeUnlockContext):
        self._content[self._type]["unlock"] = {"context": unlock_context.value}
        return self

    def unlock_items(self, unlock_items: list[Identifier]):
        self._content[self._type]["unlock"] = [
            {"item": str(item)} for item in unlock_items
        ]
        return self

    def queue(self):
        return super().queue()


class SmeltingRecipe(_BaseRecipe):
    _object_type = "Smelting Recipe"
    _type = "minecraft:recipe_furnace"

    def __init__(self, name: str, tags: list[SmeltingTags]):
        super().__init__(name)
        self._tags = tags
        self.content(JsonSchemes.recipe_smelting(self.identifier, tags))

    def input(self, identifier: Identifier):
        self._content["minecraft:recipe_furnace"]["input"] = {"item": identifier}
        return self

    def output(self, identifier: Identifier):
        self._content["minecraft:recipe_furnace"]["output"] = identifier
        return self

    def queue(self):
        return super().queue()


class SmithingRecipe(_BaseRecipe):
    _object_type = "Smithing Recipe"
    _extension = ".recipe.json"

    def __init__(self, name: str, tags: list[str] = ["smithing_table"]):
        self._name = name
        self.content(JsonSchemes.recipe_smithing_table(self.identifier, tags))
        super().__init__(name)

    def base(self, identifier: Identifier):
        self._content["minecraft:recipe_smithing_transform"]["base"] = identifier
        return self

    def addition(self, identifier: Identifier):
        self._content["minecraft:recipe_smithing_transform"]["addition"] = identifier
        return self

    def result(self, identifier: Identifier):
        self._content["minecraft:recipe_smithing_transform"]["result"] = identifier
        return self

    def queue(self):
        return super().queue()


class ShapelessRecipe(_BaseRecipe):
    _object_type = "Shapeless Recipe"
    _type = "minecraft:recipe_shapeless"

    def __init__(self, name: str, tags: list[str] = ["crafting_table"]):
        super().__init__(name)
        self.content(JsonSchemes.recipe_shapeless_crafting(self.identifier, tags))

    def ingredients(self, items: list[tuple[Identifier, int]]):
        if len(items) > 9:
            raise ValueError(
                f"Too many items in shapeless recipe, maximum is 9. {self._object_type}[{self._name}]"
            )

        for i, item in enumerate(items):
            if not item is None:
                if not isinstance(item, tuple):
                    data = {"item": str(item)}
                else:
                    data = {"item": str(item[0]), "data": item[1]}
                self._content["minecraft:recipe_shapeless"]["ingredients"].append(data)
        return self

    def result(
        self,
        item: MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier,
        count: int = 1,
        data: int = 0,
    ):
        self._content["minecraft:recipe_shapeless"]["result"] = {
            "item": str(item),
            "count": count,
            "data": data if data != 0 else {},
        }
        return self

    def queue(self):
        return super().queue()


class StoneCutterRecipe(ShapelessRecipe):
    _object_type = "Stone Cutter Recipe"

    def __init__(self, name: str, tags: list[str] = ["stonecutter"]):
        super().__init__(name)
        self.content(JsonSchemes.recipe_shapeless_crafting(self.identifier, tags))

    def ingredient(
        self,
        item: MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier,
        data: int = 0,
    ):
        return super().ingredient([(item, data)])

    def queue(self):
        return super().queue()


class ShapedCraftingRecipe(_BaseRecipe):
    _object_type = "Shaped Crafting Recipe"
    _type = "minecraft:recipe_shaped"

    def __init__(
        self,
        name: str,
        assume_symmetry: bool = True,
        tags: list[str] = ["crafting_table"],
    ):
        self._recipe_exactly = False
        super().__init__(name)
        self.content(
            JsonSchemes.recipe_shaped_crafting(self.identifier, assume_symmetry, tags)
        )

    def ingredients(
        self,
        items: list[
            list[
                tuple[
                    MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier, int
                ]
            ]
        ],
        keep_empty_slots: bool = False,
    ) -> None:
        max_items = 9
        if len(items) > max_items:
            raise ValueError(
                f"Too many items in shaped recipe, maximum is {max_items}. {self._object_type}[{self.identifier}]"
            )

        keys = "abcdefghijklmnopqrstuvwxyz"
        pattern = ["   ", "   ", "   "]
        added_items = {}

        for i, row in enumerate(items):
            for j, item in enumerate(row):
                if not item is None:
                    if not isinstance(item, tuple):
                        data = {"item": str(item)}
                    else:
                        data = {"item": str(item[0]), "data": item[1]}

                    key = next((k for k, v in added_items.items() if v == data), None)
                    if key is None:
                        key = keys[len(added_items)]
                        added_items[key] = data

                    pattern[i] = pattern[i][:j] + key + pattern[i][j + 1 :]

        self._content["minecraft:recipe_shaped"]["key"] = added_items

        if not keep_empty_slots:
            while pattern and pattern[0] == "   ":
                pattern.pop(0)
            while pattern and pattern[-1] == "   ":
                pattern.pop(-1)

            while all(row[0] == " " for row in pattern):
                pattern = [row[1:] for row in pattern]
            while all(row[-1] == " " for row in pattern):
                pattern = [row[:-1] for row in pattern]

        self._content["minecraft:recipe_shaped"]["pattern"] = pattern
        return self

    def result(
        self,
        item: MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier,
        count: int = 1,
        data: int = 0,
    ):
        self._content["minecraft:recipe_shaped"]["result"] = {
            "item": str(item),
            "count": count,
            "data": data if data != 0 else {},
        }
        return self

    def queue(self):
        return super().queue()


class SmithingTrimRecipe(_BaseRecipe):
    _object_type = "Smithing Trim Recipe"
    _extension = ".recipe.json"

    def __init__(self, name: str, tags: list[str] = ["smithing_table"]):
        self._name = name
        self.content(JsonSchemes.recipe_smithing_table_trim(self.identifier, tags))
        super().__init__(name)

    def base(
        self, item: MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier
    ):
        self._content["minecraft:recipe_smithing_transform"]["base"] = item
        return self

    def addition(
        self, item: MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier
    ):
        self._content["minecraft:recipe_smithing_transform"]["addition"] = item
        return self

    def queue(self):
        return super().queue()


class PotionBrewingRecipe(_BaseRecipe):
    _object_type = "Potion Brewing Recipe"
    _extension = ".recipe.json"
    _type = "minecraft:recipe_brewing_container"

    def __init__(self, name: str, tags: list[str] = ["brewing_stand"]):
        """Represents a Potion Brewing Container Recipe.

        Args:
            name (str): The name of the recipe.
            tags (list[str], optional): Tags associated with the recipe. Defaults to ["brewing_stand"].

        [Documentation](https://learn.microsoft.com/en-us/minecraft/creator/reference/content/recipereference/examples/recipedefinitions/minecraftrecipe_potionbrewing)
        """
        super().__init__(name)
        self.content(JsonSchemes.recipe_brewing_container(self.identifier, tags))

    def recipe(
        self,
        item: MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier,
        reagent: MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier,
        output: MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier,
    ):
        """Sets the recipe for the potion brewing container.

        Args:
            item (MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier): Input potion used in the brewing container recipe.
            reagent (MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier): MinecraftItemDescriptor | MinecraftBlockDescriptor used in the brewing container recipe with the input potio
            output (MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier): Output potion from the brewing container recipe.

        Returns:
            PotionBrewingRecipe: The current instance of PotionBrewingRecipe.
        """
        self._content[self._type]["input"] = str(item)
        self._content[self._type]["reagent"] = str(reagent)
        self._content[self._type]["output"] = str(output)
        return self

    def queue(self):
        return super().queue()


class PotionMixingRecipe(_BaseRecipe):
    _object_type = "Potion Mixing Recipe"
    _extension = ".recipe.json"
    _type = "minecraft:recipe_brewing_mix"

    def __init__(self, name: str, tags: list[str] = ["brewing_stand"]):
        """Represents a Potion Mixing Recipe.

        Args:
            name (str): The name of the recipe.
            tags (list[str], optional): Tags associated with the recipe. Defaults to ["brewing_stand"].

        [Documentation](https://learn.microsoft.com/en-us/minecraft/creator/reference/content/recipereference/examples/recipedefinitions/minecraftrecipe_potionbrewingmix)
        """
        super().__init__(name)
        self.content(JsonSchemes.recipe_brewing_mix(self.identifier, tags))

    def recipe(
        self,
        item: MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier,
        reagent: MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier,
        output: MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier,
    ):
        """Sets the recipe for the potion mixing.

        Args:
            item (MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier): Input potion used in the mixing recipe.
            reagent (MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier): MinecraftItemDescriptor | MinecraftBlockDescriptor used in the mixing recipe with the input potion.
            output (MinecraftItemDescriptor | MinecraftBlockDescriptor | Identifier): Output potion from the mixing recipe.

        Returns:
            PotionMixingRecipe: The current instance of PotionMixingRecipe.
        """
        self._content[self._type]["input"] = str(item)
        self._content[self._type]["reagent"] = str(reagent)
        self._content[self._type]["output"] = str(output)
        return self

    def queue(self):
        return super().queue()
