import { system, world, BlockPermutation } from "@minecraft/server";
import { settingEnabled } from "../settings";
import { getOrCreateScoreboard } from "../util/scoreboard.js";
import { Random } from "../util/random";
import BIOMES from "../util/biomes";
import { biomeBlockMappingsAbove, biomeBlockMappingsSide } from "../util/biomeBlockMappings";

// Variables
const randomBlocksDelayTicks = 20; // Delay between block spawn attempts in ticks

const directionToPermutation = {
    north: 0,
    west: 1,
    south: 2,
    east: 3
};

// Grab all possible option blocks from the block mappings
const vanillaBlocksAbove = Object.values(biomeBlockMappingsAbove).flatMap(mapping => Object.keys(mapping));
const vanillaBlocksSide = Object.values(biomeBlockMappingsSide).flatMap(mapping => Object.keys(mapping));

// Setup scoreboard
const settingsScoreboard = getOrCreateScoreboard("SD_realism_loaded_chunks");

// Generate scoreboard name
function generateChunkKey(x, z) {
    // Format chunk key to work on a scoreboard
    const xKey = x < 0 ? `neg_${Math.abs(x)}` : x;
    const zKey = z < 0 ? `neg_${Math.abs(z)}` : z;
    return `chunk_${xKey}_${zKey}`;
}

// Get scoreboard objective
function getObjective(objectiveName) {
    return world.scoreboard.getObjective(objectiveName);
}

// Mark as loaded and check if it was newly loaded
function markChunkAsLoaded(x, z) {
    const chunkKey = generateChunkKey(x, z);
    const loadedChunksObjective = getObjective("SD_realism_loaded_chunks");

    if (loadedChunksObjective) {
        if (loadedChunksObjective.hasParticipant(chunkKey)) {
            // Chunk has been marked as loaded before
            return false;
        } else {
            // Chunk has not been marked as loaded before
            loadedChunksObjective.setScore(chunkKey, 1);
            return true; // New chunk
        }
    }
    return false; // Scoreboard objective not found
}

// Center of chunk
function getChunkCenterCoordinates(chunkX, chunkZ) {
    return {
        x: chunkX * 16 + 8,
        z: chunkZ * 16 + 8
    };
}

// Function to get the highest air block
function getHighestAirBlock(dimension, location, maxHeight = 320) {
    let highestY = -1;
    for (let y = maxHeight; y >= 0; y--) {
        const block = dimension.getBlock({ x: location.x, y, z: location.z });
        if (block.typeId === "minecraft:air") {
            highestY = y;
        } else if (highestY >= 0) {
            // Stop checking when we hit a non-air block after finding the highest air block
            break;
        }
    }
    if (highestY < 0) return null;
    return { x: location.x, y: highestY, z: location.z };
}

// Function to get block type ID
function getBlockTypeId(block) {
    if (!block) {
        return undefined;
    }
    return vanillaBlocksAbove.find(typeId => block.permutation && block.permutation.matches(typeId)) ||
        vanillaBlocksSide.find(typeId => block.permutation && block.permutation.matches(typeId));
}

// Place randomizer
function shouldPlaceBlock(blockPlaceChance) {
    return Math.random() < blockPlaceChance;
}

// Define weights per biome
const biomeWeights = {
    [BIOMES.desert]: [0.825, 0.1, 0.05, 0.025],
    [BIOMES.jungle]: [0.05, 0.05, 0.10, 0.20, 0.25, 0.25],
    [BIOMES.forest]: [0.1, 0.1, 0.25, 0.25, 0.20, 0.1],
    [BIOMES.birch]: [0.1, 0.1, 0.25, 0.25, 0.20, 0.1],
    [BIOMES.beach]: [0.825, 0.1, 0.05, 0.025],
    [BIOMES.taiga]: [0.825, 0.1, 0.05, 0.025],
    [BIOMES.mesa]: [0.825, 0.1, 0.05, 0.025],
    [BIOMES.swamp]: [0.1, 0.1, 0.2, 0.4, 0.2],
    [BIOMES.mangrove]: [0.1, 0.20, 0.3, 0.75]
};

