/**
 * SET 25b — delimiter oracle. Literal rules for every punctuation demarcator:
 * regex literals, quotes, template backticks, braces/brackets/parens,
 * semicolons/commas/colons, arrow `=>`, block comments `/*  \*\/`.
 *
 * Each entry ships with fixtureBefore + fixtureAfter. At gen time we prove:
 *   1. fixtureAfter parses clean via sucrase (`suiteSyntaxError` null)
 *   2. fixtureBefore either fails parse OR matches the entry's detectPattern
 *      (applied to an evalErrorSample when the crash surfaces only at eval)
 *
 * The runtime side (src/tier1/delimiterOracle.ts) doesn't iterate these entries
 * literally; it drives its own candidate healers whose classKeys line up with
 * these ids. The frozen list documents the covered demarcator family and its
 * verified before/after invariants (fixture + mutation) — matches the
 * gen-repair-oracle.ts / gen-testgen-oracle.ts pattern.
 *
 *   Run:  npx tsx scripts/gen-delimiter-oracle.ts
 */
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { suiteSyntaxError } from '../src/tier1/testExec';

const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const OUT = path.join(ROOT, 'src', 'tier1', 'delimiterOracle.generated.ts');

export type DelimiterKind = 'parse-fail' | 'eval-crash';

export interface DelimiterOracleSpec {
  id: string;
  kind: DelimiterKind;
  /** Regex source applied to a parse OR eval error message. */
  detectPattern: string;
  /** Human-readable list of mechanical edits the runtime tries. */
  candidateEdits: string;
  fixtureBefore: string;
  fixtureAfter: string;
  /** Only required for eval-crash kinds (fixture parses clean; crash surfaces at runtime). */
  evalErrorSample?: string;
}

const SPEC: DelimiterOracleSpec[] = [
  {
    id: 'regex-literal',
    kind: 'eval-crash',
    detectPattern: 'Invalid regular expression|Unterminated regular expression',
    candidateEdits: 'heal unterminated /…/: append trailing / on lines with odd unescaped slash count; close char-class ] before the final /; escape intentional slashes',
    fixtureBefore: 'const re = /[a-z+;\n',
    fixtureAfter: 'const re = /[a-z+;]/;\n',
    evalErrorSample: 'Invalid regular expression: missing /',
  },
  {
    id: 'quote-single',
    kind: 'parse-fail',
    detectPattern: 'Unterminated string|Unexpected token',
    candidateEdits: "close unterminated single-quoted string with matching '; strip stray leading/trailing quote when the run is un-paired",
    fixtureBefore: "const x = 'hello\n",
    fixtureAfter: "const x = 'hello';\n",
  },
  {
    id: 'quote-double',
    kind: 'parse-fail',
    detectPattern: 'Unterminated string|Unexpected token',
    candidateEdits: 'close unterminated double-quoted string with matching "; strip stray leading/trailing quote when the run is un-paired',
    fixtureBefore: 'const x = "hello\n',
    fixtureAfter: 'const x = "hello";\n',
  },
  {
    id: 'template-backtick',
    kind: 'parse-fail',
    detectPattern: 'Unterminated (?:template|string)|Unexpected token',
    candidateEdits: 'close unterminated template literal with matching `; balance `${…}` interpolation braces before closing',
    fixtureBefore: 'const x = `hello\n',
    fixtureAfter: 'const x = `hello`;\n',
  },
  {
    id: 'brace',
    kind: 'parse-fail',
    detectPattern: "Unexpected token|Unexpected end of input|expected .?\\}.?",
    candidateEdits: 'append missing } for unclosed block; strip surplus } via parser-guided delete',
    fixtureBefore: 'function f() { return 1;\n',
    fixtureAfter: 'function f() { return 1; }\n',
  },
  {
    id: 'bracket',
    kind: 'parse-fail',
    detectPattern: "Unexpected token|Unexpected end of input|expected .?\\].?",
    candidateEdits: 'append missing ] for unclosed array/index; balance closer stack against ] openers',
    fixtureBefore: 'const a = [1, 2, 3\n',
    fixtureAfter: 'const a = [1, 2, 3];\n',
  },
  {
    id: 'paren',
    kind: 'parse-fail',
    detectPattern: "Unexpected token|Unexpected end of input|expected .?\\).?",
    candidateEdits: 'append missing ) for unclosed call/group; insert ) before .prop when depth > 0 (missing-closer-before-dot)',
    fixtureBefore: 'const x = Math.max(1, 2\n',
    fixtureAfter: 'const x = Math.max(1, 2);\n',
  },
  {
    id: 'comma',
    kind: 'parse-fail',
    detectPattern: "Unexpected (?:token|number)|expected .?,.?",
    candidateEdits: 'insert missing , between call args / array/object elements at the error offset; strip trailing , before closers',
    fixtureBefore: 'const x = Math.max(1 2);\n',
    fixtureAfter: 'const x = Math.max(1, 2);\n',
  },
  {
    id: 'colon-object',
    kind: 'parse-fail',
    detectPattern: 'Unexpected token|expected .?:.?',
    candidateEdits: 'insert missing : between object property key and value at the error offset',
    fixtureBefore: 'const obj = { a 1 };\n',
    fixtureAfter: 'const obj = { a: 1 };\n',
  },
  {
    id: 'arrow',
    kind: 'parse-fail',
    detectPattern: 'Unexpected token|expected .?=>.?',
    candidateEdits: 'insert missing => between arrow parameter list and body block',
    fixtureBefore: 'const f = () { return 1; };\n',
    fixtureAfter: 'const f = () => { return 1; };\n',
  },
  {
    id: 'block-comment',
    kind: 'parse-fail',
    detectPattern: 'Unterminated comment|Unexpected end of input',
    candidateEdits: 'append */ to unclosed /* comment when it runs to EOF',
    fixtureBefore: '/* hello\nconst x = 1;\n',
    fixtureAfter: '/* hello */\nconst x = 1;\n',
  },
];

