/* ==================================================
   ПОЛУЧЕНИЕ HTML-ЭЛЕМЕНТОВ
   Этот блок находит игровое поле, показатели результата
   и элементы окна победы или поражения.
   ================================================== */

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");


/* ==================================================
   ИЗОБРАЖЕНИЕ ИГРОКА
   Здесь задается путь к изображению игрового персонажа.

   Если изображение не загрузится, игра автоматически
   нарисует прежний неоновый корабль.
   ================================================== */

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

/* Путь к изображению игрока */
playerImage.src = "player.png";

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

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

  console.warn(
    "Player image could not be loaded. The default player will be used."
  );
});


/* ==================================================
   ИЗОБРАЖЕНИЕ ФОНА
   Здесь задается путь к изображению игрового фона.

   Если изображение не загрузится, игра автоматически
   использует прежний фон со звездами и сеткой.
   ================================================== */

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

/* Путь к изображению фона */
backgroundImage.src = "background.png";

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

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

  console.warn(
    "Background image could not be loaded. The default background will be used."
  );
});


/* ==================================================
   ОСНОВНЫЕ ПЕРЕМЕННЫЕ ИГРЫ
   Этот блок хранит нажатые клавиши, счет, жизни
   и состояние игрового цикла.
   ================================================== */

const keys = {};

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


/* ==================================================
   НАСТРОЙКИ ИГРОКА
   Этот объект хранит позицию, размер, скорость,
   состояние неуязвимости и визуальный размер картинки.

   imageWidth и imageHeight влияют только на внешний вид.
   Размер столкновения по-прежнему задается через size.
   ================================================== */

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

  /* Визуальный размер изображения игрока */
  imageWidth: 46,
  imageHeight: 36
};


/* ==================================================
   НАСТРОЙКИ ФОНОВОГО ИЗОБРАЖЕНИЯ
   overlayOpacity задает степень затемнения картинки.

   0 — затемнения нет.
   1 — изображение полностью затемнено.
   ================================================== */

const backgroundSettings = {
  overlayOpacity: 0.28
};


/* ==================================================
   МАССИВЫ ИГРОВЫХ ОБЪЕКТОВ
   Здесь хранятся кристаллы, враги,
   частицы и фоновые звезды.
   ================================================== */

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


/* ==================================================
   СОЗДАНИЕ ФОНОВЫХ ЗВЕЗД
   Функция создает случайные точки для стандартного фона.
   Они используются, если изображение фона не загрузилось.
   ================================================== */

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
    });
  }
}


/* ==================================================
   СОЗДАНИЕ КРИСТАЛЛОВ
   Функция размещает на поле объекты, которые игрок
   должен собрать для победы.
   ================================================== */

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
  }));
}


/* ==================================================
   СОЗДАНИЕ ВРАГОВ
   Функция создает красные дроны и задает им
   стартовые позиции и скорость движения.
   ================================================== */

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
    }
  ];
}


/* ==================================================
   ПЕРЕЗАПУСК ИГРЫ
   Функция возвращает счет, жизни и персонажа
   в начальное состояние и заново создает объекты.
   ================================================== */

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();
}


/* ==================================================
   ОБНОВЛЕНИЕ ПАНЕЛИ РЕЗУЛЬТАТОВ
   Функция показывает текущий счет, жизни
   и количество оставшихся кристаллов.
   ================================================== */

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

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


/* ==================================================
   СОЗДАНИЕ ЧАСТИЦ
   Функция создает визуальный эффект при сборе
   кристалла или столкновении с дроном.
   ================================================== */

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
    });
  }
}


/* ==================================================
   ПРОВЕРКА СТОЛКНОВЕНИЙ
   Функция определяет, соприкоснулся ли игрок
   с кристаллом или дроном.
   ================================================== */

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;
}


/* ==================================================
   УПРАВЛЕНИЕ ИГРОКОМ
   Функция перемещает персонажа по нажатым стрелкам
   и не позволяет ему выйти за границы поля.
   ================================================== */

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)
  );
}


/* ==================================================
   ОБНОВЛЕНИЕ КРИСТАЛЛОВ
   Функция вращает кристаллы, проверяет их сбор,
   начисляет очки и запускает победу.
   ================================================== */

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);
      }
    }
  });
}


