// eventHooks.js
import { world, system } from '@minecraft/server';
import * as mc from "@minecraft/server";
import { entityEventsManager } from "../../main";
import { sleep, randomInt } from "../utils/index";

mc.world.afterEvents.entityHitEntity.subscribe((event) => {
    entityEventsManager.onEntityHitEntity(event);
});

mc.world.afterEvents.entitySpawn.subscribe(async (event) => {
    entityEventsManager.onEntitySpawn(event);
});

mc.world.afterEvents.entityDie.subscribe((event) => {
    entityEventsManager.onEntityDie(event);
});

mc.world.afterEvents.itemStartUse.subscribe((event) => {
    entityEventsManager.onitemStartUse(event);
});

mc.world.afterEvents.itemStopUse.subscribe((event) => {
    entityEventsManager.onitemStopUse(event);
});

mc.world.afterEvents.itemReleaseUse.subscribe((event) => {
    entityEventsManager.onitemReleaseUse(event);
});
mc.world.afterEvents.itemUse.subscribe((event) => {
    entityEventsManager.onitemUse(event);
});

mc.world.afterEvents.projectileHitBlock.subscribe((event) => {
    entityEventsManager.onprojectileHitBlock(event);
    const { dimension, getBlockHit, hitVector, location, projectile, source } = event;
    if (projectile.typeId === 'lv_mo:grappling_hook_projectile') {
        projectile.clearVelocity();
        projectile.triggerEvent(`lv_mo:player_sensor`)
    }
});

const THROW_FORCE = { x: 2, y: 1, z: 2 }; // Force vector for throwing the player diagonally
const THROW_DELAY = 10; // Duration (in ticks) the player is immobilized before being thrown
const THROW_DAMAGE = 5; // Damage dealt when the player is thrown

// Store a "thrown" state for players
const playerThrownState = new Map();

mc.world.afterEvents.entityHurt.subscribe((event) => {
    const { hurtEntity, damageSource } = event;

    // Check if the damaging entity is a Pitcher and the hurt entity is a player
    if (damageSource.damagingEntity?.typeId === "lv_mo:pitcher" && hurtEntity instanceof mc.Player) {
        // Check if the player is already in a "thrown" state
        if (!playerThrownState.get(hurtEntity)) {
            grabAndThrowPlayer(damageSource.damagingEntity, hurtEntity);
        }
    }
});

function grabAndThrowPlayer(pitcher, player) {
    // Mark the player as "thrown"
    playerThrownState.set(player, true);

    // Immobilize the player temporarily
    setPlayerInputPermission(player, false);

    // Delay before throwing the player
    mc.system.runTimeout(() => {
        // Release immobilization
        setPlayerInputPermission(player, true);

        // Calculate throw direction
        const throwVector = calculateThrowVector(pitcher, player);

        // Apply throw velocity
        player.applyKnockback(throwVector.x, throwVector.z, THROW_FORCE.x, THROW_FORCE.y);

        // Apply damage immediately after throwing
        player.runCommandAsync(`damage @s ${THROW_DAMAGE} entity_attack entity ${pitcher.typeId}`);

        // Allow the player to be grabbed again after a delay (landing simulation)
        mc.system.runTimeout(() => {
            playerThrownState.delete(player); // Reset thrown state
        }, 30); // 1.5 seconds (adjust as needed) 
    }, THROW_DELAY); // Delay in ticks
}

function calculateThrowVector(pitcher, player) {
    if (pitcher.isValid()) {
        player.runCommandAsync(`effect @s slowness 2 1 true`);
        const directionX = player.location.x - pitcher.location.x;
        const directionZ = player.location.z - pitcher.location.z;
        const magnitude = Math.sqrt(directionX * directionX + directionZ * directionZ);
        return {
            x: (directionX / magnitude) * THROW_FORCE.x,
            z: (directionZ / magnitude) * THROW_FORCE.z,
        };
    }
}

function setPlayerInputPermission(player, allowInput) {
    const inputPermissions = player.inputPermissions;

    if (inputPermissions) {
        inputPermissions.movementEnabled = allowInput;
    }
}





mc.world.afterEvents.playerPlaceBlock.subscribe((event) => {
    entityEventsManager.onplayerPlaceBlock(event);
});

const playerLogCache = new Map();

const BATCH_SIZE = 20;  // Number of blocks to process per tick
const MAX_RADIUS = 3;  // Maximum radius in X and Z directions from the original block
const BREAK_DELAY = 1;  // Delay between breaking each block in ticks

