import { system,world,MolangVariableMap,EntityComponentTypes,EntityDamageCause} from '@minecraft/server'
import v3 from '../../common/v3'

export default class abilities {

    static #ns = "sqst_mnts"

    constructor () {}

    static melee_attack(inputEnt,rider,attackData){
        //exit with error if no inputs
        if(inputEnt === undefined || rider === undefined || attackData === undefined) return -1
        //force mount rotation in view direction
        inputEnt.applyImpulse(v3.multiply(inputEnt.getViewDirection(),0.1))
        //run animation
        inputEnt.playAnimation("attack",{blendOutTime:0.2,stopExpression:"q.any_animation_finished"});
        //attack delay
        system.runTimeout(()=>{
            //exit with error if no inputs
            if(inputEnt === undefined || rider === undefined || attackData === undefined) return -1
            //create unique tag... because scai
            let selfTag = "sqst:"+inputEnt.id
            //add tag to self and rider to protect form attack
            inputEnt.addTag(selfTag)
            rider.addTag(selfTag)
            //initialize needed variables
            let attackDistance = attackData.range
            let frontBlock = null
            let targets = []
            do {
                //exit if negative range
                if(attackDistance < 0 ) break
                //get attack location
                frontBlock =v3.addV3(inputEnt.location,v3.multiply(v3.xz(inputEnt.getViewDirection()),attackDistance))
                //get targets at location, excluding self and items
                targets = inputEnt.dimension.getEntities({location:v3.shiftUp(frontBlock,0.5),maxDistance:attackData.radius,excludeTags:[selfTag],excludeTypes:["minecraft:item"]});
                //prepare a closer attack distance if no targets found
                attackDistance-= attackData.radius
            } while(targets[0]===undefined)
            //show attack particle at max range
            inputEnt.dimension.spawnParticle(attackData.particle,v3.shiftUp(v3.addV3(inputEnt.location,v3.multiply(v3.xz(inputEnt.getViewDirection()),attackData.range)),0.5))
            //apply attack if target found
            if(targets[0]!==undefined)
                try{
                    let damageBuff = 0
                    if(inputEnt.getEffect("strength") !==undefined) damageBuff=3;
                    targets[0]?.applyKnockback(inputEnt.getViewDirection().x, inputEnt.getViewDirection().z,attackData.knockback_forward,attackData.knockback_up)
                    targets[0]?.applyDamage(attackData.damage+damageBuff,{cause:EntityDamageCause.entityAttack,damagingEntity:inputEnt})
                } catch (error) {
                    console.warn("cannot attack: "+error)
                }
            //remove tag from self and rider
            inputEnt.removeTag(inputEnt.id)
            rider.removeTag(inputEnt.id)
        },attackData.delay)
        //return time of attack
        return(system.currentTick)
    }

