var _a;
import * as mc from "@minecraft/server";
import { SafetyCheckedPlayer, getAllPlayersSafe } from "./entity";
import { clamp } from "./math";
import { mcerror, mclog } from "./log";
import { GlobalNamespace } from "./namespace";
import { waitForEvent, waitForLoopEvent, waitForNamespace, waitForWorldLoad } from "./events";
import { VANILLA_ITEMS } from "./vanilla_items";
/**
 * @remarks Modifies the durability of an item in a given entity's specified equipment slot.
 * @param entity The safety checked entity who's equipment to modify.
 * @param equipmentSlot The equipment slot to modify the equipment in.
 * @param durabilityChangeAmount The amount by which to change the durability, use negative values to decrease the durability.
 * @example
 * ```typescript
 * modifyEquipmentDurability(new SafetyCheckedEntity(player), mc.EquipmentSlot.Mainhand, -1); // Decreases durability of held item by 1
 * ```
 */
export function modifyEquipmentDurability(entity, equipmentSlot, durabilityChangeAmount) {
    if (entity.matches({ gameMode: mc.GameMode.creative }))
        return;
    const equipment = entity.getComponent(mc.EntityEquippableComponent.componentId);
    if (!equipment)
        return;
    const item = equipment.getEquipment(equipmentSlot);
    if (!item)
        return;
    const durability = item.getComponent(mc.ItemDurabilityComponent.componentId);
    if (!durability) {
        mcerror(`${item.typeId} Has No Durability Component`);
        return;
    }
    ;
    durability.damage = clamp(durability.damage + (durabilityChangeAmount * -1), 0, durability.maxDurability);
    if (durability.damage === durability.maxDurability) {
        mc.world.playSound('random.break', entity.location);
        equipment.setEquipment(equipmentSlot);
    }
    else {
        equipment.setEquipment(equipmentSlot, item);
    }
}
/**
 * @remarks Gets the lang key RawMessage for an item.
 * @param item The item to get the lang key for.
 * @returns The translate RawMessage for the item.
 */
export function itemLangKey(item) {
    const vanillaEntry = VANILLA_ITEMS[item.typeId];
    if (vanillaEntry)
        return { translate: vanillaEntry.langKey };
    try {
        if (mc.BlockPermutation.resolve(item.typeId).type.id === item.typeId) {
            return { translate: `tile.${item.typeId}.name` };
        }
    }
    catch (error) { }
    return { translate: `item.${item.typeId}.name` };
}
/**
 * @remarks Safely returns stacks of items without getting stack size errors, capping each ItemStack to the max amount of the item.
 * @param itemId The item id to get stacks of.
 * @param amount The amount of that item.
 * @returns An array of ItemStacks of the item id with the specified amount.
 * @example
 * ```typescript
 * const pearls = getStacksSafe("minecraft:ender_pearl", 32); // Returns an array containing 2 ItemStacks of ender pearls each with an amount of 16.
 * const stone = getStacksSafe("minecraft:stone", 0); // Returns an empty array without the stack size 0 error.
 * ```
 * @throws If the item id is invalid.
 */
export function getStacksSafe(itemId, amount) {
    const stacks = [];
    if (amount <= 0)
        return stacks;
    const stack = new mc.ItemStack(itemId);
    const fullStacks = Math.floor(amount / stack.maxAmount);
    const remainder = amount % stack.maxAmount;
    for (let i = 0; i < fullStacks; i++) {
        stacks.push(new mc.ItemStack(itemId, stack.maxAmount));
    }
    if (remainder)
        stacks.push(new mc.ItemStack(itemId, remainder));
    return stacks;
}
const CUSTOM_ITEM_RECORD = {};
/**
 * @remarks How should this custom item handle a melee animation?
 */
