import { world, ItemStopUseAfterEvent, ItemStartUseAfterEvent, Player, ItemStack, ItemDurabilityComponent, EntityEquippableComponent, EquipmentSlot, EntityProjectileComponent, ProjectileHitEntityAfterEvent, ProjectileHitBlockAfterEvent } from '@minecraft/server';
import { Server } from '../../libs/Core/index.js'
import { setOnSoulFire } from '../server/setOnSoulFire.js';

export class StrikerBow {
    /** @type {Map<Player, number: time>} */
    #players = new Map();

    /** @type {Map<Player, number: chargeDuration} */
    #playerChargeTimes = new Map();

    constructor() {
        this.#initEvents();
    }

    #initEvents() {
        world.afterEvents.itemStartUse.subscribe(this.#itemStartUse.bind(this));
        world.afterEvents.itemStopUse.subscribe(this.#itemEndUse.bind(this));
        world.afterEvents.projectileHitEntity.subscribe(this.#projectileHitEntity.bind(this));
        world.afterEvents.projectileHitBlock.subscribe(this.#projectileHitBlock.bind(this));
    }

    /**
     * 
     * @param {ProjectileHitEntityAfterEvent} e 
     * @returns {void}
     */
    #projectileHitEntity(e) {
        if (!e.projectile.isValid() || e.projectile.typeId !== 'plank_waao:striker_bow_projectile') return;
        const owner = e.projectile.getComponent('projectile')?.owner;
        const hit = e.getEntityHit()?.entity;

        if (!hit || !hit.isValid() || owner && owner === hit || (hit.typeId === 'plank_waao:sculk_jaw' && hit.hasTag('plank_waao:is_tamed'))) return;

        const duration = this.#playerChargeTimes.get(e.source) / 1000;
        const damage = 17 * Math.min((duration / 0.72), 1);

        hit.applyDamage(damage);
        setOnSoulFire.setOnSoulFire(hit);

        if (hit.typeId === 'minecraft:player') {
            if (e.projectile.hasTag('plank_waao:has_played_sound')) return;
            e.projectile.addTag('plank_waao:has_played_sound');

            const owner = e.projectile.getComponent('projectile')?.owner;
            if (!owner) return;

            owner.runCommandAsync('playsound item.plank_waao.striker_bow.hit_entity @s ~~~');
        }
    }

    /**
     * 
     * @param {ProjectileHitBlockAfterEvent} e
     * @returns {void} 
     */
    #projectileHitBlock(e) {
        if (!e.projectile.isValid()) return;
        if (e.projectile.typeId !== 'plank_waao:striker_bow_projectile') return;
        if (e.projectile.hasTag('plank_waao:has_played_sound')) return;
        e.projectile.addTag('plank_waao:has_played_sound');

        e.projectile.runCommandAsync('playsound item.plank_waao.striker_bow.hit_block @a ~~~');
    }

    /**
     * @param {ItemStartUseAfterEvent} e 
     * @returns {void}
     */
    #itemStartUse(e) {
        if (e.itemStack.typeId !== 'plank_waao:striker_bow') return;
        this.#players.set(e.source, Date.now());
        e.source.runCommandAsync('playsound item.plank_waao.striker_bow.charge @s ~~~');
    }

    /**
     * @param {ItemStopUseAfterEvent} e 
     * @returns {void}
     */
    #itemEndUse(e) {
        if (e.source.getComponent('equippable').getEquipment(EquipmentSlot.Mainhand)?.typeId !== 'plank_waao:striker_bow') return;
        if (!this.#players.has(e.source)) return;

        e.source.runCommandAsync('stopsound @s item.plank_waao.striker_bow.charge');

        const duration = Date.now() - this.#players.get(e.source);
        if (duration < 180) return;

        e.source.runCommandAsync('playsound item.plank_waao.striker_bow.release @s ~~~');
        this.#playerChargeTimes.set(e.source, duration);
        this.#shoot(e.source, e.itemStack, duration);
    }

    /**
     * Shoot arrow
     * @param {Player} player - the shooter
     * @param {ItemStack} item - the bow
     * @param {number} duration - duration of pull in ms
     * @returns {void}
     */
    #shoot(player, item, duration) {
        this.#decrementDurability(player, item, 1);

        const powerAmplifier = Math.min(duration / 1000, 0.72) / 0.72;
        let power = 0.9 * powerAmplifier;
        power = power.toFixed(2);

        for (let i = 0; i < 3; i++) {
            Server.entityShootProjectile('plank_waao:striker_bow_projectile', player, power, {
                facingOffset: { x: 1, y: 0, z: 0 },
                uncertainty: 3,
                initialUncertainty: 0.2
            });
        }
    }

    /**
     * Decrements the durability of the item in hand
     * @param {Player} player 
     * @param {ItemStack} item 
     * @param {number} amount
     */
    #decrementDurability(player, item, amount) {
        const gamemode = player.getGameMode();
        if (gamemode == 'creative' || gamemode == 'spectator') return;
        /** @type {ItemDurabilityComponent} */
        let durabilityComponent = item.getComponent('durability');
        /** @type {EntityEquippableComponent} */
        const equippableComponent = player.getComponent("equippable");

        if (durabilityComponent.damage + amount >= durabilityComponent.maxDurability) {
            equippableComponent.setEquipment(EquipmentSlot.Mainhand);
            return;
        }
        durabilityComponent.damage = durabilityComponent.damage + amount;
        equippableComponent.setEquipment(EquipmentSlot.Mainhand, item);
    }
}

export const strikerBow = new StrikerBow();