/* ==================================================
   OBTENCIÓN DE ELEMENTOS HTML
   Este bloque encuentra el campo de juego, los indicadores de resultado
   y los elementos de la ventana de victoria o derrota.
   ================================================== */

const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");

const scoreElement = document.getElementById("score");
const remainingElement = document.getElementById("remaining");
const livesElement = document.getElementById("lives");

const overlay = document.getElementById("overlay");
const resultTitle = document.getElementById("resultTitle");
const resultText = document.getElementById("resultText");
const restartButton = document.getElementById("restartButton");


/* ==================================================
   IMAGEN DEL JUGADOR
   Aquí se establece la ruta a la imagen del personaje.

   Si la imagen no se carga, el juego dibujará
   automáticamente la antigua nave de neón.
   ================================================== */

const playerImage = new Image();
let playerImageLoaded = false;

/* Ruta a la imagen del jugador */
playerImage.src = "player.png";

playerImage.addEventListener("load", () => {
  playerImageLoaded = true;
});

playerImage.addEventListener("error", () => {
  playerImageLoaded = false;

  console.warn(
    "No se pudo cargar la imagen del jugador. Se usará el jugador predeterminado."
  );
});


/* ==================================================
   IMAGEN DE FONDO
   Aquí se establece la ruta a la imagen de fondo del juego.

   Si la imagen no se carga, el juego usará
   automáticamente el fondo anterior con estrellas y cuadrícula.
   ================================================== */

const backgroundImage = new Image();
let backgroundImageLoaded = false;

/* Ruta a la imagen de fondo */
backgroundImage.src = "background.png";

backgroundImage.addEventListener("load", () => {
  backgroundImageLoaded = true;
});

backgroundImage.addEventListener("error", () => {
  backgroundImageLoaded = false;

  console.warn(
    "No se pudo cargar la imagen de fondo. Se usará el fondo predeterminado."
  );
});


/* ==================================================
   VARIABLES PRINCIPALES DEL JUEGO
   Este bloque almacena las teclas presionadas, la puntuación, las vidas
   y el estado del bucle del juego.
   ================================================== */

const keys = {};

let score = 0;
let lives = 3;
let gameRunning = true;
let animationId;


/* ==================================================
   CONFIGURACIÓN DEL JUGADOR
   Este objeto almacena la posición, el tamaño, la velocidad,
   el estado de invencibilidad y el tamaño visual de la imagen.

   imageWidth e imageHeight solo afectan la apariencia.
   El tamaño de colisión sigue definido por size.
   ================================================== */

const player = {
  x: 70,
  y: 220,
  size: 30,
  speed: 4.5,
  invincible: false,

  /* Tamaño visual de la imagen del jugador */
  imageWidth: 46,
  imageHeight: 36
};


/* ==================================================
   CONFIGURACIÓN DE LA IMAGEN DE FONDO
   overlayOpacity define el nivel de oscurecimiento de la imagen.

   0 — sin oscurecimiento.
   1 — imagen completamente oscurecida.
   ================================================== */

const backgroundSettings = {
  overlayOpacity: 0.28
};


/* ==================================================
   ARRAYS DE OBJETOS DEL JUEGO
   Aquí se almacenan los cristales, enemigos,
   partículas y estrellas de fondo.
   ================================================== */

let crystals = [];
let enemies = [];
let particles = [];
let stars = [];


/* ==================================================
   CREACIÓN DE ESTRELLAS DE FONDO
   La función crea puntos aleatorios para el fondo estándar.
   Se usan si la imagen de fondo no se cargó.
   ================================================== */

function createStars() {
  stars = [];

  for (let i = 0; i < 90; i++) {
    stars.push({
      x: Math.random() * canvas.width,
      y: Math.random() * canvas.height,
      size: Math.random() * 2 + 0.5,
      alpha: Math.random() * 0.7 + 0.2
    });
  }
}


/* ==================================================
   CREACIÓN DE CRISTALES
   La función coloca en el campo los objetos que el jugador
   debe recolectar para ganar.
   ================================================== */

