import { system, world } from "@minecraft/server";
import CustomVector3 from "./calc/vector3";
import { namespace } from "../config/_config";
// Amount of ticks to check the player's biome.
const checkBiomeInterval = 60; // = 3 seconds
export const playerHasFog = new Map();
export const playerBiomeTags = new Map();
// Enumeration for properties to check for ambient effects
export var CheckProperty;
(function (CheckProperty) {
    CheckProperty[CheckProperty["BIOME"] = 0] = "BIOME";
    CheckProperty[CheckProperty["BLOCK"] = 1] = "BLOCK";
})(CheckProperty || (CheckProperty = {}));
/**
 * Represents an ambient particle.
 */
export class AmbientParticle {
    constructor(params) {
        this.identifier = params.identifier;
        this.shape = params.shape;
        this.radius = params.radius;
        this.height = params.height;
        this.y = params.y;
        this.amount = params.amount;
        this.offset = params.offset;
        this.surface = params.surface;
        this.loopTime = params.loopTime;
        this.conditions = params.conditions;
        this.checkProperty = params.checkProperty;
        this.dynamicProperty = params.dynamicProperty;
    }
    // Method to register a new ambient particle
    static register(ambientParticle) {
        this.ambientParticles.push(ambientParticle);
    }
    // Method to emit particles for a player
    static emitForPlayer(ambientParticle) {
        const allowedGlobal = world.getDynamicProperty(`${namespace}:allow_particles`);
        const allowedSpecific = world.getDynamicProperty(ambientParticle.dynamicProperty || "");
        if (allowedGlobal === false)
            return;
        if (allowedSpecific === false)
            return;
        const players = world.getPlayers();
        let totalSpawned = 0;
        const coveredAreas = [];
        players.forEach((player) => {
            const tags = playerBiomeTags.get(player) || [];
            if (!ambientParticle.conditions || ambientParticle.conditions(player, tags)) {
                const intensityProperty = world.getDynamicProperty(`${namespace}:particle_intensity`);
                const intensity = typeof intensityProperty === "number" ? intensityProperty : 5;
                const intensityMultiplier = intensity === 0 ? 0 : 0 + intensity / 10;
                const location = CustomVector3.fromMC(player.location);
                const baseLocation = ambientParticle.y
                    ? new CustomVector3(player.location.x, ambientParticle.y, player.location.z)
                    : location.yOffset(ambientParticle.offset || 0);
                const adjustedAmount = Math.ceil(ambientParticle.amount * intensityMultiplier);
                const positions = ambientParticle.shape === "sphere"
                    ? CustomVector3.createSphere(baseLocation, ambientParticle.radius, adjustedAmount, ambientParticle.surface || false)
                    : CustomVector3.createCylinder(baseLocation, ambientParticle.radius, ambientParticle.height || 0, adjustedAmount, ambientParticle.surface || false);
                const filteredPositions = positions.filter((pos) => !coveredAreas.some((area) => pos.subtract(area.location).length() < area.radius));
                filteredPositions.forEach((position) => {
                    if (position.subtract(location).length() >= 5) {
                        try {
                            world.getDimension("overworld").spawnParticle(ambientParticle.identifier, position);
                            totalSpawned++;
                        }
                        catch (error) { }
                    }
                });
                coveredAreas.push({ location, radius: ambientParticle.radius });
            }
        });
        // Debug
        //world.sendMessage(`Total particles spawned: ${totalSpawned} for ${ambientParticle.identifier}`);
    }
    // Initialize the ambient particles system
    static initialize() {
        this.ambientParticles.forEach((ambientParticle) => {
            system.runInterval(() => {
                this.emitForPlayer(ambientParticle);
            }, ambientParticle.loopTime * 20); // loopTime in seconds converted to ticks
        });
    }
}
// Static array to hold all registered ambient particles
AmbientParticle.ambientParticles = [];
/**
 * Represents an ambient sound.
 */
