var _a;
import * as mc from "@minecraft/server";
import * as utils from "../utils/index";
import * as db from "../utils/database/index";
// The buffer of space around the graves that is considered the graveyard.
const GRAVEYARD_BOUNDS = 8;
const ADDITIONAL_EFFECT_RANGE = 8;
const MIN_GHOST_SECONDS = 20;
const MAX_GHOST_SECONDS = 120;
export class GraveyardManager {
    static get GraveyardIdIndex() {
        for (let index = 0; index < this.database.Size; index++) {
            const overworld = this.database.Tables.overworld_graveyards.hasEntry(`graveyard_${index}`);
            const the_end = this.database.Tables.the_end_graveyards.hasEntry(`graveyard_${index}`);
            const nether = this.database.Tables.nether_graveyards.hasEntry(`graveyard_${index}`);
            if (!overworld && !the_end && !nether) {
                return index;
            }
        }
        return this.database.Size;
    }
    static debugHighlightGraveyard(player, graveyard) {
        if (!player.hasTag(utils.addName("dev")))
            return;
        for (const key of Object.keys(graveyard.graves)) {
            const location = new utils.Vector3(JSON.parse(key));
            try {
                player.dimension.spawnParticle("minecraft:endrod", location.center().up(2));
            }
            catch (error) { }
        }
        const min = graveyard.bounds.min;
        const max = graveyard.bounds.max;
        // Iterate over the edges of the bounding box
        for (let x = min.x; x <= max.x; x++) {
            for (let y = min.y; y <= max.y; y++) {
                for (let z = min.z; z <= max.z; z++) {
                    // Draw the edges of the bounding box
                    if (x === min.x || x === max.x || y === min.y || y === max.y || z === min.z || z === max.z) {
                        const location = new utils.Vector3({ x, y, z });
                        try {
                            player.dimension.spawnParticle("minecraft:endrod", location);
                        }
                        catch (error) { }
                    }
                }
            }
        }
    }
    static joinIntersectingGraveyards(player, graveyardKey, graveyardId) {
        let graveyard = this.database.getTable(graveyardKey).getEntry(graveyardId);
        if (!graveyard)
            return;
        let graveyardAABB = new utils.AABB(graveyard.bounds.min, graveyard.bounds.max);
        for (const [key, otherGraveyard] of this.database.getTable(graveyardKey).getEntries()) {
            const otherGraveyardAABB = new utils.AABB(otherGraveyard.bounds.min, otherGraveyard.bounds.max);
            if (graveyardId === key || !graveyardAABB.intersects(otherGraveyardAABB))
                continue;
            // Update intersected graveyard to absorb this graveyard
            otherGraveyard.graves = Object.assign(Object.assign({}, otherGraveyard.graves), graveyard.graves);
            otherGraveyard.activityLevel = Math.floor(Object.keys(otherGraveyard.graves).length / 5);
            otherGraveyard.bounds.min = {
                x: Math.min(otherGraveyard.bounds.min.x, graveyardAABB.min.x),
                y: Math.min(otherGraveyard.bounds.min.y, graveyardAABB.min.y),
                z: Math.min(otherGraveyard.bounds.min.z, graveyardAABB.min.z)
            };
            otherGraveyard.bounds.max = {
                x: Math.max(otherGraveyard.bounds.max.x, graveyardAABB.max.x),
                y: Math.max(otherGraveyard.bounds.max.y, graveyardAABB.max.y),
                z: Math.max(otherGraveyard.bounds.max.z, graveyardAABB.max.z)
            };
            this.database.getTable(graveyardKey).setEntry(key, otherGraveyard);
            this.database.getTable(graveyardKey).deleteEntry(graveyardId);
            utils.mclog(`Joining graveyard ${graveyardId} into ${key} in ${graveyardKey}`, "graveyard");
            this.debugHighlightGraveyard(player, otherGraveyard);
            this.joinIntersectingGraveyards(player, graveyardKey, key);
            return;
        }
        this.debugHighlightGraveyard(player, graveyard);
    }
    static async graveFogLoop() {
        await utils.waitForWorldLoad();
        await utils.waitForTicks(1);
        while (true) {
            const dims = [utils.overworld, utils.end, utils.nether];
            for (const dimension of dims) {
                const graveyards = this.database.getTable(`${dimension.id.replace("minecraft:", "")}_graveyards`);
                graveyards.forEach((data, key) => {
                    if (data.activityLevel > 0) {
                        const origin = new utils.Vector3(JSON.parse(utils.getRandomArrayEntry(Object.keys(data.graves))));
                        if (origin) {
                            try {
                                dimension.spawnParticle("ldz_250121:fog_spreader", origin);
                            }
                            catch (error) {
                                utils.mcerror(`Failed to spawn fog for ${key} in ${dimension.id}`, "graveyard");
                            }
                        }
                    }
                });
            }
            await utils.waitForTicks(100);
        }
    }
    static spawnGhost(dimension, graveyardId, graveyard, graveyardKey) {
        var _b;
        const ghostEntry = (_b = this.ghostRegistry[graveyardId]) !== null && _b !== void 0 ? _b : { playerIds: [], graveyardKey, ghosts: [] };
        try {
            let entity = utils.getRandomArrayEntry(Object.values(graveyard.graves).map(grave => grave.split(":")[1].replace("_grave", "")));
            if (ghostEntry.ghosts.some(ghost => ghost.typeId.includes(entity))) {
                utils.mclog(`${entity} already exists`, "graveyard");
                for (const e of Object.values(graveyard.graves).map(grave => grave.split(":")[1].split("_")[0])) {
                    if (!ghostEntry.ghosts.some(ghost => ghost.typeId.includes(e))) {
                        utils.mclog(`Found entity ${e}`, "graveyard");
                        entity = e;
                        break;
                    }
                }
            }
            utils.mclog(`Spawning ghost ldz_250121:ghost_${entity}.`, "graveyard");
            const origin = new utils.Vector3(JSON.parse(utils.getRandomArrayEntry(Object.keys(graveyard.graves).filter(key => graveyard.graves[key].split(":")[1].replace("_grave", "") === entity))));
            const ghost = dimension.spawnEntity(`ldz_250121:ghost_${entity}`, origin.center());
            ghostEntry.ghosts.push(new utils.SafetyCheckedEntity(ghost));
            this.ghostGraveyardRegistry[ghost.id] = {
                graveyardID: graveyardId
            };
        }
        catch (error) {
            utils.mcerror(`Failed to spawn ghost for ${graveyardId} in ${graveyardKey}. Likely out of bounds. ${error}`, "graveyard");
        }
    }
    static async gravePlayerEffectsLoop() {
        await utils.waitForWorldLoad();
        await utils.waitForTicks(100);
        while (true) {
            utils.getAllPlayersSafe().forEach(player => {
                var _b, _c;
                // Player Entry
                if (!this.effectManagerRegistry[player.id]) {
                    const graveyardKey = `${player.dimension.id.replace("minecraft:", "")}_graveyards`;
                    for (const [graveyardId, graveyard] of this.database.getTable(graveyardKey).getEntries()) {
                        if (new utils.AABB(new utils.Vector3(graveyard.bounds.min).subtract(ADDITIONAL_EFFECT_RANGE), new utils.Vector3(graveyard.bounds.max).add(ADDITIONAL_EFFECT_RANGE)).contains(player.location) && graveyard.activityLevel > 0) {
                            const effectManager = utils.spawnEntitySafe(player.dimension, "ldz_250121:graveyard_effects_manager", player.location);
                            player.runCommand("fog @s push ldz_250121:graveyard_fog ldz_250121:graveyard_fog");
                            player.runCommand("music play music.game.ldz_250121:graveyard.ambient 1 2 loop");
                            this.effectManagerRegistry[player.id] = {
                                effectManager,
                                graveyardKey,
                                graveyardId,
                            };
                            utils.waitForEvent(2, () => {
                                effectManager.playAnimation("animation.ldz_250121.graveyard_effects_manager.appear", { players: [player.nameTag] });
                            });
                            utils.waitForLoopEvent(160 + Math.floor(Math.random() * 200), () => {
                                var _b, _c;
                                let location = player.location;
                                let radius = (Math.random() * 10 - 5) + 20;
                                const angle = Math.random() * Math.PI * 2;
                                location = location.add({ x: radius * Math.cos(angle), y: 0, z: radius * Math.sin(angle) });
                                location.y = ((_c = (_b = player.dimension.getBlockFromRay({ x: location.x, y: location.y + 3, z: location.z }, { x: 0, y: -1, z: 0 })) === null || _b === void 0 ? void 0 : _b.block.location.y) !== null && _c !== void 0 ? _c : location.y) + 2;
                                const effect = utils.getRandomInteger(0, 10);
                                if (effect < 4) {
                                    player.dimension.spawnParticle("ldz_250121:lamp", location);
                                    player.dimension.playSound("ldz_250121:graveyard.ambient_whistle", location, { volume: 15 });
                                }
                                else {
                                    player.dimension.playSound("ldz_250121:graveyard.ambient_whoosh", location, { volume: 30 });
                                }
                            }, `whistle_effect_${player.id}`);
                        }
                    }
                }
                // Ghost spawning system
                const graveyardKey = `${player.dimension.id.replace("minecraft:", "")}_graveyards`;
                for (const [graveyardId, graveyard] of this.database.getTable(graveyardKey).getEntries()) {
                    if (new utils.AABB(new utils.Vector3(graveyard.bounds.min).subtract(ADDITIONAL_EFFECT_RANGE), new utils.Vector3(graveyard.bounds.max).add(ADDITIONAL_EFFECT_RANGE)).contains(player.location) && graveyard.activityLevel > 0) {
                        const ghostEntry = (_b = this.ghostRegistry[graveyardId]) !== null && _b !== void 0 ? _b : { playerIds: [], graveyardKey, ghosts: [] };
                        utils.addUniqueToArray(ghostEntry.playerIds, player.id);
                        if (!ghostEntry.ghosts.length) {
                            utils.waitForEvent(1, () => {
                                for (let index = 0; index < graveyard.activityLevel; index++) {
                                    this.spawnGhost(player.dimension, graveyardId, graveyard, graveyardKey);
                                }
                            });
                        }
                        else {
                            for (let index = ghostEntry.ghosts.length; index < graveyard.activityLevel; index++) {
                                this.spawnGhost(player.dimension, graveyardId, graveyard, graveyardKey);
                            }
                        }
                        this.ghostRegistry[graveyardId] = ghostEntry;
                    }
                }
                // Loop
                for (const [key, effectManagement] of Object.entries(this.effectManagerRegistry)) {
                    const player = (_c = utils.getEntitySafe(key)) === null || _c === void 0 ? void 0 : _c.getPlayer();
                    if (!player || !effectManagement.effectManager.isValid()) {
                        delete this.effectManagerRegistry[key];
                        continue;
                    }
                    effectManagement.effectManager.teleport(player.location);
                }
                // Player Exit
                if (this.effectManagerRegistry[player.id]) {
                    const { graveyardKey, graveyardId, effectManager } = this.effectManagerRegistry[player.id];
                    const graveyard = this.database.getTable(graveyardKey).getEntry(graveyardId);
                    if (!graveyard || !graveyard.activityLevel || !new utils.AABB(new utils.Vector3(graveyard.bounds.min).subtract(ADDITIONAL_EFFECT_RANGE), new utils.Vector3(graveyard.bounds.max).add(ADDITIONAL_EFFECT_RANGE)).contains(player.location)) {
                        effectManager.playAnimation("animation.ldz_250121.graveyard_effects_manager.disappear", { players: [player.nameTag] });
                        player.runCommand("fog @s remove ldz_250121:graveyard_fog");
                        player.runCommand("music play fake 1 2");
                        utils.abortEvents(`whistle_effect_${player.id}`);
                        delete this.effectManagerRegistry[player.id];
                        if (this.ghostRegistry[graveyardId]) {
                            utils.removeFromArray(this.ghostRegistry[graveyardId].playerIds, player.id);
                        }
                        let ticks = 10;
                        utils.waitForLoopEvent(2, () => {
                            if (effectManager.isValid()) {
                                effectManager.teleport(player.location);
                                effectManager.runCommand(`ride @s start_riding "${player.nameTag}"`);
                                ticks--;
                            }
                            if (ticks <= 0 && effectManager.isValid()) {
                                effectManager.remove();
                            }
                            return ticks <= 0 || !effectManager.isValid();
                        });
                    }
                }
            });
            await utils.waitForTicks(2);
        }
    }
}
_a = GraveyardManager;
GraveyardManager.database = new db.DataBase("graveyard", {
    tables: {
        overworld_graveyards: {
            maxSize: Number.MAX_SAFE_INTEGER,
        },
        the_end_graveyards: {
            maxSize: Number.MAX_SAFE_INTEGER,
        },
        nether_graveyards: {
            maxSize: Number.MAX_SAFE_INTEGER,
        }
    }
});
GraveyardManager.effectManagerRegistry = {};
GraveyardManager.ghostRegistry = {};
GraveyardManager.ghostGraveyardRegistry = {};
(() => {
    mc.world.afterEvents.playerPlaceBlock.subscribe(event => {
        if (!event.block.hasTag("ldz_250121:mob_grave"))
            return;
        const origin = new utils.Vector3(event.block.location);
        const graveyardKey = `${event.dimension.id.replace("minecraft:", "")}_graveyards`;
        // Creates new graveyard
        const graveyardId = `graveyard_${_a.GraveyardIdIndex}`;
        const graveyard = {
            bounds: {
                min: origin.subtract(GRAVEYARD_BOUNDS),
                max: origin.add(GRAVEYARD_BOUNDS),
            },
            graves: {
                [JSON.stringify(origin)]: event.block.typeId
            },
            activityLevel: 0
        };
        utils.mclog(`Creating graveyard ${graveyardId} at ${origin} in ${graveyardKey}`, "graveyard");
        _a.database.getTable(graveyardKey).setEntry(graveyardId, graveyard);
        _a.joinIntersectingGraveyards(event.player, graveyardKey, graveyardId);
    });
    mc.world.afterEvents.playerBreakBlock.subscribe(event => {
        if (!event.brokenBlockPermutation.hasTag("ldz_250121:mob_grave"))
            return;
        const origin = new utils.Vector3(event.block.location);
        const graveKey = JSON.stringify(origin);
        const graveyardKey = `${event.dimension.id.replace("minecraft:", "")}_graveyards`;
        for (const [key, graveyard] of _a.database.getTable(graveyardKey).getEntries()) {
            if (graveKey in graveyard.graves) {
                // Updates owning graveyard to remove grave
                delete graveyard.graves[graveKey];
                graveyard.activityLevel = Math.floor(Object.keys(graveyard.graves).length / 5);
                // Deletes graveyard if no graves remain and removes ghosts
                if (Object.keys(graveyard.graves).length === 0) {
                    utils.mclog(`Deleting graveyard ${key} in ${graveyardKey}`, "graveyard");
                    _a.database.getTable(graveyardKey).deleteEntry(key);
                    if (_a.ghostRegistry[key]) {
                        for (const ghost of _a.ghostRegistry[key].ghosts) {
                            if (ghost.isValid()) {
                                ghost.triggerEvent("enable_despawn");
                            }
                        }
                        delete _a.ghostRegistry[key];
                    }
                    return;
                }
                // No need to subdivide if only one grave remains, just update bounds
                if (Object.keys(graveyard.graves).length === 1) {
                    const origin = new utils.Vector3(JSON.parse(Object.keys(graveyard.graves)[0]));
                    graveyard.bounds = {
                        min: origin.subtract(GRAVEYARD_BOUNDS),
                        max: origin.add(GRAVEYARD_BOUNDS),
                    };
                    _a.debugHighlightGraveyard(event.player, graveyard);
                    _a.database.getTable(graveyardKey).setEntry(key, graveyard);
                    return;
                }
                utils.mclog(`Subdividing graveyard ${key} in ${graveyardKey}`, "graveyard");
                // Create temporary graveyard out of first remaining grave
                const remainingGraves = Object.keys(graveyard.graves);
                const initialGraveKey = remainingGraves.shift();
                const initialGravePosition = new utils.Vector3(JSON.parse(initialGraveKey));
                const tempGraveyards = {
                    [key]: {
                        bounds: new utils.AABB(initialGravePosition.subtract(GRAVEYARD_BOUNDS), initialGravePosition.add(GRAVEYARD_BOUNDS)),
                        graves: {
                            [initialGraveKey]: graveyard.graves[initialGraveKey]
                        }
                    }
                };
                // Iterate through remaining graves for intersections
                for (const remainingGraveKey of remainingGraves) {
                    const remainingGravePosition = new utils.Vector3(JSON.parse(remainingGraveKey));
                    const remainingGraveAABB = new utils.AABB(remainingGravePosition.subtract(GRAVEYARD_BOUNDS), remainingGravePosition.add(GRAVEYARD_BOUNDS));
                    // Check for intersections with existing graveyards
                    let intersects = false;
                    for (const [tempGraveyardKey, tempGraveyard] of Object.entries(tempGraveyards)) {
                        if (tempGraveyard.bounds.intersects(remainingGraveAABB)) {
                            tempGraveyard.bounds = new utils.AABB({
                                x: Math.min(tempGraveyard.bounds.min.x, remainingGraveAABB.min.x),
                                y: Math.min(tempGraveyard.bounds.min.y, remainingGraveAABB.min.y),
                                z: Math.min(tempGraveyard.bounds.min.z, remainingGraveAABB.min.z)
                            }, {
                                x: Math.max(tempGraveyard.bounds.max.x, remainingGraveAABB.max.x),
                                y: Math.max(tempGraveyard.bounds.max.y, remainingGraveAABB.max.y),
                                z: Math.max(tempGraveyard.bounds.max.z, remainingGraveAABB.max.z)
                            });
                            tempGraveyards[tempGraveyardKey].graves[remainingGraveKey] = graveyard.graves[remainingGraveKey];
                            intersects = true;
                            break;
                        }
                    }
                    // Create new temporary graveyard if no intersections found
                    if (!intersects) {
                        const newGraveyardId = `graveyard_${_a.GraveyardIdIndex}`;
                        tempGraveyards[newGraveyardId] = {
                            bounds: new utils.AABB(remainingGravePosition.subtract(GRAVEYARD_BOUNDS), remainingGravePosition.add(GRAVEYARD_BOUNDS)),
                            graves: {
                                [remainingGraveKey]: graveyard.graves[remainingGraveKey]
                            }
                        };
                    }
                }
                // Update database with new graveyard data
                for (const [key, tempGraveyard] of Object.entries(tempGraveyards)) {
                    const graveyard = {
                        bounds: tempGraveyard.bounds,
                        graves: tempGraveyard.graves,
                        activityLevel: Math.floor(Object.keys(tempGraveyard.graves).length / 5)
                    };
                    utils.mclog(`Creating subdivision ${key} from ${initialGraveKey} in ${graveyardKey}`, "graveyard");
                    _a.debugHighlightGraveyard(event.player, graveyard);
                    _a.database.getTable(graveyardKey).setEntry(key, graveyard);
                }
                return;
            }
        }
    });
    mc.world.afterEvents.entityRemove.subscribe(({ removedEntityId, typeId }) => {
        var _b;
        if (typeId.includes("ldz_250121:ghost")) {
            const graveyardID = (_b = _a.ghostGraveyardRegistry[removedEntityId]) === null || _b === void 0 ? void 0 : _b.graveyardID;
            if (graveyardID) {
                delete _a.ghostGraveyardRegistry[removedEntityId];
                const ghostEntry = _a.ghostRegistry[graveyardID];
                if (ghostEntry) {
                    ghostEntry.ghosts = ghostEntry.ghosts.filter(ghost => ghost.id !== removedEntityId);
                    _a.ghostRegistry[graveyardID] = ghostEntry;
                    if (!ghostEntry.ghosts.length) {
                        delete _a.ghostRegistry[graveyardID];
                    }
                }
            }
        }
    });
    _a.graveFogLoop();
    _a.gravePlayerEffectsLoop();
})();