function createCrystals() {
  const positions = [
    [150, 90],
    [310, 180],
    [470, 80],
    [650, 150],
    [210, 380],
    [410, 330],
    [610, 400],
    [735, 275]
  ];

  crystals = positions.map(([x, y]) => ({
    x,
    y,
    size: 13,
    collected: false,
    angle: Math.random() * Math.PI * 2
  }));
}


/* ==================================================
   CREACIÓN DE ENEMIGOS
   La función crea drones rojos y les asigna
   posiciones iniciales y velocidad de movimiento.
   ================================================== */

function createEnemies() {
  enemies = [
    {
      x: 250,
      y: 60,
      size: 28,
      vx: 2.2,
      vy: 1.6
    },
    {
      x: 540,
      y: 240,
      size: 30,
      vx: -2.5,
      vy: 1.8
    },
    {
      x: 680,
      y: 430,
      size: 26,
      vx: 2,
      vy: -2.3
    }
  ];
}


/* ==================================================
   REINICIO DEL JUEGO
   La función devuelve la puntuación, las vidas y el personaje
   a su estado inicial y vuelve a crear los objetos.
   ================================================== */

function resetGame() {
  cancelAnimationFrame(animationId);

  score = 0;
  lives = 3;
  gameRunning = true;

  player.x = 70;
  player.y = 220;
  player.invincible = false;

  particles = [];

  createStars();
  createCrystals();
  createEnemies();

  updatePanel();

  overlay.classList.remove("visible");

  gameLoop();
}


/* ==================================================
   ACTUALIZACIÓN DEL PANEL DE RESULTADOS
   La función muestra la puntuación actual, las vidas
   y la cantidad de cristales restantes.
   ================================================== */

function updatePanel() {
  const remaining = crystals.filter(
    crystal => !crystal.collected
  ).length;

  scoreElement.textContent = score;
  remainingElement.textContent = remaining;
  livesElement.textContent = lives;
}


/* ==================================================
   CREACIÓN DE PARTÍCULAS
   La función crea un efecto visual al recolectar
   un cristal o al chocar con un dron.
   ================================================== */

function createParticles(x, y, color, count) {
  for (let i = 0; i < count; i++) {
    particles.push({
      x,
      y,
      vx: (Math.random() - 0.5) * 5,
      vy: (Math.random() - 0.5) * 5,
      size: Math.random() * 4 + 2,
      life: 35,
      color
    });
  }
}


/* ==================================================
   DETECCIÓN DE COLISIONES
   La función determina si el jugador ha chocado
   con un cristal o un dron.
   ================================================== */

function isColliding(a, b) {
  const dx = a.x - b.x;
  const dy = a.y - b.y;

  const distance = Math.sqrt(
    dx * dx + dy * dy
  );

  return distance < a.size / 2 + b.size;
}


/* ==================================================
   CONTROL DEL JUGADOR
   La función mueve al personaje según las flechas presionadas
   y evita que salga de los límites del campo.
   ================================================== */

function updatePlayer() {
  if (keys.ArrowUp) {
    player.y -= player.speed;
  }

  if (keys.ArrowDown) {
    player.y += player.speed;
  }

  if (keys.ArrowLeft) {
    player.x -= player.speed;
  }

  if (keys.ArrowRight) {
    player.x += player.speed;
  }

  const half = player.size / 2;

  player.x = Math.max(
    half,
    Math.min(canvas.width - half, player.x)
  );

  player.y = Math.max(
    half,
    Math.min(canvas.height - half, player.y)
  );
}


/* ==================================================
   ACTUALIZACIÓN DE CRISTALES
   La función rota los cristales, verifica su recolección,
   otorga puntos y activa la victoria.
   ================================================== */

function updateCrystals() {
  crystals.forEach(crystal => {
    crystal.angle += 0.04;

    if (
      !crystal.collected &&
      isColliding(player, crystal)
    ) {
      crystal.collected = true;
      score += 100;

      createParticles(
        crystal.x,
        crystal.y,
        "#fde047",
        18
      );

      updatePanel();

      const allCollected = crystals.every(
        item => item.collected
      );

      if (allCollected) {
        endGame(true);
      }
    }
  });
}


