import { ItemStack, system, world } from "@minecraft/server";
import { EntityTracker } from "./util/entityTracker";
import { vecAdd, vecScale, vecSum } from "./util/mthUtils";
import { setItemInMainHand, traverseBlocksUntil } from "./util/miscUtils";
// why is this here? not only due to lack of pojectile related api but most importantly because ANY entity show with velocity will NOT follow a parabolic trajectory for some reason. Probably some logic like gravity is applied twice or something dumb like that
// Crazy projectile manager, don't distribute
// Author: MehVahdJukaar
class ProjectilesManager extends EntityTracker {
    constructor() {
        super();
        this.canHitTag = "razz_sup:can_hit";
        this.posTag = "razz_sup:pos";
        this.velTag = "razz_sup:vel";
        this.hasHitTag = "razz_sup:has_hit";
        this.gravity = 0.03;
        this.airFriction = 0.99;
        this.waterFriction = 0.95;
        this.knownProjectiles = new Map();
    }
    getProjectileVelocity(entity) {
        return entity.getDynamicProperty(this.velTag);
    }
    shouldTrack(entity) {
        return this.knownProjectiles.has(entity.typeId);
    }
    shootProjectileFromEntity(player, speed, entityId, itemId) {
        const shootPos = vecAdd(vecSum(player.getHeadLocation(), vecScale(player.getViewDirection(), -0.5)), 0, -0.12, 0);
        let view = player.getViewDirection();
        return this.shootProjectile(player.dimension, entityId, shootPos, view, speed, itemId);
    }
    shootProjectile(dimension, entityId, shootPos, view, speed, itemId) {
        const entity = dimension.spawnEntity(entityId, shootPos);
        const vel = vecScale(view, speed);
        let pos = entity.location;
        this.setOldPosAndVel(entity, pos, vel);
        entity.applyImpulse(vel);
        if (itemId) {
            setItemInMainHand(entity, new ItemStack(itemId, 1));
        }
        const id = entity.id;
        system.runTimeout(() => {
            const b = world.getEntity(id);
            if (b) {
                b.setDynamicProperty(this.canHitTag, true);
            }
            else {
                console.warn("Projectile not found");
            }
        }, 3);
        return entity;
    }
    registerProjectile(id, projectile) {
        this.knownProjectiles.set(id, projectile);
    }
    onTick(entity) {
        //tick entities
        const prevPos = entity.getDynamicProperty(this.posTag);
        const prevVel = entity.getDynamicProperty(this.velTag);
        if (!prevVel || !prevPos)
            return;
        //set new entity location and apply velocity
        //gravity
        let newVel = this.calculateNewVelocity(prevVel);
        const newPos = vecSum(prevPos, newVel);
        //gravity an deceleration
        //dumb check, we should raytrace all the time... we can phase though stuff like this
        const level = entity.dimension;
        if (entity.getDynamicProperty(this.hasHitTag) == undefined) {
            //if we hit we need to raytrace
            //raytrace is super buggy...
            /*
            const [length, normalizedVel] = normalizeVec(newVel);

            const hit = level.getBlockFromRay(prevPos, normalizedVel,
                {
                    maxDistance: Math.max(1.5, length * 3),
                    includePassableBlocks: true
                }) //mega dumb

             */
            const hit = traverseBlocksUntil(level, prevPos, newVel);
            if (hit === null || (hit && !hit.hitBlock.isValid()))
                return; //unloaded chunk
            if (hit) {
                entity.setDynamicProperty(this.hasHitTag, true);
                newVel = { x: 0, y: 0, z: 0 };
                this.knownProjectiles.get(entity.typeId)?.onHitBlock?.(entity, hit);
            }
        }
        this.knownProjectiles.get(entity.typeId)?.onTick?.(entity, prevPos);
        entity.clearVelocity();
        entity.applyImpulse(newVel);
        //overshoot
        entity.teleport(this.predictPositionInNTicks(newPos, newVel, 1), { keepVelocity: true });
        //check hits
        this.setOldPosAndVel(entity, newPos, newVel);
    }
    calculateNewVelocity(currentVel) {
        let newVel = vecAdd(currentVel, 0, -this.gravity, 0);
        //inertia
        return vecScale(newVel, this.airFriction);
    }
    //mega dumb. we use entity as just a visual thing. its position will be ahead just for the sake of the visuals that always lag behind for some reason...
    predictPositionInNTicks(newPos, newVel, ticks) {
        let predictedPos = newPos;
        for (let i = 0; i < ticks; i++) {
            predictedPos = vecSum(predictedPos, newVel);
            newVel = this.calculateNewVelocity(newVel);
        }
        return predictedPos;
    }
    setOldPosAndVel(entity, pos, vel) {
        entity.setDynamicProperty(this.posTag, pos);
        entity.setDynamicProperty(this.velTag, vel);
    }
}
export const projectileManager = new ProjectilesManager();
