import * as mc from "@minecraft/server";
import * as utils from "../../utils/index";
import * as comp from "../components/index";
import { BotBase } from "../bot";
export const TORCH_MAX_FUEL = 99;
export const TORCH_FUEL_ITEM = "minecraft:coal";
const LIGHT_LEVEL_CUTOFF = 7;
const VALID_BLOCK_TAGS = [
    "wood",
    "stone",
    "dirt",
    "sand",
    "gravel",
    "snow",
];
const WHITE_LIST = [
    "minecraft:deepslate",
    "minecraft:snow",
    "minecraft:tuff",
    "minecraft:waxed_copper_block",
    "minecraft:waxed_oxidized_copper",
    "minecraft:waxed_copper_grate",
    "minecraft:waxed_oxidized_grate"
];
export class TorchBot extends BotBase {
    constructor() {
        super(...arguments);
        this.mode = this.getProperty("mode");
        this.devBlocks = [];
        this.aborting = false;
        this.dev = false;
    }
    get LightLevel() {
        return this.getProperty("light_level");
    }
    get State() {
        return this.getProperty("state");
    }
    set State(v) {
        this.setProperty("state", v);
    }
    get Mode() {
        return this.mode;
    }
    set Mode(v) {
        if (this.Mode === v)
            return;
        this.mode = v;
        this.aborting = true;
        this.setProperty("mode", v);
        utils.abortEvents(this.id + "abort");
        this.removeActorComponent(comp.NavigateComponent);
        if (v === "follow") {
            this.triggerEvent("add_follow_owner");
        }
        else {
            this.triggerEvent("remove_follow_owner");
        }
    }
    get Owner() {
        if (!this.isValid())
            return;
        if (this.owner)
            return this.owner;
        const tame = this.getComponent("tameable");
        if (tame && tame.tamedToPlayerId) {
            const owner = utils.getAllPlayersSafe().find(player => player.id === tame.tamedToPlayerId);
            this.owner = owner;
            return owner;
        }
    }
    onConstructed() {
        super.onConstructed();
        this.addActorComponent(comp.AutotameComponent);
        this.addActorComponent(comp.BotFuelComponent, { fuelItem: TORCH_FUEL_ITEM, max: TORCH_MAX_FUEL });
        this.subscribe("player_hit", () => this.Mode = "stay");
    }
    destructor() {
        if (this.dev) {
            this.devBlocks.forEach(block => {
                try {
                    if (block.typeId === "minecraft:torch") {
                        block.setPermutation(mc.BlockPermutation.resolve("minecraft:air"));
                    }
                }
                catch (error) { }
            });
        }
        super.destructor();
    }
    onNonFuelInteract(player) {
        var _a;
        if (player.id !== ((_a = this.Owner) === null || _a === void 0 ? void 0 : _a.id))
            return;
        player.playSound(utils.addName("menu.bot.open"));
        new utils.MenuBuilder({
            type: "buttons",
            title: { translate: utils.addName("torch_bot.menu.title") },
            contents: [
                {
                    text: { translate: utils.addName("torch_bot.menu.stay") },
                    texture: "textures/ldz/240927/ui/torch/stay",
                    shouldHide: () => this.Mode === "stay",
                    onClick: () => {
                        this.Mode = "stay",
                            player.playSound(utils.addName("menu.bot.select_stay"));
                    },
                },
                {
                    text: { translate: utils.addName("torch_bot.menu.roam") },
                    texture: "textures/ldz/240927/ui/torch/roam",
                    shouldHide: () => this.Mode === "roam",
                    onClick: () => {
                        this.Mode = "roam",
                            player.playSound(utils.addName("menu.bot.select_roam"));
                    },
                },
                {
                    text: { translate: utils.addName("torch_bot.menu.follow") },
                    texture: "textures/ldz/240927/ui/torch/follow",
                    shouldHide: () => this.Mode === "follow",
                    onClick: () => {
                        this.Mode = "follow",
                            player.playSound(utils.addName("menu.bot.select_follow"));
                    },
                },
                {
                    text: "Toggle Dev Mode",
                    shouldHide: () => { var _a; return !((_a = this.Owner) === null || _a === void 0 ? void 0 : _a.hasTag("dev")); },
                    onClick: () => {
                        this.dev = !this.dev;
                        utils.mclog(`Set ${this.ActorId}'s Dev Mode To ${this.dev}`);
                    },
                },
            ]
        }).showMenuToPlayer(player);
    }
    onFuelExhausted() {
        this.dimension.playSound(utils.addName("entity.bot.power_down"), this.location);
    }
    onFuelRestored() {
        this.torchLoop();
    }
    async torchLoop() {
        var _a;
        while (this.isValid() && this.Fuel > 0) {
            this.State = "none";
            if (this.Mode === "stay") {
                await utils.waitForTicks(60, this.id + "abort");
                continue;
            }
            this.State = "scanning";
            this.removeActorComponent(comp.NavigateComponent);
            utils.mclog("Starting Torch Loop", this.ActorId);
            const navigableBlocks = await this.getNavigableBlocks();
            utils.mclog(`Checking Blocks ${navigableBlocks.length}`, this.ActorId);
            const lowestLightBlock = await this.findLowestLightBlock(navigableBlocks);
            utils.mclog(`Found block with light level ${lowestLightBlock === null || lowestLightBlock === void 0 ? void 0 : lowestLightBlock.lightLevel} at ${new utils.Vector3((_a = lowestLightBlock === null || lowestLightBlock === void 0 ? void 0 : lowestLightBlock.block) !== null && _a !== void 0 ? _a : Number.NaN)}`, this.ActorId);
            if (lowestLightBlock)
                await this.placeTorchAtBlock(lowestLightBlock.block);
            await utils.waitForTicks(20, this.id + "abort");
            this.aborting = false;
        }
    }
    async getNavigableBlocks() {
        var _a;
        const blocks = [];
        const dimension = this.dimension;
        let origin = this.getHeadLocation();
        if (this.Mode === "follow" && ((_a = this.Owner) === null || _a === void 0 ? void 0 : _a.isValid()))
            origin = this.Owner.getHeadLocation();
        await utils.iterateOverBlocks(this.dimension, origin, 8, (block) => {
            var _a;
            try {
                if ((block.isAir || block.permutation.matches("minecraft:snow_layer")) && (VALID_BLOCK_TAGS.some(val => { var _a; return (_a = block.below()) === null || _a === void 0 ? void 0 : _a.hasTag(val); }) || WHITE_LIST.some(val => { var _a; return (_a = block.below()) === null || _a === void 0 ? void 0 : _a.typeId.match(val); })) && ((_a = block.above()) === null || _a === void 0 ? void 0 : _a.isAir) && this.canReachBlock(block, origin, dimension)) {
                    blocks.push(block);
                }
            }
            catch (error) { }
            return this.aborting;
        });
        return blocks;
    }
    canReachBlock(block, origin, dimension) {
        const dir = new utils.Vector3(block.location).subtract(origin);
        const rayHit = dimension.getBlockFromRay(origin, dir, { includePassableBlocks: false, maxDistance: origin.distance(block.location) });
        return rayHit === undefined;
    }
    async findLowestLightBlock(blocks) {
        if (!this.isValid() || this.aborting)
            return;
        const promises = [];
        const dev = this.dev;
        const bot = this;
        await new Promise(resolve => {
            function* getLightLevels(dimension, blocks) {
                for (const block of blocks) {
                    try {
                        if (block.isAir || block.permutation.matches("minecraft:snow_layer")) {
                            const lightChecker = utils.spawnEntitySafe(dimension, utils.addName("light_check"), new utils.Vector3(block.location));
                            utils.despawnOnReload(lightChecker);
                            if (dev)
                                dimension.spawnParticle("minecraft:endrod", block.location);
                            const promise = utils.waitForEvent(2, () => {
                                const lightLevel = Number(lightChecker.getProperty("light_level"));
                                lightChecker.remove();
                                return { block, lightLevel };
                            });
                            promises.push(promise);
                        }
                    }
                    catch (error) { }
                    if (!bot.isValid() || bot.aborting) {
                        resolve();
                        return;
                    }
                    ;
                    yield;
                }
                resolve();
            }
            mc.system.runJob(getLightLevels(this.dimension, blocks));
        });
        const results = await Promise.all(promises);
        return results.find(element => {
            if (!element)
                return 15;
            return element.lightLevel < LIGHT_LEVEL_CUTOFF;
        });
    }
    async placeTorchAtBlock(block) {
        if (!this.isValid() || this.aborting)
            return;
        this.State = "holding_torch";
        if (this.dev) {
            const origin = new utils.Vector3(block.location);
            for (let index = 0; index < 5; index++) {
                this.dimension.spawnParticle("minecraft:endrod", origin.up(index).center());
            }
        }
        try {
            await new Promise((resolve, reject) => {
                this.dimension.playSound(utils.addName("entity.bot.hover"), this.location);
                this.addActorComponent(comp.NavigateComponent, {
                    destination: block.location,
                    navNodeId: utils.addName("nav_node"),
                    onSuccess: async () => {
                        if (this.isValid() && (block.isAir || block.permutation.matches("minecraft:snow_layer")) && (VALID_BLOCK_TAGS.some(val => { var _a; return (_a = block.below()) === null || _a === void 0 ? void 0 : _a.hasTag(val); }) || WHITE_LIST.some(val => { var _a; return (_a = block.below()) === null || _a === void 0 ? void 0 : _a.typeId.match(val); }))) {
                            this.State = "placing_torch";
                            this.runCommand(`stopsound @a ldz_240927:entity.bot.hover`);
                            await utils.waitForTicks(10);
                            block.setPermutation(mc.BlockPermutation.resolve("minecraft:air"));
                            await utils.waitForTicks(10);
                            block.setPermutation(mc.BlockPermutation.resolve("minecraft:torch", { "torch_facing_direction": "top" }));
                            this.dimension.playSound(utils.addName("entity.torch_bot.place_torch"), this.location);
                            this.Fuel--;
                            if (this.dev)
                                this.devBlocks.push(block);
                            resolve();
                        }
                        reject();
                    },
                    onFailure: reject,
                });
            });
        }
        catch (error) {
            utils.mcerror(`Failed to place torch at ${new utils.Vector3(block.location)}`);
        }
    }
}
utils.Actor.makeActors({ type: "ldz_240927:torch_bot" }, TorchBot);
