import { EntityComponentTypes, ItemStack, world } from "@minecraft/server";
/**
 * @remarks
 * 配方管理器by诗栩MSolo
 * 提供配方管理器的基础功能
*/
export class RecipeHolder {
    /**
     * @remarks
     * 创建配方管理器
     * @param typeId 配方管理器绑定的实体标识符
     * @param inputSlots 输入栏位的数量
     * @param outputSlots 输出栏位的数量
     * @param tags 接受的配方tag
     * @param recipeList 初始配方数组
    */
    constructor(typeId, inputSlots, outputSlots, tags, recipeList) {
        this.typeId = typeId;
        this.inputSlots = inputSlots;
        this.outputSlots = outputSlots;
        this.tags = tags;
        this.entities = new Map();
        this.recipeList = recipeList ? recipeList : [];
        world.afterEvents.entitySpawn.subscribe(({ entity }) => {
            if (entity.typeId != this.typeId)
                return;
            this.entities.set(entity.id, entity);
            entity.setDynamicProperty('recipe:current_recipe');
            entity.setDynamicProperty('recipe:current_tick', 0);
        });
        world.afterEvents.dataDrivenEntityTrigger.subscribe(({ entity }) => {
            if (!this.entities.get(entity.id))
                this.entities.set(entity.id, entity);
            this.update(entity.id);
        }, { eventTypes: ['block_entity:tick'], entityTypes: [this.typeId] });
    }
    /**
     * @remarks
     * 添加配方, 若添加的配方的标识符与已有配方相同则会根据优先级选择保留哪个配方
     * @param addingRecipe 要添加的配方
    */
    addRecipe(addingRecipe) {
        let alter = false;
        for (const index in this.recipeList) {
            if (addingRecipe.identifer == this.recipeList[index].identifer) {
                if (addingRecipe.priority > this.recipeList[index].priority) {
                    this.recipeList[index] = addingRecipe;
                    alter = true;
                }
            }
        }
        if (!alter) {
            this.recipeList.push(addingRecipe);
        }
    }
    /**
     * @remarks
     * 通过原料获取配方
     * @param ingredients 原料数组, 可通过 getInputs() 方法获取
    */
    getRecipe(ingredientStacks) {
        let ingredients = [];
        const map = new Map();
        for (let item of ingredientStacks) {
            if (item instanceof ItemStack) {
                if (map.has(item.typeId)) {
                    map.set(item.typeId, item.amount + map.get(item.typeId));
                }
                else {
                    map.set(item.typeId, item.amount);
                }
            }
            else {
                ingredients.push(item);
            }
        }
        map.forEach((amount, typeId) => {
            ingredients.push({ item: typeId, amount });
        });
        for (const recipe of this.recipeList) {
            let needs = JSON.parse(JSON.stringify(recipe.ingredients));
            if (ingredients.length == needs.length) {
                let found = ingredients.every((item, index) => {
                    for (const index2 in needs) {
                        if (this.isIngredient(item, needs[index2])) {
                            needs.splice(Number(index2), 1);
                            return true;
                        }
                    }
                    return false;
                });
                if (found)
                    return recipe;
            }
        }
        return false;
    }
    /**
     * @remarks
     * 通过配方标识符获取配方
     * @param recipeId 配方标识符
    */
    getRecipeById(recipeId) {
        for (const recipe of this.recipeList) {
            if (recipe.identifer == recipeId) {
                return recipe;
            }
        }
        return false;
    }
    /**
     * @remarks
     * 根据条件获取配方, 若无条件则获取所有配方
    */
    getRecipes(options) {
        if (!options)
            return this.recipeList;
        else {
            let list = [];
            if (options.tags) {
                for (const tag of options.tags) {
                    for (const recipe of this.recipeList) {
                        if (recipe.tags.includes(tag)) {
                            list.push(recipe);
                        }
                    }
                }
            }
            return list;
        }
    }
    /**
     * @remarks
     * 检查是否可以继续进行配方
     * 具体功能在子类中实现
    */
    canContinue(entityId) {
        return true;
    }
    /**
     * @remarks
     * 更新配方进度, 每tick调用
     * 具体功能在子类中实现
    */
    update(entityId) {
    }
    /**
     * @remarks
     * 获取当前配方进度(0-1小数), 若当前没有配方进行则返回false
    */
    getProgress(entityId) {
        const entity = this.entities.get(entityId);
        if (!entity || !entity.isValid())
            return;
        const currentRecipeId = entity.getDynamicProperty('recipe:current_recipe');
        if (!currentRecipeId)
            return 0;
        const currentTick = entity.getDynamicProperty('recipe:current_tick');
        const currentRecipe = this.getRecipeById(currentRecipeId);
        if (currentRecipe) {
            return currentTick / currentRecipe.time;
        }
        else {
            return 0;
        }
    }
    /**
     * @remarks
     * 获取当前实体容器输入槽中的物品, 不包括空槽位
    */
    getInputs(entityId) {
        const entity = this.entities.get(entityId);
        if (!entity || !entity.isValid())
            return;
        const container = entity.getComponent(EntityComponentTypes.Inventory)?.container;
        let itemList = [];
        for (let i = 0; i < this.inputSlots; i++) {
            const item = container?.getItem(i);
            if (item)
                itemList.push(item);
        }
        return itemList;
    }
    /**
     * @remarks
     * 获取当前实体容器输出槽中的物品, 不包括空槽位
    */
    getOutputs(entityId) {
        const entity = this.entities.get(entityId);
        if (!entity || !entity.isValid())
            return;
        const container = entity.getComponent(EntityComponentTypes.Inventory)?.container;
        let itemList = [];
        for (let i = 0; i < this.outputSlots; i++) {
            const item = container?.getItem(i + this.inputSlots);
            if (item)
                itemList.push(item);
        }
        return itemList;
    }
    /**
     * @remarks
     * 设置输出槽物品
     * @param slot 输出槽位
     * @param item 设置的物品堆叠
    */
    setOutput(entityId, slot, item) {
        const entity = this.entities.get(entityId);
        if (!entity || !entity.isValid())
            return;
        const container = entity.getComponent(EntityComponentTypes.Inventory)?.container;
        container?.setItem(slot + this.inputSlots, item);
    }
    /**
     * @remarks
     * 向输出槽添加物品, 若添加失败返回false, 添加成功返回true
     * @param result 添加的物品结果或物品结果的数组
    */
    addOutput(entityId, result) {
        const entity = this.entities.get(entityId);
        if (!entity || !entity.isValid())
            return;
        const container = entity.getComponent(EntityComponentTypes.Inventory)?.container;
        let added = false;
        if (Array.isArray(result)) {
            added = true;
            result.forEach((result) => {
                const item = new ItemStack(result.item, result.amount);
                for (let i = 0; i < this.outputSlots; i++) {
                    const output = container?.getItem(i + this.inputSlots);
                    if (!output) {
                        container?.setItem(i + this.inputSlots, item);
                        return true;
                    }
                    else if (output?.isStackableWith(item)) {
                        if (output.amount + item.amount <= output.maxAmount) {
                            output.amount += item.amount;
                            return true;
                        }
                        else {
                            item.amount -= output.maxAmount - output.amount;
                            output.amount = output.maxAmount;
                        }
                        container?.setItem(i + this.inputSlots, output);
                    }
                }
                added = false;
            });
            return added;
        }
        else {
            const item = new ItemStack(result.item, result.amount);
            for (let i = 0; i < this.outputSlots; i++) {
                const output = container?.getItem(i + this.inputSlots);
                if (!output) {
                    container?.setItem(i + this.inputSlots, item);
                    return true;
                }
                else if (output?.isStackableWith(item)) {
                    if (output.amount + item.amount <= output.maxAmount) {
                        output.amount += item.amount;
                        return true;
                    }
                    else {
                        item.amount -= output.maxAmount - output.amount;
                        output.amount = output.maxAmount;
                    }
                    container?.setItem(i + this.inputSlots, output);
                }
            }
            return false;
        }
    }
    /**
     * @remarks
     * 检查能否向输出栏输出物品(组), 若不能则返回false, 可以则返回true
     * @param result 添加的物品结果或物品结果的数组
    */
    canOutput(entityId, result) {
        const entity = this.entities.get(entityId);
        if (!entity || !entity.isValid())
            return;
        const container = entity.getComponent(EntityComponentTypes.Inventory)?.container;
        let added = false;
        if (Array.isArray(result)) {
            let outputs = [];
            for (let i = 0; i < this.outputSlots; i++) {
                outputs.push(container?.getItem(i + this.inputSlots));
            }
            for (const item of result) {
                added = false;
                const itemStack = new ItemStack(item.item, item.amount);
                for (let i in outputs) {
                    const output = outputs[i];
                    if (!output) {
                        added = true;
                    }
                    else if (output?.isStackableWith(itemStack)) {
                        if (output.amount + itemStack.amount <= output.maxAmount) {
                            output.amount += itemStack.amount;
                            added = true;
                        }
                        else {
                            itemStack.amount -= output.maxAmount - output.amount;
                            output.amount = output.maxAmount;
                        }
                    }
                }
                if (!added)
                    return false;
            }
            return true;
        }
        else {
            const item = new ItemStack(result.item, result.amount);
            for (let i = 0; i < this.outputSlots; i++) {
                const output = container?.getItem(i + this.inputSlots);
                if (!output) {
                    added = true;
                }
                else if (output?.isStackableWith(item)) {
                    if (output.amount + item.amount <= output.maxAmount) {
                        output.amount += item.amount;
                        added = true;
                    }
                    else {
                        item.amount -= output.maxAmount - output.amount;
                        output.amount = output.maxAmount;
                    }
                }
            }
            return added;
        }
    }
    /**
     * @remarks
     * 检测是否可以输入
    */
    canAddInput(entityId, inputs) {
        const entity = this.entities.get(entityId);
        if (!entity || !entity.isValid())
            return;
        const container = entity.getComponent(EntityComponentTypes.Inventory)?.container;
        let hasInputs = [];
        for (let i = 0; i < this.inputSlots; i++) {
            hasInputs.push(container?.getItem(i));
        }
        for (const itemStack of inputs) {
            let added = false;
            for (let i in hasInputs) {
                const input = hasInputs[i];
                if (!input) {
                    added = true;
                }
                else if (input?.isStackableWith(itemStack)) {
                    if (input.amount + itemStack.amount <= input.maxAmount) {
                        input.amount += itemStack.amount;
                        added = true;
                    }
                    else {
                        itemStack.amount -= input.maxAmount - input.amount;
                        input.amount = input.maxAmount;
                    }
                }
            }
            if (!added)
                return false;
        }
        return true;
    }
    /**
     * @remarks
     * 完成一次配方, 按传入的配方调整物品
     * @param recipe 配方
    */
    consume(entityId, recipe) {
        const entity = this.entities.get(entityId);
        if (!entity || !entity.isValid())
            return;
        const container = entity.getComponent(EntityComponentTypes.Inventory)?.container;
        if (!container)
            return;
        if (this.canOutput(entityId, recipe.results)) {
            this.addOutput(entityId, recipe.results);
            for (const ingredient of recipe.ingredients) {
                if ('item' in ingredient) {
                    const item = new ItemStack(ingredient.item, ingredient.amount);
                    this.consumeItem(container, item);
                }
                else if ('tag' in ingredient) {
                    const inputs = this.getInputs(entityId);
                    if (!inputs)
                        return;
                    for (const input of inputs) {
                        if (input.hasTag(ingredient.tag)) {
                            this.consumeItem(container, new ItemStack(input.typeId));
                        }
                    }
                }
                else if ('property' in ingredient) {
                    const property = entity.getDynamicProperty(ingredient.property);
                    if (typeof (property) != 'number')
                        return;
                    if (!ingredient.amount) {
                        ingredient.amount = 1;
                    }
                    entity.setDynamicProperty(ingredient.property, property - ingredient.amount);
                }
            }
        }
    }
    /**
     * @remarks
     * 判断传入的物品是否为所需原料
    */
    isIngredient(need, ingredient) {
        if ('item' in need) {
            if ('tag' in ingredient) {
                ingredient;
                return new ItemStack(need.item).hasTag(ingredient.tag);
            }
            else if ('item' in ingredient) {
                ingredient;
                if (need.item == ingredient.item) {
                    if (!need.amount) {
                        need.amount = 1;
                    }
                    if (!ingredient.amount) {
                        ingredient.amount = 1;
                    }
                    return need.amount >= ingredient.amount;
                }
                return false;
            }
        }
        else if ('property' in need && 'property' in ingredient) {
            if (need.property == ingredient.property) {
                if (!need.amount) {
                    need.amount = 1;
                }
                if (!ingredient.amount) {
                    ingredient.amount = 1;
                }
                return need.amount >= ingredient.amount;
            }
            return false;
        }
        if (Array.isArray(ingredient)) {
            ingredient;
            let result = false;
            for (const a of ingredient) {
                if (this.isIngredient(need, a)) {
                    result = true;
                }
            }
            return result;
        }
        return false;
    }
    consumeItem(container, item) {
        if (!container.isValid())
            return;
        let slot = 0;
        let amount = item.amount;
        let invItem;
        let obj = [];
        while (slot < container.size) {
            invItem = container.getItem(slot);
            if (invItem) {
                if (invItem.typeId == item.typeId) {
                    if (invItem.amount > amount) {
                        invItem.amount -= amount;
                        obj[slot] = amount;
                        amount = 0;
                    }
                    else if (invItem.amount == amount) {
                        obj[slot] = undefined;
                        amount = 0;
                    }
                    else {
                        amount -= invItem.amount;
                        obj[slot] = undefined;
                    }
                }
            }
            slot++;
        }
        if (amount == 0) {
            for (let i in obj) {
                const amount = obj[i];
                if (amount) {
                    const item1 = container.getItem(Number(i));
                    if (item1) {
                        item1.amount -= amount;
                        container.setItem(Number(i), item1);
                    }
                }
                else {
                    container.setItem(Number(i), undefined);
                }
            }
            return true;
        }
        else {
            return false;
        }
    }
}
