import { EntityDamageCause, MolangVariableMap, } from '@minecraft/server';
import WitherbossAttack from './witherbossAttack';
import WBVector3 from '../../util/wbVector3';
import WBVector2 from '../../util/wbVector2';
import { SHORT_RANGE_SQ, LONG_RANGE_SQ, IMMUNE_ATTACK_ENTITIES } from '../witherbossSettings';
import { directionToNormal } from '../../util/utils';
export function soulBeamUpdate(beam, direction, options) {
    if (!beam.isValid())
        return;
    const { dimension, location } = beam;
    let maxEntityHitRange = options?.maxRange || 50;
    const blockHit = dimension.getBlockFromRay(location, direction);
    if (blockHit) {
        const hitLocation = WBVector3.fromMC(blockHit.block).add(blockHit.faceLocation);
        const hitDistance = hitLocation.subtract(location).length();
        try {
            beam.setProperty('twb_wisu:beam_length', hitDistance + 0.5);
        }
        catch {
            console.warn(`Failed to set beam length: ${hitDistance + 0.5}`);
        }
        maxEntityHitRange = hitDistance;
        // Spawn particle
        const normal = directionToNormal(blockHit.face);
        const molangMap = new MolangVariableMap();
        molangMap.setVector3('direction', normal);
        try {
            dimension.spawnParticle('twb_wisu:soul_beam_impact', hitLocation, molangMap);
            // Destroy blocks
            if (options?.ticksToBreak) {
                const { block } = blockHit;
                if (!block.permutation.matches('minecraft:bedrock')) {
                    const blockKey = `${block.x} ${block.y} ${block.z}`;
                    if (options.ticksToBreak === 1 || options.blockHitMap === undefined) {
                        dimension.runCommand(`/setblock ${blockKey} air destroy`);
                    }
                    else {
                        const blockHitCount = (options.blockHitMap.get(blockKey) ?? 0) + 1;
                        options.blockHitMap.set(blockKey, blockHitCount);
                        if (blockHitCount >= options.ticksToBreak) {
                            dimension.runCommand(`/setblock ${blockKey} air destroy`);
                        }
                    }
                }
            }
        }
        catch (e) {
            // Ignore location out of bounds errors
        }
    }
    const entitiesHit = dimension.getEntitiesFromRay(location, direction, {
        ...options?.entityRaycastOptions,
        maxDistance: maxEntityHitRange,
    });
    if (options?.damage) {
        const canHitEntity = options?.canHitEntity ?? (() => true);
        for (const hit of entitiesHit) {
            if (hit.entity !== options?.owner && canHitEntity(hit.entity)) {
                hit.entity.applyDamage(options?.damage, {
                    damagingProjectile: beam,
                    damagingEntity: options?.owner ?? beam,
                    cause: EntityDamageCause.sonicBoom,
                });
                try {
                    hit.entity.addEffect('wither', 20 * 6);
                }
                catch (e) {
                    // Effects do not apply to this entity
                }
            }
        }
    }
}
export default class SoulBeamAttack extends WitherbossAttack {
    constructor(wither, trg) {
        super(wither);
        this.trg = trg;
        this.tickCount = 0;
        this.blockHitCount = new Map();
        this.beamFocalPoint = new WBVector3(0, 0, 0);
    }
    getTarget() {
        return this.trg.isValid() ? this.trg : undefined;
    }
    onStart() {
        const entity = this.wither.getEntity();
        const target = this.getTarget();
        if (!entity || !target) {
            return;
        }
        entity.triggerEvent('twb_wisu:make_stationary');
        // Start wither animation
        this.wither.setAnimationState('attack_soul_beam');
        // Play the sound
        entity.dimension.playSound('twb_wisu:witherboss.attack.soul_beam', entity.location, { volume: 16 });
        // Start beam
        const sourceLocation = this.getSourceLocation();
        if (!sourceLocation)
            return;
        this.beam = target.dimension.spawnEntity('twb_wisu:soul_beam', sourceLocation);
        this.beamFocalPoint = WBVector3.fromMC(target.location);
        const targetRotation = this.getIdealBeamRotation();
        if (!targetRotation)
            return;
        // this.beam.setRotation(targetRotation);
        this.beam.setProperty('twb_wisu:target_x_rotation', targetRotation.x);
        this.beam.setProperty('twb_wisu:target_y_rotation', targetRotation.y);
    }
    getSourceLocation() {
        const entity = this.wither.getEntity();
        if (!entity)
            return undefined;
        return new WBVector3(0, 3.5, 2.5)
            .rotateY(entity.getRotation().y)
            .add(entity.location);
    }
    getBeamRotation() {
        return new WBVector2((this.beam?.getProperty('twb_wisu:target_x_rotation') || 0), (this.beam?.getProperty('twb_wisu:target_y_rotation') || 0));
    }
    setBeamRotation(rotation) {
        this.beam?.setProperty('twb_wisu:target_x_rotation', rotation.x);
        this.beam?.setProperty('twb_wisu:target_y_rotation', rotation.y);
    }
    getIdealBeamRotation() {
        const sourceLocation = this.getSourceLocation();
        if (!sourceLocation)
            return undefined;
        const target = this.getTarget();
        if (!target)
            return undefined;
        const targetLocation = WBVector3.fromMC(target.location)
            .add({ x: 0, y: 2, z: 0 });
        return targetLocation.subtract(sourceLocation).getRotation();
    }
    tickAttack() {
        const sourceLocation = this.getSourceLocation();
        if (!this.beam || !sourceLocation) {
            this.endAttack();
            return;
        }
        const target = this.getTarget();
        if (!target)
            return;
        this.beam.teleport(sourceLocation);
        // Let AC automatically return to 'idle' once the animation is finished
        if (this.tickCount === 1) {
            this.wither.setIdleAnimationState();
        }
        if (this.tickCount > SoulBeamAttack.ACTIVATION_END_TIME) {
            const targetLocation = WBVector3.fromMC(target.location)
                .add({ x: 0, y: 1, z: 0 });
            const targetOffset = targetLocation.subtract(sourceLocation);
            const moveSpeed = Math.min(SoulBeamAttack.TARGET_MAX_MOVE_SPEED, (SoulBeamAttack.TARGET_MOVE_SPEED_BASE
                + targetOffset.length() * SoulBeamAttack.TARGET_MOVE_SPEED_PER_BLOCK));
            const targetDelta = targetLocation.subtract(this.beamFocalPoint);
            const targetStep = targetDelta.length() <= moveSpeed
                ? targetDelta
                : targetDelta.normalize().multiply(moveSpeed);
            this.beamFocalPoint = this.beamFocalPoint.add(targetStep);
            const localBeamFocalPoint = this.beamFocalPoint.subtract(sourceLocation);
            const newDirection = localBeamFocalPoint.normalize();
            const newRotation = localBeamFocalPoint.getRotation();
            this.setBeamRotation(newRotation);
            if (this.tickCount < SoulBeamAttack.DEACTIVATION_START_TIME) {
                this.checkBeamCollision(newDirection);
            }
            else if (this.tickCount === SoulBeamAttack.DEACTIVATION_START_TIME) {
                this.beam?.playAnimation('deactivate', {
                    controller: 'activate_controller',
                    nextState: 'activate',
                    stopExpression: '0',
                    blendOutTime: 0,
                });
            }
        }
        this.tickCount++;
    }
    checkBeamCollision(direction) {
        if (!this.beam)
            return;
        const owner = this.wither.getEntity();
        if (!owner)
            return;
        soulBeamUpdate(this.beam, direction, {
            owner,
            maxRange: 50,
            ticksToBreak: 2,
            damage: 2,
            blockHitMap: this.blockHitCount,
            entityRaycastOptions: {
                excludeTypes: IMMUNE_ATTACK_ENTITIES
            }
        });
    }
    endAttack() {
        if (this.tickCount >= SoulBeamAttack.DURATION || !this.getTarget()) {
            this.wither.getEntity()?.triggerEvent('twb_wisu:make_moving');
            this.beam?.remove();
            return true;
        }
        return false;
    }
    static getAttackRegistry() {
        return {
            canStartAttack(wither) {
                const entity = wither.getEntity();
                if (!entity)
                    return false;
                return wither.getTargetController()
                    .getTargets()
                    .find((target) => {
                    const dSq = WBVector3.distanceSquared(target.location, entity.location);
                    return dSq >= SHORT_RANGE_SQ && dSq <= LONG_RANGE_SQ;
                }) !== undefined;
            },
            attackChance: 0.007,
            createNewAttack(wither) {
                return new SoulBeamAttack(wither, wither.getTargetController()
                    .getTargets()
                    .find((target) => {
                    const entity = wither.getEntity();
                    if (!entity)
                        return false;
                    const dSq = WBVector3.distanceSquared(target.location, entity.location);
                    return dSq >= SHORT_RANGE_SQ && dSq <= LONG_RANGE_SQ;
                }));
            }
        };
    }
}
SoulBeamAttack.ACTIVATION_END_TIME = Math.ceil(0.5 * 20);
SoulBeamAttack.DEACTIVATION_START_TIME = Math.ceil(7 * 20);
SoulBeamAttack.DURATION = Math.ceil(5 * 20);
SoulBeamAttack.TARGET_MOVE_SPEED_BASE = 3 / 20;
SoulBeamAttack.TARGET_MOVE_SPEED_PER_BLOCK = 0.008;
SoulBeamAttack.TARGET_MAX_MOVE_SPEED = 8 / 20;
