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 = {}));
export class PlayerParticle {
    constructor(params) {
        this.dynamicProperty = params.dynamicProperty;
        this.setting = params.setting;
        this.conditions = params.conditions;
        this.checkProperty = params.checkProperty;
    }
    static register(playerParticle) {
        this.playerParticles.push(playerParticle);
    }
    // Improved sync function to apply particle effects per player
    static syncPropertiesWithEntity(player, entity) {
        this.playerParticles.forEach((particle) => {
            const settingEnabled = world.getDynamicProperty(particle.setting);
            if (settingEnabled === false) {
                // Disable particle if setting is globally disabled
                try {
                    entity.setProperty(particle.dynamicProperty, false);
                }
                catch (error) { }
                return;
            }
            const playerConditionMet = particle.conditions
                ? particle.conditions(player, playerBiomeTags.get(player) || [])
                : true;
            // Set property based on individual player conditions
            try {
                entity.setProperty(particle.dynamicProperty, playerConditionMet);
            }
            catch (error) { }
        });
    }
    // Check player particles on spawn and at intervals
    static initialize() {
        const interval = 20; // 1 second
        // Initial sync on player spawn
        world.afterEvents.playerSpawn.subscribe((event) => {
            const player = event.player;
            const entity = PlayerSunGlareEntity.get(player);
            if (entity) {
                // Ensure initial properties sync on spawn
                this.syncPropertiesWithEntity(player, entity);
            }
        });
        // Interval sync
        system.runInterval(() => {
            // Check if each particle's world setting is enabled and apply per-player
            this.playerParticles.forEach((particle) => {
                const settingAllowed = world.getDynamicProperty(particle.setting) === true;
                if (!settingAllowed)
                    return;
                // Update particles for each player individually
                world.getPlayers().forEach((player) => {
                    const tags = playerBiomeTags.get(player) || [];
                    const entity = PlayerSunGlareEntity.get(player);
                    if (entity) {
                        const conditionsMet = particle.conditions ? particle.conditions(player, tags) : true;
                        entity.setProperty(particle.dynamicProperty, conditionsMet);
                    }
                });
            });
        }, interval);
    }
}
PlayerParticle.playerParticles = [];
/**
 * 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) => {
            // Skip AFK players
            if (this.playerIsAfk.get(player)) {
                return;
            }
            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 {
                            player.dimension.spawnParticle(ambientParticle.identifier, position);
                            totalSpawned++;
                        }
                        catch (error) { }
                    }
                });
                coveredAreas.push({ location, radius: ambientParticle.radius });
            }
        });
        // Debug
        // world.sendMessage(`Total particles spawned: ${totalSpawned} for ${ambientParticle.identifier}`);
    }
    // Method to initialize and register ambient particles
    static initialize() {
        this.ambientParticles.forEach((ambientParticle) => {
            system.runInterval(() => {
                this.emitForPlayer(ambientParticle);
            }, ambientParticle.loopTime * 20); // loopTime in seconds converted to ticks
        });
        // Start AFK detection
        this.trackPlayerAfkStatus();
    }
    // Static method to track player AFK status based on velocity and view direction
    static trackPlayerAfkStatus() {
        system.runInterval(() => {
            const players = world.getPlayers();
            players.forEach((player) => {
                const currentVelocity = CustomVector3.fromMC(player.getVelocity());
                const currentViewDirection = CustomVector3.fromMC(player.getViewDirection()); // Get player's view direction
                // Retrieve the last recorded velocity and view direction
                const lastVelocity = this.playerLastVelocity.get(player) || new CustomVector3(0, 0, 0);
                const lastViewDirection = this.playerLastViewDirection.get(player) || new CustomVector3(0, 0, 0);
                // If both velocity and view direction haven't changed, increment the AFK timer
                if (currentVelocity.equals(lastVelocity) &&
                    currentViewDirection.equals(lastViewDirection)) {
                    const afkTime = this.playerAfkTime.get(player) || 0;
                    this.playerAfkTime.set(player, afkTime + this.afkCheckInterval);
                    // If the player has been AFK for longer than the threshold, mark them as AFK
                    if (afkTime + this.afkCheckInterval >= this.afkThreshold) {
                        this.playerIsAfk.set(player, true);
                    }
                }
                else {
                    // Reset the AFK timer and mark the player as not AFK
                    this.playerAfkTime.set(player, 0);
                    this.playerIsAfk.set(player, false);
                }
                // Update the player's last known velocity and view direction
                this.playerLastVelocity.set(player, currentVelocity);
                this.playerLastViewDirection.set(player, currentViewDirection);
            });
        }, this.afkCheckInterval);
    }
}
// AFK detection maps
AmbientParticle.playerLastVelocity = new Map();
AmbientParticle.playerAfkTime = new Map();
AmbientParticle.playerIsAfk = new Map();
AmbientParticle.playerLastViewDirection = new Map(); // Track view direction
AmbientParticle.afkCheckInterval = 20; // Check every second (1 second = 20 ticks)
AmbientParticle.afkThreshold = 20 * 20; // 20 seconds (1 second = 20 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.location.y + 10,
                    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);
}
const PlayerHasSunGlare = new Map();
const PlayerSunGlareEntity = new Map();
const EntitySunGlarePlayer = new Map();
export function sunGlare() {
    world.afterEvents.worldInitialize.subscribe(() => {
        const dimensions = ["overworld", "nether", "the_end"];
        dimensions.forEach((dimension) => {
            world
                .getDimension(dimension)
                .getEntities()
                .forEach((entity) => {
                if (entity.typeId === `${namespace}:player_particles`) {
                    entity.remove();
                }
            });
        });
    });
    world.afterEvents.playerDimensionChange.subscribe((event) => {
        const player = event.player;
        PlayerHasSunGlare.set(player, false);
        const entity = PlayerSunGlareEntity.get(player);
        if (entity) {
            EntitySunGlarePlayer.delete(entity);
            PlayerSunGlareEntity.delete(player);
        }
    });
    world.afterEvents.playerSpawn.subscribe((event) => {
        const player = event.player;
        PlayerHasSunGlare.set(player, false);
        const entity = PlayerSunGlareEntity.get(player);
        if (entity) {
            EntitySunGlarePlayer.delete(entity);
            PlayerSunGlareEntity.delete(player);
        }
    });
    world.afterEvents.entityDie.subscribe((event) => {
        const entity = event.deadEntity;
        if (EntitySunGlarePlayer.has(entity)) {
            const player = EntitySunGlarePlayer.get(entity);
            PlayerHasSunGlare.set(player, false);
            PlayerSunGlareEntity.delete(player);
            EntitySunGlarePlayer.delete(entity);
        }
    });
    system.runInterval(() => {
        const dimensions = ["overworld", "nether", "the_end"];
        try {
            world.getPlayers().forEach((player) => {
                const location = {
                    x: player.location.x,
                    y: player.location.y + 10,
                    z: player.location.z,
                };
                let entity = PlayerSunGlareEntity.get(player);
                // If the player doesn't have a valid sun glare entity, spawn one
                if (!entity || !PlayerHasSunGlare.get(player)) {
                    entity = player.dimension.spawnEntity(`${namespace}:player_particles`, location);
                    PlayerHasSunGlare.set(player, true);
                    EntitySunGlarePlayer.set(entity, player);
                    PlayerSunGlareEntity.set(player, entity);
                    PlayerParticle.syncPropertiesWithEntity(player, entity); // Sync properties dynamically
                }
                // Update the entity's position if it still exists
                if (entity) {
                    try {
                        entity.teleport(location, { keepVelocity: true });
                        PlayerParticle.syncPropertiesWithEntity(player, entity); // Sync properties dynamically
                    }
                    catch {
                        // If teleportation fails, remove the entity and flag the player for respawn
                        PlayerHasSunGlare.set(player, false);
                        EntitySunGlarePlayer.delete(entity);
                        PlayerSunGlareEntity.delete(player);
                        try {
                            entity.remove();
                        }
                        catch { }
                    }
                }
            });
        }
        catch (error) { }
        // Clean up any `sunglare` entities not associated with players
        dimensions.forEach((dimension) => {
            world
                .getDimension(dimension)
                .getEntities()
                .forEach((entity) => {
                if (entity.typeId === `${namespace}:player_particles`) {
                    const matchedPlayer = EntitySunGlarePlayer.get(entity);
                    if (!matchedPlayer) {
                        entity.remove();
                    }
                    else {
                        world.getPlayers().forEach((player) => {
                            entity.playAnimation(matchedPlayer === player
                                ? "animation.cc_re.sunglare.visible"
                                : "animation.cc_re.sunglare.invisible", { players: [player.name] });
                        });
                    }
                }
            });
        });
    }, 1);
}
export function blockExplode() {
    world.beforeEvents.explosion.subscribe((event) => {
        const location = event.source.location;
        system.runTimeout(() => {
            event.dimension.spawnParticle("cc_re:explosion_smoke", location);
            event.dimension.spawnParticle("cc_re:explosion_bolt", location);
            event.dimension.spawnParticle("cc_re:explosion_circle", location);
            event.dimension.spawnParticle("cc_re:explosion_spark", location);
        }, 1);
    });
}