    static aoe_buff(inputEnt,rider,aoeData){
        //exit with error if no inputs
        if(inputEnt === undefined || rider === undefined || aoeData === undefined) return -1
        //show aoe particle at entity location
        if(aoeData.particle!==undefined)
            inputEnt.dimension.spawnParticle(aoeData.particle,inputEnt.location)
        //play animation
        if(aoeData.animation!==undefined)
            inputEnt.playAnimation(aoeData.animation,{controller:"controller.animation.sqst_mnts."+inputEnt.typeId.replace(this.#ns+":","")})
       
        //aoe delay
        system.runTimeout(()=>{
            //exit with error if no inputs
            if(inputEnt === undefined || rider === undefined || aoeData === undefined) return -1
            //get targets from same family in range
            let targets =  inputEnt.dimension.getEntities({location:inputEnt.location,maxDistance:aoeData.radius,families:aoeData.familly_affected})
            //get players in range
            targets =  targets.concat(inputEnt.dimension.getEntities({location:inputEnt.location,maxDistance:aoeData.radius,families:["player"]}))
            //apply aoe
            if(targets[0]!=undefined)
                try{
                    targets.forEach(ent => {
                        //if only mounted enabled, skip if entity is not player and not tamed
                        if(aoeData.only_tamed)
                            if(ent.typeId !== "minecraft:player" && ent.getComponent(EntityComponentTypes.IsTamed)===undefined)
                                return
                        ent.addEffect(aoeData.effect,aoeData.duration,{amplifier:aoeData.strength, showParticles:true})
                        if(aoeData.affectedAnimation!==undefined)
                            system.runTimeout(()=>{
                                if(ent.typeId !== "minecraft:player"&& ent != inputEnt)
                                ent.playAnimation(aoeData.affectedAnimation,{controller:"controller.animation.sqst_mnts."+inputEnt.typeId.replace(this.#ns+":","")})
                            },v3.getRandomInt(20)+5)
                    });
                } catch (error) {
                    console.warn("cannot buff: "+error)
                }
            },aoeData.delay)
        //return time of activation
        return(system.currentTick)
    }

    static aoe_debuff(inputEnt,rider,aoeData){
        //exit with error if no inputs
        if(inputEnt === undefined || rider === undefined || aoeData === undefined) return -1
        //show aoe particle at entity location
        if(aoeData.particle!==undefined)
            inputEnt.dimension.spawnParticle(aoeData.particle,inputEnt.location)
        //play animation
        if(aoeData.animation!==undefined)
            inputEnt.playAnimation(aoeData.animation,{controller:"controller.animation.sqst_mnts."+inputEnt.typeId.replace(this.#ns+":","")})
        //aoe delay
        system.runTimeout(()=>{
            //create unique tag... because scai
            let selfTag = "sqst:"+inputEnt.id
            //add tag to self and rider to protect form attack
            inputEnt.addTag(selfTag)
            rider.addTag(selfTag)
            //exit with error if no inputs
            if(inputEnt === undefined || rider === undefined || aoeData === undefined) return -1
            //get targets from same family in range
            let targets =  inputEnt.dimension.getEntities({location:inputEnt.location,maxDistance:aoeData.radius,excludeTags:[selfTag],excludeTypes:["minecraft:item"]})
            //apply aoe
            if(targets[0]!=undefined)
                try{
                    targets.forEach(ent => {
                        ent.addEffect(aoeData.effect,aoeData.duration,{amplifier:aoeData.strength, showParticles:true})
                    });
                } catch (error) {
                    console.warn("cannot buff: "+error)
                }
            },aoeData.delay)
        //remove tag from self and rider
        inputEnt.removeTag(inputEnt.id)
        rider.removeTag(inputEnt.id)
        //return time of activation
        return(system.currentTick)
    }

    static rollup(inputEnt,rider,rollData){
        //exit with error if no inputs
        if(inputEnt === undefined || rider === undefined || rollData === undefined) return -1

        //rollup delay
        system.runTimeout(()=>{
            //exit with error if no inputs
            if(inputEnt === undefined || rider === undefined || rollData === undefined) return -1
            //get entity property for behavior and animation interaction
            let prp_roll = inputEnt.getProperty(rollData.prp)
            //if no propery, exit
            if(prp_roll === undefined) return -1
            //if prp in state 1 do rollup activations else do unroll ones
            if(prp_roll===1){
                try{
                    inputEnt.setProperty(rollData.prp,2)
                }catch (error) {
                    console.warn(`setProp(${prop}): ${error}`)
                    return -1
                }
                inputEnt.triggerEvent("sqst:ev_start_rolling")
            }else if(prp_roll===2){
                try{
                    inputEnt.setProperty(rollData.prp,1)
                }catch (error) {
                    console.warn(`setProp(${prop}): ${error}`)
                    return -1
                }
                inputEnt.triggerEvent("sqst:ev_start_walking")
            }
        },rollData.delay)
        //return time of activation
        return(system.currentTick)
    }

    static self_buff(inputEnt,rider,buffData){
        //exit with error if no inputs
        if(inputEnt === undefined || rider === undefined || buffData === undefined) return -1
        //show aoe particle at entity location
        if(buffData.particle!==undefined)
            inputEnt.dimension.spawnParticle(buffData.particle,inputEnt.location)
        //aoe delay
        system.runTimeout(()=>{
            //exit with error if no inputs
            if(inputEnt === undefined || rider === undefined || buffData === undefined) return -1

            try{
                rider.addEffect(buffData.effect,buffData.duration,{amplifier:buffData.strength, showParticles:true})
                inputEnt.addEffect(buffData.effect,buffData.duration,{amplifier:buffData.strength, showParticles:true})
            } catch (error) {
                console.warn("cannot buff: "+error)
            }
            },buffData.delay)
        //return time of activation
        return(system.currentTick)
    }
}