import { EntityDamageCause, MolangVariableMap, system, world, ItemStack } from "@minecraft/server";
import { Vector3 } from "math/Vector3";

// Thank you very much @__16 for the help with making the particle line visuals!
// Thank you @cennac2 for the help with making the typewriter!
/**
 * 
 * @param {Vector3} startingPoint 
 * @param {Vector3} endingPoint 
 * @returns {number}
 */
function getDistance(startingPoint, endingPoint) {
    return new Vector3(startingPoint.x, startingPoint.y, startingPoint.z).sub(endingPoint).length();
}

/**
 * 
 * @param {Vector3} startingPoint 
 * @param {Vector3} endingPoint 
 * @returns {Vector3}
 */
function getDirection(startingPoint, endingPoint) {
    return new Vector3(endingPoint.x, endingPoint.y, endingPoint.z).sub(startingPoint).normalize();
}
/**
 * 
 * @param {Vector3} startingPoint 
 * @param {Vector3} endingPoint 
 * @param {number} stepSize
 * @returns {Vector3[]}
 */
function getLine(startingPoint, endingPoint, stepSize = 1) {
    /**@type {Vector3[]} */
    let list = []
    const point   = new Vector3(startingPoint.x, startingPoint.y, startingPoint.z);
    const dist    = getDistance(startingPoint, endingPoint);
    const normDir = getDirection(startingPoint, endingPoint);
    for (let i = 0; i < dist-stepSize; i += stepSize) {
        const newVec = point.add(new Vector3(normDir.x, normDir.y, normDir.z).multiplyScalar(stepSize));
        list.push(new Vector3(newVec.x, newVec.y, newVec.z));
    }

    const particleDirection = new MolangVariableMap();
    particleDirection.setVector3('variable.direction', {x:normDir.x, y:normDir.y, z:normDir.z});

    return [list, particleDirection];
}

const DistortedYanID = 'felixchats:distorted_yan_vismok';
const dimensions = ['minecraft:overworld', 'minecraft:nether', 'minecraft:the_end'];
const flavorText = [
    `§b§lTo ... Pack a lunchbox and consume it on top of a trash can in the streets of District 11 at 1 PM today.`,
    `§b§lTo ... Bake dacquoise while the hour hand rests between 7 and 8, and eat it while watching a movie.`,
    `§b§lTo ... Initiate a game of Never Have I Ever with the first five people you encounter. When one folds a finger, break it.`,
    `§b§lTo ... Neatly clip the nails of the sixty-second person you come across.`,
    `§b§lTo ... Pet quadrupedal animals five times.`,
    `§b§lTo ... Spin a wheel and throw a cake at the person determined by the result.`,
    `§b§lTo ... Consume eight crabs stored at room temperature and ripe persimmon at once.`,
    `§b§lTo ... At the railing on the roof of a building, shout out the name of the person you dislike, then jump off. The height of the building does not matter.`,
    `§b§lTo ... After a meal, discard all dishes that were used to serve it.`,
    `§b§lTo ... On the morning after receiving the Prescript, drink three cups of water as soon as you get up.`,
    `§b§lTo ... Race against residents that live in the same building as you to District 7. Measure the distance every twenty-three minutes and disqualify the one farthest away from the destination.`,
    `§b§lTo ... Within three days, knit a scarf with a butterfly pattern.`,
    `§b§lTo ... Dial any number. Give a New Year's greetings and words of blessing to whoever receives the call.`,
    `§b§lTo ... See green from a white wall.`,
    `§b§lTo ... When hungry, consume a Cheeki's cheeseburger with added onion.`,
    `§b§lTo ... Fold thirty-nine paper cranes and throw them from the rooftop.`,
    `§b§lTo ... At work, cut the ear of the first person to fulminate against you.`,
    `§b§lTo ... When your eyes meet another person's, nod at them.`,
    `§b§lTo ... Return to your home this instant. You may leave once a dog barks in front of your house one time.`,
    `§b§lTo ... Wear light green clothing and take 10 steps in a triangle-shaped alley.`
];


