import BaseBoss from '../BaseBoss';
import { world, system, EntityDamageCause, MolangVariableMap } from "@minecraft/server";
import { Knockback } from "../../utils/Knockback.js";
import { GetRandomInt } from "../../utils/GetRandomInt.js";
import * as utils from "../misc";
import { startCoroutineForBoss } from "../CoroutineClass";

const ABILITY_COOLDOWN_CONFIG = new Map([
  ["summon_attack", 10000],// Cooldown in milliseconds
  ["infestation_attack", 3000] // Cooldown in milliseconds
]);

class TheVoid extends BaseBoss {
  abilityCd;
  isSubscribed = false;

  constructor(entityId) {
    super('TheVoid', entityId);
    this.abilityCd = new Map();
  }

  async specialBehavior() {
    if (!this.isSubscribed) {
      world.afterEvents.dataDrivenEntityTrigger.subscribe((args) => {
        const { entity, eventId } = args;
        if (!this.entity.isValid()) return;
        if (entity.id === this.entityId) {
          switch (eventId) {
            case 'lv_mo:laser_attack':
              this.laser_attack();
              break;
            case 'lv_mo:summon_attack':
              this.summon_attack();
              break;
            case 'lv_mo:infestation_attack':
              this.infestation_attack();
              break;
          }
        }
      });
      this.isSubscribed = true
    }
    this.tryActiveAbility();
  }

  async tryActiveAbility() {
    ABILITY_COOLDOWN_CONFIG.forEach((cooldownTime, abilityId) => {
      if (!this.entity.isValid()) return;
      if (this.isAbilityReady(abilityId)) {
        this.entity.removeTag(abilityId);
        this.abilityCd.set(abilityId, Date.now());
      }
    });
  }

  isAbilityReady(abilityId) {
    const now = Date.now();
    const lastUsed = this.abilityCd.get(abilityId) || 0;
    const cooldownTime = ABILITY_COOLDOWN_CONFIG.get(abilityId) || 0;
    return (now - lastUsed) >= cooldownTime;
  }

  LASER_ATTACK_DURATION = parseFloat(1 * 20)
  LASER_ATTACK_TIME = parseFloat(0.75 * 20)
  LASER_ATTACK_RADIUS = 12;
  LASER_ATTACK_DAMAGE = 4;
  async laser_attack() {
    if (!this.entity.isValid()) return;

    // Start the laser attack animation
    this.entity.setProperty('lv_mo:animations', 'laser_attack');
    let i = 0;
    const test = system.runInterval(() => {
      if (!this.entity.isValid() || i >= this.LASER_ATTACK_DURATION) {
        system.clearRun(test)
      }
      if (i % 5 == 0) {
        this.entity.runCommand("/execute as @s facing entity @p eyes run tp @s ~ ~ ~ ~ ~");
      }
      i++
    }, 1)

    if (!this.entity.isValid()) return;


    utils.delayExecute(((this.LASER_ATTACK_TIME - 10) / 2), () => {
      if (!this.entity.isValid()) return;
      // Find victims in the radius
      let victim = utils.getRadiusEntity(this.entity, this.entity.location, this.LASER_ATTACK_RADIUS, ['player']);
      const victimLocation = victim[0]?.location;
      const distance = utils.calculateDistance(this.entity.location, victimLocation) + 15;

      const viewDirection = this.entity.getViewDirection();
      // Set Molang variables for direction and range
      const particleVariables = new MolangVariableMap();
      particleVariables.setFloat("directionx", -viewDirection.x);
      particleVariables.setFloat("directiony", -viewDirection.y + 0.3);
      particleVariables.setFloat("directionz", -viewDirection.z);
      particleVariables.setFloat("range", distance);

      // Spawn particle effect directed towards the victim
      const pLoc = {
        x: this.entity.location.x,
        y: this.entity.location.y + 5, // Adjusted height for particle spawn
        z: this.entity.location.z
      };
      this.entity.dimension.spawnParticle('lv_mo:laser', pLoc, particleVariables);

      utils.delayExecute((this.LASER_ATTACK_TIME + 10), () => {
        if (!this.entity.isValid()) return;
        this.entity.dimension.createExplosion(victimLocation, 2, { breaksBlocks: false,  causesFire: false});

        let aoeTimer = 0;
        const aoe = system.runInterval(() => {
          aoeTimer++;
          if (aoeTimer > (12 * 20) || !this.entity.isValid()) return system.clearRun(aoe);
          try {
            let targetvictim = utils.getRadiusEntity(this.entity, victimLocation, 2, ['player']);
            if (aoeTimer % 20 == 0) return this.entity.dimension.spawnParticle('lv_mo:void_aoe', victimLocation);

            for (let radiusVictim of targetvictim) {
              radiusVictim.applyDamage(this.LASER_ATTACK_DAMAGE + 2, {
                cause: EntityDamageCause.entityAttack,
                damagingEntity: this.entity,
              });
            }
          } catch (error) { }
        }, 1)
      });
    });

    // Wait for remaining time in attack duration before resetting
    await this.wait((this.LASER_ATTACK_DURATION - this.LASER_ATTACK_TIME + 20));
    await this.resetAndReadyAbility();
  }

