import * as mc from "@minecraft/server";
import { Vector3 } from "./vector3";
import { mcerror, mclog, mcwarn } from "./log";
import { runCommand } from "./commands";
import { waitForEvent, waitForLoopEvent, waitForTicks, waitForWorldLoad } from "./events";
import { getRandomInteger } from "./math";
import { getScoreAsBool } from "./scores";
import { isBlockAtLocation } from "./block";
import { GlobalNamespace } from "./namespace";
/**
 * @remarks A class representing builds created with the random seed system.
 */
export class BuildData {
    /**
     * @remarks Creates a new BuildData object.
     * @param data The options for the build.
     * @example
     * ```typescript
     *
     * ```
     */
    constructor(data) {
        this.buildStructureName = data.buildStructureName || buildStructureName;
        this.foundationStructureName = data.foundationStructureName || foundationStructureName;
        this.buildSize = data.buildSize || buildSize;
        this.foundationSize = data.foundationSize || foundationSize;
        this.floorOffset = data.floorOffset || 1;
        this.cellCount = data.cellCount;
        this.spawnOffset = data.spawnOffset;
    }
}
const buildStructureName = 'spawn_cell';
const foundationStructureName = 'spawn_foundation';
const buildSize = new Vector3({ x: 16, y: 16, z: 16 });
const foundationSize = new Vector3({ x: 16, y: 1, z: 16 });
const cellCountDefault = new Vector3({ x: 1, y: 1, z: 1 });
const down = new Vector3({ x: 0, y: -1, z: 0 });
const buildScoreName = 'HasBuiltSpawn';
const buildDataOptions = [];
const minFoundationHeight = 60;
const ignoreBlocks = [
    { block: 'oak_log' },
    { block: 'spruce_log' },
    { block: 'birch_log' },
    { block: 'jungle_log' },
    { block: 'acacia_log' },
    { block: 'mangrove_log' },
    { block: 'cherry_log' },
    { block: 'dark_oak_log' },
    { block: 'leaves' },
    { block: 'leaves2' },
    { block: 'mangrove_leaves' },
    { block: 'cherry_leaves' },
    { block: 'azalea_leaves' },
    { block: 'vine' },
    { block: 'red_mushroom_block' },
    { block: 'brown_mushroom_block' },
];
const biomeTerrainOptions = {
    plains: {
        cover: [{ block: 'minecraft:grass' }],
        name: 'Plains',
        replaceOptions: []
    },
    sand: {
        cover: [{ block: 'minecraft:sand', properties: { 'sand_type': 'normal' } }],
        name: 'Sand',
        replaceOptions: [
            { from: { block: 'minecraft:grass' }, to: { block: 'minecraft:sand' } },
            { from: { block: 'minecraft:dirt' }, to: { block: 'minecraft:sandstone' } },
            { from: { block: 'minecraft:tallgrass' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:double_plant' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:yellow_flower' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:red_flower' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:stone', properties: { 'stone_type': 'stone' } }, to: { block: 'minecraft:sandstone' } },
        ]
    },
    red_sand: {
        cover: [{ block: 'minecraft:sand', properties: { 'sand_type': 'red' } }],
        name: 'Red Sand',
        replaceOptions: [
            { from: { block: 'minecraft:grass' }, to: { block: 'minecraft:sand', properties: { 'sand_type': 'red' } } },
            { from: { block: 'minecraft:dirt' }, to: { block: 'minecraft:red_sandstone' } },
            { from: { block: 'minecraft:tallgrass' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:double_plant' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:yellow_flower' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:red_flower' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:stone', properties: { 'stone_type': 'stone' } }, to: { block: 'minecraft:red_sandstone' } },
        ]
    },
    mesa: {
        cover: [{ block: 'minecraft:hardened_clay' }],
        name: 'Mesa',
        replaceOptions: [
            { from: { block: 'minecraft:grass' }, to: { block: 'minecraft:sand', properties: { 'sand_type': 'red' } } },
            { from: { block: 'minecraft:dirt' }, to: { block: 'minecraft:hardened_clay' } },
            { from: { block: 'minecraft:tallgrass' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:double_plant' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:yellow_flower' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:red_flower' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:stone', properties: { 'stone_type': 'stone' } }, to: { block: 'minecraft:hardened_clay' } },
        ]
    },
    snowy: {
        cover: [{ block: 'minecraft:snow_layer' }, { block: 'minecraft:snow' }],
        name: 'Snowy',
        replaceOptions: [
            { from: { block: 'minecraft:grass' }, to: { block: 'minecraft:snow' } },
            { from: { block: 'minecraft:dirt' }, to: { block: 'minecraft:snow' } },
            { from: { block: 'minecraft:tallgrass' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:double_plant' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:yellow_flower' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:red_flower' }, to: { block: 'minecraft:air' } },
        ]
    },
    ice: {
        cover: [{ block: 'minecraft:ice' }, { block: 'minecraft:packed_ice' }],
        name: 'Ice',
        replaceOptions: [
            { from: { block: 'minecraft:grass' }, to: { block: 'minecraft:ice' } },
            { from: { block: 'minecraft:dirt' }, to: { block: 'minecraft:packed_ice' } },
            { from: { block: 'minecraft:tallgrass' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:double_plant' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:yellow_flower' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:red_flower' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:stone', properties: { 'stone_type': 'stone' } }, to: { block: 'minecraft:packed_ice' } },
        ]
    }
};
let replaceOptions = [];
/**
 * @remarks Call this function to activate Random Seed saving and loading.
 * @example
 * https://github.com/Dotzip-Developments/craftee-items-dx/blob/main/scripts/main.ts
 * ```typescript
 * utils.initRandomSeed(
 *     new utils.BuildData({ buildStructureName: "house_1", foundationStructureName: "house_1_foundation", floorOffset: 2, cellCount: new utils.Vector3({ x: 2, y: 2, z: 2 }), spawnOffset: new utils.Vector3({ x: 13, y: 10, z: 13 }) }),
 *     new utils.BuildData({ floorOffset: 2, cellCount: new utils.Vector3({ x: 2, y: 2, z: 2 }), spawnOffset: new utils.Vector3({ x: 17, y: 10, z: 14 }) })
 * );
 * ```
 */
export function initRandomSeed(...builds) {
    buildDataOptions.push(...builds);
    runSaveBuild();
    runLoadBuild();
}
// #region Save Build
/**
 * Subscribes to the ldz:save_build event
 */
function runSaveBuild() {
    mc.system.afterEvents.scriptEventReceive.subscribe(event => {
        if (event.id === 'ldz:save_build' && event.sourceEntity) {
            let cellCount = cellCountDefault;
            if (event.message) {
                try {
                    cellCount = new Vector3(JSON.parse(event.message));
                }
                catch (error) {
                    event.sourceEntity.sendMessage({ rawtext: [{ text: `§c${event.message}\n${error}\nUsage: /scriptevent ldz:save_build {"x": 1, "y": 1, "z": 1}` }] });
                    return;
                }
            }
            saveBuild(new Vector3(event.sourceEntity.location), event.sourceEntity.dimension, cellCount);
        }
    });
}
/**
 * Saves the build
 * @param origin The origin to save from, should be the negative-most corner of the foundation
 * @param dimension The dimension to save the build from
 * @param cellCount How many cells are included in the build
 */
async function saveBuild(origin, dimension, cellCount) {
    showParticles(origin, buildSize.multiply(cellCount).add(foundationSize.multiply({ x: 0, y: 1, z: 0 })), dimension);
    origin.y += foundationSize.y;
    for (let x = 1; x <= cellCount.x; x++) {
        for (let y = 1; y <= cellCount.y; y++) {
            for (let z = 1; z <= cellCount.z; z++) {
                const cellIndex = new Vector3({ x, y, z });
                const cellLocation = origin.add(buildSize.multiply(cellIndex.subtract(new Vector3(1))));
                await saveCell(cellLocation, cellIndex, dimension);
            }
        }
    }
    origin.y -= foundationSize.y;
    for (let x = 1; x <= cellCount.x; x++) {
        for (let z = 1; z <= cellCount.z; z++) {
            const cellIndex = new Vector3({ x, y: 0, z });
            const cellLocation = origin.add(foundationSize.multiply(cellIndex.subtract(new Vector3({ x: 1, y: 0, z: 1 }))));
            await savefoundation(cellLocation, cellIndex, dimension);
        }
    }
}
async function saveCell(cellLocation, cellIndex, dimension) {
    const name = `${buildStructureName}_${cellIndex.x}_${cellIndex.y}_${cellIndex.z}`;
    const start = cellLocation.toCommandString();
    const end = cellLocation.add(buildSize.subtract(new Vector3(1))).toCommandString();
    runCommand(`tickingarea add ${start} ${end} ${name}`);
    await waitForTicks(1);
    runCommand(`structure save ${name} ${start} ${end} disk`, dimension);
    await waitForTicks(1);
    runCommand(`tickingarea remove ${name}`);
    runCommand(`structure-export ${name}`);
    mclog(name);
}
async function savefoundation(cellLocation, cellIndex, dimension) {
    const name = `${foundationStructureName}_${cellIndex.x}_${cellIndex.z}`;
    const start = cellLocation.toCommandString();
    const end = cellLocation.add(foundationSize.subtract(new Vector3(1))).toCommandString();
    runCommand(`tickingarea add ${start} ${end} ${name}`);
    waitForTicks(1);
    runCommand(`structure save ${name} ${start} ${end} true disk`, dimension);
    waitForTicks(1);
    runCommand(`tickingarea remove ${name}`);
    runCommand(`structure-export ${name}`);
    mclog(name);
}
function showParticles(origin, size, dimension) {
    let loopCount = 5;
    waitForLoopEvent(10, () => {
        for (let x = origin.x; x < origin.x + size.x; x++) {
            dimension.spawnParticle('minecraft:endrod', { x, y: origin.y, z: origin.z }, new mc.MolangVariableMap());
        }
        for (let y = origin.y; y < origin.y + size.y; y++) {
            dimension.spawnParticle('minecraft:endrod', { x: origin.x, y, z: origin.z }, new mc.MolangVariableMap());
        }
        for (let z = origin.z; z < origin.z + size.z; z++) {
            dimension.spawnParticle('minecraft:endrod', { x: origin.x, y: origin.y, z: z }, new mc.MolangVariableMap());
        }
        loopCount--;
        return loopCount <= 0;
    });
}
// #endregion
// #region Load Build
async function runLoadBuild() {
    await waitForWorldLoad();
    mc.system.afterEvents.scriptEventReceive.subscribe(event => {
        if (event.id === 'ldz:load_build' && event.sourceEntity) {
            loadBuild(event.sourceEntity);
        }
    });
    const hasBuilt = getScoreAsBool('.var', buildScoreName);
    if (!hasBuilt) {
        loadBuild(mc.world.getAllPlayers()[0]);
        return;
    }
    mclog('Spawn Already Built');
}
async function loadBuild(player) {
    mclog('Attempting To Load Build');
    let buildData = buildDataOptions[getRandomInteger(0, buildDataOptions.length - 1)];
    const structureLocation = await getOriginBlock(buildData, player);
    structureLocation.y += foundationSize.y;
    for (let x = 1; x <= buildData.cellCount.x; x++) {
        for (let y = 1; y <= buildData.cellCount.y; y++) {
            for (let z = 1; z <= buildData.cellCount.z; z++) {
                const cellIndex = new Vector3({ x, y, z });
                const cellLocation = structureLocation.add(buildData.buildSize.multiply(cellIndex.subtract(new Vector3(1))));
                buildStructureCell(cellLocation, cellIndex, player.dimension, buildData);
            }
        }
    }
    await waitForEvent(5, () => { setWorldData(player, structureLocation, buildData); });
    for (let x = 1; x <= buildData.cellCount.x; x++) {
        for (let z = 1; z <= buildData.cellCount.z; z++) {
            const cellIndex = new Vector3({ x, y: 0, z });
            const cellLocation = structureLocation.add(buildData.buildSize.multiply(cellIndex.subtract(new Vector3(1))));
            for (let height = structureLocation.y - 1; height > minFoundationHeight; height -= buildData.foundationSize.y) {
                cellLocation.y = height;
                buildStructurefoundation(cellLocation, cellIndex, player.dimension, buildData);
                await waitForTicks(1);
            }
        }
    }
}
function setWorldData(player, structureLocation, buildData) {
    runCommand(`scoreboard objectives add ${buildScoreName} dummy`, player);
    runCommand(`scoreboard players set .var ${buildScoreName} 1`, player);
    runCommand(`tp @a[tag=!${GlobalNamespace.Namespace}dev] ${structureLocation.add(buildData.spawnOffset).toCommandString()}`, player);
    runCommand(`spawnpoint @a ${structureLocation.add(buildData.spawnOffset).toCommandString()}`, player);
    runCommand(`setworldspawn ${structureLocation.add(buildData.spawnOffset).toCommandString()}`, player);
    runCommand(`gamerule spawnradius 0`, player);
    mclog(`Building Spawn At: ${structureLocation.toString()}`);
}
async function getOriginBlock(buildData, player) {
    const corners = [];
    const yHeight = Math.max(player.location.y + (buildData.buildSize.y * buildData.cellCount.x), 64);
    const startLocation = new Vector3({ x: player.location.x, y: yHeight, z: player.location.z });
    let heighestBlock;
    while (!heighestBlock) {
        let origin = new Vector3({ x: startLocation.x, y: startLocation.y, z: startLocation.z });
        await addBlockFromRaycast(origin, player.dimension, corners);
        origin = new Vector3({ x: startLocation.x + (buildData.buildSize.x * buildData.cellCount.x), y: startLocation.y, z: startLocation.z });
        await addBlockFromRaycast(origin, player.dimension, corners);
        origin = new Vector3({ x: startLocation.x, y: startLocation.y, z: startLocation.z + (buildData.buildSize.z * buildData.cellCount.z) });
        await addBlockFromRaycast(origin, player.dimension, corners);
        origin = new Vector3({ x: startLocation.x + (buildData.buildSize.x * buildData.cellCount.x), y: startLocation.y, z: startLocation.z + (buildData.buildSize.z * buildData.cellCount.z) });
        await addBlockFromRaycast(origin, player.dimension, corners);
        heighestBlock = corners.sort((a, b) => a.location.y - b.location.y)[corners.length - 1];
        if (!heighestBlock) {
            await waitForTicks(1);
        }
    }
    getGroundCoverType(heighestBlock);
    return new Vector3({ x: startLocation.x, y: heighestBlock.location.y - buildData.floorOffset, z: startLocation.z });
}
async function addBlockFromRaycast(origin, dimension, corners) {
    await waitForLoopEvent(1, () => {
        const distance = origin.y;
        const hitResult = dimension.getBlockFromRay(origin, down, { maxDistance: distance, includeLiquidBlocks: true });
        if (hitResult && hitResult.block) {
            if (ignoreBlocks.some(invalidBlock => isBlockAtLocation(hitResult.block.location, invalidBlock.block, invalidBlock.properties))) {
                mcwarn(`Raycast hit invalid block`);
                origin.y = hitResult.block.location.y - 1;
            }
            else {
                corners.push(hitResult.block);
                return true;
            }
        }
        else {
            mcerror('Raycast Hit Nothing');
            return false;
        }
        return origin.y <= 0;
    });
}
function getGroundCoverType(block) {
    for (const groundCover of Object.values(biomeTerrainOptions)) {
        for (const cover of groundCover.cover) {
            if (block.permutation.matches(cover.block, cover.properties)) {
                mclog(`Biome Type: ${groundCover.name}`);
                replaceOptions = groundCover.replaceOptions;
                return;
            }
        }
    }
    mcwarn(`Could Not Determine Biome, Will Not Replace Blocks`);
}
function buildStructureCell(structureLocation, cellIndex, dimension, buildData) {
    mclog(`Building Cell ${buildData.buildStructureName}_${cellIndex.x}_${cellIndex.z}}`);
    runCommand(`structure load ${buildData.buildStructureName}_${cellIndex.x}_${cellIndex.y}_${cellIndex.z} ${structureLocation.toCommandString()}`, dimension);
    const fillEndLocation = structureLocation.add(buildData.buildSize);
    replaceOptions.forEach(option => {
        const fromOptions = option.from.properties ? JSON.stringify(option.from.properties).replace(':', '=').replace('{', '[').replace('}', ']') : '';
        const toOptions = option.to.properties ? JSON.stringify(option.to.properties).replace(':', '=').replace('{', '[').replace('}', ']') : '';
        runCommand(`fill ${structureLocation.toCommandString()} ${fillEndLocation.toCommandString()} ${option.to.block} ${toOptions} replace ${option.from.block} ${fromOptions}`);
    });
}
function buildStructurefoundation(location, cellIndex, dimension, buildData) {
    mclog(`Building Foundation ${buildData.foundationStructureName}_${cellIndex.x}_${cellIndex.z}}`);
    runCommand(`structure load ${buildData.foundationStructureName}_${cellIndex.x}_${cellIndex.z} ${location.toCommandString()}`, dimension);
    const fillEndLocation = location.add(buildData.buildSize);
    replaceOptions.forEach(option => {
        const fromOptions = option.from.properties ? JSON.stringify(option.from.properties).replace(':', '=').replace('{', '[').replace('}', ']') : '';
        const toOptions = option.to.properties ? JSON.stringify(option.to.properties).replace(':', '=').replace('{', '[').replace('}', ']') : '';
        runCommand(`fill ${location.toCommandString()} ${fillEndLocation.toCommandString()} ${option.to.block} ${toOptions} replace ${option.from.block} ${fromOptions}`);
    });
}
// #endregion
