import { system } from "@minecraft/server";
// Reusable spawn function at a specific location
export function summonEntitiesInRange(location, dimension, entitiesConfig) {
    const summonedEntities = []; // Collect all summoned entities
    entitiesConfig.forEach((entityConfig) => {
        const amount = entityConfig.amount && entityConfig.amount.min && entityConfig.amount.max
            ? getRandomInt(entityConfig.amount.min, entityConfig.amount.max)
            : 1;
        let spawnedCount = 0;
        let attempts = 0;
        // Retry until all entities are spawned or a certain limit is reached
        while (spawnedCount < amount && attempts < 50) {
            attempts++;
            const spawnLocation = getValidSpawnLocation(dimension, location, entityConfig.horizontalRadius, entityConfig.verticalHeight || 1, entityConfig.yOffset, entityConfig.inAir || false // Pass the inAir flag
            );
            if (spawnLocation) {
                const spawnedEntity = dimension.spawnEntity(entityConfig.entity, spawnLocation);
                if (entityConfig.events) {
                    entityConfig.events.forEach((event) => {
                        spawnedEntity.triggerEvent(event);
                    });
                }
                if (entityConfig.tag) {
                    spawnedEntity.addTag(entityConfig.tag);
                }
                summonedEntities.push(spawnedEntity); // Track the summoned entity
                spawnedCount++;
                // Apply spawn effects at the entity's spawn location
                if (entityConfig.spawnEffects) {
                    applySpawnEffects(dimension, spawnLocation, entityConfig.spawnEffects);
                }
            }
        }
    });
    return summonedEntities; // Return all summoned entities
}
// Utility function to get a valid spawn location based on a location
function getValidSpawnLocation(dimension, location, horizontalRadius, verticalHeight, yOffset, inAir = false // Default to false, meaning spawn on solid ground by default
) {
    const { x, y, z } = location;
    // Start with the original y position adjusted by yOffset if provided
    const baseY = y + (yOffset ?? 0);
    // Generate an array of possible Y offsets within the verticalHeight range
    const yOffsets = [];
    for (let offsetY = -verticalHeight; offsetY <= verticalHeight; offsetY++) {
        yOffsets.push(offsetY);
    }
    // Shuffle the Y offsets array to randomize the order of checking
    shuffleArray(yOffsets);
    for (let attempt = 0; attempt < 10; attempt++) {
        const randomX = x + (Math.random() * 2 - 1) * horizontalRadius;
        const randomZ = z + (Math.random() * 2 - 1) * horizontalRadius;
        // Check vertical positions within the randomized order of Y offsets
        for (const offsetY of yOffsets) {
            const spawnY = baseY + offsetY;
            const spawnLocation = { x: randomX, y: spawnY, z: randomZ };
            if (isValidSpawnLocation(dimension, spawnLocation, inAir)) {
                return spawnLocation;
            }
        }
    }
    // If a valid location wasn't found in 10 attempts, return null
    return null;
}
// Utility function to validate if the spawn location is valid
function isValidSpawnLocation(dimension, location, inAir = false // Default to false, meaning check solid ground
) {
    const blockAtLocation = dimension.getBlock(location);
    const blockBelow = blockAtLocation?.below();
    const invalidBlocks = ["minecraft:water", "minecraft:lava"];
    if ((blockAtLocation && invalidBlocks.includes(blockAtLocation.typeId)) ||
        invalidBlocks.includes(blockBelow?.typeId ?? "")) {
        // The block at the location is water or lava, so it's not a valid spawn location
        return false;
    }
    if (inAir) {
        // If the entity can spawn in air, we only care that the block at the location is air
        return blockAtLocation === undefined || blockAtLocation.typeId === "minecraft:air";
    }
    else {
        // If the entity must spawn on solid ground, check both the block and the block below
        return ((blockAtLocation === undefined || blockAtLocation.typeId === "minecraft:air") &&
            (blockBelow?.typeId !== "minecraft:air" || false));
    }
}
// Apply spawn effects (sound and particles) at a location
function applySpawnEffects(dimension, location, spawnEffects) {
    // Play sound effect (if provided)
    if (spawnEffects.sounds) {
        spawnEffects.sounds.forEach((sound) => {
            dimension.playSound(sound, location);
        });
    }
    // Spawn particle effects (if provided)
    if (spawnEffects.particles) {
        spawnEffects.particles.forEach((particle) => {
            dimension.spawnParticle(particle, location);
        });
    }
}
// Utility function to shuffle an array (Fisher-Yates shuffle algorithm)
function shuffleArray(array) {
    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
    }
}
// Utility function to get a random integer between two values
function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
export function spawnEntitiesInCircle(entity, properties) {
    const thetaStep = (2 * Math.PI) / properties.count;
    const yOffset = properties.yOffset ?? 0; // Default yOffset to 0 if not provided
    const entities = Array.isArray(properties.entities) ? properties.entities : [properties.entities];
    // Normalize weights if provided
    const totalWeight = entities.reduce((sum, ent) => sum + (ent.weight ?? 1), 0);
    const weightedEntities = entities.map((ent) => ({
        ...ent,
        weight: (ent.weight ?? 1) / totalWeight,
    }));
    // Utility function to select an entity based on weight
    function selectEntity() {
        const rand = Math.random();
        let cumulativeWeight = 0;
        for (const entity of weightedEntities) {
            cumulativeWeight += entity.weight;
            if (rand < cumulativeWeight) {
                return entity;
            }
        }
        return weightedEntities[0]; // Fallback
    }
    for (let i = 0; i < properties.count; i++) {
        const theta = properties.randomLocations ? Math.random() * 2 * Math.PI : i * thetaStep;
        const x = (properties.location ? properties.location.x : entity.location.x) +
            properties.radius * Math.cos(theta);
        const z = (properties.location ? properties.location.z : entity.location.z) +
            properties.radius * Math.sin(theta);
        let y = (properties.location ? properties.location.y : entity.location.y) + yOffset;
        // Check location suitability if the checkLocation property is true or undefined (default)
        if (properties.checkLocation !== false) {
            let foundSuitableLocation = isLocationSuitable(x, y, z, entity.dimension);
            if (!foundSuitableLocation) {
                // First, check upwards
                const maxY = y + 10;
                while (y <= maxY) {
                    y++;
                    if (isLocationSuitable(x, y, z, entity.dimension)) {
                        foundSuitableLocation = true;
                        break;
                    }
                }
                // If not found upwards, check downwards
                if (!foundSuitableLocation) {
                    y = (properties.location ? properties.location.y : entity.location.y) + yOffset; // Reset y to the original position
                    const minY = y - 10;
                    while (y >= minY) {
                        y--;
                        if (isLocationSuitable(x, y, z, entity.dimension)) {
                            foundSuitableLocation = true;
                            break;
                        }
                    }
                }
            }
            if (!foundSuitableLocation) {
                continue; // Skip spawning this entity if no suitable location is found
            }
        }
        // Select an entity to spawn
        const entityToSpawn = selectEntity();
        // Spawn the entity at the found suitable location or given location
        const spawnedEntity = entity.dimension.spawnEntity(entityToSpawn.id, { x, y, z });
        // Add tag if specified
        if (entityToSpawn.tag) {
            spawnedEntity.addTag(entityToSpawn.tag);
        }
        // Trigger events if specified
        if (entityToSpawn.events) {
            entityToSpawn.events.forEach((event) => {
                spawnedEntity.triggerEvent(event);
            });
        }
        if (properties.spawnEffects) {
            applySpawnEffects(entity.dimension, spawnedEntity.location, properties.spawnEffects);
        }
        if (properties.entityLifespan) {
            let currentTick = 0;
            function lifespanTick() {
                currentTick++;
                if (!spawnedEntity || !spawnedEntity.isValid())
                    return;
                if (properties.entityLifespan?.duration &&
                    currentTick >= properties.entityLifespan.duration * 20) {
                    if (properties.entityLifespan.removeEffects) {
                        applySpawnEffects(spawnedEntity.dimension, spawnedEntity.location, properties.entityLifespan.removeEffects);
                    }
                    properties.entityLifespan.style === "kill"
                        ? spawnedEntity.kill()
                        : spawnedEntity.remove();
                    return;
                }
                system.runTimeout(() => lifespanTick(), 1);
            }
            lifespanTick();
        }
        if (properties.linkToOwner) {
            function linkTick() {
                if (!spawnedEntity || !spawnedEntity.isValid())
                    return;
                if (!entity || !entity.isValid()) {
                    if (properties.linkToOwner?.removeEffects) {
                        applySpawnEffects(spawnedEntity.dimension, spawnedEntity.location, properties.linkToOwner.removeEffects);
                    }
                    properties.linkToOwner?.style === "kill" ? spawnedEntity.kill() : spawnedEntity.remove();
                    return;
                }
                system.runTimeout(() => linkTick(), 1);
            }
            linkTick();
        }
    }
}
function isLocationSuitable(x, y, z, dimension) {
    try {
        // Check if the block at the spawn location is air
        const blockAtLocation = dimension.getBlock({ x, y, z });
        if (!blockAtLocation || !blockAtLocation.permutation.matches("minecraft:air")) {
            return false;
        }
        // Check if there's a solid block below
        const blockBelow = dimension.getBlock({ x, y: y - 1, z });
        if (!blockBelow || blockBelow.permutation.matches("minecraft:air")) {
            return false;
        }
        return true;
    }
    catch (error) {
        return false;
    }
}
