import { world, system } from "@minecraft/server"

// Custom component registration
world.beforeEvents.worldInitialize.subscribe(
    ({ blockComponentRegistry }) => {
        // The random ticking for blocks to conditionally use in order to attempt a decay.
        blockComponentRegistry.registerCustomComponent('pu_biomes:leaves.decay', {
            onRandomTick: (event) => {
                const { block } = event
                system.runJob(leafDecayTick(block))
            }
        })
        // Flags player placed leaves with a state so they will never decay.
        blockComponentRegistry.registerCustomComponent('pu_biomes:leaves.place', {
            beforeOnPlayerPlace: (event) => {
                event.permutationToPlace = event.permutationToPlace.withState(leafStateProperty, states.placed)
            }
        })
    }
);

// general strings
const leafStateProperty = 'pu_biomes:leaf_decay'
const leafString = '_leaves'
const logTag = 'log'

// states
const states = {
    decay: -1,    // Marked for decay check
    natural: 0,   // Default state
    placed: 1     // Player-placed, won't decay
};

// adjacent offsets
const adjacentOffsets = [
    { x: 0, y: 1, z: 0 },
    { x: 0, y: -1, z: 0 },
    { x: 1, y: 0, z: 0 },
    { x: -1, y: 0, z: 0 },
    { x: 0, y: 0, z: 1 },
    { x: 0, y: 0, z: -1 }
];

/** Leaf decay activation. 
 * Makes a volume of valid leave blocks attempt random ticking, in order to potentially decay.
 * @param {Block} block*/
function* randomTickVolume(block) {
    const dimension = block.dimension
    const { x: baseX, y: baseY, z: baseZ } = block.location
    const radius = 4;

    const minPos = {
        x: baseX - radius,
        y: baseY - radius,
        z: baseZ - radius
    };
    const maxPos = {
        x: baseX + radius,
        y: baseY + radius,
        z: baseZ + radius
    };

    let processCount = 0;
    for (let y = minPos.y; y <= maxPos.y; y++) {
        for (let x = minPos.x; x <= maxPos.x; x++) {
            for (let z = minPos.z; z <= maxPos.z; z++) {
                const checkBlock = dimension.getBlock({ x, y, z })

                if (checkBlock?.hasTag('pu_biomes:custom_leaves')) {
                    const state = checkBlock.permutation.getState(leafStateProperty) ?? states.natural
                    if (state === states.natural) {
                        checkBlock.setPermutation(
                            checkBlock.permutation.withState(leafStateProperty, states.decay)
                        );
                    }
                }

                if (++processCount % 16 === 0) yield
            }
        }
        yield;
    }
}

// Break event to run function that prepares nearby leaves for decay - when a valid block is broken that is.
world.afterEvents.playerBreakBlock.subscribe((event) => {
    const { block, brokenBlockPermutation } = event
    if (brokenBlockPermutation.hasTag(logTag) || brokenBlockPermutation.type.id.includes(leafString)) system.runJob(randomTickVolume(block))
});

/** Generic leaf decay funtion. 
 * For random ticking leaves, on tick will run the vanilla-like survivability checks, then either break or stop ticking.
 * @param {Block} block*/
function* leafDecayTick(block) {
    const dimension = block.dimension
    const location = block.location
    const { x, y, z } = location

    for (const offset of adjacentOffsets) {
        const adjBlock = dimension.getBlock({
            x: x + offset.x,
            y: y + offset.y,
            z: z + offset.z
        })
        if (adjBlock?.hasTag(logTag)) {
            block.setPermutation(block.permutation.withState(leafStateProperty, states.natural))
            return
        }
    }
    yield

    const queue = [{ pos: location, cost: 0 }]
    const visited = new Set()
    let foundLog = false

    while (queue.length > 0 && !foundLog) {
        const current = queue.shift()
        const posKey = `${current.pos.x},${current.pos.y},${current.pos.z}`

        if (visited.has(posKey) || current.cost > 3) continue;
        visited.add(posKey)

        if (visited.size % 8 === 0) yield;

        for (const offset of adjacentOffsets) {
            const nextPos = {
                x: current.pos.x + offset.x,
                y: current.pos.y + offset.y,
                z: current.pos.z + offset.z
            };

            const nextBlock = dimension.getBlock(nextPos);
            if (!nextBlock) continue;

            if (nextBlock.hasTag(logTag)) {
                foundLog = true
                break;
            }

            const blockId = nextBlock.typeId;
            if (blockId.includes(leafString)) {
                queue.push({
                    pos: nextPos,
                    cost: current.cost + 1
                });
            }
        }
    }

    if (foundLog) {
        if (block.typeId.includes(leafString)) {
            block.setPermutation(
                block.permutation.withState(leafStateProperty, states.natural)
            );
        }
    } else if (block.typeId.includes(leafString)) {
        dimension.runCommand(`setblock ${x} ${y} ${z} air destroy`)
    }
}