import * as mc from "@minecraft/server";
import * as utils from "../utils/index";
;
/**
 * A class for checking if the player has placed a block that matches a particular structure pattern.
 * Use the static {@link StructureChecker.register} method to register a structure to be checked against.
 */
export class StructureChecker {
    static listen() {
        mc.world.afterEvents.playerPlaceBlock.subscribe(async (event) => {
            const checkers = this.blockRegistry.get(event.block.typeId);
            if (!checkers)
                return;
            const player = new utils.SafetyCheckedPlayer(event.player);
            const referencePoint = new utils.Vector3(event.block.location);
            for (const checker of checkers) {
                utils.mclog(`Checking ${checker.structure.id} against ${event.block.typeId} at ${referencePoint}`, StructureChecker.name);
                const result = checker.checkStructure(referencePoint, event.block.typeId, event.dimension);
                if (result) {
                    utils.mclog(`✅ Structure ${checker.structure.id} matches ${event.block.typeId} at ${result.origin} with rotation ${result.rotation}`, StructureChecker.name);
                    checker.callback(player, result);
                    return;
                }
                else {
                    utils.mclog(`❌ Structure ${checker.structure.id} does not match ${event.block.typeId} at ${referencePoint}`, StructureChecker.name);
                }
            }
        });
    }
    /**
     * @remarks Registers a structure to be checked against when a block is placed.
     * @param id The id of the structure to check against, i.e. `mystructure:test`.
     * @param callback The callback to run when the structure is placed, takes in a player, a structure result containing
     * the origin (i.e. the negative-most coordinate), the rotation of the structure, and the volume of the structure (useful for knowing the rotated bounds).
     * @param blockSubset An optional subset of blocks to check when placed, if excluded all blocks in the structure will be checked.
     * @example Checks for a carved pumpkin and spawns an iron golem when placed:
     * ```typescript
     * StructureChecker.register("mystructure:golem", (player, structure) => {
     *     player.dimension.fillBlocks(new mc.BlockVolume(structure.origin, origin.add(3)), "minecraft:air");
     *     player.dimension.spawnEntity("minecraft:iron_golem", structure.origin.add({x: 1.5, y: 0, z: 1.5}));
     * }, ["minecraft:carved_pumpkin"]);
     */
    static register(id, callback, blockSubset) {
        if (!this.registry.size)
            this.listen();
        new StructureChecker(id, callback, blockSubset);
    }
    /**
     * @remarks Validates an arbitrary block against a structure. Useful for checking if a block is part of a structure without needing to place it.
     * @param id The id of the structure to check against, i.e. `mystructure:test`.
     * @param block The block to check as part of the structure.
     * @returns The origin and rotation of the structure if the block is part of the structure, otherwise undefined.
     */
    static validate(id, block) {
        var _a;
        const checker = (_a = this.registry.get(id)) !== null && _a !== void 0 ? _a : new StructureChecker(id, () => { }, []);
        return checker.checkStructure(new utils.Vector3(block.location), block.typeId, block.dimension);
        ;
    }
    static registerBlock(id, checker) {
        var _a;
        if (!this.blockRegistry.has(id)) {
            this.blockRegistry.set(id, []);
        }
        (_a = this.blockRegistry.get(id)) === null || _a === void 0 ? void 0 : _a.push(checker);
    }
    constructor(id, callback, blockSubset) {
        this.callback = callback;
        const structure = mc.world.structureManager.get(id);
        if (structure === undefined) {
            throw new Error(`Structure ${id} not found`);
        }
        else {
            this.structure = structure;
            StructureChecker.registry.set(id, this);
        }
        this.size = new utils.Vector3(this.structure.size).subtract(1);
        const permutations = new Set();
        if (blockSubset) {
            for (const block of blockSubset) {
                permutations.add(block);
            }
        }
        else {
            for (const location of new mc.BlockVolume(utils.Vector3.ZERO, this.size).getBlockLocationIterator()) {
                const permutation = this.structure.getBlockPermutation(location);
                if (permutation && (permutation === null || permutation === void 0 ? void 0 : permutation.type.id) !== "minecraft:air" && !permutations.has(permutation.type.id)) {
                    permutations.add(permutation.type.id);
                }
            }
        }
        permutations.forEach(block => StructureChecker.registerBlock(block, this));
        utils.mclog(`Checking [${[...permutations]}] against structure: ${id}`, StructureChecker.name);
    }
    checkStructure(referencePoint, typeId, dimension) {
        var _a;
        for (const structureIterator of new mc.BlockVolume(utils.Vector3.ZERO, this.size).getBlockLocationIterator()) {
            if (((_a = this.structure.getBlockPermutation(structureIterator)) === null || _a === void 0 ? void 0 : _a.type.id) !== typeId) {
                continue;
            }
            const structureLocation = new utils.Vector3(structureIterator);
            const rotatedValues = [
                { offset: new utils.Vector3({ x: 0, y: 0, z: 0 }), rotation: 0 },
                { offset: new utils.Vector3({ x: 0, y: 0, z: this.size.x }), rotation: 90 },
                { offset: new utils.Vector3({ x: this.size.z, y: 0, z: 0 }), rotation: -90 },
                { offset: new utils.Vector3({ x: this.size.x, y: 0, z: this.size.z }), rotation: -180 },
            ];
            for (const { offset, rotation } of rotatedValues) {
                const origin = referencePoint.subtract(structureLocation.rotateYaw(rotation).round());
                const dest = origin.add(this.size.rotateYaw(rotation).round());
                const volume = new mc.BlockVolume(origin, dest);
                let success = true;
                for (const location of volume.getBlockLocationIterator()) {
                    if (dimension.heightRange.min > location.y || dimension.heightRange.max < location.y) {
                        success = false;
                        break;
                    }
                    const worldLocation = new utils.Vector3(location);
                    const structureLocation = worldLocation.subtract(origin).rotateYaw(-rotation).round();
                    const block = dimension.getBlock(worldLocation);
                    const structureBlock = this.structure.getBlockPermutation(structureLocation);
                    if (structureBlock && (block === null || block === void 0 ? void 0 : block.typeId) !== structureBlock.type.id) {
                        success = false;
                        break;
                    }
                }
                if (success) {
                    return { origin: origin.subtract(offset), rotation, volume };
                }
            }
        }
        return;
    }
}
StructureChecker.registry = new Map();
StructureChecker.blockRegistry = new Map();
