import { Entity, EntityEquippableComponent, EquipmentSlot, Player, world, EntityProjectileComponent, system } from "@minecraft/server";
import { Vector3, Vector5 } from "../Math/index";


/**
 * 
 * @param {Entity} entity 
 * @param {Vector3} direction 
 * @param {boolean} [ignorePitch=false]
 */
export function getRelativePosition(entity, direction, ignorePitch = false) {
    let rotation = entity.getRotation();
    const location = entity.getHeadLocation();

    let correctedRotation = Vector5.getCorrectedRotation(rotation.y, rotation.x);
    if (ignorePitch) correctedRotation.x = 0;
    const entityLocationVector = new Vector5(location.x, location.y, location.z, correctedRotation.x, correctedRotation.y);
    const newLocation = entityLocationVector.getRelativePosition(direction);

    return newLocation;
}


/**
 * Gets the item in the player's hand
 * @param {Player} player 
 */
export function getItemInHand(player) {
    /** @type {EntityEquippableComponent} */
    const equippableComponent = player.getComponent('equippable');
    const item = equippableComponent.getEquipment(EquipmentSlot.Mainhand);
    return item !== null ? item : undefined
}


/**
 * Spawns a projectile entity and then shoots it using shootProjectile.
 * @param {string} projectile - The projectile identifier.
 * @param {Entity} entity - The entity that is shooting the projectile.
 * @param {number} power - The power with which to shoot the projectile.
 * @param {ShootProjectileOptions} options - Additional shooting options.
 * @returns {Promise<Entity>} - The spawned projectile entity.
 */
export function entityShootProjectile(projectile, entity, power, options = {}) {
    let {
        gravity = 0.05,
        ignorePitch = false,
        uncertainty = 0.0,
        facingOffset = { x: 1, y: 0, z: 0 },
        initialUncertainty = 0,
    } = options;

    const viewOffsetX = (Math.random() * 2 - 1) * initialUncertainty;
    const viewOffsetY = (Math.random() * 2 - 1) * initialUncertainty;
    const viewOffsetZ = (Math.random() * 2 - 1) * initialUncertainty;

    // Spawn Projectile
    const relativeLocation = getRelativePosition(entity, facingOffset, true);
    if (relativeLocation.y > 318) return;
    const projectileEntity = entity.dimension.spawnEntity(projectile, relativeLocation);

    let entityRotation = entity.getRotation();
    if (ignorePitch) entityRotation.y = 0;
    projectileEntity.setRotation(entityRotation);

    // Shoot the projectile
    shootProjectile(projectileEntity, entity, power, {
        gravity,
        ignorePitch,
        uncertainty,
        initialUncertainty,
        facingOffset,
        viewOffsets: { x: viewOffsetX, y: viewOffsetY, z: viewOffsetZ },
    });

    return projectileEntity;
}

/**
 * Handles the firing logic of the projectile entity.
 * @param {Entity} projectileEntity - The projectile entity to be shot.
 * @param {Entity} shooterEntity - The entity that is shooting the projectile.
 * @param {number} power - The power with which to shoot the projectile.
 * @param {ShootProjectileOptions & { viewOffsets?: Vector3, targetLocation?: Vector3 }} options - Additional shooting options.
 */
export function shootProjectile(projectileEntity, shooterEntity, power, options = {}) {
    let {
        gravity = 0.05,
        ignorePitch = false,
        uncertainty = 0.0,
        facingOffset = { x: 0, y: 0, z: 0 },
        viewOffsets = { x: 0, y: 0, z: 0 },
        targetLocation = null,
    } = options;


    /** @type {EntityProjectileComponent} */
    let projectileComponent = projectileEntity.getComponent('projectile');
    projectileComponent.owner = shooterEntity;

    const relativeLocation = getRelativePosition(shooterEntity, facingOffset, true);
    projectileEntity.teleport(relativeLocation, { keepVelocity: true, rotation: shooterEntity.getRotation() });

    let viewDirection;

    if (targetLocation) {
        // Compute direction from projectile position to target location
        const projectilePosition = projectileEntity.location;
        viewDirection = {
            x: targetLocation.x - projectilePosition.x,
            y: targetLocation.y - projectilePosition.y,
            z: targetLocation.z - projectilePosition.z,
        };
        viewDirection = Vector3.normalizeVector(viewDirection);
    } else {
        // Use shooter entity's view direction
        viewDirection = ignorePitch
            ? { ...shooterEntity.getViewDirection(), y: 0 }
            : shooterEntity.getViewDirection();
        viewDirection = {
            x: viewDirection.x + viewOffsets.x,
            y: viewDirection.y + viewOffsets.y,
            z: viewDirection.z + viewOffsets.z,
        };
        viewDirection = {
            x: Math.max(Math.min(viewDirection.x, 180), -180),
            y: Math.max(Math.min(viewDirection.y, 180), -180),
            z: Math.max(Math.min(viewDirection.z, 180), -180),
        };
    }

    projectileComponent.shoot(viewDirection, { uncertainty: uncertainty });


    // Ensure Projectile Velocity
    system.runTimeout(() => {
        gravity *= 0.49;
        let velocity = projectileEntity.getVelocity();
        const normalizedVelocity = Vector3.normalizeVector(velocity);
        let newVelocity = {
            x: normalizedVelocity.x * power,
            y: normalizedVelocity.y * power,
            z: normalizedVelocity.z * power,
        };


        const handler = system.runInterval(() => {
            if (!projectileEntity?.isValid()) {
                system.clearRun(handler);
                return;
            }

            if (projectileEntity.location.y > 318) return;

            const location = projectileEntity.location;
            if (
                !projectileEntity.dimension.getBlock({ ...location, y: location.y - 0.2 })?.isAir ||
                !projectileEntity?.isValid()
            ) {
                system.clearRun(handler);
                return;
            }

            // Apply gravity
            newVelocity.y -= gravity;

            projectileEntity.clearVelocity();
            projectileEntity.applyImpulse(newVelocity);
        });
    }, 2);
}


/**
 * @typedef {object} ShootProjectileOptions
 * @property {number} [gravity=0.05]
 * @property {boolean} [ignorePitch=false]
 * @property {number} [uncertainty=0]
 * @property {Vector3} [facingOffset={ x: 1, y: 0, z: 0 }]
 * @property {number} [initialUncertainty=0]
 */

