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

class Vector3OP {
    multiply(a, b) {
        return {
            x: (a.x ?? 0) * (b.x ?? 0),
            y: (a.y ?? 0) * (b.y ?? 0),
            z: (a.z ?? 0) * (b.z ?? 0),
        }
    }
    add(a, b) {
        return {
            x: (a.x ?? 0) + (b.x ?? 0),
            y: (a.y ?? 0) + (b.y ?? 0),
            z: (a.z ?? 0) + (b.z ?? 0),
        }
    }
    sub(a, b) {
        return {
            x: (a.x ?? 0) - (b.x ?? 0),
            y: (a.y ?? 0) - (b.y ?? 0),
            z: (a.z ?? 0) - (b.z ?? 0),
        }
    }
}

const VOP = new Vector3OP()
const PRC = {}
const PPR = {
    "none": "default"
}
const PDR = {
    "default": {
        emitter: "none",

        vel: 1,

        damage: {
            max: 1,
            min: 1
        },

        ignore_shooter: false,
        ignore_self: false,

        physics: {
            gravity: 1,
            mass: 1
        },

        remove_after_hit: {
            all: false,
            block: false,
            entity: false
        },

        on_hit_block: ( source, block, data) => {},
        on_hit_entity: ( source, entity, data) => {},
        on_out_of_render: ( source, data) => {},

        custom_filter: (event) => {}
    }
}

var projectile_pool = []

function registerProjectile( typeId, data) {
    if(data.emitter)
        PPR[data.emitter] = typeId
    if(data.emitter && data.custom_filter)
        PRC[data.emitter] = data.custom_filter

    PDR[typeId] = {
        ...PDR.default,
        ...data
    }
}

function getProjectile( entity ) {
    return PDR[entity.typeId] ?? PDR.default
}

system.runInterval(()=>{
    projectile_pool.forEach((data,idx)=> {
        const pr_data = getProjectile(data.entity)

        try {
            const block = data.entity.dimension.getBlock(data.entity.location)

            var entity_remove_cause = false
            var block_remove_cause = (block && !block.isAir && !block.isLiquid) || (
                data.location.x == data.entity.location.x &&
                data.location.y == data.entity.location.y &&
                data.location.z == data.entity.location.z)
            
            data.entity.dimension.getEntitiesAtBlockLocation(data.location).forEach(entity=>{
                if((!pr_data.ignore_self || entity != data.entity) && (!pr_data.ignore_shooter || entity != data.source)) {
                    entity.applyDamage((Math.random()*(pr_data.damage.max-pr_data.damage.min))+pr_data.damage.min)
                    
                    pr_data.on_hit_entity( data.entity, entity, data)

                    entity_remove_cause = true
                }
            })

            if(entity_remove_cause || block_remove_cause){
                if(block_remove_cause)
                    pr_data.on_hit_block( data.entity, block, data)

                const cb = (pr_data.remove_after_hit.block || pr_data.remove_after_hit.all);
                const ce = (pr_data.remove_after_hit.entity || pr_data.remove_after_hit.all);

                if(
                    (block_remove_cause && cb) ||
                    (entity_remove_cause && ce)
                ) {
                    data.entity.clearVelocity()
                    data.entity.remove()

                    delete projectile_pool[idx]
                }

                return
            }

            projectile_pool[idx].location = projectile_pool[idx].entity.location

            if(pr_data.physics){
                projectile_pool[idx].gravity += (pr_data.physics.mass/100)

                if(projectile_pool[idx].gravity > pr_data.physics.terminal)
                    projectile_pool[idx].gravity = pr_data.physics.terminal
            }

            const cv = projectile_pool[idx].gravity
            const rt = Math.floor(cv/pr_data.physics.terminal)*135

            data.entity.setProperty("ink:rxfix",parseInt((data.view.y)*-45+rt))
            data.entity.setProperty("ink:ryfix",parseInt(data.rotation.y))

            data.entity.clearVelocity()
            data.entity.applyImpulse(VOP.add(
                VOP.multiply(data.view, { x: pr_data.vel, y: pr_data.vel, z: pr_data.vel }),
                {y:-cv}
            ))
        } catch(e) {
            pr_data.on_out_of_render( data.entity, data)

            if(data.entity) {
               try {
                    data.entity.clearVelocity()
                    data.entity.remove()
               } catch (e) {}
            }

            delete projectile_pool[idx]
        }
    })
},1)

world.afterEvents.itemUse.subscribe((event) => {
    try {
        const { itemStack, source } = event

        const filter = PRC[itemStack.typeId]?PRC[itemStack.typeId]( event ) : true
        const prk = PPR[itemStack.typeId]

        if (filter && prk) {
            const pr_data = getProjectile({typeId:prk})

            const view = VOP.multiply(source.getViewDirection(),{x:1.5, y:1.5, z:1.5})
            const location = VOP.add(source.location, { y: 1.5 })
            const rotation = source.getRotation()
            const projectile = source.dimension.spawnEntity(prk, VOP.add(view, location))

            projectile.setProperty("ink:rxfix",parseInt((view.y)*-45))
            projectile.setProperty("ink:ryfix",parseInt(rotation.y))
            
            projectile.clearVelocity()
            projectile.applyImpulse(VOP.multiply(view, { x: pr_data.vel, y: pr_data.vel, z: pr_data.vel }))

            projectile_pool.push({
                entity: projectile,
                location: projectile.location,
                rotation, view, source, gravity: 0
            })
        }
    } catch(e) {}
})

const Projectile = {
    registerProjectile, Vector3OP
}

export { Projectile }