/**
 * @fileoverview Utility module for game.js
 * 
 * @module games.mole-mash.game
 */

import Phaser from "phaser";
import { BaseGameScene } from "../../../shared/BaseGameScene.js";
import { setupMoleMashAPI } from "./hooks/interpreterSetup.js";
import { validateSolution } from "./validation/validators.js";
import { matrixConfig } from "./config/matrixConfig.js";

import sprites from "./assets/sprites.png";
import background from "./assets/background.png";
import ground_pop from "./assets/ground_pop.mp3";
import game_loop from "./../../../assets/game_loop.mp3";

const ASSETS = {
  IMG: { SPRITES: "sprites", BG: "background" },
  AUDIO: { POP: "ground_pop", LOOP: "game_loop" },
};

const CONSTANTES = {
  LARGURA: 800,
  ALTURA: 600,
  TAMANHO_TILE: 120,
  OFFSET_VERTICAL: 0.16,
  COR_FUNDO: "#242527",
};

class MoleMashScene extends BaseGameScene {
  constructor() {
    super("MoleMashScene");
    this.toupeira = null;
  }

  preload() {
    // 1. Carrega os assets globais (win/fail) da Base
    this.preloadGlobalAssets();

    // 2. Carrega assets específicos do Mole Mash
    this.load.image(ASSETS.IMG.BG, background);
    this.load.spritesheet(ASSETS.IMG.SPRITES, sprites, {
      frameWidth: 190,
      frameHeight: 144,
    });

    this.load.audio(ASSETS.AUDIO.POP, ground_pop);
    this.load.audio(ASSETS.AUDIO.LOOP, game_loop);
  }
  /**
   * Preload dos assets específicos do Mole Mash (sprites e áudio).
   * @returns {void}
   */
  preload() {
    // 1. Carrega os assets globais (win/fail) da Base
    this.preloadGlobalAssets();

    // 2. Carrega assets específicos do Mole Mash
    this.load.image(ASSETS.IMG.BG, background);
    this.load.spritesheet(ASSETS.IMG.SPRITES, sprites, {
      frameWidth: 190,
      frameHeight: 144,
    });

    this.load.audio(ASSETS.AUDIO.POP, ground_pop);
    this.load.audio(ASSETS.AUDIO.LOOP, game_loop);
  }

  /**
   * Inicializa a cena: elementos visuais, animações e controlador padrão.
   * @returns {void}
   */
  create() {
    this.setupVisuals();
    this.setupAnimations();
    this.setupMatrix();
    this.setupStandardController(setupMoleMashAPI, validateSolution);
  }

  // --- HOOKS DA BASE ---

  /**
   * Executado antes da execução do código do aluno; prepara áudio e visual.
   * @returns {Promise<void>}
   */
  // Chamado automaticamente antes de rodar o código do aluno
  async onBeforeRun() {
    this.playAudio(ASSETS.AUDIO.LOOP, { loop: true, volume: 0.1 });
    this.toupeira.setVisible(false);
    return this.aguardar(0.5);
  }
  /**
   * Executado quando o usuário reseta a cena; restaura visual e áudio.
   * @returns {void}
   */
  // Chamado quando o usuário clica em Resetar
  onReset() {
    this.stopAudio(ASSETS.AUDIO.LOOP);
    this.toupeira.setVisible(false);
    // Reseta posição visualmente
    const pos = this.getGridPixels(2, 2);
    this.toupeira.setPosition(pos.x, pos.y);
  }
  /**
   * Chamado quando a validação for bem sucedida; limpa áudios/efeitos.
   * @returns {void}
   */
  // Chamado no Sucesso
  onSuccess() {
    this.stopAudio(ASSETS.AUDIO.LOOP);
  }

  /**
   * Chamado quando a validação falha; limpa áudios/efeitos.
   * @returns {void}
   */
  // Chamado na Falha
  onFailure() {
    this.stopAudio(ASSETS.AUDIO.LOOP);
  }

  // --- LÓGICA VISUAL ESPECÍFICA ---