// I don't know how to remove the error when spawning 2 yans, so whatever
function summonHands(entity) {
    system.run(() => {
        entity?.triggerEvent('summon_right_hand');
        system.runTimeout(() => {
            entity?.triggerEvent('summon_left_hand');
        }, 2);
    });
}

function getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

// i love scriptevents :)
system.afterEvents.scriptEventReceive.subscribe(({ sourceEntity: entity, id, message }) => {
    if (id === 'felixchats:distorted_yan') {
        switch(message){
            case 'flavor_text':
                let {x, y, z} = entity.location;
                const textEntity = entity.dimension.spawnEntity('felixchats:distorted_yan_flavor_text', {x:x,y:y+3,z:z});
                const selectedText = flavorText[Math.floor(Math.random() * flavorText.length)];
                for (let i = 0; i <= selectedText.length; i++) {
                   system.runTimeout(() => {
                        textEntity.nameTag = selectedText.slice(0, i);
                        textEntity.dimension.playSound('mob.felixchats.distorted_yan.typing', textEntity.location, { volume: 0.75, pitch: 1 });
                        if(i >= selectedText.length){
                            system.runTimeout(() => {
                                textEntity.triggerEvent('despawn');
                            }, 100);
                        }
                    }, 1 * i);
                }
            break;

            case 'summon_hands':
            summonHands(entity);
            break;

            case 'summon_distorted_blade':
            const target = entity.dimension.getEntities({excludeTypes:['minecraft:item'],excludeFamilies:['distorted_yan','inanimate','projectile'],closest:1,location:entity.location});
            if(target === undefined){
                entity.dimension.spawnEntity('felixchats:distorted_blade',entity.location);
            } else {
                for (let targetEntity of target){
                    targetEntity.dimension.spawnEntity('felixchats:distorted_blade',targetEntity.location);
                }
            }
            break;
            
            case 'distorted_blade_damage':
            let sourceNametag = entity.nameTag.split(`'s Distorted Blade`)[0];
            if(sourceNametag === '' || sourceNametag === undefined){
                let hitEntities = entity.dimension.getEntities({location:entity.location,minDistance:10,maxDistance:12});
                for(let entity of hitEntities){
                    entity.runCommandAsync('damage @s[family=!distorted_yan,type=!item] 18 entity_attack entity @e[type=felixchats:distorted_blade,c=1]');
                }
                let hitEntities2 = entity.dimension.getEntities({location:entity.location,minDistance:8,maxDistance:10});
                for(let entity of hitEntities2){
                    entity.runCommandAsync('damage @s[family=!distorted_yan,type=!item] 22 entity_attack entity @e[type=felixchats:distorted_blade,c=1]');
                }
                let hitEntities3 = entity.dimension.getEntities({location:entity.location,minDistance:4,maxDistance:8});
                for(let entity of hitEntities3){
                    entity.runCommandAsync('damage @s[family=!distorted_yan,type=!item] 28 entity_attack entity @e[type=felixchats:distorted_blade,c=1]');
                }
                let hitEntities4 = entity.dimension.getEntities({location:entity.location,minDistance:0,maxDistance:4});
                for(let entity of hitEntities4){
                    entity.runCommandAsync('damage @s[family=!distorted_yan,type=!item] 33 entity_attack entity @e[type=felixchats:distorted_blade,c=1]');
                }
            } else {
                let hitEntities = entity.dimension.getEntities({location:entity.location,minDistance:10,maxDistance:12});
                for(let entity of hitEntities){
                    entity.runCommandAsync('damage @s[name=!"'+sourceNametag+'",type=!item] 18 entity_attack entity '+sourceNametag);
                }
                let hitEntities2 = entity.dimension.getEntities({location:entity.location,minDistance:8,maxDistance:10});
                for(let entity of hitEntities2){
                    entity.runCommandAsync('damage @s[name=!"'+sourceNametag+'",type=!item] 22 entity_attack entity '+sourceNametag);
                }
                let hitEntities3 = entity.dimension.getEntities({location:entity.location,minDistance:4,maxDistance:8});
                for(let entity of hitEntities3){
                    entity.runCommandAsync('damage @s[name=!"'+sourceNametag+'",type=!item] 28 entity_attack entity '+sourceNametag);
                }
                let hitEntities4 = entity.dimension.getEntities({location:entity.location,minDistance:0,maxDistance:4});
                for(let entity of hitEntities4){
                    entity.runCommandAsync('damage @s[name=!"'+sourceNametag+'",type=!item] 33 entity_attack entity '+sourceNametag);
                }
            }
            break;
        }
    }
});

