/**
 * @fileoverview Utility module for game.js
 * 
 * @module games.automato.game
 */

import Phaser from "phaser";
import { BaseGameScene } from "../../../shared/BaseGameScene.js";
import { gameEventBus } from "../../../utils/gameEvents";
import { GameInterpreter } from "../../../interpreters/GameInterpreter.js";
import { setupAutomatoAPI } from "./hooks/interpreterSetup.js";
import { validateSolution } from "./validation/validators.js";

import tiles from "./assets/tiles_pegman.png";
import pegman from "./assets/pegman.png";
import marker from "./assets/marker.png";

const ASSETS = {
  IMG: {
    TILES: "tiles",
    PEGMAN: "pegman",
    MARKER: "marker",
  },
};

const CONSTANTES = {
  TAMANHO_TILE: 50,
  PEGMAN_HEIGHT: 51,
  PEGMAN_WIDTH: 49,
  VELOCIDADE_ANIMACAO: 200,
};

const Direcao = {
  NORTE: 0,
  LESTE: 1,
  SUL: 2,
  OESTE: 3,
};

const TILE_TYPES = {
  PAREDE: 0,
  CAMINHO: 1,
  INICIO: 2,
  FIM: 3,
};

class AutomatoScene extends BaseGameScene {
  constructor() {
    super("AutomatoScene");

    // Estado específico do Automato
    this.mapa = null;
    this.posicaoInicial = null;
    this.posicaoFinal = null;
    this.posicaoJogador = null;
    this.direcaoJogador = Direcao.LESTE;
    this.pegmanSprite = null;
    this.gradeVisual = null;
    this.resultadoJogada = "em_andamento";
  }

  preload() {
    this.preloadGlobalAssets();
    this.load.image(ASSETS.IMG.MARKER, marker);
    this.load.spritesheet(ASSETS.IMG.TILES, tiles, {
      frameWidth: CONSTANTES.TAMANHO_TILE,
      frameHeight: CONSTANTES.TAMANHO_TILE,
    });
    this.load.spritesheet(ASSETS.IMG.PEGMAN, pegman, {
      frameHeight: CONSTANTES.PEGMAN_HEIGHT,
      frameWidth: CONSTANTES.PEGMAN_WIDTH,
    });
  }

  /**
   * Preload dos assets do autômato (tiles, spritesheets e sons se houver).
   * @returns {void}
   */

  init(data) {
    super.init(data);

    this.mapa = this.configFase?.mapa || [];
    this.direcaoJogador = Direcao.LESTE;
    this.resultadoJogada = "em_andamento";
  }

  /**
   * Inicialização específica do AutomatoScene.
   * Recebe `data` e configura estado derivado da `configFase`.
   * @param {Object} data - Dados opcionais da cena
   * @returns {void}
   */

  onBeforeRun() {
    this.posicaoJogador = { ...this.posicaoInicial };
    this.direcaoJogador = Direcao.LESTE;
    this.resultadoJogada = "em_andamento";
    this.atualizarVisualJogador();
  }

  /**
   * Hook chamado imediatamente antes da execução do código do aluno.
   * Prepara posição inicial e estado de animação.
   * @returns {void}
   */

  onReset() {
    this.posicaoJogador = { ...this.posicaoInicial };
    this.direcaoJogador = Direcao.LESTE;
    this.resultadoJogada = "em_andamento";
    this.atualizarVisualJogador();
  }

  /**
   * Hook chamado quando o usuário reseta a cena manualmente.
   * Restaura estado mas não altera configurações persistentes.
   * @returns {void}
   */

  onSuccess() {
    this.animarVitoria();
  }

  /**
   * Handler chamado quando a validação indica sucesso.
   * Deve executar animações de vitória e finalizar a execução.
   * @returns {void}
   */

  onFailure() {
    // animarFalha() já foi disparada em moverParaFrente() ao bater na parede;
    // não repetir aqui para evitar dupla animação.
  }

  /**
   * Handler chamado quando a validação indica falha.
   * Deve executar animações de falha e limpar execução.
   * @returns {void}
   */