/* ==================================================
   ACTUALIZACIÓN DE ENEMIGOS
   La función mueve los drones, los rebota contra las paredes
   y maneja las colisiones con el jugador.
   ================================================== */

function updateEnemies() {
  enemies.forEach(enemy => {
    enemy.x += enemy.vx;
    enemy.y += enemy.vy;

    const halfSize = enemy.size / 2;

    if (
      enemy.x < halfSize ||
      enemy.x > canvas.width - halfSize
    ) {
      enemy.vx *= -1;
    }

    if (
      enemy.y < halfSize ||
      enemy.y > canvas.height - halfSize
    ) {
      enemy.vy *= -1;
    }

    if (
      !player.invincible &&
      isColliding(player, enemy)
    ) {
      lives -= 1;
      score = Math.max(0, score - 50);

      player.invincible = true;

      createParticles(
        player.x,
        player.y,
        "#fb7185",
        25
      );

      player.x = 70;
      player.y = 220;

      updatePanel();

      setTimeout(() => {
        player.invincible = false;
      }, 1300);

      if (lives <= 0) {
        endGame(false);
      }
    }
  });
}


/* ==================================================
   ACTUALIZACIÓN DE PARTÍCULAS
   La función mueve las partículas, reduce gradualmente
   su tamaño y elimina las partículas que han terminado.
   ================================================== */

function updateParticles() {
  particles.forEach(particle => {
    particle.x += particle.vx;
    particle.y += particle.vy;

    particle.vx *= 0.97;
    particle.vy *= 0.97;

    particle.life -= 1;
    particle.size *= 0.97;
  });

  particles = particles.filter(
    particle => particle.life > 0
  );
}


/* ==================================================
   DIBUJO DEL FONDO
   Si la imagen se cargó, se usa la imagen.

   Si la imagen no se cargó, se usa
   el fondo estándar con estrellas y cuadrícula de neón.
   ================================================== */

function drawBackground() {
  if (backgroundImageLoaded) {
    drawBackgroundImage();
  } else {
    drawDefaultBackground();
  }
}


/* ==================================================
   DIBUJO DE LA IMAGEN DE FONDO
   La imagen llena todo el canvas sin distorsionar las proporciones.

   Si las proporciones de la imagen y del campo de juego difieren,
   la parte sobrante de la imagen se recorta cuidadosamente por los bordes.
   ================================================== */

function drawBackgroundImage() {
  const canvasRatio = canvas.width / canvas.height;

  const imageRatio =
    backgroundImage.naturalWidth /
    backgroundImage.naturalHeight;

  let sourceX = 0;
  let sourceY = 0;
  let sourceWidth = backgroundImage.naturalWidth;
  let sourceHeight = backgroundImage.naturalHeight;

  if (imageRatio > canvasRatio) {
    sourceWidth =
      backgroundImage.naturalHeight * canvasRatio;

    sourceX =
      (backgroundImage.naturalWidth - sourceWidth) / 2;
  } else {
    sourceHeight =
      backgroundImage.naturalWidth / canvasRatio;

    sourceY =
      (backgroundImage.naturalHeight - sourceHeight) / 2;
  }

  ctx.drawImage(
    backgroundImage,
    sourceX,
    sourceY,
    sourceWidth,
    sourceHeight,
    0,
    0,
    canvas.width,
    canvas.height
  );

  /* El oscurecimiento mejora la visibilidad de los objetos del juego */
  ctx.fillStyle =
    `rgba(3, 4, 10, ${backgroundSettings.overlayOpacity})`;

  ctx.fillRect(
    0,
    0,
    canvas.width,
    canvas.height
  );
}


/* ==================================================
   DIBUJO ESTÁNDAR DEL FONDO
   Esta función conserva el fondo anterior del juego.
   Se usa como alternativa.
   ================================================== */