// Chain Visual
system.runInterval(() => {
    dimensions.forEach((dim) => {
        const entityAs = world.getDimension(dim).getEntities({ families: ['distorted_yan_vismok'] });
        if(entityAs.length === 0) { return; }
        const entityA = entityAs[0];
        const entityBs = world.getDimension(dim).getEntities({ families: ['distorted_yan_hand'] });
        entityBs.forEach(entityB => {
            const startingPoint = new Vector3(entityA.getHeadLocation().x, entityA.getHeadLocation().y, entityA.getHeadLocation().z);
            const endingPoint = new Vector3(entityB.getHeadLocation().x, entityB.getHeadLocation().y, entityB.getHeadLocation().z);
            const [points, particleDirection] = getLine(startingPoint, endingPoint, 0.5);
            points.forEach(point => {
                entityA.dimension.spawnParticle("felixchats:distorted_yan_hand_connector", point, particleDirection);
                entityA.dimension.spawnParticle("felixchats:distorted_yan_hand_connector2", point, particleDirection);
            });
        });
    });
});

world.afterEvents.itemUse.subscribe(({itemStack, source}) =>{
    const inv = source?.getComponent('minecraft:inventory')?.container;
    const slot = inv?.getSlot(source.selectedSlotIndex);
    if(itemStack.typeId === 'felixchats:prescript_invitation'){
        source.dimension.spawnEntity('felixchats:distorted_yan_vismok',source.location);
        source.runCommandAsync('replaceitem entity @s slot.weapon.mainhand 0 air');
    }
    if(itemStack.typeId === 'felixchats:book_of_distorted_yan'){
        source.playSound('mob.felixchats.distorted_yan.death', { volume: 0.25 , pitch: 1 });
        source.dimension.spawnParticle("felixchats:distorted_yan_death", source.location);
        let item = new ItemStack('felixchats:distorted_index_blade');
        item.keepOnDeath = true;
        let owner = source.nameTag;
        item?.setLore([
            'Obtained by '+owner,
            'Distorted Blade (18-33)',
            'Guard (3-7)'
        ]);
        slot?.setItem(item);
    }
});

world.afterEvents.itemStopUse.subscribe(({itemStack, source}) =>{
    if(itemStack.typeId === 'felixchats:distorted_index_blade'){
        if(source.isSneaking){
            let targetPosition = source.getBlockFromViewDirection({includeLiquidBlocks:false,includePassableBlocks:false,maxDistance:32});
            let {x, y, z} = targetPosition.block.location;
            source.runCommandAsync(`summon felixchats:distorted_blade "`+source.nameTag+`'s Distorted Blade" `+(x+0.5)+` `+(y+1)+` `+(z+0.5));
        } else {
            let target = source.getEntitiesFromViewDirection({maxDistance:32})[0];
            if(target === undefined){
                let {x, y, z} = source.location; 
                source.runCommandAsync(`summon felixchats:distorted_blade "`+source.nameTag+`'s Distorted Blade" `+x+` `+y+` `+z);   
            } else {
                let {x, y, z} = target.entity.location;
                source.runCommandAsync(`summon felixchats:distorted_blade "`+source.nameTag+`'s Distorted Blade" `+x+` `+y+` `+z);
            }
        }
    }
});

world.afterEvents.entityHitEntity.subscribe(({ hitEntity, damagingEntity }) => {
    if (hitEntity.typeId !== DistortedYanID || !damagingEntity) return;
    if (hitEntity.typeId === DistortedYanID && hitEntity.getProperty('felixchats:is_invulnerable') === true) {
        const { x, z } = damagingEntity?.getViewDirection();
        hitEntity.applyKnockback(x, z, 50, 1);
    }
});