  /**
   * Atualiza a posição e animação visual do Pegman
   * Sincroniza sprite com estado lógico (posição e direção)
   */
  atualizarVisualJogador() {
    if (this.posicaoJogador) {
      const posX =
        this.posicaoJogador.x * CONSTANTES.TAMANHO_TILE +
        CONSTANTES.TAMANHO_TILE / 2;
      const posY =
        this.posicaoJogador.y * CONSTANTES.TAMANHO_TILE +
        CONSTANTES.TAMANHO_TILE / 2 -
        6;
      this.pegmanSprite.setPosition(posX, posY);

      const animacoesDirecao = [
        "pegman_idle_norte",
        "pegman_idle_leste",
        "pegman_idle_sul",
        "pegman_idle_oeste",
      ];
      this.pegmanSprite.play(animacoesDirecao[this.direcaoJogador]);
    }
  }

  /**
   * Move o Pegman para frente na direção atual
   * Verifica colisão com paredes e atualiza animação
   * Se colidir, marca como falha e para a execução
   *
   * @returns {Promise<void>} Promise que resolve quando movimento completa
   */
  moverParaFrente() {
    this.historico.push({
      tipo: "move",
      direcao: this.direcaoJogador,
      posicao: { ...this.posicaoJogador },
    });

    if (this.resultadoJogada !== "em_andamento") return Promise.resolve();

    let { x, y } = this.posicaoJogador;

    if (this.direcaoJogador === Direcao.NORTE) y--;
    else if (this.direcaoJogador === Direcao.LESTE) x++;
    else if (this.direcaoJogador === Direcao.SUL) y++;
    else if (this.direcaoJogador === Direcao.OESTE) x--;

    const proximoTile =
      this.mapa[y] && this.mapa[y][x] !== undefined ? this.mapa[y][x] : -1;

    if (proximoTile === TILE_TYPES.PAREDE || proximoTile === -1) {
      this.resultadoJogada = "falha";
      this.animarFalha();

      if (this.gameInterpreter) {
        this.gameInterpreter.stopInternal();
      }

      return Promise.resolve();
    } else {
      return new Promise((resolve) => {
        const novaX = x * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2;
        const novaY =
          y * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2 - 6;

        this.tweens.add({
          targets: this.pegmanSprite,
          x: novaX,
          y: novaY,
          duration: CONSTANTES.VELOCIDADE_ANIMACAO / 2,
          ease: "Power2",
          onComplete: () => {
            this.posicaoJogador = { x, y };
            this.atualizarVisualJogador();
            resolve();
          },
        });
      });
    }
  }

  /**
   * Anima a falha do Pegman (batida na parede)
   * Faz bounce back e depois animação de queda
   */
  animarFalha() {
    const deltaX =
      this.direcaoJogador === Direcao.LESTE
        ? 1
        : this.direcaoJogador === Direcao.OESTE
          ? -1
          : 0;
    const deltaY =
      this.direcaoJogador === Direcao.NORTE
        ? -1
        : this.direcaoJogador === Direcao.SUL
          ? 1
          : 0;

    const bounceX =
      this.pegmanSprite.x + (deltaX * CONSTANTES.TAMANHO_TILE) / 4;
    const bounceY =
      this.pegmanSprite.y + (deltaY * CONSTANTES.TAMANHO_TILE) / 4;

    this.tweens.add({
      targets: this.pegmanSprite,
      x: bounceX,
      y: bounceY,
      duration: CONSTANTES.VELOCIDADE_ANIMACAO / 3,
      yoyo: true,
      repeat: 1,
      ease: "Power2",
      onComplete: () => {
        this.pegmanSprite.play("pegman_fall");
        this.resultadoJogada = "falha";
      },
    });
  }

  /**
   * Vira o Pegman 90° para a esquerda
   * Anima a rotação do sprite
   *
   * @returns {Promise<void>} Promise que resolve quando rotação completa
   */
  virarEsquerda() {
    this.historico.push({
      tipo: "turnLeft",
      de: this.direcaoJogador,
      para: (this.direcaoJogador + 3) % 4,
    });

    const novaDirecao = (this.direcaoJogador + 3) % 4;
    return this.animarRotacao(novaDirecao);
  }