function drawDefaultBackground() {
  ctx.fillStyle = "#070a18";

  ctx.fillRect(
    0,
    0,
    canvas.width,
    canvas.height
  );

  stars.forEach(star => {
    ctx.globalAlpha = star.alpha;
    ctx.fillStyle = "#ffffff";

    ctx.beginPath();

    ctx.arc(
      star.x,
      star.y,
      star.size,
      0,
      Math.PI * 2
    );

    ctx.fill();
  });

  ctx.globalAlpha = 1;
  ctx.strokeStyle = "rgba(34, 211, 238, 0.08)";
  ctx.lineWidth = 1;

  for (let x = 0; x <= canvas.width; x += 40) {
    ctx.beginPath();
    ctx.moveTo(x, 0);
    ctx.lineTo(x, canvas.height);
    ctx.stroke();
  }

  for (let y = 0; y <= canvas.height; y += 40) {
    ctx.beginPath();
    ctx.moveTo(0, y);
    ctx.lineTo(canvas.width, y);
    ctx.stroke();
  }
}


/* ==================================================
   DIBUJO DEL JUGADOR
   Si la imagen se cargó, la función la dibuja.

   Si la imagen no se cargó, se usa
   el dibujo anterior de la nave mediante Canvas.
   ================================================== */

function drawPlayer() {
  const isHidden =
    player.invincible &&
    Math.floor(Date.now() / 100) % 2 === 0;

  if (isHidden) {
    return;
  }

  if (playerImageLoaded) {
    drawPlayerImage();
  } else {
    drawDefaultPlayer();
  }
}


/* ==================================================
   DIBUJO DE LA IMAGEN DEL JUGADOR
   La imagen se coloca en el centro de las coordenadas del jugador.

   Su tamaño visual no afecta al tamaño de colisión.
   ================================================== */

function drawPlayerImage() {
  ctx.save();

  ctx.shadowColor = "#22d3ee";
  ctx.shadowBlur = 18;

  ctx.drawImage(
    playerImage,
    player.x - player.imageWidth / 2,
    player.y - player.imageHeight / 2,
    player.imageWidth,
    player.imageHeight
  );

  ctx.restore();
}


/* ==================================================
   DIBUJO ESTÁNDAR DEL JUGADOR
   Esta función contiene el código anterior del dibujo de la nave.
   Se usa como alternativa.
   ================================================== */

function drawDefaultPlayer() {
  ctx.save();
  ctx.translate(player.x, player.y);

  ctx.shadowColor = "#22d3ee";
  ctx.shadowBlur = 20;
  ctx.fillStyle = "#67e8f9";

  ctx.beginPath();
  ctx.moveTo(18, 0);
  ctx.lineTo(-14, -13);
  ctx.lineTo(-7, 0);
  ctx.lineTo(-14, 13);
  ctx.closePath();
  ctx.fill();

  ctx.shadowBlur = 12;
  ctx.fillStyle = "#a855f7";

  ctx.beginPath();
  ctx.moveTo(-10, -6);
  ctx.lineTo(-23, 0);
  ctx.lineTo(-10, 6);
  ctx.closePath();
  ctx.fill();

  ctx.restore();
}


/* ==================================================
   DIBUJO DE CRISTALES
   La función dibuja todos los cristales que el jugador
   aún no ha recolectado.
   ================================================== */

function drawCrystals() {
  crystals.forEach(crystal => {
    if (crystal.collected) {
      return;
    }

    ctx.save();

    ctx.translate(
      crystal.x,
      crystal.y
    );

    ctx.rotate(crystal.angle);

    ctx.shadowColor = "#fde047";
    ctx.shadowBlur = 18;
    ctx.fillStyle = "#fde047";

    ctx.beginPath();
    ctx.moveTo(0, -crystal.size);
    ctx.lineTo(crystal.size * 0.75, 0);
    ctx.lineTo(0, crystal.size);
    ctx.lineTo(-crystal.size * 0.75, 0);
    ctx.closePath();
    ctx.fill();

    ctx.fillStyle = "#ffffff";
    ctx.globalAlpha = 0.7;

    ctx.beginPath();
    ctx.moveTo(0, -crystal.size + 3);
    ctx.lineTo(3, 0);
    ctx.lineTo(0, 4);
    ctx.lineTo(-3, 0);
    ctx.closePath();
    ctx.fill();

    ctx.restore();
  });

  ctx.globalAlpha = 1;
}


