/**
 * @fileoverview Configuração da API do interpretador para o jogo Ordenação.
 * @module games.ordenacao.hooks.interpreterSetup
 */

import { ApiHelpers } from "../../../../interpreters/ApiHelpers.js";
import { createTraceableArrayProxy } from "../validation/core/createTraceableArrayProxy.js";

const toNative = (value) => {
  if (value && typeof value === "object" && "data" in value) return value.data;
  return value;
};

const toIndex = (value) => Number(toNative(value));

const assertIndex = (scene, i, operacao) => {
  const n = scene.lista.length;
  if (!Number.isInteger(i) || i < 0 || i >= n) {
    throw new Error(
      `Índice inválido ao tentar ${operacao}: posição ${i} está fora dos limites [0, ${n - 1}].`,
    );
  }
};

const assertIndexContagem = (scene, i, operacao) => {
  if (!Number.isInteger(i) || i < 0) {
    throw new Error(
      `Índice inválido ao tentar ${operacao} na contagem: posição ${i} é inválida (deve ser inteiro >= 0).`,
    );
  }
  if (operacao === "ler" && scene.contagem.length > 0 && i >= scene.contagem.length) {
    const maxIdx = scene.contagem.length - 1;
    throw new Error(
      `Índice inválido ao tentar ${operacao} na contagem: posição ${i} está fora dos limites [0, ${maxIdx}].`,
    );
  }
};

/**
 * Cria um proxy para o array que sincroniza mutações
 * com scene.lista. Necessário porque interpreter.nativeToPseudo()
 * cria uma cópia, não uma referência.
 *
 * @param {Array} originalArray - o scene.lista original
 * @param {Object} interpreter - instância do js-interpreter
 * @returns {Proxy} proxy que reflete mudanças de volta ao original
 */
const createArrayProxy = (originalArray, historico) =>
  createTraceableArrayProxy(originalArray, historico, {
    trackReads: true,
    inferSwap: true,
  });

/**
 * Registra a API mínima para a atividade de ordenação.
 *
 * Funções/valores expostos:
 *   - lista                    - array com os números da fase (com proxy de sincronização)
 *   - highlightBlock(id)       - destaque de bloco (padrão da plataforma)
 *
 * @param {Object} scene - Instância de OrdenacaoScene
 * @returns {Function} Callback (interpreter, globalScope) para GameInterpreter
 */
export const setupOrdenacaoAPI = (scene) => {
  return (interpreter, globalScope) => {
    // Proxy sincroniza mutações e coleta trilha de eventos no scene.historico.
    const listaProxy = createArrayProxy(scene.lista, scene.historico);

    interpreter.setProperty(
      globalScope,
      "lista",
      interpreter.nativeToPseudo(listaProxy),
    );

    ApiHelpers.registerFunction(
      interpreter,
      globalScope,
      "lista_tamanho",
      () => scene.lista.length,
      false,
    );

    ApiHelpers.registerFunction(
      interpreter,
      globalScope,
      "lista_ler",
      (iArg) => {
        const i = toIndex(iArg);
        assertIndex(scene, i, "ler");
        scene.historico.push({ type: "read", i });
        return scene.lista[i];
      },
      false,
    );

    ApiHelpers.registerFunction(
      interpreter,
      globalScope,
      "lista_escrever",
      (iArg, valueArg, callback) => {
        const i = toIndex(iArg);
        const value = Number(toNative(valueArg));
        assertIndex(scene, i, "escrever");
        scene.lista[i] = value;
        scene.historico.push({ type: "write", i, value });

        Promise.resolve(scene.animarEscrita(i))
          .then(() => callback())
          .catch(() => callback());
      },
      true,
    );

    ApiHelpers.registerFunction(
      interpreter,
      globalScope,
      "lista_contagem_ler",
      (iArg) => {
        const i = toIndex(iArg);
        assertIndexContagem(scene, i, "ler");
        scene.historico.push({ type: "contagem_read", i });
        return scene.contagem[i] ?? 0;
      },
      false,
    );

    ApiHelpers.registerFunction(
      interpreter,
      globalScope,
      "lista_contagem_escrever",
      (iArg, valueArg, callback) => {
        const i = toIndex(iArg);
        const value = Number(toNative(valueArg));
        assertIndexContagem(scene, i, "escrever");
        scene.contagem[i] = value;
        scene.historico.push({ type: "contagem_write", i, value });

        Promise.resolve(scene.animarEscrita(i))
          .then(() => callback())
          .catch(() => callback());
      },
      true,
    );

    ApiHelpers.registerFunction(
      interpreter,
      globalScope,
      "lista_trocar",
      (iArg, jArg, callback) => {
        const i = toIndex(iArg);
        const j = toIndex(jArg);
        assertIndex(scene, i, "trocar");
        assertIndex(scene, j, "trocar");
        const temp = scene.lista[i];
        scene.lista[i] = scene.lista[j];
        scene.lista[j] = temp;
        scene.historico.push({ type: "swap", i, j });

        Promise.resolve(scene.animarTroca(i, j))
          .then(() => callback())
          .catch(() => callback());
      },
      true,
    );

    ApiHelpers.registerFunction(
      interpreter,
      globalScope,
      "lista_comparar",
      (iArg, jArg, opArg, callback) => {
        const i = toIndex(iArg);
        const j = toIndex(jArg);
        const op = String(toNative(opArg));
        assertIndex(scene, i, "comparar");
        assertIndex(scene, j, "comparar");
        scene.historico.push({ type: "compare", i, j });

        const a = scene.lista[i];
        const b = scene.lista[j];
        let result;
        switch (op) {
          case ">": result = a > b; break;
          case "<": result = a < b; break;
          case ">=": result = a >= b; break;
          case "<=": result = a <= b; break;
          case "==": result = a === b; break;
          case "!=": result = a !== b; break;
          default:
            throw new Error(`Operador inválido em lista_comparar: ${op}`);
        }

        Promise.resolve(scene.animarComparacao(i, j))
          .then(() => callback(result))
          .catch(() => callback(result));
      },
      true,
    );

    ApiHelpers.registerFunction(
      interpreter,
      globalScope,
      "highlightBlock",
      ApiHelpers.createHighlightWrapper(scene),
      false,
    );
  };
};