export class AmbientSound {
    constructor(params) {
        this.identifier = params.identifier;
        this.soundOptions = params.soundOptions;
        this.duration = params.duration;
        this.chance = params.chance;
        this.canStop = params.canStop;
        this.conditions = params.conditions;
        this.checkProperty = params.checkProperty;
        this.category = params.category;
        this.dynamicProperty = params.dynamicProperty;
    }
    // Method to register a new ambient sound
    static register(ambientSound) {
        AmbientSound.ambientSounds.push(ambientSound);
    }
    // Initialize the ambient sounds system
    static initialize() {
        AmbientSound.ambientSounds.forEach((ambientSound) => {
            switch (ambientSound.checkProperty) {
                case CheckProperty.BIOME:
                    system.runInterval(() => this.checkBiomeSounds(ambientSound), checkBiomeInterval);
                    break;
                case CheckProperty.BLOCK:
                    system.runInterval(() => this.checkBlockSounds(ambientSound), 1);
                    break;
            }
        });
    }
    // Method to check and play sounds based on the player's biome
    static checkBiomeSounds(ambientSound) {
        const players = world.getPlayers();
        players.forEach((player) => {
            const tags = playerBiomeTags.get(player) || [];
            this.checkAndPlaySound(ambientSound, player, tags);
        });
    }
    // Method to check and play sounds based on the player's block position
    static checkBlockSounds(ambientSound) {
        const players = world.getPlayers();
        players.forEach((player) => {
            const tags = [];
            this.checkAndPlaySound(ambientSound, player, tags);
        });
    }
    // Method to check conditions and play or stop the sound
    static checkAndPlaySound(ambientSound, player, tags) {
        if (ambientSound.conditions && !ambientSound.conditions(player, tags)) {
            try {
                this.stopSound(ambientSound, player);
            }
            catch (error) { }
        }
        else {
            try {
                this.playSound(ambientSound, player, tags);
            }
            catch (error) { }
        }
    }
    // Method to stop a sound for a player
    static stopSound(ambientSound, player) {
        const activeCategories = this.ACTIVE_SOUND_CATEGORIES.get(player);
        if (activeCategories && activeCategories.has(ambientSound.category)) {
            if (ambientSound.canStop) {
                player.runCommandAsync(`stopsound @s ${ambientSound.identifier}`);
                // world.sendMessage(`Stopping sound ${ambientSound.identifier} for ${player.name}`);
            }
            activeCategories.delete(ambientSound.category);
            const playerTimeouts = this.scheduledTimeouts.get(player);
            if (playerTimeouts) {
                const timeoutId = playerTimeouts.get(ambientSound.identifier);
                if (typeof timeoutId === "number") {
                    system.clearRun(timeoutId);
                }
                playerTimeouts.delete(ambientSound.identifier);
            }
            const lastPlayTimes = this.LAST_PLAY_TIMES.get(player) || new Map();
            lastPlayTimes.set(ambientSound.identifier, system.currentTick);
            this.LAST_PLAY_TIMES.set(player, lastPlayTimes);
        }
    }
    // Method to play a sound for a player
    static playSound(ambientSound, player, tags) {
        const allowedGlobal = world.getDynamicProperty(`${namespace}:allow_sounds`);
        const allowedSpecific = world.getDynamicProperty(ambientSound.dynamicProperty || "");
        if (allowedGlobal === false || allowedSpecific === false)
            return;
        const activeCategories = this.ACTIVE_SOUND_CATEGORIES.get(player) ||
            this.ACTIVE_SOUND_CATEGORIES.set(player, new Set()).get(player);
        const lastPlayTimes = this.LAST_PLAY_TIMES.get(player) || this.LAST_PLAY_TIMES.set(player, new Map()).get(player);
        const currentTime = system.currentTick;
        if (activeCategories.has(ambientSound.category))
            return;
        const lastPlayTime = lastPlayTimes.get(ambientSound.identifier) || 0;
        if (!ambientSound.canStop && currentTime - lastPlayTime < ambientSound.duration * 20)
            return;
        if (Math.random() >= ambientSound.chance)
            return;
        try {
            player.playSound(ambientSound.identifier, {
                pitch: ambientSound.soundOptions.pitch,
                volume: ambientSound.soundOptions.volume,
                location: {
                    x: player.location.x,
                    y: player.location.y + 10,
                    z: player.location.z,
                },
            });
        }
        catch (error) { }
        activeCategories.add(ambientSound.category);
        lastPlayTimes.set(ambientSound.identifier, currentTime);
        const timeoutId = system.runTimeout(() => {
            const categories = this.ACTIVE_SOUND_CATEGORIES.get(player);
            if (categories) {
                categories.delete(ambientSound.category);
            }
        }, ambientSound.duration * 20);
        let playerTimeouts = this.scheduledTimeouts.get(player);
        if (!playerTimeouts) {
            playerTimeouts = new Map();
            this.scheduledTimeouts.set(player, playerTimeouts);
        }
        playerTimeouts.set(ambientSound.identifier, timeoutId);
    }
    // Handle player disconnection by clearing sound data
    static handlePlayerDisconnect(player) {
        this.ACTIVE_SOUND_CATEGORIES.delete(player);
        this.LAST_PLAY_TIMES.delete(player);
        const playerTimeouts = this.scheduledTimeouts.get(player);
        if (playerTimeouts) {
            playerTimeouts.forEach((timeoutId) => {
                system.clearRun(timeoutId);
            });
        }
        this.scheduledTimeouts.delete(player);
    }
}
// Static array to hold all registered ambient sounds
AmbientSound.ambientSounds = [];
AmbientSound.ACTIVE_SOUND_CATEGORIES = new Map();
AmbientSound.LAST_PLAY_TIMES = new Map();
AmbientSound.scheduledTimeouts = new Map();
/**
 * Represents ambient fog.
 */