/* ==================================================
   DIBUJO DE ENEMIGOS
   La función dibuja los drones rojos en movimiento.
   ================================================== */

function drawEnemies() {
  enemies.forEach(enemy => {
    ctx.save();

    ctx.translate(
      enemy.x,
      enemy.y
    );

    ctx.shadowColor = "#ef4444";
    ctx.shadowBlur = 20;
    ctx.fillStyle = "#ef4444";

    ctx.beginPath();

    ctx.arc(
      0,
      0,
      enemy.size / 2,
      0,
      Math.PI * 2
    );

    ctx.fill();

    ctx.fillStyle = "#450a0a";

    ctx.beginPath();

    ctx.arc(
      0,
      0,
      enemy.size / 4,
      0,
      Math.PI * 2
    );

    ctx.fill();

    ctx.strokeStyle = "#fca5a5";
    ctx.lineWidth = 3;

    ctx.beginPath();
    ctx.moveTo(-enemy.size / 2 - 7, 0);
    ctx.lineTo(enemy.size / 2 + 7, 0);
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(0, -enemy.size / 2 - 7);
    ctx.lineTo(0, enemy.size / 2 + 7);
    ctx.stroke();

    ctx.restore();
  });
}


/* ==================================================
   DIBUJO DE PARTÍCULAS
   La función dibuja pequeños elementos brillantes
   después de recolectar un cristal o chocar.
   ================================================== */

function drawParticles() {
  particles.forEach(particle => {
    ctx.globalAlpha = particle.life / 35;
    ctx.fillStyle = particle.color;
    ctx.shadowColor = particle.color;
    ctx.shadowBlur = 10;

    ctx.beginPath();

    ctx.arc(
      particle.x,
      particle.y,
      Math.max(0.5, particle.size),
      0,
      Math.PI * 2
    );

    ctx.fill();
  });

  ctx.globalAlpha = 1;
  ctx.shadowBlur = 0;
}


/* ==================================================
   FINAL DEL JUEGO
   La función detiene el juego y muestra un mensaje
   de victoria o derrota con la puntuación final.
   ================================================== */

function endGame(won) {
  gameRunning = false;

  resultTitle.textContent = won
    ? "¡Ganaste!"
    : "Game Over";

  resultText.textContent = won
    ? `Recogiste todos los cristales. Puntuación final: ${score}.`
    : `Los drones detuvieron tu misión. Puntuación final: ${score}.`;

  overlay.classList.add("visible");
}


/* ==================================================
   BUCLE PRINCIPAL DEL JUEGO
   La función actualiza constantemente el estado del juego
   y vuelve a dibujar todos los objetos del juego.
   ================================================== */

function gameLoop() {
  drawBackground();

  if (gameRunning) {
    updatePlayer();
    updateCrystals();
    updateEnemies();
    updateParticles();
  }

  drawCrystals();
  drawEnemies();
  drawPlayer();
  drawParticles();

  if (gameRunning) {
    animationId = requestAnimationFrame(gameLoop);
  }
}


/* ==================================================
   MANEJO DE TECLAS PRESIONADAS
   Este bloque recuerda qué flechas están presionadas
   y desactiva el desplazamiento estándar de la página.
   ================================================== */

window.addEventListener("keydown", event => {
  const arrowKeys = [
    "ArrowUp",
    "ArrowDown",
    "ArrowLeft",
    "ArrowRight"
  ];

  if (arrowKeys.includes(event.key)) {
    event.preventDefault();
    keys[event.key] = true;
  }
});


/* ==================================================
   MANEJO DE TECLAS SOLTADAS
   Este bloque detiene el movimiento en la dirección
   cuya tecla ha sido soltada.
   ================================================== */

window.addEventListener("keyup", event => {
  keys[event.key] = false;
});


/* ==================================================
   BOTÓN DE REINICIO
   Al hacer clic en el botón, el juego se reinicia completamente.
   ================================================== */

restartButton.addEventListener(
  "click",
  resetGame
);


/* ==================================================
   PRIMERA EJECUCIÓN DEL JUEGO
   Este comando inicia el juego después de cargar la página.
   ================================================== */

resetGame();