  /**
   * Vira o Pegman 90° para a direita
   * Anima a rotação do sprite
   *
   * @returns {Promise<void>} Promise que resolve quando rotação completa
   */
  virarDireita() {
    this.historico.push({
      tipo: "turnRight",
      de: this.direcaoJogador,
      para: (this.direcaoJogador + 1) % 4,
    });

    const novaDirecao = (this.direcaoJogador + 1) % 4;
    return this.animarRotacao(novaDirecao);
  }

  /**
   * Anima a rotação do Pegman de uma direção para outra
   * Usa animações pré-definidas ou atualiza visual diretamente
   *
   * @param {number} novaDirecao - Nova direção (0=Norte, 1=Leste, 2=Sul, 3=Oeste)
   * @returns {Promise<void>} Promise que resolve quando animação completa
   */
  animarRotacao(novaDirecao) {
    return new Promise((resolve) => {
      const direcaoAtual = this.direcaoJogador;
      const nomesDirecoes = ["norte", "leste", "sul", "oeste"];
      const direcaoAtualNome = nomesDirecoes[direcaoAtual];
      const novaDirecaoNome = nomesDirecoes[novaDirecao];

      this.direcaoJogador = novaDirecao;

      const chaveAnimacao = `${direcaoAtualNome}_para_${novaDirecaoNome}`;

      if (this.anims.exists(chaveAnimacao)) {
        this.pegmanSprite.play(chaveAnimacao);

        const onRotationComplete = () => {
          this.atualizarVisualJogador();
          this.pegmanSprite.off("animationcomplete", onRotationComplete);
          resolve();
        };

        this.pegmanSprite.on("animationcomplete", onRotationComplete);
      } else {
        this.atualizarVisualJogador();
        resolve();
      }
    });
  }

  /**
   * Verifica se o Pegman chegou ao objetivo (tile tipo FIM)
   *
   * @returns {boolean} true se chegou no alvo, false caso contrário
   */
  chegouNoAlvo() {
    return (
      this.posicaoJogador.x === this.posicaoFinal.x &&
      this.posicaoJogador.y === this.posicaoFinal.y
    );
  }

  /**
   * Verifica se há caminho livre em uma direção relativa
   *
   * @param {string} direcaoRelativa - 'frente', 'esquerda' ou 'direita'
   * @returns {boolean} true se há caminho, false se é parede ou fora do mapa
   */
  haCaminho(direcaoRelativa) {
    let direcaoAbsoluta = this.direcaoJogador;
    if (direcaoRelativa === "esquerda") {
      direcaoAbsoluta = (this.direcaoJogador + 3) % 4;
    } else if (direcaoRelativa === "direita") {
      direcaoAbsoluta = (this.direcaoJogador + 1) % 4;
    }

    let { x, y } = this.posicaoJogador;
    if (direcaoAbsoluta === Direcao.NORTE) y--;
    else if (direcaoAbsoluta === Direcao.LESTE) x++;
    else if (direcaoAbsoluta === Direcao.SUL) y++;
    else if (direcaoAbsoluta === Direcao.OESTE) x--;

    const proximoTile =
      this.mapa[y] && this.mapa[y][x] !== undefined ? this.mapa[y][x] : -1;
    return proximoTile !== TILE_TYPES.PAREDE && proximoTile !== -1;
  }

  /**
   * Anima a vitória do Pegman
   * Sequência de frames de comemoração
   */
  animarVitoria() {
    const stepSpeed = 150;
    this.pegmanSprite.setFrame(16);
    setTimeout(() => this.pegmanSprite.setFrame(18), stepSpeed);
    setTimeout(() => this.pegmanSprite.setFrame(16), stepSpeed * 2);
    setTimeout(() => {
      const framesBase = [0, 4, 8, 12];
      this.pegmanSprite.setFrame(framesBase[this.direcaoJogador]);
    }, stepSpeed * 3);
  }

  /**
   * Destaca um bloco no workspace do Blockly e sinaliza pausa visual.
   * Usado pelo interpreter para indicar o bloco atualmente executado.
   * @param {string} id - Id do bloco a ser destacado
   * @returns {void}
   */
  highlightBlock(id) {
    if (this.workspace) this.workspace.highlightBlock(id);
    this.highlightPause = true;
  }

