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

const DEFAULT_GRAVITY = -0.03; // Default gravity applied each tick; unless modified by cubc_ra_su:gravity component
const DEFAULT_POWER = 3; // Default Initial speed of the projectile; unless modified by cubc_ra_su:power component
const DEFAULT_AIR_RESISTANCE = 0.05; // Default Fraction of velocity lost per tick; unless modified by cubc_ra_su:air_resistance component

function setVelocity(projectile, velocity) {
    projectile.setDynamicProperty("cubc_ra_su:velocity", velocity);
}

function onHit(projectile) {
    projectile.triggerEvent('cubc_ra_su:hit');
}

function detectCollision(projectile) {
    if (predictFutureCollision(projectile, 1)) {
        projectile.addTag("cubc_ra_su:hit");
    }

    projectile.runCommand("tag @e remove cubc_ra_su:owning_entity");
    world.getEntity(projectile.getDynamicProperty("cubc_ra_su:owner")).addTag("cubc_ra_su:owning_entity");

    projectile.runCommand("execute if entity @e[rm=0.01, r=1.7, tag=!cubc_ra_su:owning_entity, type=!cubc_ra_su:enemy_ship, family=!cubc_ra_su:pirate_skeleton, family=!trash] run tag @s add cubc_ra_su:hit");
    projectile.runCommand("execute unless block ~~~ air unless block ~~~ water run tag @s add cubc_ra_su:hit");

    if (projectile.hasTag("cubc_ra_su:hit")) {
        onHit(projectile);
    }
}

function getVelocity(projectile, velocity) {
    return projectile.getDynamicProperty("cubc_ra_su:velocity");
}

function initializeProjectile(projectile, launchingPlayer) {
    // Get the direction the launcher is looking
    const direction = launchingPlayer.getViewDirection();

    projectile.setDynamicProperty("cubc_ra_su:owner", launchingPlayer.id);

    // Set gravity, power, air resistance
    projectile.setDynamicProperty("cubc_ra_su:gravity", projectile.getProperty("cubc_ra_su:gravity") ?? DEFAULT_GRAVITY)
    projectile.setDynamicProperty("cubc_ra_su:power", projectile.getProperty("cubc_ra_su:power") ?? DEFAULT_POWER)
    projectile.setDynamicProperty("cubc_ra_su:air_resistance", projectile.getProperty("cubc_ra_su:air_resistance") ?? DEFAULT_AIR_RESISTANCE)


    direction.y += 0.2

    // Normalize the direction vector
    const length = Math.sqrt(direction.x * direction.x + direction.y * direction.y + direction.z * direction.z);
    const normalizedDirection = {
        x: direction.x / length,
        y: direction.y / length,
        z: direction.z / length
    };

    // Initial velocity along the launch direction
    const initialVelocity = {
        x: normalizedDirection.x * projectile.getDynamicProperty("cubc_ra_su:power"),
        y: normalizedDirection.y * projectile.getDynamicProperty("cubc_ra_su:power"),
        z: normalizedDirection.z * projectile.getDynamicProperty("cubc_ra_su:power")
    };

    // Set the projectile's initial velocity
    setVelocity(projectile, {x: initialVelocity.x, y: initialVelocity.y, z: initialVelocity.z});
}

function predictFutureCollision(projectile, ticksAhead = 5) {
    // Copy current state
    const currentPosition = {...projectile.location};
    let currentVelocity = {...getVelocity(projectile)};
    let predictedPosition = {...currentPosition};

    for (let i = 0; i < ticksAhead; i++) {
        // Apply gravity
        currentVelocity.y += projectile.getDynamicProperty("cubc_ra_su:gravity");

        // Compute speed for drag calculation
        const speed = Math.sqrt(
            currentVelocity.x ** 2 +
            currentVelocity.y ** 2 +
            currentVelocity.z ** 2
        );

        if (speed > 0) {
            const dragFactor = projectile.getDynamicProperty("cubc_ra_su:air_resistance") * speed;
            currentVelocity.x -= currentVelocity.x * dragFactor;
            currentVelocity.y -= currentVelocity.y * dragFactor;
            currentVelocity.z -= currentVelocity.z * dragFactor;
        }

        // Update predicted position
        predictedPosition.x += currentVelocity.x;
        predictedPosition.y += currentVelocity.y;
        predictedPosition.z += currentVelocity.z;
    }

    // Now we have predictedPosition 5 ticks in the future
    // Check collision at predictedPosition
    // For example, using a command to detect block collision:
    const dimension = projectile.dimension;
    try {
        const block = dimension.getBlock(predictedPosition);
        if (block.typeId !== "minecraft:air" && block.typeId !== "minecraft:water") {
            return true;
        }
    } catch (e) {
        return false;
    }

    return false;
}

function updateProjectile(projectile) {
    let currentVelocity = getVelocity(projectile);
    if (!currentVelocity) {
        return; // If somehow velocity isn't available, skip
    }

    // Apply gravity to vertical component
    currentVelocity.y += projectile.getDynamicProperty("cubc_ra_su:gravity");

    // Compute the speed of the projectile
    const speed = Math.sqrt(currentVelocity.x * currentVelocity.x
        + currentVelocity.y * currentVelocity.y
        + currentVelocity.z * currentVelocity.z);

    if (speed > 0) {
        // Define a drag factor that scales with speed
        // For example, if AIR_RESISTANCE is a small constant like 0.01,
        // scaling by speed makes drag greater at higher speeds.
        const dragFactor = projectile.getDynamicProperty("cubc_ra_su:air_resistance") * speed;

        // Apply drag, which removes a portion of velocity proportional to that speed
        currentVelocity.x -= currentVelocity.x * dragFactor;
        currentVelocity.y -= currentVelocity.y * dragFactor;
        currentVelocity.z -= currentVelocity.z * dragFactor;
    }

    // Get current position from projectile.location
    const currentPosition = projectile.location;

    // Calculate new position based on velocity
    const newPosition = {
        x: currentPosition.x + currentVelocity.x,
        y: currentPosition.y + currentVelocity.y,
        z: currentPosition.z + currentVelocity.z
    };

    // Teleport the projectile to the new position
    projectile.teleport({x: newPosition.x, y: newPosition.y, z: newPosition.z});

    // Update the projectile's velocity
    setVelocity(projectile, {x: currentVelocity.x, y: currentVelocity.y, z: currentVelocity.z});
}

export function getClosestEntity(sourceEntity, identifier) {
    return sourceEntity.dimension.getEntities({
        type: identifier,
        closest: 10,
        location: sourceEntity.location,
        maxDistance: 10
    })[0];
}

system.runInterval(() => {
    let dimensions = ["overworld", "nether", "the_end"];
    for (const dimension of dimensions) {

        const projectile_entities = world.getDimension(dimension).getEntities({
            families: ["cubc_ra_su:custom_projectile"]
        });
        for (const projectile of projectile_entities) {
            const shooter = getClosestEntity(projectile, "cubc_ra_su:enemy_ship");

            if (!projectile.hasTag('cubc_ra_su:projectile_initialized')) {
                initializeProjectile(projectile, shooter);
                projectile.addTag('cubc_ra_su:projectile_initialized');
            }

            updateProjectile(projectile);
            detectCollision(projectile);
        }
    }
}, 1);