import { system, world } from "@minecraft/server";

class Vector {
  constructor(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
  static add(a, b) {
    return new Vector(a.x + b.x, a.y + b.y, a.z + b.z);
  }
  static subtract(a, b) {
    return new Vector(a.x - b.x, a.y - b.y, a.z - b.z);
  }
  static multiplyScalar(a, scalar) {
    return new Vector(a.x * scalar, a.y * scalar, a.z * scalar);
  }
  normalized() {
    let len = Math.sqrt(this.x ** 2 + this.y ** 2 + this.z ** 2) || 1;
    return new Vector(this.x / len, this.y / len, this.z / len);
  }
  toString() {
    return `${this.x.toFixed(2)} ${this.y.toFixed(2)} ${this.z.toFixed(2)}`;
  }
}

const SPAWN_DISTANCE = 60;
const SPAWN_Y_OFFSET = 60;
const FALL_SPEED = 0.1;
const GRAVITY = 0.005;
const EVENT_DURATION_TICKS = 20 * 20;
const GROUND_Y_OFFSET = -5;
const MIN_HORIZONTAL_SPEED = 0.1;
const MAX_HORIZONTAL_SPEED = 0.3;
const DESPAWN_DISTANCE_MULTIPLIER = 24;

class Comet {
  constructor(entity, position, velocity, dimension, impact = false) {
    this.entity = entity;
    this.position = position;
    this.velocity = velocity;
    this.dimension = dimension;
    this.impact = impact;
  }
  update() {
    this.position = Vector.add(this.position, this.velocity);
    if (this.impact) {
      this.velocity.y -= GRAVITY;
    }
    if (this.entity.isValid()) {
    this.entity.teleport({
      x: this.position.x,
      y: this.position.y,
      z: this.position.z,
    });
  }
  }
}

class CometEvent {
  constructor() {
    this.isActive = false;
    this.comets = [];
    this.ticksRemaining = EVENT_DURATION_TICKS;
  }
  
  triggerOnPlayer(player) {
    // Limit event to night only (Minecraft time: 12000 <= time < 24000)
    const timeOfDay = world.getTimeOfDay();
    if (timeOfDay < 12000 || timeOfDay >= 24000) {
      player.sendMessage("Comet event can only occur at night.");
      return;
    }
    if (this.isActive) {
      player.sendMessage("There's already a comet shower in the sky!");
      return;
    }
    player.runCommandAsync("playsound block.bell.use @s ~ ~ ~ 5 1 1");
    this.origin = new Vector(player.location.x, player.location.y, player.location.z);
    this.dimension = player.dimension;
    this.isActive = true;
    player.sendMessage("A comet shower has been summoned! Look up.");
    this.start();
    system.run(() => this.tick());
  }
  
  start() {
    const directions = ["north", "south", "east", "west"];
    const chosenDirection = directions[Math.floor(Math.random() * directions.length)];
    const totalComets = 15; // total number of comets to spawn
    let spawnedComets = 0;
    const spawnDelay = 40; // delay in ticks between each group (40 ticks ≈ 2 sec)
    
    const spawnNextGroup = () => {
      if (spawnedComets >= totalComets) {
        // Once all comets have been spawned, randomly assign one as the impact comet.
        if (this.comets.length > 0) {
          const impactIndex = Math.floor(Math.random() * this.comets.length);
          const impactComet = this.comets[impactIndex];
          impactComet.impact = true;
          impactComet.velocity.y = -FALL_SPEED;
        }
        return;
      }
      
      // Decide whether to spawn 1 or 2 comets in this group
      let groupCount = Math.random() < 0.5 ? 1 : 2;
      if (spawnedComets + groupCount > totalComets) {
        groupCount = totalComets - spawnedComets;
      }
      
      for (let j = 0; j < groupCount; j++) {
        let spawnX = this.origin.x;
        let spawnZ = this.origin.z;
        let velocity = new Vector(0, 0, 0);
        // Increase offset for greater horizontal dispersion
        const offset = (Math.random() - 0.5) * 100;
        const modSpeed = Math.random() * (MAX_HORIZONTAL_SPEED - MIN_HORIZONTAL_SPEED) + MIN_HORIZONTAL_SPEED;
        
        if (chosenDirection === "north") {
          spawnZ = this.origin.z - SPAWN_DISTANCE;
          spawnX += offset;
          velocity = new Vector(0, 0, modSpeed);
        } else if (chosenDirection === "south") {
          spawnZ = this.origin.z + SPAWN_DISTANCE;
          spawnX += offset;
          velocity = new Vector(0, 0, -modSpeed);
        } else if (chosenDirection === "east") {
          spawnX = this.origin.x + SPAWN_DISTANCE;
          spawnZ += offset;
          velocity = new Vector(-modSpeed, 0, 0);
        } else if (chosenDirection === "west") {
          spawnX = this.origin.x - SPAWN_DISTANCE;
          spawnZ += offset;
          velocity = new Vector(modSpeed, 0, 0);
        }
        
        const spawnY = this.origin.y + SPAWN_Y_OFFSET;
        let spawnPos = new Vector(spawnX, spawnY, spawnZ);
        let blockAtSpawn = null;
        try {
          blockAtSpawn = this.dimension.getBlock({ 
            x: spawnPos.x, 
            y: spawnPos.y, 
            z: spawnPos.z 
          });
        } catch (ex) {
          blockAtSpawn = null;
        }
        
        if (!blockAtSpawn) {
          spawnPos = new Vector(this.origin.x, this.origin.y + SPAWN_Y_OFFSET, this.origin.z);
        }
        
        const posObject = { x: spawnPos.x, y: spawnPos.y, z: spawnPos.z };
        let cometEntity;
        try {
          cometEntity = this.dimension.spawnEntity("sapphire_meteors:comet_entity", posObject);
        } catch (e) {
          console.error("Error spawning comet:", e);
          continue;
        }
        
        const comet = new Comet(cometEntity, spawnPos, velocity, this.dimension, false);
        this.comets.push(comet);
        spawnedComets++;
      }
      
      // Schedule next group spawn after a delay
      system.runTimeout(() => spawnNextGroup(), spawnDelay);
    };
    
    spawnNextGroup();
  }
  