  /**
   * Trata o resultado final da execução do código do aluno.
   * Em caso de sucesso dispara evento de sucesso; caso contrário, falha.
   * @param {string} result - Resultado retornado pelo interpretador
   * @returns {void}
   */
  handleExecutionResult(result) {
    if (result === "failure" || this.resultadoJogada === "falha") {
      gameEventBus.gameFailure();
      return;
    }
    if (this.chegouNoAlvo()) {
      this.animarVitoria();
      setTimeout(() => gameEventBus.gameSuccess(), 800);
    } else {
      gameEventBus.gameFailure();
    }
  }

  createAnimations() {
    this.anims.create({
      key: "pegman_idle_norte",
      frames: [{ key: "pegman", frame: 0 }],
      frameRate: 1,
    });
    this.anims.create({
      key: "pegman_idle_leste",
      frames: [{ key: "pegman", frame: 4 }],
      frameRate: 1,
    });
    this.anims.create({
      key: "pegman_idle_sul",
      frames: [{ key: "pegman", frame: 8 }],
      frameRate: 1,
    });
    this.anims.create({
      key: "pegman_idle_oeste",
      frames: [{ key: "pegman", frame: 12 }],
      frameRate: 1,
    });
    this.anims.create({
      key: "pegman_fall",
      frames: this.anims.generateFrameNumbers("pegman", { start: 18, end: 20 }),
      frameRate: 10,
      repeat: 0,
    });
    this.createRotationAnimations();
  }

  createRotationAnimations() {
    this.anims.create({
      key: "norte_para_leste",
      frames: [0, 1, 2, 3, 4].map((frame) => ({ key: "pegman", frame })),
      frameRate: 30,
    });
    this.anims.create({
      key: "norte_para_oeste",
      frames: [0, 15, 14, 13, 12].map((frame) => ({ key: "pegman", frame })),
      frameRate: 30,
    });
    this.anims.create({
      key: "oeste_para_sul",
      frames: [12, 11, 10, 9, 8].map((frame) => ({ key: "pegman", frame })),
      frameRate: 30,
    });
    this.anims.create({
      key: "oeste_para_norte",
      frames: [12, 13, 14, 15, 0].map((frame) => ({ key: "pegman", frame })),
      frameRate: 30,
    });
    this.anims.create({
      key: "leste_para_sul",
      frames: [4, 5, 6, 7, 8].map((frame) => ({ key: "pegman", frame })),
      frameRate: 30,
    });
    this.anims.create({
      key: "leste_para_norte",
      frames: [4, 3, 2, 1, 0].map((frame) => ({ key: "pegman", frame })),
      frameRate: 30,
    });
    this.anims.create({
      key: "sul_para_oeste",
      frames: [8, 9, 10, 11, 12].map((frame) => ({ key: "pegman", frame })),
      frameRate: 30,
    });
    this.anims.create({
      key: "sul_para_leste",
      frames: [8, 7, 6, 5, 4].map((frame) => ({ key: "pegman", frame })),
      frameRate: 30,
    });
  }

  createVisualGrid(TILE_SHAPES, normalize) {
    this.gradeVisual = this.add.group();
    for (let y = 0; y < this.mapa.length; y++) {
      for (let x = 0; x < this.mapa[y].length; x++) {
        let tileShape =
          normalize(x, y) +
          normalize(x, y - 1) +
          normalize(x + 1, y) +
          normalize(x, y + 1) +
          normalize(x - 1, y);
        if (!TILE_SHAPES[tileShape]) {
          tileShape =
            tileShape === "00000" && Math.random() > 0.3
              ? "null0"
              : "null" + Math.floor(1 + Math.random() * 4);
        }
        const [tileX, tileY] = TILE_SHAPES[tileShape];
        const frameIndex = tileY * 5 + tileX;
        const tileSprite = this.add.sprite(
          x * CONSTANTES.TAMANHO_TILE,
          y * CONSTANTES.TAMANHO_TILE,
          "tiles",
          frameIndex,
        );
        tileSprite.setDisplaySize(
          CONSTANTES.TAMANHO_TILE,
          CONSTANTES.TAMANHO_TILE,
        );
        tileSprite.setOrigin(0);
        this.gradeVisual.add(tileSprite);
      }
    }
    for (let y = 0; y < this.mapa.length; y++) {
      for (let x = 0; x < this.mapa[y].length; x++) {
        if (this.mapa[y][x] === TILE_TYPES.FIM) {
          const markerImg = this.add.image(
            x * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2,
            y * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2 - 10,
            "marker",
          );
          markerImg.setDisplaySize(12, 20.04);
          markerImg.setOrigin(0.5);
          this.gradeVisual.add(markerImg);
        }
      }
    }
  }