export var CustomItemMeleeType;
(function (CustomItemMeleeType) {
    /**
     * @remarks Performs a vanilla melee attack. Expects the third person idle to have `override_previous_animation` true.
     */
    CustomItemMeleeType[CustomItemMeleeType["vanilla"] = 0] = "vanilla";
    /**
     * @remarks Performs the custom attack animation **instead** of the vanilla melee. Requires the first and third person animations to have `override_previous_animation` true and the attachable's transition from idle to be `"attack": "query.is_using_item || query.is_name_any('§a§t§t§a§c§k§e§r')"`.
     */
    CustomItemMeleeType[CustomItemMeleeType["meleeOverride"] = 1] = "meleeOverride";
    /**
     * @remarks Performs the custom attack animation **instead** of the vanilla melee and does **not** perform the attack on interact. Requires the first and third person animations to have `override_previous_animation` true and the attachable's transition from idle to be `"attack": "query.is_using_item || query.is_name_any('§a§t§t§a§c§k§e§r')"`.
     */
    CustomItemMeleeType[CustomItemMeleeType["meleeOnly"] = 2] = "meleeOnly";
    /**
     * @remarks Same as vanilla, but exits the animation when `q.is_using_item` is false rather than waiting for the animation to finish.
     */
    CustomItemMeleeType[CustomItemMeleeType["held"] = 3] = "held";
})(CustomItemMeleeType || (CustomItemMeleeType = {}));
/**
 * @remarks Initialized an array of {@link ICustomItem}s to play animations and perform effects.
 * @param items An array of {@link ICustomItem}s.
 */