  /**
   * Configura elementos visuais da cena (fundo, sprites, posições iniciais).
   * @returns {void}
   */
  setupVisuals() {
    // Fundo
    const bg = this.add
      .image(CONSTANTES.LARGURA / 2, 0, ASSETS.IMG.BG)
      .setOrigin(0.5, 0);
    bg.setScale(this.scale.width / bg.width);

    // Toupeira
    const pos = this.getGridPixels(2, 2);
    this.toupeira = this.add.sprite(pos.x, pos.y, ASSETS.IMG.SPRITES, 0);
    this.toupeira.setScale(0.8).setVisible(false).setDepth(2);
  }
  setupAnimations() {
    if (!this.anims.exists("aparecer")) {
      this.anims.create({
        key: "aparecer",
        frames: this.anims.generateFrameNumbers(ASSETS.IMG.SPRITES, {
          start: 0,
          end: 7,
        }),
        frameRate: 20,
        hideOnComplete: false,
      });
    }
    if (!this.anims.exists("sumir")) {
      this.anims.create({
        key: "sumir",
        frames: this.anims.generateFrameNumbers(ASSETS.IMG.SPRITES, {
          start: 7,
          end: 0,
        }),
        frameRate: 20,
      });
    }
  }
  getGridPixels(l, c) {
    const larguraGrade = 3 * CONSTANTES.TAMANHO_TILE;
    const alturaGrade = 3 * CONSTANTES.TAMANHO_TILE;

    const offsetX = (CONSTANTES.LARGURA - larguraGrade) / 2;
    const offsetY =
      (CONSTANTES.ALTURA - alturaGrade) / 2 +
      CONSTANTES.ALTURA * CONSTANTES.OFFSET_VERTICAL;

    const x =
      (c - 1) * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2 + offsetX;
    const y =
      (l - 1) * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2 + offsetY;

    return { x, y };
  }
  /**
   * Registra animações usadas pela cena (aparecer/sumir da toupeira).
   * @returns {void}
   */
  setupAnimations() {
    if (!this.anims.exists("aparecer")) {
      this.anims.create({
        key: "aparecer",
        frames: this.anims.generateFrameNumbers(ASSETS.IMG.SPRITES, {
          start: 0,
          end: 7,
        }),
        frameRate: 20,
        hideOnComplete: false,
      });
    }
    if (!this.anims.exists("sumir")) {
      this.anims.create({
        key: "sumir",
        frames: this.anims.generateFrameNumbers(ASSETS.IMG.SPRITES, {
          start: 7,
          end: 0,
        }),
        frameRate: 20,
      });
    }
  }

  /**
   * Converte coordenadas de grade (linha, coluna) para pixels na tela.
   * @param {number} l - Linha (1..3)
   * @param {number} c - Coluna (1..3)
   * @returns {{x:number,y:number}} Coordenadas em pixels
   */
  getGridPixels(l, c) {
    const larguraGrade = 3 * CONSTANTES.TAMANHO_TILE;
    const alturaGrade = 3 * CONSTANTES.TAMANHO_TILE;

    const offsetX = (CONSTANTES.LARGURA - larguraGrade) / 2;
    const offsetY =
      (CONSTANTES.ALTURA - alturaGrade) / 2 +
      CONSTANTES.ALTURA * CONSTANTES.OFFSET_VERTICAL;

    const x =
      (c - 1) * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2 + offsetX;
    const y =
      (l - 1) * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2 + offsetY;

    return { x, y };
  }

  // --- SETUP AUXILIAR ---

  /**
   * Desenha a grade auxiliar de coordenadas na tela, se ativada pela fase.
   * Ativada via `configFase.showMatrix: true`; parametrizada em `matrixConfig.js`.
   * @returns {void}
   */
  setupMatrix() {
    if (!this.configFase?.showMatrix) return;

    const { linhas, colunas, linha, celula, rotulo, rotuloCanto } = matrixConfig;
    const tileSize = CONSTANTES.TAMANHO_TILE;
    const larguraGrade = colunas * tileSize;
    const alturaGrade = linhas * tileSize;
    const originX = (CONSTANTES.LARGURA - larguraGrade) / 2;
    const originY =
      (CONSTANTES.ALTURA - alturaGrade) / 2 +
      CONSTANTES.ALTURA * CONSTANTES.OFFSET_VERTICAL;

    const graphics = this.add.graphics();
    graphics.setDepth(1);

    // Preenchimento alternado leve (padrão xadrez)
    for (let r = 0; r < linhas; r++) {
      for (let c = 0; c < colunas; c++) {
        if ((r + c) % 2 === 0) {
          graphics.fillStyle(celula.cor, celula.alpha);
          graphics.fillRect(
            originX + c * tileSize,
            originY + r * tileSize,
            tileSize,
            tileSize,
          );
        }
      }
    }

    // Borda externa e linhas divisórias internas
    graphics.lineStyle(linha.espessura, linha.cor, linha.alpha);
    graphics.strokeRect(originX, originY, larguraGrade, alturaGrade);
    for (let r = 1; r < linhas; r++) {
      graphics.beginPath();
      graphics.moveTo(originX, originY + r * tileSize);
      graphics.lineTo(originX + larguraGrade, originY + r * tileSize);
      graphics.strokePath();
    }
    for (let c = 1; c < colunas; c++) {
      graphics.beginPath();
      graphics.moveTo(originX + c * tileSize, originY);
      graphics.lineTo(originX + c * tileSize, originY + alturaGrade);
      graphics.strokePath();
    }

    const estiloTexto = {
      fontSize: rotulo.tamanhoFonte,
      fontFamily: rotulo.familia,
      fontStyle: rotulo.estilo,
      color: rotulo.cor,
    };

    // Rótulos de coluna — acima da grade (C1, C2, C3)
    for (let c = 0; c < colunas; c++) {
      this.add
        .text(
          originX + c * tileSize + tileSize / 2,
          originY + rotulo.offsetColunaY,
          String(c + 1),
          estiloTexto,
        )
        .setOrigin(0.5, 1)
        .setDepth(1);
    }

    // Rótulos de linha — à esquerda da grade (L1, L2, L3)
    for (let r = 0; r < linhas; r++) {
      this.add
        .text(
          originX + rotulo.offsetLinhaX,
          originY + r * tileSize + tileSize / 2,
          String(r + 1),
          estiloTexto,
        )
        .setOrigin(1, 0.5)
        .setDepth(1);
    }

    // Rótulo do canto (L\C) para orientar a leitura dos eixos
    if (rotuloCanto.visivel) {
      this.add
        .text(
          originX + rotulo.offsetLinhaX,
          originY + rotulo.offsetColunaY,
          rotuloCanto.texto,
          {
            fontSize: rotuloCanto.tamanhoFonte,
            fontFamily: rotulo.familia,
            color: rotuloCanto.cor,
          },
        )
        .setOrigin(1, 1)
        .setDepth(1);
    }
  }