function verify(t: DelimiterOracleSpec): void {
  const afterErr = suiteSyntaxError(t.fixtureAfter);
  if (afterErr != null) {
    throw new Error(`${t.id}: fixtureAfter must parse clean — sucrase says: ${afterErr}`);
  }
  const beforeErr = suiteSyntaxError(t.fixtureBefore);
  const detect = new RegExp(t.detectPattern);
  const detectHit = beforeErr != null && detect.test(beforeErr);
  const evalHit = t.evalErrorSample != null && detect.test(t.evalErrorSample);
  if (beforeErr == null && !evalHit) {
    throw new Error(
      `${t.id}: fixtureBefore parses clean AND detectPattern doesn't match evalErrorSample — no signal to trigger the entry`,
    );
  }
  if (beforeErr != null && !detectHit && !evalHit) {
    // Parse fails, but neither the parse error nor the eval sample matches detect —
    // the entry's detectPattern won't route this class at runtime.
    throw new Error(
      `${t.id}: fixtureBefore parse error "${beforeErr}" does not match /${t.detectPattern}/`,
    );
  }
  if (!t.candidateEdits.trim() || t.candidateEdits.length < 20) {
    throw new Error(`${t.id}: candidateEdits description too short`);
  }
}

function main() {
  const failures: string[] = [];
  for (const t of SPEC) {
    try { verify(t); }
    catch (e) { failures.push(e instanceof Error ? e.message : String(e)); }
  }
  if (failures.length) {
    console.error('delimiter oracle verification FAILED:\n' + failures.join('\n'));
    process.exit(1);
  }
  const rows = SPEC.map(t => ({
    id: t.id,
    kind: t.kind,
    detectPattern: t.detectPattern,
    candidateEdits: t.candidateEdits,
    fixtureBefore: t.fixtureBefore,
    fixtureAfter: t.fixtureAfter,
    ...(t.evalErrorSample ? { evalErrorSample: t.evalErrorSample } : {}),
  }));
  const src = `/** AUTO-GENERATED by scripts/gen-delimiter-oracle.ts — DO NOT EDIT. */
export type DelimiterOracleKind = 'parse-fail' | 'eval-crash';

export interface DelimiterOracleEntry {
  id: string;
  kind: DelimiterOracleKind;
  /** Regex applied to a parse OR eval error message. */
  detectPattern: string;
  /** Human-readable list of mechanical edits the runtime tries. */
  candidateEdits: string;
  fixtureBefore: string;
  fixtureAfter: string;
  /** Populated only when the crash surfaces at eval time (parse-clean before). */
  evalErrorSample?: string;
}

export const DELIMITER_ORACLE: DelimiterOracleEntry[] = ${JSON.stringify(rows, null, 2)};

/** Match an error message to the first delimiter-oracle entry, or null. */
export function lookupDelimiterOracle(errorMessage: string | undefined | null): DelimiterOracleEntry | null {
  if (!errorMessage) return null;
  for (const e of DELIMITER_ORACLE) {
    if (new RegExp(e.detectPattern).test(errorMessage)) return e;
  }
  return null;
}
`;
  fs.writeFileSync(OUT, src);
  console.log(`froze ${rows.length} verified delimiter-oracle entries → ${OUT}`);
}

main();
