import { system, world } from "@minecraft/server";
import { Random } from "../util/random";
import { blockRegion } from "../util/block";
import { FXSequencer } from "../util/fx";
import { clamp } from "../util/math";
import { settingEnabled } from "../settings";

// Variables
const fallingLeavesDelayTicks = 20; // Delay between leaf spawn attempts in ticks
const motionPredictionInfluence = 80; // Influence of player motion on leaf spawn prediction
const motionPredictionMaxDistance = 25; // Maximum distance of motion prediction
const regionRandomSpreadXZ = 7; // Random spread in XZ direction for region selection
const regionRandomSpreadYMin = 3; // Minimum random spread in Y for region selection
const regionRandomSpreadYMax = 12; // Maximum random spread in Y for region selection
const regionCheckHalfExtentsXZ = 1; // Half-extents in XZ direction for block region check
const regionCheckHalfExtentsY = 2; // Half-extents in Y for block region check
const leafSpawnDownwardIterations = 5; // Number of downward iterations for finding leaf blocks

// fx sequences
let _seq;
_seq = FXSequencer.register("falling_leaves_oak");
_seq.addParticle(0, "squaredreams_realism:oak_leaves");
_seq = FXSequencer.register("falling_leaves_spruce");
_seq.addParticle(0, "squaredreams_realism:spruce_leaves");
_seq = FXSequencer.register("falling_leaves_birch");
_seq.addParticle(0, "squaredreams_realism:birch_leaves");
_seq = FXSequencer.register("falling_leaves_jungle");
_seq.addParticle(0, "squaredreams_realism:jungle_leaves");
_seq = FXSequencer.register("falling_leaves_acacia");
_seq.addParticle(0, "squaredreams_realism:acacia_leaves");
_seq = FXSequencer.register("falling_leaves_dark_oak");
_seq.addParticle(0, "squaredreams_realism:acacia_dark_oak");

// leaf types and their corresponding fx sequence names
const leafFXSequences = {
    oak: "falling_leaves_oak",
    spruce: "falling_leaves_spruce",
    birch: "falling_leaves_birch",
    jungle: "falling_leaves_jungle",
    acacia: "falling_leaves_acacia",
    dark_oak: "falling_leaves_dark_oak"
};

// Vanilla leave blocks
const leafBlockIds = ["minecraft:leaves", "minecraft:leaves2"];

function getBlockTypeId(block, possibleTypeIds) {
    for (let typeId of possibleTypeIds) {
        if (block.permutation.matches(typeId)) return typeId;
    }
    return undefined;
}

function getLeafType(block) {
    const typeId = getBlockTypeId(block, leafBlockIds);

    if (typeId === undefined) {
        return {
            typeId: undefined,
            leafType: undefined,
        };
    }

    if (typeId === "minecraft:leaves") {
        // Test each old_leaf_type
        for (let BlockIDTest of["oak", "spruce", "birch", "jungle"]) {
            if (block.permutation.matches(typeId, { old_leaf_type: BlockIDTest })) {
                return {
                    typeId: typeId,
                    leafType: BlockIDTest,
                };
            }
        }
    } else if (typeId === "minecraft:leaves2") {
        // Test each new_leaf_type
        for (let BlockIDTest of["acacia", "dark_oak"]) {
            if (block.permutation.matches(typeId, { new_leaf_type: BlockIDTest })) {
                return {
                    typeId: typeId,
                    leafType: BlockIDTest,
                };
            }
        }
    }
    // Return undefined if no matching leaf type is found
    return {
        typeId: typeId,
        leafType: undefined,
    };
}

// Run main loop
system.runInterval(function() {
    // Return if not enabled
    if (!settingEnabled("falling_leaves")) return;
    // Choose random player, then a random location slightly above them. Then scan for leaf blocks and run particles
    // Get all players in the world
    const allPlayers = world.getAllPlayers();
    if (allPlayers.length === 0) return; // If no players are present, stop

    // Choose a random player
    const chosenPlayer = Random.chooseFromArray(allPlayers);
    const { dimension, location } = chosenPlayer;
    const velocity = chosenPlayer.getVelocity();

    // Calculate velocity contribution for motion prediction
    const velocityContributionX = clamp(velocity.x * motionPredictionInfluence, -motionPredictionMaxDistance, motionPredictionMaxDistance);
    const velocityContributionZ = clamp(velocity.z * motionPredictionInfluence, -motionPredictionMaxDistance, motionPredictionMaxDistance);

    // Calculate the origin location for block region selection
    // Factoring in the velocity here too
    const regionLocationOrigin = {
        x: Math.floor(location.x + velocityContributionX + Random.IntegerWithinRange(-1, 1) * regionRandomSpreadXZ) + 0.5,
        y: Math.floor(location.y + Random.IntegerWithinRange(regionRandomSpreadYMin, regionRandomSpreadYMax)),
        z: Math.floor(location.z + velocityContributionZ + Random.IntegerWithinRange(-1, 1) * regionRandomSpreadXZ) + 0.5,
    };

    // Create a new region with the origin calculated above
    const region = blockRegion.aroundOrigin(dimension, regionLocationOrigin, {
        x: regionCheckHalfExtentsXZ,
        y: regionCheckHalfExtentsY,
        z: regionCheckHalfExtentsXZ,
    });

    // Run a matching block id test which runs through each block
    // in the region and checks if it matches any ids. This returns
    // the first match info in the result
    const result = region.anyBlockMatch(leafBlockIds);
    if (!result.success) return; // If no matching block is found, exit function

    // Iterate downward from result.location and check blocks
    // to find the lowest block before air (this is where we want to spawn
    // the particles from)
    const resultLocation = result.location;
    const leafSet = new Set(leafBlockIds);
    let offsetY = 0;

    for (offsetY = 0; offsetY > -leafSpawnDownwardIterations; offsetY--) {
        const block = dimension.getBlock({
            x: resultLocation.x,
            y: resultLocation.y + offsetY,
            z: resultLocation.z,
        });
        if (!leafSet.has(block.permutation)) break; // Exit loop if a non-leaf block is found
    }

    // Determine the type of leaf and play corresponding FX sequence if defined
    const leafType = getLeafType(result.block);
    const sequenceName = leafFXSequences[leafType.leafType];
    if (sequenceName !== undefined) {
        FXSequencer.play(sequenceName, dimension, {
            x: resultLocation.x,
            y: resultLocation.y + offsetY,
            z: resultLocation.z,
        });
    }
}, fallingLeavesDelayTicks); // Run the function periodically with defined delay