import { BlockPermutation, EquipmentSlot, ItemStack, LocationInUnloadedChunkError, LocationOutOfWorldBoundariesError, MolangVariableMap, system, world } from '@minecraft/server';
import ScriptingModule from '../scriptingModule';
import WBVector3 from '../util/wbVector3';
import WBVector2 from '../util/wbVector2';
import { soulBeamUpdate } from '../witherboss/attacks/soulBeamAttack';
import { IMMUNE_PLAYER_ATTACK_ENTITIES } from '../witherboss/witherbossSettings';
import { addItemToInventory, decreaseItemAmountInHand, getItemInSlot, setItemInSlot, translate } from '../util/utils';
import BookModule from '../book/bookModule';
import createWitherWraithSkullHitEffect from '../effects/witherWraithSkullHit';
export default class ItemsModule extends ScriptingModule {
    constructor() {
        super('ItemsModule');
        this.playerToBeam = new Map();
        this.playerToSkull = new Map();
        this.activeSkulls = new Map();
        this.playerCampfireInteraction = new Set();
    }
    onInitialize() {
        world.afterEvents.itemStartUse.subscribe((e) => {
            if (!e.itemStack)
                return;
            if (e.itemStack.typeId === 'twb_wisu:withered_staff')
                this.startUsingStaff(e.source);
            else if (e.itemStack.typeId === 'twb_wisu:withered_sword')
                this.startUsingBlade(e.source);
        });
        world.afterEvents.itemStopUse.subscribe((e) => {
            if (!e.itemStack)
                return;
            if (e.itemStack.typeId === 'twb_wisu:withered_staff')
                this.stopUsingStaff(e.source);
            else if (e.itemStack.typeId === 'twb_wisu:withered_sword')
                this.stopUsingBlade(e.source);
        });
        world.beforeEvents.itemUseOn.subscribe((e) => {
            if (e.itemStack.typeId !== 'minecraft:nether_star')
                return;
            if (this.playerCampfireInteraction.has(e.source))
                return;
            let isCampfire = false;
            if (e.block.permutation.matches('minecraft:campfire')) {
                system.run(() => e.block.setPermutation(BlockPermutation.resolve('minecraft:soul_campfire')));
                isCampfire = true;
            }
            else if (e.block.permutation.matches('minecraft:soul_campfire')) {
                isCampfire = true;
            }
            if (isCampfire) {
                e.cancel = true;
                this.playerCampfireInteraction.add(e.source);
                system.runTimeout(() => this.playerCampfireInteraction.delete(e.source), 10);
                system.run(() => {
                    const witherCoreItem = new ItemStack('twb_wisu:withered_core');
                    if (e.itemStack.amount === 1) {
                        setItemInSlot(e.source, EquipmentSlot.Mainhand, witherCoreItem);
                    }
                    else if (decreaseItemAmountInHand(e.source)) {
                        addItemToInventory(e.source, witherCoreItem);
                    }
                    e.source.dimension.playSound('mob.ghast.fireball', e.block, { pitch: 0.6 });
                    e.source.dimension.playSound('twb_wisu:witherboss.attack.skull_projectile.hit', e.block, { pitch: 1.3 });
                    // Spawn the particle
                    for (let i = 0; i < 10; i++) {
                        const variableMap = new MolangVariableMap();
                        variableMap.setVector3('direction', {
                            x: Math.random() * 2 - 1,
                            y: Math.random() * 2 - 1,
                            z: Math.random() * 2 - 1
                        });
                        e.source.dimension.spawnParticle('minecraft:soul_particle', WBVector3.fromMC(e.block.location).add({
                            x: Math.random(),
                            y: 0.2 + Math.random(),
                            z: Math.random()
                        }), variableMap);
                    }
                });
            }
        });
        world.afterEvents.playerSpawn.subscribe((e) => {
            const hasBook = !!e.player.getDynamicProperty('hasBook');
            const hasCore = !!world.getDynamicProperty('hasCore');
            if (!hasCore) {
                addItemToInventory(e.player, new ItemStack('twb_wisu:withered_core'));
                world.setDynamicProperty('hasCore', true);
            }
            if (!hasBook) {
                addItemToInventory(e.player, BookModule.getBookItemStack());
                e.player.setDynamicProperty('hasBook', true);
                const message = hasCore
                    ? translate('twb_wisu.on_join.book_only')
                    : translate('twb_wisu.on_join.book_and_core');
                system.runTimeout(() => e.player.sendMessage(message), 100);
            }
        });
        world.afterEvents.entityDie.subscribe((e) => {
            const player = e.deadEntity;
            if (this.playerToBeam.has(player)) {
                this.stopUsingStaff(player);
            }
        }, { entityTypes: ['minecraft:player'] });
    }
    startUsingStaff(player) {
        player.startItemCooldown('withered_staff', 0);
        player.dimension.playSound('twb_wisu:player.withered_staff.soul_beam_start', player.location, { volume: 16 });
        player.playAnimation('animation.twb_wisu.player.cancel_item_use_anim', {
            stopExpression: '!q.is_using_item',
            blendOutTime: 0.5,
            controller: 'item',
        });
        player.playAnimation('animation.twb_wisu.player.withered_staff.use', {
            stopExpression: '!q.is_using_item',
            blendOutTime: 0.3,
            controller: 'weapon',
        });
        const beam = player.dimension.spawnEntity('twb_wisu:soul_beam', player.location);
        this.playerToBeam.set(player, { beam, map: new Map() });
    }
    stopUsingStaff(player) {
        player.startItemCooldown('withered_staff', 100);
        player.dimension.playSound('twb_wisu:player.withered_staff.soul_beam_end', player.location, { volume: 16 });
        player.runCommand('stopsound @s twb_wisu:player.withered_staff.soul_beam_start');
        const beam = this.playerToBeam.get(player);
        if (beam) {
            this.playerToBeam.delete(player);
            beam.beam.playAnimation('deactivate', {
                controller: 'activate_controller',
                nextState: 'activate',
                stopExpression: '0',
                blendOutTime: 0,
            });
            system.runTimeout(() => beam.beam.triggerEvent('twb_wisu:despawn'), 20);
        }
    }
    startUsingBlade(player) {
        const headPos = player.getHeadLocation();
        player.startItemCooldown('withered_sword', 0);
        player.dimension.playSound('twb_wisu:witherboss.attack.skull_projectile.shoot', headPos, { volume: 16 });
        player.playAnimation('animation.twb_wisu.player.cancel_item_use_anim', {
            stopExpression: '!q.is_using_item',
            blendOutTime: 0.5,
            controller: 'item',
        });
        player.playAnimation('animation.twb_wisu.player.withered_sword.use', {
            stopExpression: '!q.is_using_item',
            blendOutTime: 0.3,
            controller: 'weapon',
        });
        const rotation = WBVector2.fromMC(player.getRotation());
        const skull = player.dimension.spawnEntity('twb_wisu:witherboss_skull_projectile', headPos);
        skull.setRotation(rotation);
        this.playerToSkull.set(player, skull);
        this.activeSkulls.set(skull, { player, rotation });
    }
    stopUsingBlade(player) {
        player.startItemCooldown('withered_sword', 100);
        const skull = this.playerToSkull.get(player);
        if (skull) {
            this.playerToSkull.delete(player);
        }
    }
    onTick() {
        world.getAllPlayers().forEach((player) => {
            const item = getItemInSlot(player);
            if (item?.typeId === 'twb_wisu:withered_sword') {
                player.playAnimation('animation.twb_wisu.player.withered_sword.attack.rotations', {
                    stopExpression: "!q.is_item_name_any('slot.weapon.mainhand', 'twb_wisu:withered_sword')"
                });
            }
        });
        this.playerToBeam.forEach((beam, player) => {
            const rotation = WBVector2.fromMC(player.getRotation());
            const beamLocation = new WBVector3(0.0, player.isSneaking ? 1.27 : 1.62, 0.0)
                .add(player.location)
                .add(new WBVector3(-0.08, -0.25, 0.5)
                .rotate(rotation));
            beam.beam.teleport(beamLocation.add(WBVector3.fromMC(player.getVelocity()).multiply(3)));
            beam.beam.setProperty('twb_wisu:target_x_rotation', rotation.x);
            beam.beam.playAnimation('player_scale', { stopExpression: '0' });
            beam.beam.setProperty('twb_wisu:target_y_rotation', rotation.y);
            soulBeamUpdate(beam.beam, player.getViewDirection(), {
                maxRange: 50,
                ticksToBreak: 3,
                damage: 4,
                blockHitMap: beam.map,
                owner: player,
                entityRaycastOptions: {
                    excludeTypes: IMMUNE_PLAYER_ATTACK_ENTITIES
                }
            });
        });
        this.playerToSkull.forEach((skull, player) => {
            if (!skull.isValid()) {
                this.playerToSkull.delete(player);
                if (player.isValid()) {
                    player.startItemCooldown('withered_sword', 100);
                }
                return;
            }
            const skullLocation = WBVector3.fromMC(skull.location);
            // Find t by projecting (`skullLocation` - `origin`) onto `direction`
            const origin = WBVector3.fromMC(player.getHeadLocation());
            const direction = WBVector3.fromMC(WBVector2.fromMC(player.getRotation()).toDirection());
            const t = skullLocation.subtract(origin).dot(direction) / direction.dot(direction);
            const nearestPointOnLine = origin.add(direction.multiply(t));
            const distanceToLine = WBVector3.distance(nearestPointOnLine, skullLocation);
            // t + Math.max(5 - 5 * Math.sqrt(x / 10), 2)
            const targetT = t + Math.max(5 - 5 * Math.sqrt(distanceToLine / 10), 2);
            const targetLocation = origin.add(direction.multiply(targetT));
            const targetRotation = WBVector2.fromMC(targetLocation.subtract(skullLocation).getRotation());
            const currentRotation = WBVector2.fromMC(skull.getRotation());
            const rotDiff = targetRotation.subtract(currentRotation);
            if (rotDiff.y > 180)
                rotDiff.y -= 360;
            else if (rotDiff.y < -180)
                rotDiff.y += 360;
            // Allow no more than 15 and 5 degrees of rotation for each respective axis per tick
            const rotDelta = new WBVector2(Math.min(Math.max(rotDiff.x, -15), 15), Math.min(Math.max(rotDiff.y, -5), 5));
            const newRotation = currentRotation.add(rotDelta);
            skull.setRotation(newRotation);
            this.activeSkulls.set(skull, { player, rotation: newRotation });
        });
        this.activeSkulls.forEach(({ player, rotation }, skull) => {
            if (!skull.isValid()) {
                this.activeSkulls.delete(skull);
                return;
            }
            // Move projectile forward
            const newLocation = new WBVector3(0, 0, 0.8).rotate(rotation).add(skull.location);
            skull.teleport(newLocation);
            const skullLocation = WBVector3.fromMC(skull.location);
            // Check if the projectile hit the target
            const targets = skull.dimension.getEntities({
                location: skullLocation.add({ x: -0.5, y: -0.5, z: -0.5 }),
                volume: { x: 0.0, y: 0.0, z: 0.0 },
                excludeTypes: IMMUNE_PLAYER_ATTACK_ENTITIES,
            });
            let successfulHit = false;
            for (const target of targets) {
                if (target.id !== player.id) {
                    try {
                        target.applyDamage(20, {
                            damagingEntity: player,
                            damagingProjectile: skull
                        });
                        target.addEffect('wither', 20 * 6);
                        skull.dimension.createExplosion(skullLocation.add({ x: 0, y: 0.5, z: 0 }), 1.5, {
                            breaksBlocks: true,
                            causesFire: false,
                            source: skull
                        });
                        successfulHit = true;
                    }
                    catch {
                        // Ignore. Probably not a mob.
                    }
                }
            }
            if (!successfulHit) {
                // Check if the projectile hit a block
                try {
                    const block = skull.dimension.getBlock(skull.location);
                    const isSolid = !!block?.dimension.getBlockFromRay(block.location, { x: 0, y: 1, z: 0 }, { includePassableBlocks: false, maxDistance: 1 });
                    if (block && isSolid) {
                        skull.dimension.createExplosion(skull.location, 1.5, {
                            breaksBlocks: true,
                            causesFire: false,
                            source: skull
                        });
                        successfulHit = true;
                    }
                }
                catch (e) {
                    // Ignore block access exceptions
                    // eslint-disable-next-line max-len
                    if (!(e instanceof LocationInUnloadedChunkError || e instanceof LocationOutOfWorldBoundariesError)) {
                        throw e;
                    }
                }
            }
            if (successfulHit) {
                // Spawn the particle & play sounds
                createWitherWraithSkullHitEffect({
                    ...skull.location,
                    dimension: skull.dimension
                });
                // Remove the projectile
                skull.remove();
                this.activeSkulls.delete(skull);
            }
        });
    }
}