export class AmbientFog {
    constructor(params) {
        this.identifier = params.identifier;
        this.conditions = params.conditions;
        this.checkProperty = params.checkProperty;
        this.dynamicProperty = params.dynamicProperty;
    }
    // Method to register a new ambient fog
    static register(ambientFog) {
        AmbientFog.ambientFogs.push(ambientFog);
    }
    // Initialize the ambient fog system
    static initialize() {
        AmbientFog.ambientFogs.forEach((ambientFog) => {
            switch (ambientFog.checkProperty) {
                case CheckProperty.BIOME:
                    system.runInterval(() => this.checkBiomeFog(ambientFog), checkBiomeInterval);
                    break;
                case CheckProperty.BLOCK:
                    system.runInterval(() => this.checkBlockFog(ambientFog), 1);
                    break;
            }
        });
    }
    // Method to check and emit fog based on the player's biome
    static checkBiomeFog(ambientFog) {
        const players = world.getPlayers();
        players.forEach((player) => {
            const tags = playerBiomeTags.get(player) || [];
            this.emitFog(ambientFog, player, tags);
        });
    }
    // Method to check and emit fog based on the player's block position
    static checkBlockFog(ambientFog) {
        const players = world.getPlayers();
        players.forEach((player) => {
            this.emitFog(ambientFog, player, []);
        });
    }
    // Method to emit fog for a player
    static emitFog(ambientFog, player, tags) {
        const allowedGlobal = world.getDynamicProperty(`${namespace}:allow_fog`);
        const allowedSpecific = world.getDynamicProperty(ambientFog.dynamicProperty || "");
        if (allowedGlobal === false || allowedSpecific === false) {
            player.runCommandAsync(`fog @s remove ${ambientFog.identifier}`);
            //player.sendMessage(`Removed fog ${ambientFog.identifier}`);
            playerHasFog.set(player, false);
            return;
        }
        if ((!ambientFog.conditions || ambientFog.conditions(player, tags)) &&
            !playerHasFog.get(player)) {
            player.runCommandAsync(`fog @s push ${ambientFog.identifier} ${ambientFog.identifier}`);
            player.sendMessage(`Added fog ${ambientFog.identifier}`);
            playerHasFog.set(player, true);
        }
        if (ambientFog.conditions && ambientFog.conditions(player, tags) && playerHasFog.get(player)) {
            return;
        }
        else {
            player.runCommandAsync(`fog @s remove ${ambientFog.identifier}`);
            player.sendMessage(`Removed fog ${ambientFog.identifier}`);
            playerHasFog.set(player, false);
        }
    }
}
// Static array to hold all registered ambient fogs
AmbientFog.ambientFogs = [];
// Function to check the biome of the player
export function checkBiome() {
    system.runInterval(() => {
        const players = world.getPlayers();
        players.forEach((player) => {
            try {
                const marker = player.dimension.spawnEntity(`${namespace}:biome_marker`, {
                    x: player.location.x,
                    y: player.dimension.heightRange.max - 1,
                    z: player.location.z,
                });
                marker.triggerEvent(`${namespace}:despawn_delay`);
                system.runTimeout(() => {
                    const tags = marker.getTags();
                    playerBiomeTags.set(player, tags); // Save the biome tags in the map
                    marker.triggerEvent(`${namespace}:despawn_delay`);
                }, 1);
            }
            catch (error) { }
        });
    }, checkBiomeInterval);
}
