/**
 * @fileoverview POC: Validação de algoritmos de ordenação.
 *
 * Executa todos os casos de teste definidos abaixo e imprime um relatório.
 *
 * Uso (Node.js com ESM):
 *   node --experimental-vm-modules src/atividades/programacao/ordenacao/validation/poc/runValidationPoc.js
 *
 * Ou via script no package.json:
 *   "poc:ordenacao": "node src/atividades/programacao/ordenacao/validation/poc/runValidationPoc.js"
 *
 * Critérios de pronto da POC (do estudo.md):
 *   ✓ Detectar corretamente Bubble, Selection e Insertion em casos válidos.
 *   ✓ Rejeitar ao menos 3 casos de falso positivo por perfil.
 *   ✓ Retornar mensagem pedagógica clara em todos os cenários.
 *   ✓ Produzir relatório com detectedAlgorithm, checks e message.
 */

import { InstrumentedList } from "../core/InstrumentedList.js";
import { SortingValidationEngine } from "../core/SortingValidationEngine.js";
import { bubbleProfile }    from "../profiles/bubbleProfile.js";
import { selectionProfile } from "../profiles/selectionProfile.js";
import { insertionProfile } from "../profiles/insertionProfile.js";
import { shellProfile }     from "../profiles/shellProfile.js";
import { countingProfile }  from "../profiles/countingProfile.js";
import { createTraceableArrayProxy } from "../core/createTraceableArrayProxy.js";

// ── Engine singleton ──────────────────────────────────────────────────────────

const engine = new SortingValidationEngine({
  bubble:    bubbleProfile,
  selection: selectionProfile,
  // Shell e Counting ANTES de Insertion: ambos usam read+write sem swap,
  // igual à variante shift do Insertion. Os critérios específicos de cada
  // um (gap>1 para Shell, writes sequenciais para Counting) devem ser
  // verificados primeiro para evitar falso positivo de Insertion.
  shell:     shellProfile,
  counting:  countingProfile,
  insertion: insertionProfile,
});

// ── Implementações de referência ──────────────────────────────────────────────

function bubbleSortCorreto(vm) {
  const n = vm.length();
  for (let i = 0; i < n - 1; i++) {
    for (let j = 0; j < n - 1 - i; j++) {
      if (vm.compare(j, j + 1, ">")) vm.swap(j, j + 1);
    }
  }
}

function bubbleSortComFlag(vm) {
  const n = vm.length();
  for (let i = 0; i < n - 1; i++) {
    let trocou = false;
    for (let j = 0; j < n - 1 - i; j++) {
      if (vm.compare(j, j + 1, ">")) { vm.swap(j, j + 1); trocou = true; }
    }
    if (!trocou) break;
  }
}

function selectionSortCorreto(vm) {
  const n = vm.length();
  for (let i = 0; i < n - 1; i++) {
    let min = i;
    for (let j = i + 1; j < n; j++) {
      if (vm.compare(j, min, "<")) min = j;
    }
    if (min !== i) vm.swap(i, min);
  }
}

function selectionSortMaximo(vm) {
  // Variação: encontra o máximo e posiciona no fim
  const n = vm.length();
  for (let i = n - 1; i > 0; i--) {
    let max = 0;
    for (let j = 1; j <= i; j++) {
      if (vm.compare(j, max, ">")) max = j;
    }
    if (max !== i) vm.swap(i, max);
  }
}

function insertionSortShift(vm) {
  const n = vm.length();
  for (let i = 1; i < n; i++) {
    const chave = vm.read(i);
    let j = i - 1;
    while (j >= 0 && vm.read(j) > chave) {
      vm.write(j + 1, vm.read(j));
      j--;
    }
    vm.write(j + 1, chave);
  }
}

function insertionSortSwap(vm) {
  const n = vm.length();
  for (let i = 1; i < n; i++) {
    let j = i;
    while (j > 0 && vm.compare(j - 1, j, ">")) {
      vm.swap(j - 1, j);
      j--;
    }
  }
}

function shellSortCorreto(vm) {
  const n = vm.length();
  let gap = Math.floor(n / 2);
  while (gap > 0) {
    for (let i = gap; i < n; i++) {
      const temp = vm.read(i);
      let j = i;
      while (j >= gap && vm.read(j - gap) > temp) {
        vm.write(j, vm.read(j - gap));
        j -= gap;
      }
      vm.write(j, temp);
    }
    gap = Math.floor(gap / 2);
  }
}

function countingSortCorreto(vm) {
  const n = vm.length();
  // Determina o máximo sem gerar eventos (leitura fora do vm)
  const snapshot = vm.toArray();
  const max = Math.max(...snapshot);
  const count = new Array(max + 1).fill(0);

  // Fase 1: contar (gera read events)
  for (let i = 0; i < n; i++) count[vm.read(i)]++;

  // Fase 2: reconstruir (gera write events)
  let pos = 0;
  for (let v = 0; v <= max; v++) {
    while (count[v] > 0) {
      vm.write(pos++, v);
      count[v]--;
    }
  }
}