mc.world.afterEvents.worldInitialize.subscribe(() => {
    try {
        mc.system.runInterval(() => {
            const players = mc.world.getAllPlayers();

            players.forEach((player) => {
                const targetBlock = player.getBlockFromViewDirection({ maxDistance: 8 })?.block;
                if (targetBlock && targetBlock.type.id.endsWith(`_log`) && !targetBlock.type.id.startsWith(`stripped_`)) {
                    const dimension = targetBlock.dimension;
                    const startY = targetBlock.location.y;
                    const startX = targetBlock.location.x;
                    const startZ = targetBlock.location.z;

                    const visited = new Set();
                    const connectedLogs = [];
                    const queue = [targetBlock];

                    const processQueue = () => {
                        let count = 0;
                        while (queue.length > 0 && count < BATCH_SIZE) {
                            const block = queue.shift();
                            const blockPos = `${block.location.x},${block.location.y},${block.location.z}`;

                            if (visited.has(blockPos)) continue;
                            visited.add(blockPos);

                            // Limit radius to 3 blocks in the X and Z directions and up to 25 blocks in the Y direction
                            const withinRadius = Math.abs(block.location.x - startX) <= MAX_RADIUS &&
                                Math.abs(block.location.z - startZ) <= MAX_RADIUS &&
                                block.location.y <= startY + 25;

                            if (!withinRadius) continue;

                            connectedLogs.push(block);
                            count++;

                            const directions = [
                                { x: 1, y: 0, z: 0 },
                                { x: -1, y: 0, z: 0 },
                                { x: 0, y: 0, z: 1 },
                                { x: 0, y: 0, z: -1 },
                                { x: 0, y: 1, z: 0 },
                                { x: 0, y: -1, z: 0 }
                            ];

                            for (const dir of directions) {
                                const adjacentBlock = dimension.getBlock({
                                    x: block.location.x + dir.x,
                                    y: block.location.y + dir.y,
                                    z: block.location.z + dir.z
                                });

                                if (adjacentBlock && (adjacentBlock.type.id.endsWith(`_leaves`) || adjacentBlock.type.id.endsWith(`_log`)) && !adjacentBlock.type.id.startsWith(`stripped_`)) {
                                    queue.push(adjacentBlock);
                                }
                            }
                        }

                        if (queue.length > 0) {
                            mc.system.runTimeout(processQueue, 1);  // Schedule next batch
                        } else {
                            playerLogCache.set(player, connectedLogs);
                        }
                    };

                    processQueue();  // Start processing in batches
                } else {
                    playerLogCache.delete(player);
                }
            });
        }, 1);
    } catch (error) {

    }

});


mc.world.afterEvents.projectileHitEntity.subscribe(async (event) => {
    const { source, projectile, hitVector } = event;
    try {
        const entityHit = event.getEntityHit().entity;
        if (source.typeId === 'lv_mo:endigrade' && projectile.typeId === 'lv_mo:endigrade.projectile') {
            if (!entityHit || !entityHit.isValid()) return;
            entityHit.addEffect('blindness', (3 * 20), { amplifier: 2, showParticles: false });
            entityHit.applyKnockback(hitVector.x, hitVector.y, 4, 0.7);
        }
        if (projectile.typeId === 'lv_mo:neptune_trident_projectile') {
            const targetPos = entityHit?.location;
        }
    } catch (error) {
        //suppressed
    }
});
const blocks = ["lv_mo:infestation_block_1", "lv_mo:infestation_block", "lv_mo:infestation_block_0", "lv_mo:infestation_block_0", "lv_mo:infestation_block_0", "lv_mo:infestation_block_0", "lv_mo:infestation_block_0", "lv_mo:infestation_block_0", "lv_mo:infestation_block_0", "lv_mo:infestation_block_0"];
const unobtainedBlocks = [
    "minecraft:bedrock",
    "minecraft:command_block",
    "minecraft:chain_command_block",
    "minecraft:repeating_command_block",
    "minecraft:structure_block",
    "minecraft:barrier",
    "minecraft:light_block_0",
    "minecraft:light_block_1",
    "minecraft:light_block_2",
    "minecraft:light_block_3",
    "minecraft:light_block_4",
    "minecraft:light_block_5",
    "minecraft:light_block_6",
    "minecraft:light_block_7",
    "minecraft:light_block_8",
    "minecraft:light_block_9",
    "minecraft:light_block_10",
    "minecraft:light_block_11",
    "minecraft:light_block_12",
    "minecraft:light_block_13",
    "minecraft:light_block_14",
    "minecraft:light_block_15",
    "minecraft:end_portal_frame",
    "minecraft:mob_spawner",
    "minecraft:crying_obsidian",
    "minecraft:dragon_egg",
];