  tick() {
    if (this.ticksRemaining > 0 && this.comets.length > 0) {
      for (let i = this.comets.length - 1; i >= 0; i--) {
        const comet = this.comets[i];
        comet.update();
        if (comet.impact) {
          // Force the comet to impact if either the block below is not air or it has fallen below the threshold
          let blockBelow = this.dimension.getBlock({ 
              x: comet.position.x, 
              y: comet.position.y - 1, 
              z: comet.position.z 
          });
          if ((blockBelow && !blockBelow.isAir) || (comet.position.y <= this.origin.y + GROUND_Y_OFFSET)) {
            const impactCoords = `(${comet.position.x.toFixed(2)}, ${comet.position.y.toFixed(2)}, ${comet.position.z.toFixed(2)})`;
            world.getPlayers().forEach(p => p.sendMessage("The comet impacted at: " + impactCoords + "!"));
            if (Math.random() < 0.5) {
              this.dimension.spawnEntity("sapphire_meteors:comet_villager", {
                x: comet.position.x,
                y: comet.position.y,
                z: comet.position.z,
              });
              world.getPlayers().forEach(p => p.sendMessage("A cosmic villager emerged from the comet!"));
            } else {
              const count = Math.floor(Math.random() * 4) + 2;
              for (let j = 0; j < count; j++) {
                const offsetX = (Math.random() - 0.5) * 2;
                const offsetY = Math.random() * 0.5;
                const offsetZ = (Math.random() - 0.5) * 2;
                const lootPos = {
                  x: comet.position.x + offsetX,
                  y: comet.position.y + offsetY,
                  z: comet.position.z + offsetZ,
                };
                this.dimension.runCommandAsync(`loot spawn ${lootPos.x} ${lootPos.y} ${lootPos.z} loot "sapphire/meteors/comet_loot"`);
              }
              world.getPlayers().forEach(p => p.sendMessage("The comet dropped celestial resources!"));
            }
            comet.entity.runCommandAsync("/event entity @s sapphire_meteors:despawn");
            this.comets.splice(i, 1);
          }
        } else {
          const dx = comet.position.x - this.origin.x;
          const dz = comet.position.z - this.origin.z;
          const distance = Math.sqrt(dx * dx + dz * dz);
          if (distance > SPAWN_DISTANCE * DESPAWN_DISTANCE_MULTIPLIER) {
            comet.entity.runCommandAsync("/event entity @s sapphire_meteors:despawn");
            this.comets.splice(i, 1);
          }
        }
      }
      
      // Ensure that at least one comet is marked for impact.
      if (this.comets.length > 0 && !this.comets.some(c => c.impact)) {
        const randomIndex = Math.floor(Math.random() * this.comets.length);
        const candidate = this.comets[randomIndex];
        candidate.impact = true;
        candidate.velocity.y = -FALL_SPEED;
      }
      
      this.ticksRemaining--;
      system.run(() => this.tick());
    } else {
      this.comets.forEach(c => {
        if (c.entity.isValid()) {
        c.entity.runCommand("/event entity @s sapphire_meteors:despawn");
        }
      });
      this.comets = [];
    }
  }
}

world.afterEvents.itemUse.subscribe((event) => {
  const item = event.itemStack;
  const player = event.source;
  if (item.typeId === "sapphire_meteors:comet_summoner") {
    let cometEvent = new CometEvent();
    cometEvent.triggerOnPlayer(player);
  }
});

function cleanupLeftoverComets() {
  const dimensions = ["overworld", "nether", "the_end"];
  dimensions.forEach((dimId) => {
    try {
      let dimension = world.getDimension(dimId);
      dimension.runCommandAsync("kill @e[type=sapphire_meteors:comet_entity]");
    } catch (error) {
    }
  });
}

cleanupLeftoverComets();

world.afterEvents.playerJoin.subscribe((event) => {
  for (let delay of [100, 120, 140, 160, 180, 200]) {
    system.runTimeout(() => {
      cleanupLeftoverComets();
    }, delay);
  }
});