// Helper function to choose a block based on weights
function chooseWeightedBlock(blocks) {
    const totalWeight = blocks.reduce((sum, block) => sum + block.weight, 0);
    let random = Math.random() * totalWeight;
    for (const block of blocks) {
        if (random < block.weight) {
            return block.block;
        }
        random -= block.weight;
    }
    return blocks[0].block; // Fallback
}

// Helper function to choose a direction based on weights
function chooseWeightedDirection(directions) {
    if (!Array.isArray(directions) || directions.length === 0) {
        console.error("Invalid directions array:", directions);
        return null;
    }

    const totalWeight = directions.reduce((sum, dir) => sum + dir.weight, 0);
    let random = Math.random() * totalWeight;
    for (const dir of directions) {
        if (random < dir.weight) {
            return dir.direction;
        }
        random -= dir.weight;
    }
    return directions[0].direction; // Fallback
}

// Helper function to set block permutation
function updateBlock(block, direction) {
    const directionValue = directionToPermutation[direction];
    if (directionValue === undefined) {
        console.error(`Invalid direction: ${direction}`);
        return;
    }
    const states = {
        "squaredreams_realism:rotation": directionValue,
    };
    block.setPermutation(BlockPermutation.resolve(block.typeId, states));
}

// Helper function to set block permutation with random rotation
function updateBlockWithRandomRotation(block) {
    const randomRotation = Math.floor(Math.random() * 4); // Random rotation between 0 and 3
    const states = {
        "squaredreams_realism:rotation": randomRotation,
    };
    block.setPermutation(BlockPermutation.resolve(block.typeId, states));
}

// Run main loop
system.runInterval(() => {
    // Check setting for chunk radius
    const randomBlocksSetting = settingEnabled("custom_blocks");

    // Determine chunkRadius based on the setting value
    let chunkRadius;
    switch (randomBlocksSetting) {
        case 1: // Low
            chunkRadius = 0;
            break;
        case 2: // Medium
            chunkRadius = 1;
            break;
        case 3: // High
            chunkRadius = 2;
            break;
        default: // OFF or invalid value
            return; // Disable the effect entirely
    }

    // Return if chunkRadius is set to OFF
    if (chunkRadius === undefined) return;

    // Grab all players
    const allPlayers = world.getAllPlayers();
    // If no players, return
    if (allPlayers.length === 0) return;

    // Check every player
    for (const player of allPlayers) {
        const { location } = player;
        const overworld = world.getDimension("overworld");
        // Grab chunk location
        const playerChunkX = Math.floor(location.x / 16);
        const playerChunkZ = Math.floor(location.z / 16);

        for (let dx = -chunkRadius; dx <= chunkRadius; dx++) {
            for (let dz = -chunkRadius; dz <= chunkRadius; dz++) {
                const chunkX = playerChunkX + dx;
                const chunkZ = playerChunkZ + dz;

                // Skip if the chunk is already loaded
                if (!markChunkAsLoaded(chunkX, chunkZ)) continue;

                const { x: centerX, z: centerZ } = getChunkCenterCoordinates(chunkX, chunkZ);
                const location = { x: centerX, z: centerZ, y: 0 };

                // Get the highest air block in the chunk
                const highestAirBlock = getHighestAirBlock(overworld, location);

                if (highestAirBlock) {
                    // Spawn the biome tester entity at the highest air block
                    const biomeTester = overworld.spawnEntity("squaredreams_realism:biome_tester", highestAirBlock);

                    if (!biomeTester || !biomeTester.isValid()) {
                        console.error("Biome tester entity is invalid.");
                        continue;
                    }

                    // Trigger event to query biome
                    biomeTester.triggerEvent("squaredreams_realism:query_biome");

                    // Handle biome query result after a short delay
                    system.runTimeout(() => {
                        system.runJob(placeBiomeBlocks(biomeTester, highestAirBlock))
                    }, 2);
                }
            }
        }
    }
}, randomBlocksDelayTicks);

