import {system, world} from "@minecraft/server";
import {namespace} from "../../../config/_config";

export class VisualEffectBlock {
    // Constructor to initialize a VisualEffectBlock with its properties
    constructor({id, sound, particles, conditions,}) {
        this.id = id;
        this.sound = sound;
        this.particles = particles;
        this.conditions = conditions;
    }

    static registerBehavior(config) {
        const block = new VisualEffectBlock(config);
        this.register(block);
    }

    static register(block) {
        VisualEffectBlock.blocks.push(block);
    }

    // Initialization method to register the custom visual effect block component
    static initialize() {
        // Subscribe to the world initialization event to register the custom block component
        world.beforeEvents.worldInitialize.subscribe((event) => {
            event.blockComponentRegistry.registerCustomComponent(`${namespace}:effect_emitter`, {
                onTick: (event) => this.handleTick(event), // Handle the block's tick event
            });
        });
        // Listen for block destruction events to clear any scheduled effects
        world.afterEvents.playerBreakBlock.subscribe((event) => {
            this.clearTimeouts(event.block); // Clear scheduled timeouts when the block is destroyed
        });
    }

    // Method to handle the block's tick event and apply effects
    static handleTick(event) {
        const block = event.block; // The block that triggered the tick event
        if (!block)
            return;
        // Get the registered VisualEffectBlock for this block type
        const effectBlock = this.getEffectsForBlock(block.typeId);
        if (!effectBlock)
            return;
        // Check if the block meets the optional conditions for emitting effects
        if (effectBlock.conditions && !effectBlock.conditions(block))
            return;
        // Apply sound and particle effects to the block
        this.applyEffects(block, effectBlock);
    }

    // Helper method to retrieve the VisualEffectBlock by its type ID
    static getEffectsForBlock(typeId) {
        return VisualEffectBlock.blocks.find((b) => b.id === typeId); // Return the block if found
    }

    // Method to apply sound and particle effects based on the block's configuration
    static applyEffects(block, effectBlock) {
        const currentTick = system.currentTick; // Get the current tick
        const blockKey = `${block.location.x},${block.location.y},${block.location.z}`; // Unique key for the block's location
        // Handle sound effects
        if (Math.random() < effectBlock.sound.chance) {
            // Check if the sound should play based on chance
            const soundKey = `${blockKey}:sound`; // Key for storing the last sound tick
            const lastSoundTick = this.lastEffectTick.get(soundKey) || 0;
            // Play the sound if enough time has passed since the last sound
            if (currentTick - lastSoundTick >= effectBlock.sound.interval * 20) {
                const timeoutId = system.runTimeout(() => {
                    world.playSound(effectBlock.sound.id, block.location); // Play the sound at the block's location
                }, effectBlock.sound.interval * 20);
                this.lastEffectTick.set(soundKey, currentTick); // Store the current tick for the sound
                this.storeTimeout(blockKey, timeoutId); // Store the timeout ID for cleanup
            }
        }
        // Handle particle effects
        if (effectBlock.particles) {
            effectBlock.particles.forEach((particle) => {
                const particleKey = `${blockKey}:particle:${particle.id}`; // Key for storing the last particle tick
                const lastParticleTick = this.lastEffectTick.get(particleKey) || 0;
                // Spawn the particle if enough time has passed since the last particle and the chance succeeds
                if (currentTick - lastParticleTick >= particle.interval * 20) {
                    if (Math.random() < particle.chance) {
                        const timeoutId = system.runTimeout(() => {
                            try {
                                block.dimension.spawnParticle(particle.id, block.location); // Spawn the particle at the block's location
                            } catch (e) {
                            }
                        }, particle.interval * 20);
                        this.lastEffectTick.set(particleKey, currentTick); // Store the current tick for the particle
                        this.storeTimeout(blockKey, timeoutId); // Store the timeout ID for cleanup
                    }
                }
            });
        }
    }

    // Helper method to store timeout IDs for a block to be cleared later
    static storeTimeout(blockKey, timeoutId) {
        if (!this.timeoutIds.has(blockKey)) {
            this.timeoutIds.set(blockKey, []); // Initialize an array if this block has no timeouts yet
        }
        this.timeoutIds.get(blockKey).push(timeoutId); // Store the timeout ID
    }

    // Method to clear scheduled effects (timeouts) when the block is destroyed
    static clearTimeouts(block) {
        const blockKey = `${block.location.x},${block.location.y},${block.location.z}`; // Key for the block's location
        const timeouts = this.timeoutIds.get(blockKey); // Get the stored timeout IDs for this block
        if (timeouts) {
            timeouts.forEach((timeoutId) => {
                system.clearRun(timeoutId); // Clear each scheduled timeout
            });
            this.timeoutIds.delete(blockKey); // Remove the block's timeouts from storage
        }
    }
}

// Array to store all registered VisualEffectBlock instances
VisualEffectBlock.blocks = [];
// Map to store the last tick when effects (sound/particles) were applied
VisualEffectBlock.lastEffectTick = new Map();
// Map to store timeout IDs for cleanup when the block is broken
VisualEffectBlock.timeoutIds = new Map();
