/**
 * @fileoverview Lista instrumentada para captura de trilha de execução.
 *
 * Cada operação relevante (comparar, trocar, ler, escrever) registra um evento
 * na trilha interna. Ao final da execução, o aluno chama getTrace() e a trilha
 * é passada ao SortingValidationEngine para identificar qual algoritmo foi usado.
 *
 * API pública (equivale aos blocos customizados do Blockly):
 *
 *   vm.length()              → tamanho da lista
 *   vm.read(i)               → lê o valor na posição i
 *   vm.write(i, value)       → escreve value na posição i
 *   vm.swap(i, j)            → troca os valores das posições i e j
 *   vm.compare(i, j, op)     → compara arr[i] op arr[j] e retorna boolean
 *   vm.toArray()             → retorna cópia do estado atual
 *   vm.getTrace()            → retorna a trilha de eventos capturada
 *
 * Operadores aceitos por compare(): ">" | "<" | ">=" | "<=" | "==" | "!="
 *
 * Erros de índice lançam exceções com mensagens pedagógicas claras, em vez
 * de silenciosamente retornar undefined.
 */

import { compareEvent, swapEvent, readEvent, writeEvent } from "./ExecutionTrace.js";

export class InstrumentedList {
  /**
   * @param {number[]} initialList - Lista de números a ser ordenada.
   */
  constructor(initialList) {
    if (!Array.isArray(initialList) || initialList.length === 0) {
      throw new Error("InstrumentedList: a lista inicial deve ser um array não vazio.");
    }
    this._arr = [...initialList];
    this._trace = [];
  }

  // ── Acesso ────────────────────────────────────────────────────────────────

  /** Retorna o tamanho da lista. Não gera evento de trilha. */
  length() {
    return this._arr.length;
  }

  /**
   * Lê o valor na posição i.
   * Gera evento { type: "read", i }.
   * @param {number} i
   * @returns {number}
   */
  read(i) {
    this._assertBounds(i, "ler");
    this._trace.push(readEvent(i));
    return this._arr[i];
  }

  /**
   * Escreve value na posição i.
   * Gera evento { type: "write", i, value }.
   * @param {number} i
   * @param {number} value
   */
  write(i, value) {
    this._assertBounds(i, "escrever");
    this._arr[i] = value;
    this._trace.push(writeEvent(i, value));
  }

  /**
   * Troca os valores das posições i e j.
   * Gera evento { type: "swap", i, j }.
   * @param {number} i
   * @param {number} j
   */
  swap(i, j) {
    this._assertBounds(i, "trocar");
    this._assertBounds(j, "trocar");
    [this._arr[i], this._arr[j]] = [this._arr[j], this._arr[i]];
    this._trace.push(swapEvent(i, j));
  }

  /**
   * Compara os valores nas posições i e j usando o operador fornecido.
   * Gera evento { type: "compare", i, j }.
   *
   * @param {number} i
   * @param {number} j
   * @param {">" | "<" | ">=" | "<=" | "==" | "!="} op
   * @returns {boolean}
   */
  compare(i, j, op) {
    this._assertBounds(i, "comparar");
    this._assertBounds(j, "comparar");
    this._trace.push(compareEvent(i, j));
    const a = this._arr[i];
    const b = this._arr[j];
    switch (op) {
      case ">":  return a > b;
      case "<":  return a < b;
      case ">=": return a >= b;
      case "<=": return a <= b;
      case "==": return a === b;
      case "!=": return a !== b;
      default:
        throw new Error(`InstrumentedList: operador inválido "${op}". Use >, <, >=, <=, == ou !=.`);
    }
  }

  // ── Saída ─────────────────────────────────────────────────────────────────

  /** Retorna cópia do estado atual da lista. Não gera eventos. */
  toArray() {
    return [...this._arr];
  }

  /** Retorna a trilha de eventos capturada durante a execução. */
  getTrace() {
    return [...this._trace];
  }

  // ── Interno ───────────────────────────────────────────────────────────────

  _assertBounds(i, operacao) {
    const n = this._arr.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}]. Lembre-se: a lista começa no índice 0.`
      );
    }
  }
}
