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
const AIR_BLOCKS = ["air", "water", "tall_grass", "short_grass", "seagrass", "kelp", "lava"];


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 @s[family=cubc_ra_su:player_projectile, type=!cubc_ra_su:ballista_arrow, type=!cubc_ra_su:player_skull] if entity @e[rm=0.01, r=1.7, tag=!cubc_ra_su:owning_entity, type=!cubc_ra_su:cannon, type=!item] run tag @s add cubc_ra_su:hit");
    projectile.runCommand("execute if entity @s[type=cubc_ra_su:ballista_arrow, scores={cubc_ra_su_lifetime=5..}] if entity @e[rm=0.01, r=1.7, tag=!cubc_ra_su:owning_entity, family=!trash, type=!item] run tag @s add cubc_ra_su:hit");
    projectile.runCommand("execute if entity @s[type=cubc_ra_su:player_skull, scores={cubc_ra_su_lifetime=5..}] if entity @e[rm=0.01, r=1.7, tag=!cubc_ra_su:owning_entity, type=!cubc_ra_su:friendly_ship, type=!item] run tag @s add cubc_ra_su:hit");
    projectile.runCommand("execute if entity @s[type=cubc_ra_su:wither_skull] 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, type=!item] run tag @s add cubc_ra_su:hit");

    const conditions = AIR_BLOCKS.map(block => `unless block ~~~ ${block}`).join(' ');

    projectile.runCommand(`execute ${conditions} 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");
}

// Get the direction the launcher is looking
function initializeProjectile(projectile, launchingPlayer) {
    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)

    if (projectile.typeId === "cubc_ra_su:cannon_ball") {
        direction.y += 0.1
    }
    if (projectile.typeId === "cubc_ra_su:standing_cannon_ball") {
        direction.y += 0.1
    }
    if (projectile.typeId === "cubc_ra_su:player_skull") {
        direction.y += 0.45
    }
    if (projectile.typeId === "cubc_ra_su:wither_skull") {
        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).typeId.replace("minecraft:", "");
        if (!AIR_BLOCKS.includes(block)) {
            return true;
        }
    } catch (e) {
        return false;
    }

    return false;
}

function updateProjectileRotation(projectile) {
    const velocity = getVelocity(projectile);
    if (!velocity) return;

    // Calculate horizontal rotation (yaw)
    const rotationY = -Math.atan2(velocity.x, velocity.z) * (180 / Math.PI);

    // Calculate vertical rotation (pitch)
    const horizontalSpeed = Math.sqrt(velocity.x * velocity.x + velocity.z * velocity.z);
    const rotationX = -Math.atan2(velocity.y, horizontalSpeed) * (180 / Math.PI);

    // Store the rotation values as dynamic properties

    projectile.setProperty("cubc_ra_su:rotation_x", rotationX);
    projectile.setProperty("cubc_ra_su:rotation_y", rotationY);
}


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});

    if (projectile.getProperty("cubc_ra_su:rotation_x")) {
        updateProjectileRotation(projectile);
    }
}

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) {
            let shooter = getClosestEntity(projectile, "minecraft:player");

            if (projectile.typeId === "cubc_ra_su:wither_skull") {
                shooter = getClosestEntity(projectile, "cubc_ra_su:enemy_ship");
            }

            if (projectile.typeId === "cubc_ra_su:standing_cannon_ball") {
                shooter = getClosestEntity(projectile, "cubc_ra_su:cannon");
            }

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

                updateProjectile(projectile);
                detectCollision(projectile);
            } catch (e) {
                projectile.remove();
            }
        }
    }
}, 1);