function* placeBiomeBlocks(biomeTester) {
    try {
        const biomeId = biomeTester.getProperty("squaredreams_realism:biome_query");
        const loc = biomeTester.location
            // Define the region around the biome tester to check
        const checkRadius = 7;
        const overworld = world.getDimension("overworld")
        const possibleLocationsAbove = [];
        const possibleLocationsSide = [];

        // If not a valid biome, skip
        if (biomeId === 0) return;

        for (let dx = -checkRadius; dx <= checkRadius; dx++) {
            for (let dy = -checkRadius; dy <= checkRadius; dy++) {
                for (let dz = -checkRadius; dz <= checkRadius; dz++) {
                    const pos = { x: loc.x + dx, y: loc.y + dy, z: loc.z + dz };
                    const block = overworld.getBlock(pos);
                    const blockType = getBlockTypeId(block);

                    if (blockType) {
                        const biomeMappingAbove = biomeBlockMappingsAbove[biomeId] || {};
                        const biomeMappingSide = biomeBlockMappingsSide[biomeId] || {};

                        const replacementBlocksAbove = biomeMappingAbove[blockType] || [];
                        const replacementBlocksSide = biomeMappingSide[blockType] || [];

                        if (replacementBlocksAbove.length > 0) {
                            const blockAbove = overworld.getBlock({ x: pos.x, y: pos.y + 1, z: pos.z });

                            if (blockAbove && blockAbove.typeId === "minecraft:air") {
                                possibleLocationsAbove.push({ pos, replacementBlocksAbove });
                            }
                        }

                        if (replacementBlocksSide.length > 0) {
                            replacementBlocksSide.forEach(({ block: replacementBlock, directions }) => {
                                const chosenDirection = chooseWeightedDirection(directions);
                                if (!chosenDirection) return; // Skip if no valid direction

                                const directionMappings = {
                                    north: { x: 0, z: -1 },
                                    east: { x: 1, z: 0 },
                                    south: { x: 0, z: 1 },
                                    west: { x: -1, z: 0 }
                                };
                                const directionVector = directionMappings[chosenDirection];
                                const blockBeside = overworld.getBlock({ x: pos.x + directionVector.x, y: pos.y, z: pos.z + directionVector.z });

                                if (blockBeside && blockBeside.typeId === "minecraft:air") {
                                    possibleLocationsSide.push({ pos: { x: pos.x + directionVector.x, y: pos.y, z: pos.z + directionVector.z }, replacementBlock, chosenDirection });
                                }
                            });
                        }
                    }
                    yield;
                }
            }
        }

        // Use weights specific to the biome
        const weights = biomeWeights[biomeId] || [0.825, 0.1, 0.05, 0.025];

        // Above
        const numLocationsAbove = Random.weightedChoice(weights, 100);
        if (numLocationsAbove > 0 && possibleLocationsAbove.length > 0) {
            const selectedLocationsAbove = Random.chooseN(possibleLocationsAbove, Math.min(possibleLocationsAbove.length, numLocationsAbove));
            selectedLocationsAbove.forEach(({ pos, replacementBlocksAbove }) => {
                const blockToPlace = chooseWeightedBlock(replacementBlocksAbove);
                overworld.runCommand(`fill ${pos.x} ${pos.y + 1} ${pos.z} ${pos.x} ${pos.y + 1} ${pos.z} ${blockToPlace} replace air`);
                const placedBlock = overworld.getBlock({ x: pos.x, y: pos.y + 1, z: pos.z });
                if (placedBlock) {
                    updateBlockWithRandomRotation(placedBlock);
                }
            });
        }

        // Side
        const numLocationsSide = Random.weightedChoice(weights, 100);
        if (numLocationsSide > 0 && possibleLocationsSide.length > 0) {
            const selectedLocationsSide = Random.chooseN(possibleLocationsSide, Math.min(possibleLocationsSide.length, numLocationsSide));
            selectedLocationsSide.forEach(({ pos, replacementBlock, chosenDirection }) => {
                overworld.runCommand(`fill ${pos.x} ${pos.y} ${pos.z} ${pos.x} ${pos.y} ${pos.z} ${replacementBlock} replace air`);
                const placedBlock = overworld.getBlock(pos);
                if (placedBlock) {
                    updateBlock(placedBlock, chosenDirection);
                }
            });
        }

    } catch (err) {
        console.error("Error handling biome query:", err);
    } finally {
        // Despawn the biome tester entity
        if (biomeTester.isValid()) {
            biomeTester.triggerEvent("squaredreams_realism:instant_despawn");
        }
    }
}