import { system, world, } from "@minecraft/server";
import CustomVector3 from "./calc/vector3";
import { playerBiomeTags } from "./ambienceModule";
import { namespace } from "../config/_config";
import ScriptingModule from "./scriptingModule";
import { registerWorldEvents } from "../config/worldEvents";
var WorldEventStage;
(function (WorldEventStage) {
    WorldEventStage[WorldEventStage["IDLE"] = 0] = "IDLE";
    WorldEventStage[WorldEventStage["START"] = 1] = "START";
    WorldEventStage[WorldEventStage["END"] = 2] = "END";
})(WorldEventStage || (WorldEventStage = {}));
export const playerWorldEvents = new Map();
export class WorldEvent {
    constructor(params) {
        this.id = params.id;
        this.chance = params.chance;
        this.duration = params.duration;
        this.cooldown = params.cooldown;
        this.radius = params.radius;
        this.conditions = params.conditions;
        this.customOnStart = params.customOnStart;
        this.customOnEnd = params.customOnEnd;
        this.effectOptions = params.effectOptions;
        this.dynamicProperty = params.dynamicProperty;
        this.mobSpawning = params.mobSpawning;
    }
    static register(config) {
        const worldEvent = new WorldEvent(config);
        WorldEvent.worldEvents.push(worldEvent);
    }
    static initialize() {
        WorldEvent.worldEvents.forEach((worldEvent) => {
            system.runInterval(() => {
                const globalRarityProperty = world.getDynamicProperty(`${namespace}:event_frequency`);
                const globalRarity = typeof globalRarityProperty === "number" ? globalRarityProperty : 5;
                const globalRarityMultiplier = globalRarity === 0 ? 0 : globalRarity / 10;
                const adjustedChance = worldEvent.chance * globalRarityMultiplier;
                if (Math.random() < adjustedChance) {
                    const players = world.getPlayers();
                    const eligiblePlayers = players.filter((player) => this.canStartEvent(worldEvent, player));
                    if (eligiblePlayers.length > 0) {
                        const randomIndex = Math.floor(Math.random() * eligiblePlayers.length);
                        const selectedPlayer = eligiblePlayers[randomIndex];
                        this.startEvent(worldEvent, selectedPlayer);
                    }
                }
                WorldEvent.checkEvent(worldEvent);
            }, 20);
        });
    }
    static canStartEvent(worldEvent, player) {
        const currentTime = system.currentTick;
        const eventEndTime = WorldEvent.activeEvents.get(worldEvent.id);
        const cooldownEndTime = WorldEvent.eventCooldowns.get(worldEvent.id);
        const tags = playerBiomeTags.get(player) || [];
        const propertyCheck = worldEvent.dynamicProperty
            ? world.getDynamicProperty(worldEvent.dynamicProperty) === true
            : true;
        const conditionsMet = worldEvent.conditions(player, tags);
        return (propertyCheck &&
            conditionsMet &&
            (!eventEndTime || currentTime > eventEndTime) &&
            (!cooldownEndTime || currentTime > cooldownEndTime));
    }
    static removeEvent(worldEvent) {
        const eventLocation = WorldEvent.eventLocations.get(worldEvent);
        if (eventLocation) {
            WorldEvent.eventLocations.delete(worldEvent);
            WorldEvent.activeEvents.delete(worldEvent.id);
            const cooldownEndTime = system.currentTick + worldEvent.cooldown * 20;
            WorldEvent.eventCooldowns.set(worldEvent.id, cooldownEndTime);
            const players = world.getPlayers();
            players.forEach((player) => {
                const distance = CustomVector3.distance(player.location, eventLocation);
                if (distance < worldEvent.radius) {
                    this.removeEventEffects(worldEvent, player);
                    playerWorldEvents.delete(player.name);
                    if (worldEvent.customOnEnd) {
                        const tags = playerBiomeTags.get(player) || [];
                        worldEvent.customOnEnd(player, tags);
                    }
                }
            });
            const intervals = WorldEvent.loopingIntervals.get(worldEvent.id);
            if (intervals) {
                intervals.forEach((interval) => system.clearRun(interval));
                WorldEvent.loopingIntervals.delete(worldEvent.id);
            }
            this.applyEventEndEffects(worldEvent, eventLocation);
            const spawnedMobIds = WorldEvent.spawnedMobs.get(worldEvent.id);
            if (spawnedMobIds) {
                const dimension = world.getDimension("overworld");
                spawnedMobIds.forEach((mobId) => {
                    try {
                        const mob = dimension.getEntities({ type: mobId });
                        mob.forEach((entity) => entity.kill());
                    }
                    catch (e) { }
                });
                WorldEvent.spawnedMobs.delete(worldEvent.id);
            }
        }
    }
    static startEvent(worldEvent, player) {
        const currentTime = system.currentTick;
        const endTime = currentTime + worldEvent.duration * 20;
        WorldEvent.activeEvents.set(worldEvent.id, endTime);
        WorldEvent.eventLocations.set(worldEvent, player.location);
        playerWorldEvents.set(player.name, worldEvent.id);
        WorldEvent.applyVisualEffects(worldEvent, player, WorldEventStage.START);
        if (worldEvent.mobSpawning) {
            WorldEvent.spawnMobs(worldEvent, player);
        }
        if (worldEvent.customOnStart) {
            worldEvent.customOnStart(player, playerBiomeTags.get(player) || []);
        }
        WorldEvent.spawnParticles(worldEvent, player.location);
        system.runTimeout(() => {
            WorldEvent.removeEvent(worldEvent);
        }, worldEvent.duration * 20);
    }
    static checkEvent(worldEvent) {
        if (!WorldEvent.eventLocations.has(worldEvent))
            return;
        const eventLocation = WorldEvent.eventLocations.get(worldEvent);
        if (!eventLocation)
            return;
        const players = world.getPlayers();
        for (const player of players) {
            const playerLocation = player.location;
            const distance = CustomVector3.distance(playerLocation, eventLocation);
            if (distance < worldEvent.radius) {
                this.applyEventEffects(worldEvent, player, eventLocation);
                this.applyVisualEffects(worldEvent, player, WorldEventStage.IDLE);
            }
            if (distance > worldEvent.radius && playerWorldEvents.get(player.name) === worldEvent.id) {
                this.removeEventEffects(worldEvent, player);
                playerWorldEvents.delete(player.name);
            }
        }
    }
    static applyEventEffects(worldEvent, player, location) {
        const { effects } = worldEvent.effectOptions;
        if (effects) {
            const currentTime = system.currentTick;
            const eventEndTime = WorldEvent.activeEvents.get(worldEvent.id);
            if (eventEndTime) {
                const remainingDuration = (eventEndTime - currentTime) * 20;
                effects.forEach((effect) => {
                    player.addEffect(effect.effectType, 40, effect.properties);
                    playerWorldEvents.set(player.name, worldEvent.id);
                });
            }
        }
    }
    static removeEventEffects(worldEvent, player) {
        const { effects } = worldEvent.effectOptions;
        if (effects) {
            effects.forEach((effect) => {
                player.runCommandAsync(`/effect ${player.name} ${effect.effectType} 0 0 true`);
            });
        }
    }
    static applyVisualEffects(worldEvent, player, stage) {
        const { visualEffects, blockVisualEffects } = worldEvent.effectOptions;
        const dimension = world.getDimension("overworld");
        const existingIntervals = WorldEvent.loopingIntervals.get(worldEvent.id);
        if (existingIntervals) {
            existingIntervals.forEach((interval) => system.clearRun(interval));
            WorldEvent.loopingIntervals.delete(worldEvent.id);
        }
        const intervals = [];
        if (stage === WorldEventStage.START && visualEffects?.onStart) {
            visualEffects.onStart.particles?.forEach((particle) => {
                dimension.spawnParticle(particle, player.location);
            });
            visualEffects.onStart.sounds?.forEach((sound) => {
                dimension.playSound(sound, player.location);
            });
        }
        if (stage === WorldEventStage.IDLE && visualEffects?.onInterval) {
            visualEffects.onInterval.particles?.forEach((particleEffect) => {
                const intervalTime = particleEffect.interval > 0 ? particleEffect.interval : 20;
                const interval = system.runInterval(() => {
                    dimension.spawnParticle(particleEffect.id, player.location);
                }, intervalTime);
                intervals.push(interval);
            });
            visualEffects.onInterval.sounds?.forEach((soundEffect) => {
                const intervalTime = soundEffect.interval > 0 ? soundEffect.interval : 20;
                const interval = system.runInterval(() => {
                    dimension.playSound(soundEffect.id, player.location);
                }, intervalTime);
                intervals.push(interval);
            });
        }
        if (stage === WorldEventStage.END && visualEffects?.onEnd) {
            visualEffects.onEnd.particles?.forEach((particle) => {
                dimension.spawnParticle(particle, player.location);
            });
            visualEffects.onEnd.sounds?.forEach((sound) => {
                dimension.playSound(sound, player.location);
            });
        }
        if (intervals.length > 0) {
            WorldEvent.loopingIntervals.set(worldEvent.id, intervals);
        }
    }
    static applyEventEndEffects(worldEvent, location) {
        const { visualEffects } = worldEvent.effectOptions;
        const dimension = world.getDimension("overworld");
        if (visualEffects?.onEnd) {
            visualEffects.onEnd.particles?.forEach((particle) => {
                dimension.spawnParticle(particle, location);
            });
            visualEffects.onEnd.sounds?.forEach((sound) => {
                dimension.playSound(sound, location);
            });
        }
    }
    static countActiveEvents() {
        return WorldEvent.activeEvents.size;
    }
    static spawnParticles(worldEvent, location) {
        const { borderParticles } = worldEvent.effectOptions;
        if (borderParticles) {
            const intervals = [];
            const dimension = world.getDimension("overworld");
            borderParticles.forEach((particle) => {
                const intervalTime = particle.interval > 0 ? particle.interval : 20;
                const interval = system.runInterval(() => {
                    const positions = CustomVector3.createSphere(new CustomVector3(location.x, location.y, location.z), worldEvent.radius, particle.amount, true);
                    positions.forEach((pos) => {
                        try {
                            dimension.spawnParticle(particle.identifier, pos);
                        }
                        catch (e) { }
                    });
                }, intervalTime * 20);
                intervals.push(interval);
            });
            if (intervals.length > 0) {
                WorldEvent.loopingIntervals.set(worldEvent.id, intervals);
            }
        }
    }
    static spawnMobs(worldEvent, player) {
        const dimension = world.getDimension("overworld");
        worldEvent.mobSpawning?.forEach((mobOption) => {
            const spawnedMobIds = []; // Track the mob entity IDs
            const mobCheckRadius = 15; // Define the radius to check for nearby mobs
            const maxNearbyMobs = 10; // Define the maximum number of allowed mobs within the radius
            while (spawnedMobIds.length < mobOption.amount) {
                let retries = 0;
                const maxRetries = 5;
                while (retries < maxRetries) {
                    const randomPos = CustomVector3.randomWithinRadius(CustomVector3.fromMC(player.location), worldEvent.radius);
                    const isFarFromPlayers = WorldEvent.isFarEnoughFromPlayers(randomPos, 10);
                    if (!isFarFromPlayers) {
                        retries++;
                        continue;
                    }
                    const nearbyMobCount = WorldEvent.getNearbyMobCount(dimension, randomPos, mobOption.entity, mobCheckRadius);
                    if (nearbyMobCount >= maxNearbyMobs) {
                        retries++;
                        continue;
                    }
                    const surfacePos = WorldEvent.findSurfaceLevel(dimension, randomPos, 10, 3);
                    if (surfacePos) {
                        const mob = dimension.spawnEntity(mobOption.entity, surfacePos);
                        world.sendMessage(`Spawning mob ${mobOption.entity} for event ${worldEvent.id}`);
                        if (mob) {
                            spawnedMobIds.push(mob.id);
                            break;
                        }
                    }
                    retries++;
                }
                if (retries >= maxRetries) {
                    world.sendMessage(`Retrying to spawn mobs for event ${worldEvent.id}. Current count: ${spawnedMobIds.length}`);
                }
            }
            WorldEvent.spawnedMobs.set(worldEvent.id, spawnedMobIds);
            if (mobOption.refill) {
                const refillInterval = system.runInterval(() => {
                    WorldEvent.refillMobs(worldEvent, player);
                }, 20 * 5);
                WorldEvent.loopingIntervals.set(worldEvent.id, [refillInterval]);
            }
        });
    }
    static getNearbyMobCount(dimension, position, mobType, radius) {
        const mobs = dimension.getEntities({
            location: position,
            maxDistance: radius,
            type: mobType,
        });
        return mobs.length;
    }
    static refillMobs(worldEvent, player) {
        const dimension = world.getDimension("overworld");
        const mobOptions = worldEvent.mobSpawning;
        mobOptions?.forEach((mobOption) => {
            const aliveMobs = dimension.getEntities({
                location: player.location,
                maxDistance: worldEvent.radius,
                type: mobOption.entity,
            });
            if (aliveMobs.length < mobOption.amount) {
                const mobsToSpawn = mobOption.amount - aliveMobs.length;
                for (let i = 0; i < mobsToSpawn; i++) {
                    const randomPos = CustomVector3.randomWithinRadius(player.location, worldEvent.radius);
                    const surfacePos = WorldEvent.findSurfaceLevel(dimension, randomPos, 5);
                    if (surfacePos) {
                        dimension.spawnEntity(mobOption.entity, surfacePos);
                        world.sendMessage(`Refilling mobs for event ${worldEvent.id}`);
                    }
                }
            }
        });
    }
    static isValidSpawnBlock(block) {
        const solidBlocks = [
            "minecraft:grass_block",
            "minecraft:dirt",
            "minecraft:stone",
            "minecraft:sand",
            "minecraft:snow",
        ];
        return solidBlocks.includes(block.typeId);
    }
    static isFarEnoughFromPlayers(position, minDistance) {
        const players = world.getPlayers();
        for (const player of players) {
            const playerPosition = CustomVector3.fromMC(player.location);
            const distance = CustomVector3.distance(position, playerPosition);
            if (distance < minDistance) {
                return false;
            }
        }
        return true;
    }
    static findSurfaceLevel(dimension, targetPos, yOffset = 10, retries = 3) {
        for (let attempt = 0; attempt < retries; attempt++) {
            const startY = Math.floor(targetPos.y);
            for (let y = startY + yOffset; y >= startY - yOffset; y--) {
                const pos = new CustomVector3(targetPos.x, y, targetPos.z).floorAll();
                const block = dimension.getBlock(pos);
                if (block && WorldEvent.isValidSpawnBlock(block)) {
                    const aboveBlock = dimension.getBlock(new CustomVector3(pos.x, pos.y + 1, pos.z));
                    if (aboveBlock && aboveBlock.typeId === "minecraft:air") {
                        return new CustomVector3(pos.x, pos.y + 1, pos.z);
                    }
                }
            }
            targetPos = CustomVector3.randomWithinRadius(targetPos, 10);
        }
        return null;
    }
}
WorldEvent.worldEvents = [];
WorldEvent.eventLocations = new Map();
WorldEvent.activeEvents = new Map();
WorldEvent.eventCooldowns = new Map();
WorldEvent.loopingIntervals = new Map();
WorldEvent.spawnedMobs = new Map(); // key: event ID, value: array of mob entity IDs
export default class WorldEventModule extends ScriptingModule {
    constructor() {
        super("WorldEventModule");
        this.onInitialize = () => {
            registerWorldEvents();
        };
    }
}
