import { system, world, } from "@minecraft/server";
import CustomVector3 from "../calc/vector3";
import { applyEffect } from "./effects/effectsManager";
class Projectile {
    constructor(entity, shooter, invulnerableEntities, hitParticle, onEntityHit, onBlockHit, onHit) {
        this.entity = entity;
        this.shooter = shooter;
        this.invulnerableEntities = invulnerableEntities;
        this.hitParticle = hitParticle;
        this.onEntityHit = onEntityHit;
        this.onBlockHit = onBlockHit;
        this.onHit = onHit;
        this.processed = false; // Initialize processed flag as false
    }
}
// This map is used to store the projectiles that are currently in the air
const projectilesInAir = new Map();
function attemptDespawn(entity) {
    if (projectilesInAir.delete(entity.id)) {
        if (entity.isValid()) {
            entity.remove();
        }
    }
}
// Utility function to calculate right and up vectors based on direction
export function calculateRelativeVectors(direction) {
    // Normalize the direction
    const forward = CustomVector3.fromMC(direction).normalize();
    // Calculate right vector (perpendicular to forward and assuming a flat plane)
    const right = new CustomVector3(-forward.z, 0, forward.x).normalize();
    // Calculate up vector (perpendicular to both forward and right)
    const up = right.cross(forward).normalize();
    return { forward, right, up };
}
export default function shootProjectile(entity, properties, customVelocity // New optional parameter
) {
    const { projectileID, power, maxLifetime = 10, spawnDistance = { x: 0, y: 0, z: 0 }, invulnerabilityRange = 0.9, onHitEntity, onHitBlock, gravity = 0.05, airInertia = 0.99, liquidInertia = 0.6, destroyOnHit = true, uncertainty = 0, } = properties;
    // Set up spawn location with directional adjustments
    const direction = entity.getViewDirection();
    let projectileLocation = entity.getHeadLocation();
    world.sendMessage(`${power}`);
    const { forward, right, up } = calculateRelativeVectors(direction);
    projectileLocation = {
        x: projectileLocation.x +
            forward.x * spawnDistance.z +
            right.x * spawnDistance.x +
            up.x * spawnDistance.y,
        y: projectileLocation.y +
            forward.y * spawnDistance.z +
            right.y * spawnDistance.x +
            up.y * spawnDistance.y,
        z: projectileLocation.z +
            forward.z * spawnDistance.z +
            right.z * spawnDistance.x +
            up.z * spawnDistance.y,
    };
    // Spawn the projectile entity
    const projectile = entity.dimension.spawnEntity(projectileID, projectileLocation);
    const component = projectile.getComponent("minecraft:projectile");
    if (properties.shootEffects) {
        if (properties.shootEffects.sounds) {
            for (const sound of properties.shootEffects.sounds) {
                entity.dimension.playSound(sound, entity.location);
            }
        }
        if (properties.shootEffects.particles) {
            for (const particle of properties.shootEffects.particles) {
                entity.dimension.spawnParticle(particle, entity.location);
            }
        }
    }
    component.owner = entity;
    component.airInertia = airInertia;
    component.gravity = gravity;
    component.liquidInertia = liquidInertia;
    component.stopOnHit = true;
    let velocity;
    if (customVelocity) {
        velocity = {
            x: customVelocity.x * (power || 1 + -spawnDistance.x),
            y: customVelocity.y * (power || 1 + -spawnDistance.y),
            z: customVelocity.z * (power || 1 + -spawnDistance.z),
        };
    }
    else {
        velocity = {
            x: direction.x * (power || 1 + -spawnDistance.x),
            y: direction.y * (power || 1 + -spawnDistance.y),
            z: direction.z * (power || 1 + -spawnDistance.z),
        };
    }
    component.shoot(velocity, { uncertainty: uncertainty });
    // Set up invulnerable entities within range
    let invulnerableEntities = new Set();
    if (invulnerabilityRange >= 0) {
        invulnerableEntities = new Set(entity.dimension
            .getEntities({
            location: entity.location,
            maxDistance: invulnerabilityRange,
        })
            .map((e) => e.id));
    }
    // Create and store the projectile
    const newProjectile = new Projectile(projectile, entity, invulnerableEntities, undefined, (proj, hitEntity, location) => {
        if (onHitEntity) {
            if (proj.processed)
                return;
            proj.processed = true;
            hitEntity.applyDamage(onHitEntity.damage || 0);
            const horizontalStrength = onHitEntity.knockback || 0;
            const verticalStrength = 0.5;
            const directionX = direction.x;
            const directionZ = direction.z;
            if (onHitEntity.knockback !== 0 && onHitEntity.knockback !== undefined) {
                hitEntity.applyKnockback(directionX, directionZ, horizontalStrength, verticalStrength);
            }
            if (onHitEntity.function)
                onHitEntity.function(hitEntity);
            if (onHitEntity.effect)
                applyEffect(hitEntity, onHitEntity.effect);
            if (onHitEntity.particles) {
                for (const particle of onHitEntity.particles) {
                    entity.dimension.spawnParticle(particle, location);
                }
            }
            if (onHitEntity.sound)
                entity.dimension.playSound(onHitEntity.sound, location);
            return true;
        }
        return false;
    }, (proj, hitBlock, location) => {
        if (onHitBlock) {
            if (proj.processed)
                return;
            proj.processed = true;
            if (onHitBlock.particles) {
                for (const particle of onHitBlock.particles) {
                    entity.dimension.spawnParticle(particle, location);
                }
            }
            if (onHitBlock.sound)
                entity.dimension.playSound(onHitBlock.sound, location);
            if (onHitBlock.function)
                onHitBlock.function(hitBlock.block);
            return true;
        }
        return false;
    }, (proj, location, hitEntity, hitBlock) => {
        if (proj.processed)
            return;
        proj.processed = true;
    });
    projectilesInAir.set(projectile.id, newProjectile);
    system.runTimeout(() => {
        attemptDespawn(projectile);
    }, maxLifetime * 20);
    // Handle projectile hitting an entity
    world.afterEvents.projectileHitEntity.subscribe((event) => {
        const projectile = projectilesInAir.get(event.projectile.id);
        if (!projectile || projectile.processed)
            return;
        const hitEntity = event.getEntityHit().entity;
        if (!hitEntity)
            return;
        if (projectile.invulnerableEntities.has(hitEntity.id))
            return;
        const location = {
            dimension: hitEntity.dimension,
            x: hitEntity.location.x,
            y: hitEntity.location.y,
            z: hitEntity.location.z,
        };
        if (projectile.onEntityHit(projectile, hitEntity, location) && destroyOnHit) {
            system.runTimeout(() => {
                attemptDespawn(event.projectile);
            }, 1);
        }
    });
    // Handle projectile hitting a block
    world.afterEvents.projectileHitBlock.subscribe((event) => {
        const projectile = projectilesInAir.get(event.projectile.id);
        if (!projectile || projectile.processed)
            return;
        const blockHitInfo = event.getBlockHit();
        const location = {
            dimension: event.dimension,
            x: event.location.x,
            y: event.location.y,
            z: event.location.z,
        };
        if (projectile.onBlockHit(projectile, blockHitInfo, location) && destroyOnHit) {
            system.runTimeout(() => {
                attemptDespawn(event.projectile);
            }, 1);
        }
    });
    return projectile;
}