  createPegmanSprite() {
    this.pegmanSprite = this.add.sprite(0, 0, "pegman", 4);
    this.pegmanSprite.setDisplaySize(
      CONSTANTES.TAMANHO_TILE * 0.8,
      CONSTANTES.TAMANHO_TILE * 0.8,
    );
    this.pegmanSprite.setOrigin(0.5);
    this.atualizarVisualJogador();
  }

  create() {
    this.mapa = this.configFase?.mapa || [];
    this.resultadoJogada = "em_andamento";

    const TILE_SHAPES = {
      10010: [4, 0],
      10001: [3, 3],
      11000: [0, 1],
      10100: [0, 2],
      11010: [4, 1],
      10101: [3, 2],
      10110: [0, 0],
      10011: [2, 0],
      11001: [4, 2],
      11100: [2, 3],
      11110: [1, 1],
      10111: [1, 0],
      11011: [2, 1],
      11101: [1, 2],
      11111: [2, 2],
      null0: [4, 3],
      null1: [3, 0],
      null2: [3, 1],
      null3: [0, 3],
      null4: [1, 3],
    };

    const normalize = (x, y) => {
      if (x < 0 || x >= this.mapa[0].length || y < 0 || y >= this.mapa.length)
        return "0";
      return this.mapa[y][x] === TILE_TYPES.PAREDE ? "0" : "1";
    };

    this.createAnimations();

    const encontrarPosicao = (tipo) => {
      for (let y = 0; y < this.mapa.length; y++) {
        for (let x = 0; x < this.mapa[y].length; x++) {
          if (this.mapa[y][x] === tipo) return { x, y };
        }
      }
      return null;
    };

    this.posicaoInicial = encontrarPosicao(TILE_TYPES.INICIO);
    this.posicaoFinal = encontrarPosicao(TILE_TYPES.FIM);
    this.posicaoJogador = { ...this.posicaoInicial };
    this.direcaoJogador = Direcao.LESTE;

    this.createVisualGrid(TILE_SHAPES, normalize);
    this.createPegmanSprite();

    this.gameInterpreter = new GameInterpreter({
      stepDelay: 20,
      pauseExec: true,
    });

    this.setupStandardController(
      setupAutomatoAPI,
      (history, config, gameConfig) =>
        validateSolution(history, config, gameConfig, this),
    );
  }
}

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: config.mapa[0].length * CONSTANTES.TAMANHO_TILE,
    height: config.mapa.length * CONSTANTES.TAMANHO_TILE,
    scale: {
      mode: Phaser.Scale.EXPAND,
      autoCenter: Phaser.Scale.CENTER_BOTH,
    },
    backgroundColor: "#F1EEE7",
    parent: parentElement,
    scene: [AutomatoScene],
    callbacks: {
      preBoot: (game) => {
        game.registry.merge({
          configFase: config,
          gameConfig: gameConfig,
          customFailureHandler: customFailureHandler,
          stepDelay: 20,
        });
      },
    },
  };
};

/**
 * Factory que monta a configuração Phaser para o jogo Autômato.
 * Calcula largura/altura a partir do mapa da fase e injeta callbacks.
 * @param {HTMLElement} parentElement - Elemento DOM pai para o canvas
 * @param {Object} configFaseAtual - Configuração da fase (fallback)
 * @param {Function|null} customFailureHandler - Handler opcional de falha
 * @param {number|null} idFaseAtual - Índice da fase atual (1-based)
 * @param {Object|null} gameConfig - Configuração completa do jogo
 * @returns {Object} Configuração para inicializar `Phaser.Game`
 */