export function initializeCustomItem(items) {
    items.forEach(item => {
        var _b, _c;
        let effectsCallback = (player) => {
            var _b, _c;
            if ((_b = item.options) === null || _b === void 0 ? void 0 : _b.itemCallback) {
                item.options.itemCallback(player);
            }
            if ((_c = item.options) === null || _c === void 0 ? void 0 : _c.meleeAttack) {
                item.options.meleeAttack.attack(player);
            }
        };
        if (((_b = item.options) === null || _b === void 0 ? void 0 : _b.itemCallback) && item.options.meleeAttack) {
            item.options.meleeAttack.addSwingCallback(item.options.itemCallback);
            effectsCallback = (player) => {
                item.options.meleeAttack.attack(player);
            };
        }
        //#region CUSTOM ITEM ACS
        switch ((_c = item.options) === null || _c === void 0 ? void 0 : _c.meleeAnimation) {
            case CustomItemMeleeType.meleeOverride:
                CUSTOM_ITEM_RECORD[item.typeId] = (player, attacking) => {
                    var _b, _c, _d, _e, _f, _g, _h, _j;
                    const itemShortName = item.typeId.split(':').pop();
                    // Custom Attacks Melee Transition
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.attack.first_person`, {
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.first_person`,
                        stopExpression: "query.any_animation_finished || (!(v.playing_custom_attack ?? 0))",
                        blendOutTime: (_c = (_b = item.options) === null || _b === void 0 ? void 0 : _b.attackToIdleBlend) !== null && _c !== void 0 ? _c : 0.0,
                        controller: `${itemShortName}.first_person`,
                    });
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.attack.third_person`, {
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.third_person`,
                        stopExpression: "query.any_animation_finished || (!(v.playing_custom_attack ?? 0))",
                        blendOutTime: (_e = (_d = item.options) === null || _d === void 0 ? void 0 : _d.attackToIdleBlend) !== null && _e !== void 0 ? _e : 0.0,
                        controller: `${itemShortName}.third_person`,
                    });
                    // Custom Idles
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.first_person`, {
                        stopExpression: `query.is_name_any('${GlobalNamespace.Namespace}attack') || (v.attack_time && query.cooldown_time_remaining('slot.weapon.mainhand') <= 0)`,
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.attack.first_person`,
                        blendOutTime: 0,
                        controller: `${itemShortName}.first_person`,
                    });
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.third_person`, {
                        stopExpression: `query.is_name_any('${GlobalNamespace.Namespace}attack') || (v.attack_time && query.cooldown_time_remaining('slot.weapon.mainhand') <= 0)`,
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.attack.third_person`,
                        blendOutTime: 0,
                        controller: `${itemShortName}.third_person`,
                    });
                    if (!attacking)
                        return;
                    // Custom Attacks Interact
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.attack.first_person`, {
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.first_person`,
                        stopExpression: "query.any_animation_finished || (!(v.playing_custom_attack ?? 0))",
                        blendOutTime: (_g = (_f = item.options) === null || _f === void 0 ? void 0 : _f.attackToIdleBlend) !== null && _g !== void 0 ? _g : 0.0,
                        controller: `${itemShortName}.first_person`,
                    });
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.attack.third_person`, {
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.third_person`,
                        stopExpression: "query.any_animation_finished || (!(v.playing_custom_attack ?? 0))",
                        blendOutTime: (_j = (_h = item.options) === null || _h === void 0 ? void 0 : _h.attackToIdleBlend) !== null && _j !== void 0 ? _j : 0.0,
                        controller: `${itemShortName}.third_person`,
                    });
                    effectsCallback(player);
                };
                break;
            case CustomItemMeleeType.meleeOnly:
                CUSTOM_ITEM_RECORD[item.typeId] = (player, attacking) => {
                    var _b, _c, _d, _e;
                    const itemShortName = item.typeId.split(':').pop();
                    // Custom Attacks Melee Transition
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.attack.first_person`, {
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.first_person`,
                        stopExpression: "query.any_animation_finished || (!(v.playing_custom_attack ?? 0))",
                        blendOutTime: (_c = (_b = item.options) === null || _b === void 0 ? void 0 : _b.attackToIdleBlend) !== null && _c !== void 0 ? _c : 0.0,
                        controller: `${itemShortName}.first_person`,
                    });
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.attack.third_person`, {
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.third_person`,
                        stopExpression: "query.any_animation_finished || (!(v.playing_custom_attack ?? 0))",
                        blendOutTime: (_e = (_d = item.options) === null || _d === void 0 ? void 0 : _d.attackToIdleBlend) !== null && _e !== void 0 ? _e : 0.0,
                        controller: `${itemShortName}.third_person`,
                    });
                    // Custom Idles
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.first_person`, {
                        stopExpression: "v.attack_time > 0",
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.attack.first_person`,
                        blendOutTime: 0,
                        controller: `${itemShortName}.first_person`,
                    });
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.third_person`, {
                        stopExpression: `v.attack_time > 0`,
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.attack.third_person`,
                        blendOutTime: 0,
                        controller: `${itemShortName}.third_person`,
                    });
                };
                break;
            case CustomItemMeleeType.held:
                CUSTOM_ITEM_RECORD[item.typeId] = (player, attacking) => {
                    var _b, _c, _d, _e;
                    const itemShortName = item.typeId.split(':').pop();
                    // Vanilla Punch
                    player.playAnimation(`empty`, {
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.third_person`,
                        stopExpression: "v.attack_time <= 0",
                        blendOutTime: 0,
                        controller: `${itemShortName}.third_person`,
                    });
                    // Custom Idles
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.first_person`, {
                        stopExpression: "false",
                        nextState: "default",
                        blendOutTime: 0,
                        controller: `${itemShortName}.first_person`,
                    });
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.third_person`, {
                        stopExpression: `v.attack_time > 0 && !v.is_first_person && q.is_item_name_any('slot.weapon.mainhand', 0, '${item.typeId}')`,
                        nextState: `empty`,
                        blendOutTime: 0,
                        controller: `${itemShortName}.third_person`,
                    });
                    if (!attacking)
                        return;
                    // Custom Attacks
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.attack.first_person`, {
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.first_person`,
                        stopExpression: "!query.is_using_item || (!(v.playing_custom_attack ?? 0))",
                        blendOutTime: (_c = (_b = item.options) === null || _b === void 0 ? void 0 : _b.attackToIdleBlend) !== null && _c !== void 0 ? _c : 0.0,
                        controller: `${itemShortName}.first_person`,
                    });
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.attack.third_person`, {
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.third_person`,
                        stopExpression: "!query.is_using_item || (!(v.playing_custom_attack ?? 0))",
                        blendOutTime: (_e = (_d = item.options) === null || _d === void 0 ? void 0 : _d.attackToIdleBlend) !== null && _e !== void 0 ? _e : 0.0,
                        controller: `${itemShortName}.third_person`,
                    });
                    effectsCallback(player);
                };
                break;
            default:
                CUSTOM_ITEM_RECORD[item.typeId] = (player, attacking) => {
                    var _b, _c, _d, _e;
                    const itemShortName = item.typeId.split(':').pop();
                    // Vanilla Punch
                    player.playAnimation(`empty`, {
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.third_person`,
                        stopExpression: "v.attack_time <= 0",
                        blendOutTime: 0,
                        controller: `${itemShortName}.third_person`,
                    });
                    // Custom Idles
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.first_person`, {
                        stopExpression: "false",
                        nextState: "default",
                        blendOutTime: 0,
                        controller: `${itemShortName}.first_person`,
                    });
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.third_person`, {
                        stopExpression: `v.attack_time > 0 && !v.is_first_person && q.is_item_name_any('slot.weapon.mainhand', 0, '${item.typeId}')`,
                        nextState: `empty`,
                        blendOutTime: 0,
                        controller: `${itemShortName}.third_person`,
                    });
                    if (!attacking)
                        return;
                    // Custom Attacks
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.attack.first_person`, {
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.first_person`,
                        stopExpression: "query.any_animation_finished || (!(v.playing_custom_attack ?? 0))",
                        blendOutTime: (_c = (_b = item.options) === null || _b === void 0 ? void 0 : _b.attackToIdleBlend) !== null && _c !== void 0 ? _c : 0.0,
                        controller: `${itemShortName}.first_person`,
                    });
                    player.playAnimation(`animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.attack.third_person`, {
                        nextState: `animation.${GlobalNamespace.NamespaceUnformatted}.player.${itemShortName}.idle.third_person`,
                        stopExpression: "query.any_animation_finished || (!(v.playing_custom_attack ?? 0))",
                        blendOutTime: (_e = (_d = item.options) === null || _d === void 0 ? void 0 : _d.attackToIdleBlend) !== null && _e !== void 0 ? _e : 0.0,
                        controller: `${itemShortName}.third_person`,
                    });
                    effectsCallback(player);
                };
                break;
        }
        //#endregion
        waitForEvent(100, () => getAllPlayersSafe().forEach(player => CUSTOM_ITEM_RECORD[item.typeId](player, false)));
    });
    mc.world.afterEvents.itemStartUse.subscribe(event => {
        if (CUSTOM_ITEM_RECORD[event.itemStack.typeId]) {
            CUSTOM_ITEM_RECORD[event.itemStack.typeId](new SafetyCheckedPlayer(event.source), true);
        }
    });
    mc.world.afterEvents.playerSpawn.subscribe(event => {
        waitForEvent(100, () => items.forEach(item => CUSTOM_ITEM_RECORD[item.typeId](new SafetyCheckedPlayer(event.player), false)));
    });
    // Handles Weakness For Melee Weapons
    const meleeItems = items.filter(item => { var _b; return ((_b = item.options) === null || _b === void 0 ? void 0 : _b.meleeAnimation) && !item.options.enableVanillaDamage; }).map(item => item.typeId);
    const meleePlayers = {};
    if (meleeItems.length) {
        waitForLoopEvent(10, () => {
            getAllPlayersSafe().forEach(player => {
                var _b, _c;
                if (meleeItems.includes(String((_c = (_b = player.Equipment) === null || _b === void 0 ? void 0 : _b.getEquipment(mc.EquipmentSlot.Mainhand)) === null || _c === void 0 ? void 0 : _c.typeId))) {
                    player.addEffect("weakness", 15, { amplifier: 255, showParticles: false });
                    player.runCommand("hud @s hide status_effects");
                    meleePlayers[player.id] = true;
                }
                else if (meleePlayers[player.id]) {
                    player.removeEffect("weakness");
                    player.runCommand("hud @s reset status_effects");
                    meleePlayers[player.id] = false;
                }
            });
            return false;
        });
    }
}
/**
 * @remarks A class for working with custom items, similar to the Actor framework.
 */