// ── Falsos positivos (algoritmo errado que ordena a lista) ────────────────────

// Usa Selection Sort mas dizemos que é Bubble
function selectionMascaradoComoBubble(vm) { selectionSortCorreto(vm); }

// Usa Bubble Sort mas dizemos que é Insertion
function bubbleMascaradoComoInsertion(vm) { bubbleSortCorreto(vm); }

// Usa Insertion Sort mas dizemos que é Selection
function insertionMascaradoComoSelection(vm) { insertionSortShift(vm); }

// Usa Bubble Sort mas dizemos que é Shell
function bubbleMascaradoComoShell(vm) { bubbleSortCorreto(vm); }

// Usa Selection Sort mas dizemos que é Counting
function selectionMascaradoComoCounting(vm) { selectionSortCorreto(vm); }

// Solução ingênua: copia a lista ordenada diretamente (sem algoritmo)
function cheatSort(vm) {
  const sorted = vm.toArray().sort((a, b) => a - b);
  for (let i = 0; i < sorted.length; i++) vm.write(i, sorted[i]);
}

// ── Implementações no modo REAL (blocos nativos: lista[i] = ... ) ───────────

function bubbleSortReal(lista) {
  const n = lista.length;
  for (let i = 0; i < n - 1; i++) {
    for (let j = 0; j < n - 1 - i; j++) {
      if (lista[j] > lista[j + 1]) {
        const tmp = lista[j];
        lista[j] = lista[j + 1];
        lista[j + 1] = tmp;
      }
    }
  }
}

function selectionSortReal(lista) {
  const n = lista.length;
  for (let i = 0; i < n - 1; i++) {
    let min = i;
    for (let j = i + 1; j < n; j++) {
      if (lista[j] < lista[min]) min = j;
    }
    if (min !== i) {
      const tmp = lista[i];
      lista[i] = lista[min];
      lista[min] = tmp;
    }
  }
}

function insertionSortReal(lista) {
  for (let i = 1; i < lista.length; i++) {
    const chave = lista[i];
    let j = i - 1;
    while (j >= 0 && lista[j] > chave) {
      lista[j + 1] = lista[j];
      j--;
    }
    lista[j + 1] = chave;
  }
}

function shellSortReal(lista) {
  let gap = Math.floor(lista.length / 2);
  while (gap > 0) {
    for (let i = gap; i < lista.length; i++) {
      const temp = lista[i];
      let j = i;
      while (j >= gap && lista[j - gap] > temp) {
        lista[j] = lista[j - gap];
        j -= gap;
      }
      lista[j] = temp;
    }
    gap = Math.floor(gap / 2);
  }
}

function countingSortReal(lista) {
  const max = Math.max(...lista);
  const count = new Array(max + 1).fill(0);
  for (let i = 0; i < lista.length; i++) count[lista[i]]++;
  let pos = 0;
  for (let v = 0; v <= max; v++) {
    while (count[v] > 0) {
      lista[pos++] = v;
      count[v]--;
    }
  }
}

// ── Definição dos casos de teste ──────────────────────────────────────────────