world.afterEvents.entityHurt.subscribe(({ hurtEntity, damage, damageSource:{damagingEntity, cause}}) => {
    if (!damagingEntity || damagingEntity === undefined || cause !== EntityDamageCause.entityAttack) return;
    const hurtPlayerEquippableComp = hurtEntity?.getComponent('equippable');
    const hurtPlayerHeldItem = hurtPlayerEquippableComp?.getEquipment('Mainhand');
    const guardDiceRange = getRandomInt(3,7);
    if (hurtEntity?.matches({ families: ['distorted_yan'] }) && damage > guardDiceRange || hurtEntity.typeId === 'minecraft:player' && hurtPlayerHeldItem.typeId === 'felixchats:distorted_index_blade' && damage > 3) {
        hurtEntity?.dimension.playSound('mob.felixchats.distorted_yan.hurt', hurtEntity.location, { volume: 0.25, pitch: 1 });
    } else if(hurtEntity?.matches({ families: ['distorted_yan'] }) && damage <= guardDiceRange || hurtEntity.typeId === 'minecraft:player' && hurtPlayerHeldItem.typeId === 'felixchats:distorted_index_blade' && damage <= 3){
        if(hurtEntity.typeId !== DistortedYanID){
            let {x, y, z} = hurtEntity.location;
            hurtEntity.dimension.spawnParticle("felixchats:distorted_yan_guard", {x:x,y:y+2,z:z});
            hurtEntity.dimension.playSound('mob.felixchats.distorted_yan.guard', {x:x,y:y,z:z}, { volume: 1, pitch: 1 });
        } else if(hurtEntity.typeId === DistortedYanID){
            hurtEntity.triggerEvent('guard');
        }
        const health = hurtEntity.getComponent("health");
        health.setCurrentValue(health.currentValue + damage);
    }
    if (damagingEntity?.matches({ families: ['distorted_yan_hand']})){
        let variant = damagingEntity?.getProperty('felixchats:attack_mode');
        switch(variant){
            case 'claw':
                hurtEntity.applyDamage(3,{cause:EntityDamageCause.suicide});
            break;
            case 'slam':
                hurtEntity.addEffect('weakness', 300, { amplifier:0 });
                hurtEntity.addEffect('slowness', 300, { amplifier:0 });
            break;
            case 'punch':
                hurtEntity.applyDamage(5,{cause:EntityDamageCause.suicide});
                let { x, z } = damagingEntity?.getViewDirection();
                hurtEntity.applyKnockback(x, z, 5, 0.5);
            break;
            case 'lock':
                hurtEntity.dimension.spawnParticle("felixchats:distorted_yan_lock_attack", hurtEntity.location);
                hurtEntity.addEffect('weakness', 300, { amplifier:2 });
                hurtEntity.addEffect('slowness', 300, { amplifier:5 });
            break;
            case 'baleful_brand':
                hurtEntity.dimension.spawnParticle("felixchats:distorted_yan_baleful_brand_attack", hurtEntity.location);
                hurtEntity.addEffect('wither', 300, { amplifier:2 });
            break;
        }
    }
});

world.afterEvents.entitySpawn.subscribe(({ entity }) => {
    if (entity.typeId === DistortedYanID) {
        summonHands(entity);
        entity.runCommandAsync('music play music.children_of_the_city 1 1 loop');
    }
});

world.afterEvents.entityDie.subscribe(({deadEntity, damageSource:{damagingEntity}}) => {
    if (deadEntity.typeId !== DistortedYanID) return;
    if (deadEntity.typeId === DistortedYanID) {
        deadEntity.dimension.spawnParticle("felixchats:distorted_yan_death", deadEntity.location);
        deadEntity.runCommandAsync('music stop 3');
        if(damagingEntity === undefined) return;
        damagingEntity?.runCommandAsync('execute as @a[r=32] at @s run playsound mob.felixchats.distorted_yan.death');
        system.runTimeout(() => {
            if(damagingEntity === undefined) return;
            damagingEntity?.runCommandAsync('execute as @a[r=32] at @s run playsound mob.felixchats.distorted_yan.win');
            damagingEntity?.runCommandAsync('title @a[r=32] title §l§eVictory');
            damagingEntity?.runCommandAsync('title @a[r=32] actionbar §eCurtain call for the reception of 얀샋ㄷ요무');
        }, 20);
    }
});