import { EntityComponentTypes, EntityDamageCause, EquipmentSlot, system, world, } from '@minecraft/server';
import ScriptingModule from '../scriptingModule';
import { setItemInSlot } from '../util/utils';
import { createLocationShockWave } from '../witherboss/attacks/shockWave';
import { IMMUNE_PLAYER_ATTACK_ENTITIES } from '../witherboss/witherbossSettings';
import WBVector3 from '../util/wbVector3';
/**
 * Module that manages custom armor abilities.
 * All custom armor must have exactly two item tags; "twb_wisu:armor", and "twb_wisu:<set_name>".
 */
export default class ArmorModule extends ScriptingModule {
    constructor() {
        super('ArmorModule');
        // An array containing all equipment slots of a player that can hold armor
        this.ARMOR_SLOTS = [
            EquipmentSlot.Feet,
            EquipmentSlot.Legs,
            EquipmentSlot.Chest,
            EquipmentSlot.Head,
        ];
        this.ARMOR_ABILITY_DESCRIPTIONS = new Map([
            ['twb_wisu:wither', '§r§bPush enemies away after taking multiple hits']
        ]);
        /**
         * How much to increment the wither armor hurt value by on hit. The total value decreases by one
         * each tick.
         */
        this.WITHER_ARMOR_EXPLODE_INCREMENT = 60;
        /** The threshold beyond which the wither armor will emit an explosion */
        this.WITHER_ARMOR_EXPLODE_THRESHOLD = 130;
        /** How much hurt value needs to be accumulated for each different armor level */
        this.WITHER_ARMOR_LEVEL_VALUE = 22;
        /** The maximum charge level wither armor can have */
        this.WITHER_ARMOR_MAX_LEVEL = 4;
        this.playerHurtData = new Map();
    }
    onInitialize() {
        // Message player if a new armor set is equipped
        ArmorModule.onPlayerEquipArmorSet((player, armorSet) => {
            player.onScreenDisplay.setActionBar({
                rawtext: [{
                        translate: 'twb_wisu.hint.set_bonus.equipped',
                        with: {
                            rawtext: [{
                                    translate: `twb_wisu.armor.${armorSet}`
                                }]
                        }
                    }]
            });
        });
        // Message player if an armor stand is unequipped
        ArmorModule.onPlayerUnequipArmorSet((player, armorSet) => {
            if (!ArmorModule.getEquippedArmorSet(player)) {
                // Only message player if no new armor was equipped
                player.onScreenDisplay.setActionBar({
                    rawtext: [{
                            translate: 'twb_wisu.hint.set_bonus.unequipped',
                            with: {
                                rawtext: [{
                                        translate: `twb_wisu.armor.${armorSet}`
                                    }]
                            }
                        }]
                });
            }
        });
        world.afterEvents.entityHurt.subscribe((e) => this.playerTakeDamage(e), { entityTypes: ['minecraft:player'] });
    }
    onSecond() {
        // Equip armor sets
        world.getAllPlayers().forEach((player) => {
            const equipment = player.getComponent(EntityComponentTypes.Equippable);
            let item;
            let equippedSet;
            for (const slot of this.ARMOR_SLOTS) {
                item = equipment.getEquipment(slot);
                // Not wearing custom set
                if (!item || !item.hasTag('twb_wisu:armor')) {
                    return this.playerEquipArmorSet(player, undefined);
                }
                // All armor should have exactly two tags; "twb_wisu:armor", and "twb_wisu:<set_name>".
                // Here we grab the tag that contains the set name.
                // Set the lore
                const armorTag = item.getTags()[1];
                if (item.getLore().length === 0) {
                    const abilityDescription = this.ARMOR_ABILITY_DESCRIPTIONS.get(armorTag);
                    if (abilityDescription) {
                        item.setLore(['§r§7Wear the full set to receive:', abilityDescription]);
                        setItemInSlot(player, slot, item);
                    }
                }
                if (!equippedSet) {
                    equippedSet = armorTag;
                }
                else if (equippedSet !== armorTag) {
                    // Wearing a combination of armor pieces
                    return this.playerEquipArmorSet(player, undefined);
                }
            }
            return this.playerEquipArmorSet(player, equippedSet);
        });
    }
    playerEquipArmorSet(player, armorSet) {
        const oldArmorSet = ArmorModule.playerEquippedArmorSets.get(player);
        if (armorSet === oldArmorSet)
            return;
        if (armorSet) {
            ArmorModule.playerEquippedArmorSets.set(player, armorSet);
        }
        else {
            ArmorModule.playerEquippedArmorSets.delete(player);
        }
        if (oldArmorSet)
            ArmorModule.playerArmorUnequipEvents.forEach((fn) => fn(player, oldArmorSet));
        if (armorSet)
            ArmorModule.playerArmorEquipEvents.forEach((fn) => fn(player, armorSet));
    }
    /**
     * Get the custom armor set a player is currently wearing
     * @param player A player
     * @returns The name of the armor set, if worn
     */
    static getEquippedArmorSet(player) {
        return this.playerEquippedArmorSets.get(player);
    }
    /**
     * Register a function to run when a player equips a custom armor set
     * @param fn A callback
     */
    static onPlayerEquipArmorSet(fn) {
        this.playerArmorEquipEvents.push(fn);
    }
    /**
     * Register a function to run when a player unequips a custom armor set
     * @param fn A callback
     */
    static onPlayerUnequipArmorSet(fn) {
        this.playerArmorUnequipEvents.push(fn);
    }
    playerTakeDamage(e) {
        const player = e.hurtEntity;
        if (!e.damageSource.damagingEntity)
            return;
        if (ArmorModule.getEquippedArmorSet(player) !== 'twb_wisu:wither')
            return;
        const amount = this.playerAddHurtAmount(player, this.WITHER_ARMOR_EXPLODE_INCREMENT);
        if (amount >= this.WITHER_ARMOR_EXPLODE_THRESHOLD) {
            this.playerWitherArmorTriggerExplosion(player);
            this.playerWitherArmorSetLevel(player, 0);
        }
        else {
            const level = Math.min(Math.floor(amount / this.WITHER_ARMOR_LEVEL_VALUE), this.WITHER_ARMOR_MAX_LEVEL);
            this.playerWitherArmorSetLevel(player, level);
        }
    }
    getPlayerHurtAmount(player) {
        const hurtData = this.playerHurtData.get(player);
        if (!hurtData)
            return 0;
        return Math.max(hurtData.amount - (system.currentTick - hurtData.time), 0);
    }
    playerAddHurtAmount(player, amount) {
        const hurtAmount = this.getPlayerHurtAmount(player);
        const newAmount = hurtAmount + amount;
        this.playerHurtData.set(player, { time: system.currentTick, amount: newAmount });
        return newAmount;
    }
    playerWitherArmorTriggerExplosion(player) {
        // Spawn knockback wave
        createLocationShockWave(player.dimension, player.location, {
            entity: player,
            size: 6,
            startRange: 0.5,
            speed: 1,
            knockback: 3,
            damage: 6,
            damageOptions: {
                cause: EntityDamageCause.entityAttack,
                damagingEntity: player
            },
            entityHitFunction: (e) => !IMMUNE_PLAYER_ATTACK_ENTITIES.includes(e.typeId),
            knockbackRangeFunction: (kb) => [kb, kb / 6],
            // edgeParticles: ['twb_wisu:cloud_soul'],
            // edgeParticlesHeight: 2
        });
        // Spawn VFX
        player.dimension
            .spawnEntity('twb_wisu:core_blast', WBVector3.fromMC(player.location).add({ x: 0, y: 0.8, z: 0 }))
            .triggerEvent('twb_wisu:small_core_blast');
        // Play sound
        player.dimension.playSound('twb_wisu:witherboss.attack.core_blast.cast', player.location, { volume: 1 });
        // Reset amount
        this.playerHurtData.delete(player);
    }
    playerWitherArmorSetLevel(player, level) {
        player.playAnimation(`animation.twb_wisu.withered_armor.set_level_${level}`, {
            controller: 'twb_wisu.withered_armor'
        });
    }
}
// This map stores the armor set a player has equipped, if any
ArmorModule.playerEquippedArmorSets = new Map();
ArmorModule.playerArmorEquipEvents = [];
ArmorModule.playerArmorUnequipEvents = [];