  // --- MÉTODOS DA API PÚBLICA ---

  /**
   * Move a toupeira para a célula indicada (linha, coluna) e toca efeitos sonoros.
   * Registra a ação no histórico herdado da `BaseGameScene`.
   * @param {number} linha - Linha alvo (1..3)
   *   @param {number} coluna - Coluna alvo (1..3)
   * @returns {void}
   */
/**
   * Move a toupeira para a célula indicada (linha, coluna) e toca efeitos sonoros.
   * Registra a ação no histórico herdado da `BaseGameScene`.
   * @param {number} linha - Linha alvo (1..3)
   * @param {number} coluna - Coluna alvo (1..3)
   * @returns {void}
   */
  moverToupeira(linha, coluna) {
    // 1. Registra no histórico herdado da Base
    this.historico.push({ l: linha, c: coluna, t: Date.now() });

    // 2. Lógica Visual
    const l = Phaser.Math.Clamp(linha, 1, 3);
    const c = Phaser.Math.Clamp(coluna, 1, 3);
    const { x, y } = this.getGridPixels(l, c);

    if (!this.toupeira) return;

    // Toca o som "pop" local
    this.playAudio(ASSETS.AUDIO.POP, { rate: 3, volume: 0.3 });

    // Lógica de aparecer/sumir
    if (!this.toupeira.visible) {
      // Primeira aparição
      this.toupeira.setPosition(x, y);
      this.toupeira.setVisible(true);
      this.toupeira.play("aparecer");
    } else {
      // Movimento subsequente
      this.toupeira.play("sumir");
      this.toupeira.once("animationcomplete-sumir", () => {
        if (!this.toupeira.scene) return;
        this.toupeira.setPosition(x, y);
        this.toupeira.play("aparecer");
      });
    }
  }
}

// Factory Padrão do Phaser
/**
 * Fábrica que cria a configuração do Phaser para o MoleMash.
 * @param {HTMLElement} parentElement - Elemento DOM que hospeda o canvas do jogo
 * @param {Object} configFaseAtual - Configuração da fase ativa
 * @returns {Object} Configuração do Phaser usada para inicializar o jogo
 */
/**
 * Fábrica que cria a configuração do Phaser para o MoleMash.
 * @param {HTMLElement} parentElement - Elemento DOM que hospeda o canvas do jogo
 * @param {Object} configFaseAtual - Configuração da fase ativa
 * @returns {Object} Configuração do Phaser usada para inicializar o jogo
 */
export const createGame = (
  parentElement,
  configFaseAtual,
  customFailureHandler = null,
  idFaseAtual = null,
  gameConfig = null,
) => {
  const config =
    idFaseAtual && gameConfig
      ? gameConfig.fases[idFaseAtual - 1]
      : configFaseAtual;

  return {
    type: Phaser.AUTO,
    width: CONSTANTES.LARGURA,
    height: CONSTANTES.ALTURA,
    backgroundColor: CONSTANTES.COR_FUNDO,
    parent: parentElement,
    scale: {
      mode: Phaser.Scale.FIT,
      autoCenter: Phaser.Scale.CENTER_BOTH,
    },
    scene: [MoleMashScene],
    callbacks: {
      preBoot: (game) => {
        // Injeção de dependências no Registry para a BaseGameScene ler no init()
        game.registry.merge({
          configFase: config,
          gameConfig: gameConfig,
          customFailureHandler: customFailureHandler,
          stepDelay: 50,
        });
      },
    },
  };
};
