import * as mc from "@minecraft/server";
import * as utils from "../../utils/index";
import * as comp from "../components/index";
import { BotBase } from "../bot";
import("./drill_seat");
export const DRILL_MAX_FUEL = 64;
export const DRILL_FUEL_ITEM = "minecraft:coal";
const SECONDS_PER_BLOCK = 5;
const MAX_DRILL_DEPTH = 32;
const MINING_BLACKLIST = [
    "minecraft:bedrock",
    "minecraft:barrier",
    "minecraft:command_block",
    "minecraft:end_portal_frame",
    "minecraft:end_portal",
    "minecraft:end_gateway",
    "minecraft:jigsaw",
    "minecraft:portal",
    "minecraft:border",
    "minecraft:structure_block",
    "minecraft:structure_void",
    "minecraft:bubble_column",
    "minecraft:light_block",
    "minecraft:command_block",
    "minecraft:chain_command_block",
    "minecraft:repeating_command_block",
    "minecraft:lava",
    "minecraft:flowing_lava",
];
mc.world.afterEvents.entitySpawn.subscribe(event => {
    var _a;
    if (event.entity.typeId === "ldz_240927:drill_bot") {
        const drillBot = new utils.SafetyCheckedEntity(event.entity);
        const drillSpawnRotation = drillBot.getRotation().y;
        const snappedAngle = snapAngleTo90(drillSpawnRotation);
        const seatEntity = utils.spawnEntitySafe(drillBot.dimension, "ldz_240927:drill_bot_seat", new utils.Vector3(drillBot.location), { rotation: { x: 0, y: snappedAngle } });
        (_a = seatEntity.getComponent(mc.EntityRideableComponent.componentId)) === null || _a === void 0 ? void 0 : _a.addRider(drillBot.UnsafeEntity);
        drillBot.triggerEvent("mounted_on_seat");
    }
});
export class DrillBot extends BotBase {
    constructor() {
        super(...arguments);
        this.active = this.getProperty("active");
        this.drillDepth = this.getProperty("drill_depth");
        this.nextBlock = undefined;
    }
    get Active() {
        return this.active;
    }
    set Active(v) {
        if (!this.isValid())
            return;
        this.active = v;
        this.setProperty("active", v);
    }
    get DrillDepth() {
        return this.drillDepth;
    }
    set DrillDepth(v) {
        if (!this.isValid())
            return;
        v = Math.trunc(v);
        if (v < 0)
            v = 0;
        this.drillDepth = v;
        this.setProperty("drill_depth", v);
    }
    get VirtualInventory() {
        return this.getActorComponent(comp.VirtualInventoryComponent);
    }
    onConstructed() {
        super.onConstructed();
        this.runCommand(`stopsound @a ldz_240927:entity.bot.power_down`);
        this.addActorComponent(comp.VirtualInventoryComponent);
        this.addActorComponent(comp.MineBlockComponent, {
            type: "block",
            useTileDrops: mc.world.gameRules.doTileDrops,
            onSuccess: (block) => {
                this.teleportMinedItems();
                this.Fuel -= 1;
            },
            onUnbreakable: (block) => {
                utils.mclog(`${this.ActorId} hit unbreakable block ${block === null || block === void 0 ? void 0 : block.typeId}`, this.ActorId);
                this.stop();
                this.dimension.playSound(utils.addName("entity.drill_bot.hit_unbreakable"), this.location);
            },
            onFailure: (error) => {
                utils.mcerror(`${this.ActorId} encountered error mining block`, this.ActorId);
                utils.mcerror(error, this.ActorId);
                this.stop();
            }
        });
        this.addActorComponent(comp.BotFuelComponent, { fuelItem: DRILL_FUEL_ITEM, max: DRILL_MAX_FUEL });
    }
    onNonFuelInteract(player) {
        if (this.Active) {
            this.stop();
            return;
        }
        if (this.getNextBlock()) {
            this.dimension.playSound("ldz_240927:entity.bot.activate", this.location);
            this.runDrillLoop();
        }
        else {
            this.stop();
            this.runCommand("stopsound @a[r=16] ldz_240927:entity.bot.activate");
            player.sendMessage({ translate: "ldz_240927:drill_bot.no_blocks_in_range" });
            player.playSound("ldz_240927:entity.drill_bot.operation_failed");
        }
    }
    onFuelExhausted() {
        this.dimension.playSound(utils.addName("entity.bot.power_down"), this.location);
    }
    onFuelRestored() {
    }
    getNextBlock() {
        try {
            const nextBlockHit = this.dimension.getBlockFromRay(this.location, utils.Vector3.DOWN, { includeLiquidBlocks: true, maxDistance: MAX_DRILL_DEPTH, excludeTypes: ["minecraft:water", "minecraft:flowing_water"] });
            if (nextBlockHit) {
                const block = nextBlockHit.block;
                const depth = Math.floor(this.location.distance(nextBlockHit.block.location));
                this.nextBlock = { block, depth };
                return true;
            }
        }
        catch (_error) {
            // Failed to get block from ray, likely out of bounds
        }
        this.nextBlock = undefined;
        return false;
    }
    async moveDrillToNextBlock() {
        // utils.mclog("moveDrillToNextBlock")
        this.getNextBlock();
        let drillMoveDistance;
        if (this.nextBlock !== undefined) {
            drillMoveDistance = Math.max(0, this.nextBlock.depth - this.DrillDepth - 1);
            this.DrillDepth = this.nextBlock.depth - 1;
        }
        else {
            this.stop();
            drillMoveDistance = MAX_DRILL_DEPTH - this.DrillDepth - 1;
            this.DrillDepth = MAX_DRILL_DEPTH - 1;
        }
        const drillMoveDuration = drillMoveDistance * 0.5 * mc.TicksPerSecond;
        await utils.waitForTicks(drillMoveDuration);
    }
    canMine() {
        return this.isValid() && this.Fuel > 0 && this.Active;
    }
    async runDrillLoop() {
        var _a, _b;
        this.Active = true;
        while (this.canMine()) {
            // utils.mclog("drillLoop")
            try {
                await this.waitForGrounded();
                if (!this.canMine())
                    break;
                await this.moveDrillToNextBlock();
                if (!this.canMine() || MINING_BLACKLIST.includes((_b = (_a = this.nextBlock) === null || _a === void 0 ? void 0 : _a.block.typeId) !== null && _b !== void 0 ? _b : ""))
                    break;
                await this.breakBlock();
                if (!this.isValid())
                    break;
                this.getNextBlock();
                if (!this.nextBlock || MINING_BLACKLIST.includes(this.nextBlock.block.typeId))
                    break;
            }
            catch (_error) {
                // Out of bounds or other error, stop the drill
                break;
            }
        }
        if (this.Active)
            this.stop();
    }
    async waitForGrounded() {
        // utils.mclog("waitForGrounded")
        if (this.isValid() && this.isOnGround)
            return;
        await utils.waitForLoopEvent(5, () => {
            return !this.isValid() || this.isOnGround;
        });
    }
    async breakBlock() {
        var _a;
        // utils.mclog("breakBlock")
        if (this.nextBlock === undefined) {
            this.stop();
            return;
        }
        let blockToBreak;
        try {
            const nextBlockLocation = this.location.add(utils.Vector3.DOWN.multiply(this.nextBlock.depth));
            blockToBreak = this.dimension.getBlock(nextBlockLocation);
        }
        catch (error) {
            this.stop();
            return;
        }
        if (!blockToBreak) {
            this.stop();
            return;
        }
        await utils.waitForTicks(SECONDS_PER_BLOCK * mc.TicksPerSecond);
        if (!this.Active) {
            return;
        }
        const wasBlockBroken = (_a = this.getActorComponent(comp.MineBlockComponent)) === null || _a === void 0 ? void 0 : _a.breakBlock(blockToBreak);
        if (!wasBlockBroken) {
            this.stop();
            return;
        }
    }
    teleportMinedItems() {
        const botYaw = snapAngleTo90(this.getRotation().y);
        const botSpoutRelativeLocation = new utils.Vector3({ x: 0, y: 0.35, z: 0.6 }).rotateYaw(-botYaw);
        const itemTeleportSourceLocation = this.location.add(utils.Vector3.DOWN.multiply(this.DrillDepth + 1));
        const itemTeleportTargetLocation = this.location.add(botSpoutRelativeLocation);
        const itemTargetVelocity = new utils.Vector3({ x: 0, y: 1, z: 1 }).multiply(0.15).rotateYaw(-botYaw);
        const itemsToTeleport = this.dimension.getEntitiesAtBlockLocation(itemTeleportSourceLocation).filter(entity => entity.typeId === "minecraft:item");
        itemsToTeleport.forEach(item => {
            item.clearVelocity();
            item.teleport(itemTeleportTargetLocation);
            item.applyImpulse(itemTargetVelocity);
        });
        if (itemsToTeleport.length)
            this.dimension.playSound("random.pop", itemTeleportTargetLocation);
    }
    stop() {
        if (!this.isValid())
            return;
        this.Active = false;
        this.DrillDepth = 0;
        this.runCommand("stopsound @a[r=16] ldz_240927:entity.drill_bot.drill");
        this.dimension.playSound("ldz_240927:entity.bot.deactivate", this.location, { pitch: 0.8 });
    }
}
utils.Actor.makeActors({ type: "ldz_240927:drill_bot" }, DrillBot);
function snapAngleTo90(angle) {
    return Math.floor((angle + 45) / 90) * 90;
}