const casos = [
  // ── Bubble Sort ────────────────────────────────────────────────────────────
  {
    descricao: "[BUBBLE] implementação padrão",
    lista: [5, 2, 8, 1, 9],
    solucao: bubbleSortCorreto,
    expectedAlgorithm: "bubble",
    esperado: true,
  },
  {
    descricao: "[BUBBLE] com flag de parada antecipada (otimização)",
    lista: [3, 1, 4, 2, 5],
    solucao: bubbleSortComFlag,
    expectedAlgorithm: "bubble",
    esperado: true,
  },
  {
    descricao: "[BUBBLE] lista já ordenada (zero trocas, otimização encerra rápido)",
    lista: [1, 2, 3, 4, 5],
    solucao: bubbleSortComFlag,
    expectedAlgorithm: "bubble",
    esperado: true,
  },

  // ── Selection Sort ─────────────────────────────────────────────────────────
  {
    descricao: "[SELECTION] implementação padrão (busca mínimo)",
    lista: [4, 7, 1, 6, 3],
    solucao: selectionSortCorreto,
    expectedAlgorithm: "selection",
    esperado: true,
  },
  {
    descricao: "[SELECTION] variação: busca máximo e posiciona no fim",
    lista: [8, 2, 6, 1, 4, 7, 3],
    solucao: selectionSortMaximo,
    expectedAlgorithm: "selection",
    esperado: true,
  },

  // ── Insertion Sort ─────────────────────────────────────────────────────────
  {
    descricao: "[INSERTION] variante shift (deslocamento com write)",
    lista: [6, 3, 8, 2, 9],
    solucao: insertionSortShift,
    expectedAlgorithm: "insertion",
    esperado: true,
  },
  {
    descricao: "[INSERTION] variante swap (trocas adjacentes para trás)",
    lista: [5, 9, 2, 8, 4, 1, 7],
    solucao: insertionSortSwap,
    expectedAlgorithm: "insertion",
    esperado: true,
  },
  {
    descricao: "[INSERTION] lista quase ordenada (um elemento fora do lugar)",
    lista: [1, 2, 4, 3, 5],
    solucao: insertionSortShift,
    expectedAlgorithm: "insertion",
    esperado: true,
  },

  // ── Shell Sort ─────────────────────────────────────────────────────────────
  {
    descricao: "[SHELL] implementação com gap = n/2",
    lista: [8, 3, 6, 1, 9, 2, 7, 4],
    solucao: shellSortCorreto,
    expectedAlgorithm: "shell",
    esperado: true,
  },

  // ── Counting Sort ──────────────────────────────────────────────────────────
  {
    descricao: "[COUNTING] implementação padrão",
    lista: [3, 1, 4, 1, 5, 9, 2, 6],
    solucao: countingSortCorreto,
    expectedAlgorithm: "counting",
    esperado: true,
  },

  // ── Falsos positivos: algoritmo certo mas solução errada ───────────────────
  {
    descricao: "[FALSO POSITIVO] Selection Sort declarado como Bubble",
    lista: [5, 2, 8, 1, 9],
    solucao: selectionMascaradoComoBubble,
    expectedAlgorithm: "bubble",
    esperado: false,
  },
  {
    descricao: "[FALSO POSITIVO] Bubble Sort declarado como Insertion",
    lista: [5, 2, 8, 1, 9],
    solucao: bubbleMascaradoComoInsertion,
    expectedAlgorithm: "insertion",
    esperado: false,
  },
  {
    descricao: "[FALSO POSITIVO] Insertion Sort declarado como Selection",
    lista: [6, 3, 8, 2, 9],
    solucao: insertionMascaradoComoSelection,
    expectedAlgorithm: "selection",
    esperado: false,
  },
  {
    descricao: "[FALSO POSITIVO] Bubble Sort declarado como Shell",
    lista: [5, 2, 8, 1, 9],
    solucao: bubbleMascaradoComoShell,
    expectedAlgorithm: "shell",
    esperado: false,
  },
  {
    descricao: "[FALSO POSITIVO] Selection Sort declarado como Counting",
    lista: [4, 7, 1, 6, 3],
    solucao: selectionMascaradoComoCounting,
    expectedAlgorithm: "counting",
    esperado: false,
  },
  {
    descricao: "[FALSO POSITIVO] cheatSort (escreve direto sem algoritmo) declarado como Bubble",
    lista: [5, 2, 8, 1, 9],
    solucao: cheatSort,
    expectedAlgorithm: "bubble",
    esperado: false,
  },

  // ── Erros de resultado ─────────────────────────────────────────────────────
  {
    descricao: "[ERRO] lista não ordenada (Bubble Sort com loop errado)",
    lista: [5, 2, 8, 1, 9],
    solucao: (vm) => {
      // Loop externo com apenas 1 passada — não ordena a lista toda
      const n = vm.length();
      for (let j = 0; j < n - 1; j++) {
        if (vm.compare(j, j + 1, ">")) vm.swap(j, j + 1);
      }
    },
    expectedAlgorithm: "bubble",
    esperado: false,
  },
  {
    descricao: "[ERRO] valores alterados (sobrescreve sem variável auxiliar)",
    lista: [5, 2, 8, 1, 9],
    solucao: (vm) => {
      // Troca errada: perde um valor
      vm.write(0, vm.read(1)); // sobrescreve arr[0] sem guardar
      vm.write(1, vm.read(0));
    },
    expectedAlgorithm: "bubble",
    esperado: false,
  },
];