/* ==================================================
   ОБНОВЛЕНИЕ ВРАГОВ
   Функция перемещает дроны, отражает их от стен
   и обрабатывает столкновения с игроком.
   ================================================== */

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);
      }
    }
  });
}


/* ==================================================
   ОБНОВЛЕНИЕ ЧАСТИЦ
   Функция перемещает частицы, постепенно уменьшает
   их размер и удаляет завершившиеся частицы.
   ================================================== */

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
  );
}


/* ==================================================
   ОТРИСОВКА ФОНА
   Если изображение загрузилось, используется картинка.

   Если изображение не загрузилось, используется
   стандартный фон со звездами и неоновой сеткой.
   ================================================== */

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


/* ==================================================
   ОТРИСОВКА ФОНОВОГО ИЗОБРАЖЕНИЯ
   Картинка заполняет весь canvas без искажения пропорций.

   Если пропорции картинки и игрового поля отличаются,
   лишняя часть изображения аккуратно обрезается по краям.
   ================================================== */

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
  );

  /* Затемнение повышает видимость игровых объектов */
  ctx.fillStyle =
    `rgba(3, 4, 10, ${backgroundSettings.overlayOpacity})`;

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


/* ==================================================
   СТАНДАРТНАЯ ОТРИСОВКА ФОНА
   Эта функция сохраняет прежний фон игры.
   Она используется как запасной вариант.
   ================================================== */

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();
  }
}


/* ==================================================
   ОТРИСОВКА ИГРОКА
   Если изображение загрузилось, функция рисует его.

   Если изображение не загрузилось, используется
   прежняя отрисовка корабля через Canvas.
   ================================================== */

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

  if (isHidden) {
    return;
  }

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


/* ==================================================
   ОТРИСОВКА ИЗОБРАЖЕНИЯ ИГРОКА
   Изображение располагается по центру координат игрока.

   Его визуальный размер не влияет на размер столкновения.
   ================================================== */

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();
}


/* ==================================================
   СТАНДАРТНАЯ ОТРИСОВКА ИГРОКА
   Эта функция содержит прежний код рисования корабля.
   Она используется как запасной вариант.
   ================================================== */

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();
}


/* ==================================================
   ОТРИСОВКА КРИСТАЛЛОВ
   Функция рисует все кристаллы, которые игрок
   еще не успел собрать.
   ================================================== */

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;
}


/* ==================================================
   ОТРИСОВКА ВРАГОВ
   Функция рисует движущиеся красные дроны.
   ================================================== */

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();
  });
}


/* ==================================================
   ОТРИСОВКА ЧАСТИЦ
   Функция рисует небольшие светящиеся элементы
   после сбора кристалла или столкновения.
   ================================================== */

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;
}


/* ==================================================
   ЗАВЕРШЕНИЕ ИГРЫ
   Функция останавливает игру и показывает сообщение
   о победе или поражении с итоговым счетом.
   ================================================== */

function endGame(won) {
  gameRunning = false;

  resultTitle.textContent = won
    ? "You Win!"
    : "Game Over";

  resultText.textContent = won
    ? `You collected every crystal. Final score: ${score}.`
    : `The drones stopped your mission. Final score: ${score}.`;

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


/* ==================================================
   ОСНОВНОЙ ИГРОВОЙ ЦИКЛ
   Функция постоянно обновляет состояние игры
   и заново рисует все игровые объекты.
   ================================================== */

function gameLoop() {
  drawBackground();

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

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

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


/* ==================================================
   ОБРАБОТКА НАЖАТИЯ КЛАВИШ
   Этот блок запоминает, какие стрелки сейчас нажаты,
   и отключает стандартную прокрутку страницы.
   ================================================== */

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

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


/* ==================================================
   ОБРАБОТКА ОТПУСКАНИЯ КЛАВИШ
   Этот блок останавливает движение в направлении,
   клавиша которого была отпущена.
   ================================================== */

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


/* ==================================================
   КНОПКА PLAY AGAIN
   При нажатии кнопки игра полностью запускается заново.
   ================================================== */

restartButton.addEventListener(
  "click",
  resetGame
);


/* ==================================================
   ПЕРВЫЙ ЗАПУСК ИГРЫ
   Эта команда начинает игру после загрузки страницы.
   ================================================== */

resetGame();