function explosionfillBlock(source, blockPermArray, xFrom, yFrom, zFrom, xTo, yTo, zTo) {
    // Ensure yFrom and yTo are within the specified range
    yFrom = Math.max(-63, yFrom);
    yTo = Math.min(320, yTo);

    for (let i = xFrom; i <= xTo; i++) {
        for (let j = yFrom; j <= yTo; j++) {
            for (let k = zFrom; k <= zTo; k++) {
                const block = source.dimension.getBlock({ x: i, y: j, z: k });
                if (block && block.typeId !== 'minecraft:air' && !unobtainedBlocks.includes(block.typeId)) {
                    const randomIndex = Math.floor(Math.random() * blockPermArray.length);
                    const randomPerm = blockPermArray[randomIndex];
                    const random = randomInt(1, 5);
                    if (random > 2) {
                        block.setPermutation(mc.BlockPermutation.resolve(randomPerm));
                    }
                }
            }
        }
    }
}


mc.system.afterEvents.scriptEventReceive.subscribe(async (e) => {
    const { id, sourceEntity, message, initiator, sourceBlock, sourceType } = e;

    const radius = 1;
    const sourceLocation = sourceEntity.location;
    const dimension = sourceEntity.dimension;
    let nearestLog;
    let shortestDistance;
    let shortestDistance1;
    let nearestLogAxis;
    switch (id) {
        case 'lv_mo:is_bubblewing_allowed':
            const bubblewingScoreboard = world.scoreboard.getObjective("global_toggles");
            const bubblewingGlobalEntry = bubblewingScoreboard?.getParticipants().find(p => p.displayName === "toggle_bubblewing");
            const isBubblewingEnabled = bubblewingGlobalEntry ? bubblewingScoreboard.getScore(bubblewingGlobalEntry) === 1 : true; // Default to true

            if (!isBubblewingEnabled) {
                //Removes Bubblewing
                sourceEntity.triggerEvent("lv_mo:despawn")
            }
            break;
        case 'lv_mo:is_spook_allowed':
            const spookScoreboard = world.scoreboard.getObjective("global_toggles");
            const spookGlobalEntry = spookScoreboard?.getParticipants().find(p => p.displayName === "toggle_spook");
            const isSpookEnabled = spookGlobalEntry ? spookScoreboard.getScore(spookGlobalEntry) === 1 : true; // Default to true

            if (!isSpookEnabled) {
                //Removes Spook
                sourceEntity.triggerEvent("lv_mo:despawn")
            }
            break;
        case 'lv_mo:fill_infested':
            explosionfillBlock(sourceEntity, blocks, sourceLocation.x - 1, sourceLocation.y - 1, sourceLocation.z - 1, sourceLocation.x + 1, sourceLocation.y + 1, sourceLocation.z + 1);
            break;
        case 'lv_mo:projectile':
            const players = sourceEntity.dimension.getEntities({
                location: sourceEntity.location,
                closest: 1,
                families: ['player']
            });
            if (players.length === 0) {
                return;
            }
            const player = players[0];
            const inventory = player.getComponent(mc.EntityInventoryComponent.componentId);
            const { container } = inventory;
            const projectileDamage = sourceEntity.getDynamicProperty('projectileDamage') ?? 0;
            const newItem = new mc.ItemStack(`lv_mo:${message}`, 1);
            const newDurability = newItem.getComponent("minecraft:durability");
            newDurability.damage = projectileDamage;
            if (projectileDamage === newDurability.maxDurability) {
                sourceEntity.remove();
                return;
            }
            if (container.emptySlotsCount > 0) {
                container.addItem(newItem);
            } else {
                player.dimension.spawnItem(newItem, player.location);
            }
            sourceEntity.remove();
            break;
        case 'lm_vo:wave_attack':
            try {
                const viewDirection = sourceEntity.getRotation();
                const sea_wave = mc.world.getDimension(`overworld`).spawnEntity("lv_mo:sea_wave", sourceEntity.location);
                sea_wave.setRotation(viewDirection);
                const steps = 100;
                for (let i = 0; i <= steps; i++) {
                    let blockBelow = sea_wave.dimension.getBlock(sea_wave.location).below();
                    while (blockBelow.typeId === 'minecraft:air') {
                        sea_wave.teleport({
                            x: sea_wave.location.x,
                            y: sea_wave.location.y - 1,
                            z: sea_wave.location.z
                        });
                        await sleep(1);
                        blockBelow = sea_wave.dimension.getBlock(sea_wave.location).below();
                    }
                    sea_wave.runCommand(`tp @s ^^^1`);
                    await sleep(1);
                }
            } catch (error) { }
            break;
        case 'lv_mo:bark_log':
            const sawhopperScoreboard = world.scoreboard.getObjective("global_toggles");
            const sawhopperGlobalEntry = sawhopperScoreboard?.getParticipants().find(p => p.displayName === "toggle_sawhopper");
            const isSawhopperEnabled = sawhopperGlobalEntry ? sawhopperScoreboard.getScore(sawhopperGlobalEntry) === 1 : true; // Default to true

            if (!isSawhopperEnabled) break;
            nearestLog = null;
            shortestDistance = radius + 1;
            shortestDistance1 = 2.5;
            nearestLogAxis = 'y';

            const LOG_TYPES = {
                'minecraft:acacia_log': 'minecraft:stripped_acacia_log',
                'minecraft:oak_log': 'minecraft:stripped_oak_log',
                'minecraft:spruce_log': 'minecraft:stripped_spruce_log',
                'minecraft:birch_log': 'minecraft:stripped_birch_log',
                'minecraft:jungle_log': 'minecraft:stripped_jungle_log',
                'minecraft:dark_oak_log': 'minecraft:stripped_dark_oak_log',
                'minecraft:mangrove_log': 'minecraft:stripped_mangrove_log',
                'minecraft:cherry_log': 'minecraft:stripped_cherry_log'
            };
            if (sourceEntity.typeId === `lv_mo:saw_hopper`) {
                for (let dx = -radius; dx <= radius; dx++) {
                    for (let dy = -radius; dy <= radius; dy++) {
                        for (let dz = -radius; dz <= radius; dz++) {
                            const x = sourceLocation.x + dx;
                            const y = sourceLocation.y + dy;
                            const z = sourceLocation.z + dz;

                            const block = dimension.getBlock({ x, y, z });

                            if (block && LOG_TYPES[block.typeId]) {
                                const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
                                if (distance < shortestDistance) {
                                    const axis = block.permutation.getState("pillar_axis");
                                    nearestLog = block;
                                    shortestDistance = distance;
                                    nearestLogAxis = axis;
                                }
                            }
                        }
                    }
                }
                if (nearestLog) {
                    sourceEntity.runCommand(`/particle lv_mo:sawhopper_block_eat ${nearestLog.location.x} ${nearestLog.location.y} ${nearestLog.location.z}`);
                    sourceEntity.runCommand(`/playsound dig.wood @a ${nearestLog.location.x} ${nearestLog.location.y} ${nearestLog.location.z}`);
                    nearestLog.setType(LOG_TYPES[nearestLog.typeId]);
                    nearestLog.setPermutation(nearestLog.permutation.withState("pillar_axis", nearestLogAxis));
                }
            }
            break;
        case 'lv_mo:bark_dripstone':
            let lizoredScoreboard = world.scoreboard.getObjective("global_toggles");
            let lizoredGlobalEntry = lizoredScoreboard?.getParticipants().find(p => p.displayName === "toggle_lizored");
            let isLizoredEnabled = lizoredGlobalEntry ? lizoredScoreboard.getScore(lizoredGlobalEntry) === 1 : true; // Default to true

            if (!isLizoredEnabled) break;
            nearestLog = null;
            shortestDistance = radius + 1;
            shortestDistance1 = 2.5;
            nearestLogAxis = 'y';
            //Lizored Eating Trigger
            if (sourceEntity.typeId === `lv_mo:lizored`) {
                for (let dx = -1; dx <= 1; dx++) {
                    for (let dy = -1; dy <= 1; dy++) {
                        for (let dz = -1; dz <= 1; dz++) {
                            const x = sourceLocation.x + dx;
                            const y = sourceLocation.y + dy;
                            const z = sourceLocation.z + dz;

                            const block = dimension.getBlock({ x, y, z });

                            if (block.typeId === `minecraft:dripstone_block`) {
                                const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
                                if (distance < shortestDistance1) {
                                    nearestLog = block;
                                    shortestDistance1 = distance;
                                }
                            }
                        }
                    }
                }
                const property = sourceEntity.getProperty(`lv_mo:lizored`)
                if (nearestLog) {
                    sourceEntity.playAnimation(`animation.lv_mo.lizored.eating`);
                    sourceEntity.runCommand(`/particle lv_mo:lizored_block_eat ${nearestLog.location.x} ${nearestLog.location.y} ${nearestLog.location.z}`);
                    sourceEntity.runCommand(`/playsound break.dripstone_block @a ${nearestLog.location.x} ${nearestLog.location.y} ${nearestLog.location.z}`);
                    nearestLog.setType(`minecraft:air`);
                    if (property != 3) {
                        await sleep(20)
                        sourceEntity.playAnimation(`animation.lv_mo.lizored.transition_by_eating`);
                        sourceEntity.runCommand(`/playsound break.dripstone_block @a ~~~`);
                        sourceEntity.runCommand(`/playsound mob.sniffer.eat @a ~~~ 0.75 0.5`);
                        sourceEntity.runCommand(`/playsound random.burp @a ~~~ 0.75 0.75`);
                        sourceEntity.runCommand(`/playsound mob.sniffer.eat @a ~~~ 1 1`);
                        sourceEntity.setProperty(`lv_mo:lizored`, property + 1);
                    }
                }
            }
            break;
        case 'lv_mo:hoot.scan':
            const scanRadius = 24;
            const entities = dimension.getEntities({ location: sourceEntity.location, maxDistance: scanRadius, families: ["mob"] });

            for (const target of entities) {
                if (target === sourceEntity) continue;
                if (target.typeId === "minecraft:player") continue;

                const spawnX = target.location.x;
                const spawnY = target.location.y + 1.5;
                const spawnZ = target.location.z;

                const summonCommand = `summon lv_mo:scan_dummy ${spawnX} ${spawnY} ${spawnZ} facing ${sourceEntity.location.x} ${sourceEntity.location.y} ${sourceEntity.location.z}`;
                await sourceEntity.runCommandAsync(summonCommand);

                // Small delay to let the entity actually exist in the world
                system.runTimeout(() => {
                    try {
                        // Get nearest scan dummy to that spawn point (within 1 block)
                        const possibleDummies = dimension.getEntities({
                            location: { x: spawnX, y: spawnY, z: spawnZ },
                            maxDistance: 1,
                            type: "lv_mo:scan_dummy"
                        });

                        const dummy = possibleDummies[0];
                        if (!dummy) return;

                        const dummyTag = `scan_dummy_${Math.floor(Math.random() * 100000)}`;
                        dummy.addTag(dummyTag);

                        // Follow the mob for 2 seconds (40 ticks)
                        let tick = 0;
                        const followInterval = system.runInterval(() => {
                            try {

                                tick++;
                                if (tick > 40) {
                                    system.clearRun(followInterval);
                                    return;
                                }

                                dummy.teleport(
                                    { x: target.location.x, y: target.location.y + 1.5, z: target.location.z },
                                    { facingLocation: sourceEntity.location }
                                );
                            } catch (error) {

                            }
                        }, 1);
                    } catch (error) {

                    }
                }, 1); // delay 1 tick
            }
            break;

        case 'lv_mo:sea_sentry.structure':
        /*
                    let callEntity = sourceEntity // Block impacted by this event.
                    let callDimension = sourceEntity.dimension // Dimension that contains the block.
                    sourceEntity.triggerEvent("despawn");
                    try {
                        let structureManager = world.structureManager;
                        structureManager.place(
                            "lv_mo:sea_sentry_structure",
                            callDimension,
                            { x: callEntity.location.x - 32, y: callEntity.location.y - 17, z: callEntity.location.z - 32 }
                        );
        
                    } catch (error) {
                        console.log(error);
        
                    }
                    //Boss Spawn Offset
                    const bossSpawnPos = {
                        x: callEntity.location.x + 11.58,
                        y: callEntity.location.y + 19.25,
                        z: callEntity.location.z - 0.22
                    };
                    dimension.spawnEntity("lv_mo:sea_sentry", bossSpawnPos);
                    
                    break;
        */

    }
});



