import { world } from "@minecraft/server";
import { contentLog, Server, sleep, Thread, Vector } from "./../../library/Minecraft.js";
import { getSession, hasSession } from "../sessions.js";
import config from "config.js";
class ToolBuilder {
    constructor() {
        this.tools = new Map();
        this.bindings = new Map();
        this.fixedBindings = new Map();
        this.disabled = [];
        this.currentTick = 0;
        Server.on("beforeItemUse", ev => {
            if (ev.source.typeId != "minecraft:player" || !ev.item) {
                return;
            }
            this.onItemUse(ev.item, ev.source, ev);
        });
        Server.on("beforeItemUseOn", ev => {
            if (ev.source.typeId != "minecraft:player" || !ev.item) {
                return;
            }
            // TODO: Fixed in 1.19.80
            this.onItemUse(ev.item, ev.source, ev, Vector.from(ev.source.getBlockFromViewDirection({
                includePassableBlocks: true,
                includeLiquidBlocks: false,
                maxDistance: 10
            })?.location));
        });
        Server.on("tick", ev => {
            this.currentTick = ev.currentTick;
        });
        new Thread().start(function* (self) {
            while (true) {
                for (const player of world.getPlayers()) {
                    try {
                        const item = Server.player.getHeldItem(player);
                        if (!item)
                            continue;
                        yield* self.onItemTick(item, player, self.currentTick);
                    }
                    catch (err) {
                        contentLog.error(err);
                    }
                }
                yield sleep(1);
            }
        }, this);
        if (config.useBlockBreaking) {
            Server.on("blockBreak", ev => {
                const item = Server.player.getHeldItem(ev.player);
                if (!item) {
                    return;
                }
                this.onBlockBreak(item, ev.player, ev);
            });
        }
    }
    register(toolClass, name, item) {
        this.tools.set(name, toolClass);
        if (item) {
            this.fixedBindings.set(item + "/0", new (toolClass)());
        }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    bind(toolId, itemId, itemData, player, ...args) {
        this.unbind(itemId, itemData, player);
        if (itemId) {
            const tool = new (this.tools.get(toolId))(...args);
            tool.type = toolId;
            this.createPlayerBindingMap(player);
            this.bindings.get(player.name).set(`${itemId}/${0}`, tool);
            return tool;
        }
        else {
            throw "worldedit.tool.noItem";
        }
    }
    unbind(itemId, itemData, player) {
        if (itemId) {
            if (this.fixedBindings.has(itemId + "/0")) {
                throw "worldedit.tool.fixedBind";
            }
            this.createPlayerBindingMap(player);
            this.bindings.get(player.name).delete(`${itemId}/${0}`);
        }
        else {
            throw "worldedit.tool.noItem";
        }
    }
    deleteBindings(player) {
        this.bindings.delete(player.name);
        this.setDisabled(player, false);
    }
    hasBinding(itemId, itemData, player) {
        if (itemId) {
            return this.bindings.get(player.name)?.has(`${itemId}/${0}`) || this.fixedBindings.has(itemId + "/0");
        }
        else {
            return false;
        }
    }
    getBindingType(itemId, itemData, player) {
        if (itemId) {
            const tool = this.bindings.get(player.name)?.get(`${itemId}/${0}`) || this.fixedBindings.get(itemId + "/0");
            return tool?.type ?? "";
        }
        else {
            return "";
        }
    }
    getBoundItems(player, type) {
        const tools = this.bindings.get(player.name);
        return tools ? Array.from(tools.entries())
            .filter(binding => !type || (typeof type == "string" ? binding[1].type == type : type.test(binding[1].type)))
            .map(binding => [binding[0].split("/")[0], parseInt(binding[0].split("/")[1])])
            : [];
    }
    setProperty(itemId, itemData, player, prop, value) {
        if (itemId) {
            const tool = this.bindings.get(player.name).get(`${itemId}/${0}`);
            if (tool && prop in tool) {
                tool[prop] = value;
                return true;
            }
        }
        return false;
    }
    getProperty(itemId, itemData, player, prop) {
        if (itemId) {
            const tool = this.bindings.get(player.name).get(`${itemId}/${0}`);
            if (tool && prop in tool) {
                return tool[prop];
            }
        }
        return null;
    }
    hasProperty(itemId, itemData, player, prop) {
        if (itemId) {
            const tool = this.bindings.get(player.name).get(`${itemId}/${0}`);
            if (tool && prop in tool) {
                return true;
            }
        }
        return false;
    }
    setDisabled(player, disabled) {
        if (disabled && !this.disabled.includes(player.name)) {
            this.disabled.push(player.name);
        }
        else if (!disabled && this.disabled.includes(player.name)) {
            this.disabled.splice(this.disabled.indexOf(player.name), 1);
        }
    }
    *onItemTick(item, player, tick) {
        if (this.disabled.includes(player.name) || !hasSession(player.name)) {
            return;
        }
        const key = `${item.typeId}/${0}`;
        let tool;
        if (this.bindings.get(player.name)?.has(key)) {
            tool = this.bindings.get(player.name).get(key);
        }
        else if (this.fixedBindings.has(key)) {
            tool = this.fixedBindings.get(key);
        }
        else {
            return;
        }
        const gen = tool.tick?.(tool, player, getSession(player), tick);
        if (gen)
            yield* gen;
    }
    onItemUse(item, player, ev, loc) {
        if (this.disabled.includes(player.name) || !hasSession(player.name)) {
            return;
        }
        const key = `${item.typeId}/${0}`;
        let tool;
        if (this.bindings.get(player.name)?.has(key)) {
            tool = this.bindings.get(player.name).get(key);
        }
        else if (this.fixedBindings.has(key)) {
            tool = this.fixedBindings.get(key);
        }
        else {
            return;
        }
        tool.process(getSession(player), this.currentTick, loc);
        ev.cancel = true;
    }
    onBlockBreak(item, player, ev) {
        if (this.disabled.includes(player.name)) {
            return;
        }
        const comp = player.getComponent("inventory");
        if (comp.container.getItem(player.selectedSlot) == null)
            return;
        item = comp.container.getItem(player.selectedSlot);
        const key = `${item.typeId}/${0}`;
        let tool;
        if (this.bindings.get(player.name)?.has(key)) {
            tool = this.bindings.get(player.name).get(key);
        }
        else if (this.fixedBindings.has(key)) {
            tool = this.fixedBindings.get(key);
        }
        else {
            return;
        }
        const processed = tool.process(getSession(player), this.currentTick, Vector.from(ev.block.location), ev.brokenBlockPermutation);
        if (processed) {
            player.dimension.getBlock(ev.block.location).setPermutation(ev.brokenBlockPermutation);
        }
    }
    createPlayerBindingMap(player) {
        if (!this.bindings.has(player.name)) {
            this.bindings.set(player.name, new Map());
        }
    }
}
export const Tools = new ToolBuilder();