const casosReais = [
  {
    descricao: "[REAL/BUBBLE] acesso por índice + troca com variável temporária",
    lista: [5, 2, 8, 1, 9],
    solucao: bubbleSortReal,
    expectedAlgorithm: "bubble",
    esperado: true,
  },
  {
    descricao: "[REAL/SELECTION] acesso por índice + mínimo por varredura",
    lista: [4, 7, 1, 6, 3],
    solucao: selectionSortReal,
    expectedAlgorithm: "selection",
    esperado: true,
  },
  {
    descricao: "[REAL/INSERTION] deslocamento com writes",
    lista: [6, 3, 8, 2, 9],
    solucao: insertionSortReal,
    expectedAlgorithm: "insertion",
    esperado: true,
  },
  {
    descricao: "[REAL/SHELL] gap decrescente",
    lista: [8, 3, 6, 1, 9, 2, 7, 4],
    solucao: shellSortReal,
    expectedAlgorithm: "shell",
    esperado: true,
  },
  {
    descricao: "[REAL/COUNTING] contagem e reconstrução",
    lista: [3, 1, 4, 1, 5, 2, 6, 0],
    solucao: countingSortReal,
    expectedAlgorithm: "counting",
    esperado: true,
  },
  {
    descricao: "[REAL/FALSO POSITIVO] Selection declarado como Bubble",
    lista: [5, 2, 8, 1, 9],
    solucao: selectionSortReal,
    expectedAlgorithm: "bubble",
    esperado: false,
  },
  {
    descricao: "[REAL/FALSO POSITIVO] Bubble declarado como Counting",
    lista: [5, 2, 8, 1, 9],
    solucao: bubbleSortReal,
    expectedAlgorithm: "counting",
    esperado: false,
  },
];

// ── Runner ────────────────────────────────────────────────────────────────────

function runCase(caso) {
  const initial = [...caso.lista];
  const vm = new InstrumentedList(initial);

  let runtimeError = null;
  try {
    caso.solucao(vm);
  } catch (err) {
    runtimeError = err.message;
  }

  const finalList = vm.toArray();
  const trace     = vm.getTrace();

  const result = runtimeError
    ? {
        success: false,
        message: `Erro durante a execução: ${runtimeError}`,
        detectedAlgorithm: "error",
        checks: { valuesPreserved: false, sorted: false, algorithmMatch: false },
        debug: { traceSize: trace.length },
      }
    : engine.validate({
        initialList: initial,
        finalList,
        trace,
        expectedAlgorithm: caso.expectedAlgorithm,
      });

  const passou = result.success === caso.esperado;
  return { caso, result, passou };
}

function runCaseReal(caso) {
  const initial = [...caso.lista];
  const final = [...initial];
  const historico = [];
  const lista = createTraceableArrayProxy(final, historico, {
    trackReads: true,
    inferSwap: true,
  });

  let runtimeError = null;
  try {
    caso.solucao(lista);
  } catch (err) {
    runtimeError = err.message;
  }

  const result = runtimeError
    ? {
        success: false,
        message: `Erro durante a execução: ${runtimeError}`,
        detectedAlgorithm: "error",
        checks: { valuesPreserved: false, sorted: false, algorithmMatch: false },
        debug: { traceSize: historico.length },
      }
    : engine.validate({
        initialList: initial,
        finalList: final,
        trace: historico,
        expectedAlgorithm: caso.expectedAlgorithm,
      });

  const passou = result.success === caso.esperado;
  return { caso, result, passou };
}

function imprimir(resultados, titulo) {
  let aprovados = 0;
  let reprovados = 0;

  console.log("\n══════════════════════════════════════════════════════════════");
  console.log(`  ${titulo}`);
  console.log("══════════════════════════════════════════════════════════════\n");

  for (const { caso, result, passou } of resultados) {
    const icone = passou ? "✓" : "✗";
    const status = passou ? "OK" : "FALHOU";
    console.log(`${icone} ${caso.descricao}`);
    console.log(`  Esperado: ${caso.esperado ? "sucesso" : "falha"} | Obtido: ${result.success ? "sucesso" : "falha"} | ${status}`);
    console.log(`  Algoritmo detectado: ${result.detectedAlgorithm}`);
    console.log(`  Mensagem: ${result.message}`);
    if (result.debug) {
      const d = result.debug;
      const parts = [];
      if (d.compareCount !== undefined) parts.push(`compares=${d.compareCount}`);
      if (d.swapCount    !== undefined) parts.push(`swaps=${d.swapCount}`);
      if (d.writeCount   !== undefined) parts.push(`writes=${d.writeCount}`);
      if (d.readCount    !== undefined) parts.push(`reads=${d.readCount}`);
      parts.push(`trace=${d.traceSize}`);
      console.log(`  Debug: ${parts.join(", ")}`);
    }
    console.log();
    passou ? aprovados++ : reprovados++;
  }

  console.log("══════════════════════════════════════════════════════════════");
  console.log(`  Resultado: ${aprovados} aprovados, ${reprovados} reprovados (total: ${resultados.length})`);
  console.log("══════════════════════════════════════════════════════════════\n");

  if (reprovados > 0) process.exit(1);
}

// ── Entrada principal ─────────────────────────────────────────────────────────

const resultadosInstrumented = casos.map(runCase);
imprimir(resultadosInstrumented, "POC — Validação (modo InstrumentedList)");

const resultadosReais = casosReais.map(runCaseReal);
imprimir(resultadosReais, "POC — Validação (modo Atividade Real / Proxy)");
