/* eslint-disable max-len */
import { EntityDamageCause, EntityRideableComponent, ItemStack, system, world } from '@minecraft/server';
import CoreBlastAttack from './attacks/coreBlastAttack';
import SoulBeamAttack from './attacks/soulBeamAttack';
import SoulFetcherAttack from './attacks/soulFetcherAttack';
import EntityStatus from '../infectionStatus/main';
import { TargetController } from './targets/targetController';
import { HeadPosition } from './headAttacks/headAttack';
import ShootHeadAttack from './headAttacks/shootHeadAttack';
import FireBreathAttack from './headAttacks/fireBreathAttack';
import WBVector3 from '../util/wbVector3';
import SpawnWitherSkeletonsAttack from './headAttacks/spawnWitherSkeletonsAttack';
import { getBlockSafe, isBlockSolid, translate } from '../util/utils';
const WITHERBOSS_ATTACKS = [
    CoreBlastAttack.getAttackRegistry(),
    SoulBeamAttack.getAttackRegistry(),
    SoulFetcherAttack.getAttackRegistry()
];
export default class Witherboss {
    constructor(entity, newBoss) {
        // If the boss is in the summoning phase
        this.summoning = false;
        // If the boss is in the dying phase
        this.dying = false;
        // The amount of ticks until the next idle effect should play
        this.idleSoundEffectTicks = 100;
        // Map of players with the starting tick of their proximity to the wither boss
        this.playerProximityMap = new Map();
        // Initialize the wither boss
        this.entity = entity;
        this.targetController = TargetController.get(this.entity);
        this.headAttacks = [
            new SpawnWitherSkeletonsAttack(this, HeadPosition.LEFT),
            new FireBreathAttack(this, HeadPosition.CENTER),
            new ShootHeadAttack(this, HeadPosition.RIGHT)
        ];
        if (!newBoss) {
            this.resumeHeadAttacks();
            this.spawnHitboxes();
        }
        // Start infection
        const infectionStatus = new EntityStatus(entity);
        infectionStatus.setRemainingInfectionTime(-1, true);
        infectionStatus.setInfectionLevel(25);
        // If this is a new boss, trigger the spawning animation
        if (newBoss) {
            this.summoning = true;
            this.setAnimationState('summoning');
            system.runTimeout(() => this.setAnimationState('summoning'), 1);
            this.entity.triggerEvent('twb_wisu:make_stationary_no_rotation');
            system.runTimeout(() => {
                this.summoning = false;
                if (this.entity.isValid()) {
                    this.resumeHeadAttacks();
                    this.spawnHitboxes();
                    this.setIdleAnimationState();
                    this.entity.triggerEvent('twb_wisu:make_moving');
                }
            }, 225);
        }
        // If the player left/reloaded during death, restart the death sequence
        if (this.getAnimationState() === 'death') {
            this.remove(true);
        }
    }
    getEntity() {
        return this.entity.isValid() ? this.entity : undefined;
    }
    isSummoning() {
        return this.summoning;
    }
    isDying() {
        return this.dying;
    }
    getTargetController() {
        return this.targetController;
    }
    getAnimationState() {
        return this.getEntity()?.getProperty('twb_wisu:anim_state');
    }
    setAnimationState(state) {
        this.getEntity()?.setProperty('twb_wisu:anim_state', state);
    }
    setIdleAnimationState() {
        this.setAnimationState('idle');
    }
    getNumNearbyPlayers() {
        return this.entity.dimension.getEntities({
            location: this.entity.location,
            maxDistance: 200,
            type: 'minecraft:player',
        }).length;
    }
    resumeHeadAttacks() {
        this.headAttacks.forEach((attack) => attack.resumeHeadAttackMode());
    }
    spawnHitboxes() {
        const rideable = this.entity.getComponent(EntityRideableComponent.componentId);
        const numMissingRiders = rideable.seatCount - rideable.getRiders().length;
        for (let i = 0; i < numMissingRiders; i++) {
            const hitbox = this.entity.dimension.spawnEntity('twb_wisu:witherboss_hitbox', this.entity.location);
            rideable.addRider(hitbox);
            hitbox.setDynamicProperty('owner', this.entity.id);
            hitbox.addEffect('regeneration', 20000000, {
                amplifier: 255,
                showParticles: false,
            });
        }
        const riders = rideable.getRiders();
        riders[0].triggerEvent('twb_wisu:hitbox_center');
        riders[1].triggerEvent('twb_wisu:hitbox_head');
        riders[2].triggerEvent('twb_wisu:hitbox_head');
        riders[3].triggerEvent('twb_wisu:hitbox_head');
        riders[4].triggerEvent('twb_wisu:hitbox_tail');
    }
    tickHeadAttacks() {
        this.headAttacks.forEach((attack) => attack.tickHeadAttack());
    }
    // Returns true if any head attack is currently being performed
    tickHeadAttacksWhenActive() {
        let result = false;
        for (const attack of this.headAttacks) {
            result = result || attack.tickHeadAttackWhenActive();
        }
        return result;
    }
    pauseHeadAttacks() {
        this.headAttacks.forEach((attack) => attack.pauseHeadAttackMode());
    }
    /**
     * Ticks the wither boss
     * @returns True if the boss is still valid, false if it should be deleted
     */
    tick() {
        // If the body is no longer valid, return false
        if (!this.entity.isValid()) {
            this.remove(false);
            return false;
        }
        // If the entity is summoning or dying, don't tick anything
        if (this.summoning || this.dying) {
            return true;
        }
        // Handle movement logic (if not attacking)
        if (!this.currentAttack) {
            this.updateMovement();
        }
        // Handle attack logic
        this.updateAttack();
        /// Handle proximity effect logic
        // 1. Delete all players that are no longer valid
        for (const player of this.playerProximityMap.keys()) {
            if (!player.isValid() || WBVector3.distance(player.location, this.entity.location) > Witherboss.WITHER_EFFECT_PROXIMITY_DISTANCE) {
                this.playerProximityMap.delete(player);
            }
        }
        // 2. Add all players that are close to the wither boss that are not already in the map
        world.getPlayers().forEach((player) => {
            if (!this.playerProximityMap.has(player)
                && WBVector3.distance(player.location, this.entity.location) <= Witherboss.WITHER_EFFECT_PROXIMITY_DISTANCE) {
                this.playerProximityMap.set(player, system.currentTick);
            }
        });
        // 3. Update the infection status of all players in the map
        this.playerProximityMap.forEach((tick, player) => {
            const proximityTicks = system.currentTick - tick;
            if (proximityTicks >= Witherboss.WITHER_EFFECT_PROXIMITY_TICKS) {
                player.addEffect('wither', 40, {
                    amplifier: 2
                });
            }
        });
        // Destroy blocks around itself
        this.entity.runCommandAsync('/execute as @s positioned ~ ~5 ~ run fill ~-2 ~-2 ~-2 ~2 ~2 ~2 air destroy');
        // Idle sound effect logic
        if (this.idleSoundEffectTicks <= 0) {
            this.entity.dimension.playSound('twb_wisu:witherboss.idle', this.entity.location, { volume: 16 });
            this.idleSoundEffectTicks = 80 + Math.random() * 50;
        }
        else {
            this.idleSoundEffectTicks--;
        }
        // Return true as the boss is still valid
        return true;
    }
    /**
     * Damages the wither boss
     * @param damageAmount The amount of damage to deal
     */
    damage(damageAmount, damageSource) {
        // If the boss is summoning or dying, don't apply damage
        if (this.summoning || this.dying || !this.getEntity()) {
            return;
        }
        // Get damaging entity information
        const { damagingEntity } = damageSource;
        const damagingPlayer = damagingEntity && damagingEntity.typeId === 'minecraft:player' ? damagingEntity : undefined;
        // Get the current shield health
        const shieldHealth = this.entity.getProperty('twb_wisu:shield_health');
        if (shieldHealth && shieldHealth > 0) {
            // If the shield is active, apply damage to the shield instead
            // If the damage type was a projectile, do not apply damage to the shield
            if (damageSource.cause === EntityDamageCause.projectile) {
                if (damagingPlayer) {
                    damagingPlayer.onScreenDisplay.setActionBar(translate('entity.twb_wisu:witherboss.info.shield_blocked'));
                }
                return;
            }
            const newShieldHealth = Math.max(0, shieldHealth - damageAmount);
            this.entity.setProperty('twb_wisu:shield_health', newShieldHealth);
            // Play damage shield sound
            this.entity.dimension.playSound('twb_wisu:witherboss.hit.shield', this.entity.location, { volume: 16 });
            return;
        }
        // If no shield was active, apply normal damage
        const healthComponent = this.entity.getComponent('minecraft:health');
        const currentHealth = healthComponent.currentValue;
        // Play damage sound
        this.entity.dimension.playSound('twb_wisu:witherboss.hit', this.entity.location, { volume: 16 });
        if (damageAmount < currentHealth) {
            // If the damage is less than the current health, apply the damage
            this.entity.applyDamage(damageAmount, { cause: EntityDamageCause.entityAttack });
            // If the boss has lost another 20% of its health, activate the shield
            const fraction = 100 / 20;
            const maxHealth = healthComponent.effectiveMax;
            const nextHealthThreshold = Math.floor(currentHealth / (maxHealth / fraction)) * (maxHealth / fraction);
            if (currentHealth > nextHealthThreshold && currentHealth - damageAmount <= nextHealthThreshold) {
                this.entity.setProperty('twb_wisu:shield_health', 50);
            }
        }
        else {
            // If the damage is greater than the current health, the wither dies
            if (currentHealth > 0.001) {
                // Leave the wither with 1 health so the boss bar renders properly
                this.entity.applyDamage(currentHealth - 0.001, { cause: EntityDamageCause.entityAttack });
            }
            // Only 'kill' the boss if it is not currently in a special attack
            if (!this.currentAttack) {
                this.remove(true);
            }
        }
    }
    /**
     * Removes the wither boss from the world
     * @param death Whether the boss is being removed due to death
     */
    remove(death) {
        if (this.dying) {
            return;
        }
        this.dying = true;
        this.headAttacks.forEach((attack) => attack.stopHeadAttack());
        // If the entity is still valid, trigger the death animation
        if (this.entity.isValid()) {
            this.entity.triggerEvent('twb_wisu:make_stationary_no_rotation');
            if (death) {
                // Start the death animation
                this.setAnimationState('death');
                // Schedule death VFX animation
                system.runTimeout(() => {
                    if (this.entity.isValid()) {
                        this.entity.dimension.spawnEntity('twb_wisu:witherboss_dying_vfx', new WBVector3(0, 13.8, 0).add(this.entity.location));
                    }
                }, 160);
                // Schedule entity removal
                system.runTimeout(() => {
                    // Remove entity
                    if (this.entity.isValid()) {
                        this.entity.remove();
                    }
                }, 182);
                // Schedule XP/item drop
                const dropDimension = this.entity.dimension;
                const dropLocation = new WBVector3(0, 14, 0).add(this.entity.location);
                system.runTimeout(() => {
                    const items = [
                        new ItemStack('twb_wisu:withered_boots'),
                        new ItemStack('twb_wisu:withered_leggings'),
                        new ItemStack('twb_wisu:withered_chestplate'),
                        new ItemStack('twb_wisu:withered_helmet'),
                        new ItemStack('twb_wisu:withered_sword'),
                        new ItemStack('twb_wisu:withered_staff'),
                    ];
                    items.slice(0, 4).forEach((item) => {
                        item.setLore([
                            '§r§7Wear the full set to receive:',
                            '§r§bPush enemies away after taking multiple hits'
                        ]);
                    });
                    items[4].setLore(['§r§7Hold INTERACT to cast and steer a skull', '§r§7projectile']);
                    items[5].setLore(['§r§7Hold INTERACT to cast a powerful energy beam']);
                    // Drop items
                    items.forEach((item) => dropDimension.spawnItem(item, dropLocation));
                    // Drop XP
                    dropDimension.spawnEntity('twb_wisu:witherboss_experience', dropLocation).kill();
                }, 210);
                // Play sound
                this.entity.dimension.playSound('twb_wisu:witherboss.death', this.entity.location, {
                    volume: 16
                });
            }
            else {
                this.entity.remove();
            }
        }
    }
    updateMovement() {
        const entity = this.getEntity();
        if (!entity)
            return;
        // Get height above ground
        const height = entity.location.y;
        let block = getBlockSafe(entity.dimension, entity.location);
        if (!block)
            return;
        while (!isBlockSolid(block)) {
            block = block.below();
            if (!block)
                return;
        }
        const heightAboveGround = height - block.location.y;
        // Get height above target
        const currentTarget = this.targetController.getTarget();
        const heightAboveTarget = currentTarget ? entity.location.y - currentTarget.location.y : 0;
        // If the wither is too high above the ground, move it down
        if (heightAboveGround > 3 && heightAboveTarget > 3) {
            entity.applyImpulse(new WBVector3(0, -0.03, 0));
        }
        // const decendingStr = decending ? '§eDec.' : '';
        // world.getAllPlayers().forEach((player) => {
        //   player.onScreenDisplay.setActionBar(`§c[DBG] §fAG: ${heightAboveGround.toFixed(1)} - AT: ${heightAboveTarget.toFixed(1)} - ${decendingStr}`);
        // });
    }
    updateAttack() {
        // Always tick the head attacks
        this.tickHeadAttacks();
        // If a current attack is active, tick or end it
        if (this.currentAttack) {
            if (this.currentAttack.endAttack()) {
                this.currentAttack = undefined;
                this.resumeHeadAttacks();
            }
            else {
                this.currentAttack.tickAttack();
            }
            return;
        }
        // If there are no current attacks, tick the head attacks 'when active'
        const headAttackActive = this.tickHeadAttacksWhenActive();
        if (headAttackActive) {
            // If a head attack is currently active, do not start a special attack
            return;
        }
        // If there is no current attack, try to start a new one
        const possibleAttacks = WITHERBOSS_ATTACKS.filter((att) => att.canStartAttack(this));
        // const possibleAttacks: AttackRegistry[] = [];
        let random = Math.random();
        for (const attack of possibleAttacks) {
            // If the random number is less than the attack chance, start the attack
            if (random < attack.attackChance) {
                this.pauseHeadAttacks();
                this.currentAttack = attack.createNewAttack(this);
                this.currentAttack.onStart();
                return;
            }
            // Otherwise, subtract the attack chance for the next attack
            random -= attack.attackChance;
        }
    }
}
// Number of ticks that a player has to be close to the boss in order for the wither effect to be applied
Witherboss.WITHER_EFFECT_PROXIMITY_TICKS = 60;
// Distance (in blocks) that a player has to be from the wither to be considered 'close'
Witherboss.WITHER_EFFECT_PROXIMITY_DISTANCE = 12;