export class CustomItem {
    static registerPlayer(player) {
        var _b;
        this.registry.set(player.id, new Map());
        for (const [id, item] of this.itemTypeRegistry) {
            mclog(`Registering ${id} as ${item.name} for player ${player.name}`, CustomItem.name);
            (_b = this.registry.get(player.id)) === null || _b === void 0 ? void 0 : _b.set(id, new item(player, id));
        }
    }
    static registerItemType(typeId, itemType) {
        waitForNamespace().then(() => {
            this.itemTypeRegistry.set(typeId, itemType);
        });
    }
    static getItem(player, itemType) {
        const registry = this.registry.get(player.id);
        if (!registry)
            return;
        for (const element of registry.values()) {
            if (element instanceof itemType)
                return element;
        }
    }
    get TypeId() {
        return this.typeId;
    }
    constructor(owner, typeId) {
        this.owner = owner;
        this.typeId = typeId;
        this.onConstructed();
    }
    onConstructed() { }
    onUse(itemStack) { }
    onUseOn(event) { }
    onStartUse(event) { }
    onStopUse(event) { }
    onUseOnEntity(event) { }
    onUseOnBlock(event) { }
    decrementItem() {
        if (!this.owner.matches({ excludeGameModes: [mc.GameMode.creative, mc.GameMode.spectator] }))
            return;
        const item = this.owner.MainHand;
        if ((item === null || item === void 0 ? void 0 : item.typeId) === this.TypeId) {
            if (item.amount > 1) {
                item.amount--;
                this.owner.MainHand = item;
            }
            else {
                this.owner.MainHand = undefined;
            }
        }
    }
}
_a = CustomItem;
CustomItem.registry = new Map();
CustomItem.inUseRegistry = new Map();
CustomItem.itemTypeRegistry = new Map();
(() => {
    waitForNamespace().then(() => {
        mc.world.afterEvents.playerSpawn.subscribe(event => {
            mc.world.afterEvents.playerSpawn.subscribe(event => {
                if (event.initialSpawn) {
                    _a.registerPlayer(new SafetyCheckedPlayer(event.player));
                }
            });
        });
        waitForWorldLoad().then(() => getAllPlayersSafe().forEach(_a.registerPlayer.bind(CustomItem)));
        mc.world.afterEvents.itemUse.subscribe(event => {
            const registry = _a.registry.get(event.source.id);
            const item = registry === null || registry === void 0 ? void 0 : registry.get(event.itemStack.typeId);
            item === null || item === void 0 ? void 0 : item.onUse(event.itemStack);
        });
        mc.world.afterEvents.itemUseOn.subscribe(event => {
            const registry = _a.registry.get(event.source.id);
            const item = registry === null || registry === void 0 ? void 0 : registry.get(event.itemStack.typeId);
            item === null || item === void 0 ? void 0 : item.onUseOn(event);
        });
        mc.world.afterEvents.itemStartUse.subscribe(event => {
            const registry = _a.registry.get(event.source.id);
            const item = registry === null || registry === void 0 ? void 0 : registry.get(event.itemStack.typeId);
            _a.inUseRegistry.set(event.source.id, item);
            item === null || item === void 0 ? void 0 : item.onStartUse(event);
        });
        mc.world.afterEvents.itemStopUse.subscribe(event => {
            const item = _a.inUseRegistry.get(event.source.id);
            item === null || item === void 0 ? void 0 : item.onStopUse(event);
        });
        mc.world.afterEvents.playerInteractWithEntity.subscribe(event => {
            var _b, _c;
            const registry = _a.registry.get(event.player.id);
            const item = registry === null || registry === void 0 ? void 0 : registry.get((_c = (_b = event.beforeItemStack) === null || _b === void 0 ? void 0 : _b.typeId) !== null && _c !== void 0 ? _c : "");
            item === null || item === void 0 ? void 0 : item.onUseOnEntity(event);
        });
        mc.world.afterEvents.playerInteractWithBlock.subscribe(event => {
            var _b, _c;
            const registry = _a.registry.get(event.player.id);
            const item = registry === null || registry === void 0 ? void 0 : registry.get((_c = (_b = event.beforeItemStack) === null || _b === void 0 ? void 0 : _b.typeId) !== null && _c !== void 0 ? _c : "");
            item === null || item === void 0 ? void 0 : item.onUseOnBlock(event);
        });
    });
})();
