/**
 * @fileoverview Utility module for GameInterpreter.js
 * 
 * @module interpreters.GameInterpreter
 */

import Interpreter from "js-interpreter";
import { sanitizeCode, CodeSanitizationError } from "./CodeSanitizer";

/**
 * Interpreta e executa código JavaScript dentro de um sandbox isolado usando js-interpreter.
 * Oferece controle de fluxo: execução passo-a-passo, pausa, parada e stepping para debug visual.
 * Utilizado para executar código do jogador de forma segura com animações sincronizadas.
 *
 * @class GameInterpreter
 * @example
 * const interpreter = new GameInterpreter({ stepDelay: 20 });
 * const result = await interpreter.executeCode(userCode, apiSetupFn);
 * // result: 'completed' | 'stopped' | 'stopped_by_user'
 */
export class GameInterpreter {
  /**
   * Inicializa um novo interpretador de código do jogo.
   *
   * @constructor
   * @param {Object} [config={}] - Configuração do interpretador
   * @param {number} [config.stepDelay=20] - Delay (ms) entre steps de execução para visualização
   */
  constructor(config = {}) {
    this.config = {
      stepDelay: 20,
      ...config,
    };
    this.interpreter = null;
    this.isRunning = false;
    this.shouldStop = false;
    this.stoppedByUser = false;
    this.pause = 0;
  }

  async executeCode(code, apiSetupFunction) {
    if (this.isRunning) {
      // Aguardar até 200ms como segurança (BaseGameScene já previne este cenário)
      const start = Date.now();
      while (this.isRunning && (Date.now() - start) < 200) {
        await new Promise(resolve => setTimeout(resolve, 20));
      }
    }

    this.isRunning = true;
    this.shouldStop = false;
    this.stoppedByUser = false;
    this.pause = 0;

    try {
      sanitizeCode(code);
      this.interpreter = new Interpreter(code, apiSetupFunction);

      if (
        !this.interpreter.ast ||
        !this.interpreter.ast.body ||
        this.interpreter.ast.body.length === 0
      ) {
        console.warn("No code to execute - empty AST");
        return "completed";
      }

      const result = await this._runSteps();
      return result;
    } catch (error) {
      if (error instanceof CodeSanitizationError) {
        console.warn("[GameInterpreter] Código rejeitado pelo sanitizador:", error.rule, error.message);
      } else {
        console.error("Error in executeCode:", error);
      }
      throw error;
    } finally {
      this.isRunning = false;
      this.interpreter = null;
    }
  }

  async executeFunction(func, ...args) {
    if (!this.interpreter || !this.globalScope) {
      return;
    }

    try {
      const interpreterArgs = args.map((arg) =>
        this.interpreter.nativeToPseudo(arg),
      );
      const funcObject =
        typeof func === "string"
          ? this.interpreter.getValueFromScope(func)
          : func;

      if (funcObject && funcObject.isFunc) {
        this.interpreter.queueFunction(funcObject, interpreterArgs);
        await this._runSteps();
      } else {
        console.warn(
          `Função '${typeof func === "string" ? func : "desconhecida"}' não encontrada no escopo do interpretador.`,
        );
      }
    } catch (error) {
      console.error("Erro ao execute a função no interpretador:", error);
    }
  }

  /**
   * Para a execução do código de forma segura.
   * Sinaliza para parar e marca como parado pelo usuário.
   *
   * @function stop
   * @returns {void}
   */
  stop() {
    this.shouldStop = true;
    this.stoppedByUser = true;
  }

  /**
   * Para a execução internamente (ex: jogo detectou condição de fim).
   * Não marca como parado pelo utilizador — o fluxo de validação continua normal.
   *
   * @function stopInternal
   * @returns {void}
   */
  stopInternal() {
    this.shouldStop = true;
    this.stoppedByUser = false;
  }

  /**
   * Define pausa para sincronizar com animações do jogo.
   * Interno: controla velocidade de stepping para visualização adequada.
   *
   * @function setPause
   * @param {number} pauseValue - Tempo de pausa em ms (0 = sem pausa)
   * @returns {void}
   */
  setPause(pauseValue) {
    this.pause = pauseValue;
  }

  _runSteps() {
    return new Promise((resolve, reject) => {
      let stepCount = 0;

      const executeChunk = () => {
        this.pause = 0;
        let ticks = 1000;
        let go;

        do {
          if (this.shouldStop) {
            resolve(this.stoppedByUser ? "stopped_by_user" : "stopped");
            return;
          }
          stepCount++;

          try {
            go = this.interpreter.step();
          } catch (error) {
            console.error(`Error in step ${stepCount}:`, error);
            reject(error);
            return;
          }

          if (!go) {
            resolve("completed");
            return;
          }

          // Forçar pausa para evitar travamento
          if (!ticks--) {
            this.pause = 1;
          }

          if (go && this.pause) {
            go = false;
            setTimeout(executeChunk, this.pause);
          }
        } while (go);

        // Se não há pausa, continuar imediatamente
        if (!this.pause) {
          resolve("completed");
        }
      };

      executeChunk();
    });
  }

  /**
   * Retorna se o interpretador está em execução no momento.
   * Útil para UI/controles que precisam habilitar/desabilitar botões.
   *
   * @name running
   * @type {boolean}
   * @memberof GameInterpreter#
   */
  get running() {
    return this.isRunning;
  }
}