  INFESTATION_ATTACK_ENTITY = 'lv_mo:infested_entity';
  INFESTATION_DURATION = parseFloat(1.6 * 20);
  INFESTATION_TIP_DURATION = 3 * 20;
  
  async infestation_attack() {
    this.tipPlayer(15, 'Infesting The Terrain');
    const summonAttackCoroutine = function* () {
      yield this.INFESTATION_TIP_DURATION;
  
      const summonCount = GetRandomInt(6, 12);
  
      this.entity.setProperty('lv_mo:animations', 'infestation_attack');
      for (let i = 0; i < summonCount; i++) {
        try {
          const spawnLoc = {x:this.entity.location.x, y:this.entity.location.y + 6, z:this.entity.location.z}
          const infestedEntity = this.entity.dimension.spawnEntity(this.INFESTATION_ATTACK_ENTITY, spawnLoc);
          utils.effect(infestedEntity, 'slow_falling', 20)
          const randomX = (Math.random() - 0.5) * 4;
          const randomZ = (Math.random() - 0.5) * 4;

          infestedEntity.applyImpulse({ x: randomX, y: 1, z: randomZ });

          infestedEntity.triggerEvent('lv_mo:sensor')
  
        } catch (error) {}
      }
  
      yield this.INFESTATION_DURATION;
      this.entity.addTag("infestation_attack");
      const cooldownTime = ABILITY_COOLDOWN_CONFIG.get("infestation_attack");
      this.abilityCd.set("infestation_attack", Date.now() + cooldownTime);
    }.bind(this);
  
    startCoroutineForBoss(
      summonAttackCoroutine,
      () => {
        this.resetAndReadyAbility();
      },
      this.entity.id
    );
  }
  

  SUMMON_ATTACK_ENTITY = 'lv_mo:void_eye';
  SUMMON_ATTACK_ENTITY_COUNT = 4; // Number of entities to summon each interval
  SUMMON_ATTACK_DURATION = 20 * 20; // Total duration of the attack (20 seconds)
  SUMMON_INTERVAL = 5 * 20;
  SUMMON_TIP_DURATION = 3 * 20;

  async summon_attack() {
    this.tipPlayer(15, 'Void is Summoning Eyes Be Careful');
    this.entity.runCommand(`particle lv_mo:void_transition ~~0.6~`);
    this.entity.setProperty('lv_mo:body', false);
  
    const totalTicks = this.SUMMON_ATTACK_DURATION; // Total attack duration in ticks
    const summonInterval = this.SUMMON_INTERVAL; // Interval between summoning
    let currentTick = 0; // Tick counter
  
    // Coroutine for summoning entities
    const summonAttackCoroutine = function* () {
      yield this.SUMMON_TIP_DURATION;
      while (currentTick < totalTicks) {
        if (!this.entity.isValid()) {
          return; // Stop if the entity is invalid
        }
  
        for (let i = 0; i < this.SUMMON_ATTACK_ENTITY_COUNT; i++) {
          const theta = Math.random() * Math.PI; // Horizontal angle (0 to π)
          const phi = Math.random() * (Math.PI / 2); // Vertical angle (0 to π/2)
          const radius = Math.random() * 3 + 3; // Random radius between 3 and 6
  
          const x = radius * Math.sin(phi) * Math.cos(theta);
          const y = radius * Math.sin(phi);
          const z = radius * Math.cos(theta);
  
          try {
            this.entity.runCommand( `/execute at @p positioned ~${x.toFixed(2)} ~${y.toFixed(2)} ~${z.toFixed(2)} run summon ${this.SUMMON_ATTACK_ENTITY} ~ ~ ~` );
          } catch (error) {}
        }

        currentTick += summonInterval; // Increment ticks by interval
        yield summonInterval; // Wait for the next interval
      }
  
      this.entity.addTag("summon_attack");
      this.entity.setProperty("lv_mo:body", true);
      this.entity.runCommand(`particle lv_mo:void_transition ~~0.6~`);
  
      const cooldownTime = ABILITY_COOLDOWN_CONFIG.get("summon_attack");
      this.abilityCd.set("summon_attack", Date.now() + cooldownTime);
    }.bind(this);
  
    // Start the coroutine
    startCoroutineForBoss( summonAttackCoroutine, () => { this.resetAndReadyAbility(); }, this.entity.id );
  }
}

export default TheVoid;
