/**
 * Oracle generator — the offline "compile" step for the diagnostic oracle.
 *
 * The LLM's quicksort (which fix a diagnostic class needs) is done HERE, once,
 * ahead of time, and each handler is VERIFIED against fixtures by actually
 * running the deterministic primitive. Only verified handlers are frozen into
 * src/core/oracle.generated.ts. A handler whose fixtures fail cannot be frozen
 * — the substrate stays uncorruptible because nothing unverified enters it, and
 * the runtime never calls an LLM to classify or fix a mechanical error.
 *
 *   Run:  npx tsx gen-oracle.ts
 */
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { deterministicFix, routeTarget } from './src/core/diagnostics';
import type { TscDiagnostic } from './src/core/sandbox';

const ROOT = path.dirname(fileURLToPath(import.meta.url));

type Category = 'mechanical' | 'semantic';
interface Fixture { message: string; file?: string; line?: number; before?: string; after?: string }
interface Spec {
  name: string;
  codes: number[];
  category: Category;          // mechanical → a verified primitive fixes it; semantic → bounded LLM with the hint
  route: 'reporter' | 'definer';
  hint: string;               // the scoped instruction handed to the LLM when no deterministic fix applies
  fixtures: Fixture[];        // mechanical fixtures assert before→after; semantic fixtures assert route/hint
}

// ---- THE SPEC: the bell-curve of diagnostics that actually occur in machine
// ---- codegen (type errors are ~94% of LLM compile failures; a handful of
// ---- codes dominate). Author once, verify, freeze. Extend by adding rows.
const SPEC: Spec[] = [
  {
    name: 'declared-locally-not-exported', codes: [2459], category: 'mechanical', route: 'definer',
    hint: 'export the symbol a dependent imports from this file',
    fixtures: [{
      message: `Module '"./page_manager"' declares 'Page' locally, but it is not exported.`,
      before: `interface Page { id: string }\nexport function get() { throw new Error('stub'); }`,
      after: `export interface Page { id: string }\nexport function get() { throw new Error('stub'); }`,
    }],
  },
  {
    name: 'did-you-mean-rename', codes: [2551, 2552, 2820], category: 'mechanical', route: 'reporter',
    hint: 'the compiler suggested the correct identifier — use it',
    fixtures: [{
      message: `Cannot find name 'createuser'. Did you mean 'createUser'?`, line: 1,
      before: `const u = createuser('a');`, after: `const u = createUser('a');`,
    }],
  },
  {
    name: 'no-exported-member', codes: [2305, 2614, 2724], category: 'semantic', route: 'definer',
    hint: 'a dependent imports a member this file does not provide — export it, or ADD it (throwing stub) if missing; never remove existing declarations',
    fixtures: [{ message: `Module '"./m"' has no exported member 'resolveLink'.` }],
  },
  { name: 'cannot-find-module', codes: [2307], category: 'semantic', route: 'reporter',
    hint: 'the import path is wrong or the module is not owned here — fix the path or drop the import',
    fixtures: [{ message: `Cannot find module './x'.` }] },
  { name: 'cannot-find-name', codes: [2304], category: 'semantic', route: 'reporter',
    hint: 'this name is undefined — import it from its module, or define it',
    fixtures: [{ message: `Cannot find name 'Foo'.` }] },
  { name: 'property-does-not-exist', codes: [2339], category: 'semantic', route: 'reporter',
    hint: 'this property/method is not on the type — call only what the stub declares',
    fixtures: [{ message: `Property 'foo' does not exist on type 'Bar'.` }] },
  { name: 'arg-not-assignable', codes: [2345], category: 'semantic', route: 'reporter',
    hint: 'the argument type does not match the declared signature — conform to the stub, never weaken it',
    fixtures: [{ message: `Argument of type 'string' is not assignable to parameter of type 'number'.` }] },
  { name: 'type-not-assignable', codes: [2322], category: 'semantic', route: 'reporter',
    hint: 'the value type does not match the declared type — conform to the stub, never weaken it',
    fixtures: [{ message: `Type 'string' is not assignable to type 'number'.` }] },
  { name: 'wrong-arg-count', codes: [2554, 2555], category: 'semantic', route: 'reporter',
    hint: 'argument-count mismatch — pre-freeze this routes to the CONTRACT (widen the declared parameter list to what callers pass); post-freeze conform the call to the declared arity',
    fixtures: [{ message: `Expected 2 arguments, but got 1.` }] },
  { name: 'implicit-any-param', codes: [7006], category: 'semantic', route: 'reporter',
    hint: 'the parameter needs an explicit type annotation from the declared signature',
    fixtures: [{ message: `Parameter 'x' implicitly has an 'any' type.` }] },
  { name: 'syntax', codes: [1005, 1109, 1128, 1002, 1003, 1131, 1155, 1183], category: 'semantic', route: 'reporter',
    hint: 'syntax error — fix the punctuation/structure so the file parses',
    fixtures: [{ message: `';' expected.` }] },
  // ---- mechanical: unused import (fires constantly in generated code) --------
  {
    name: 'unused-import', codes: [6133, 6196], category: 'mechanical', route: 'reporter',
    hint: 'remove the unused import',
    fixtures: [
      { message: `'foo' is declared but its value is never read.`, line: 1,
        before: `import { a, foo, b } from './m';\nconst x = a + b;`,
        after: `import { a, b } from './m';\nconst x = a + b;` },
      { message: `'foo' is declared but its value is never read.`, line: 1,
        before: `import { foo } from './m';\nconst x = 1;`,
        after: `const x = 1;` },
    ],
  },
  // ---- semantic classifications (route + scoped hint; bounded LLM applies) ---
  { name: 'missing-required-props', codes: [2739, 2740, 2741], category: 'semantic', route: 'reporter',
    hint: 'the value is missing properties its declared type requires — provide them per the stub; do not cast to bypass the type',
    fixtures: [{ message: `Type '{}' is missing the following properties from type 'Page': id, title` }] },
  { name: 'no-overload-matches', codes: [2769], category: 'semantic', route: 'reporter',
    hint: 'no declared overload matches — call with the exact signature the stub declares',
    fixtures: [{ message: `No overload matches this call.` }] },
  { name: 'possibly-nullish', codes: [2531, 2532, 2533, 18047, 18048], category: 'semantic', route: 'reporter',
    hint: 'the value may be null/undefined — narrow or guard per the declared type; call only what the stub guarantees',
    fixtures: [{ message: `'page' is possibly 'undefined'.` }] },
  { name: 'value-is-unknown', codes: [18046, 2571], category: 'semantic', route: 'reporter',
    hint: 'the value is of an unnarrowed type — narrow it before use per the stub',
    fixtures: [{ message: `'x' is of type 'unknown'.` }] },
  { name: 'used-before-assigned', codes: [2454, 2448], category: 'semantic', route: 'reporter',
    hint: 'the identifier is used before it is assigned — assign it first or reorder',
    fixtures: [{ message: `Variable 'x' is used before being assigned.` }] },
  { name: 'duplicate-identifier', codes: [2451, 2300], category: 'semantic', route: 'reporter',
    hint: 'the identifier is declared twice — remove the duplicate declaration',
    fixtures: [{ message: `Cannot redeclare block-scoped variable 'x'.` }] },
  { name: 'value-used-as-type', codes: [2749], category: 'semantic', route: 'reporter',
    hint: 'a value is being used where a type is expected — reference the type, or use `typeof value`',
    fixtures: [{ message: `'Page' refers to a value, but is being used as a type here.` }] },
  { name: 'implements-signature-mismatch', codes: [2416, 2420], category: 'semantic', route: 'reporter',
    hint: 'the member does not match the interface it implements — match the declared signature exactly',
    fixtures: [{ message: `Property 'get' in type 'PageStore' is not assignable to the same property in base type` }] },
  { name: 'not-callable-or-constructable', codes: [2348, 2349, 2351], category: 'semantic', route: 'reporter',
    hint: 'this value is not callable/constructable as used — call it the way the stub declares',
    fixtures: [{ message: `This expression is not callable.` }] },
  { name: 'unterminated-regex', codes: [1161], category: 'semantic', route: 'reporter',
    hint: 'unterminated regular expression literal — REWRITE using plain string scanning (indexOf/slice/split) instead of a regex literal; malformed regexes are a known failure class for generated code',
    fixtures: [{ message: `Unterminated regular expression literal.` }] },
];

// ---- VERIFY -----------------------------------------------------------------
const d = (o: Partial<TscDiagnostic>): TscDiagnostic => ({ file: 'src/x.ts', line: 1, code: 0, message: '', ...o });
const failures: string[] = [];
for (const s of SPEC) {
  const code = s.codes[0];
  for (const f of s.fixtures) {
    const diag = d({ code, message: f.message, file: f.file ?? 'src/x.ts', line: f.line ?? 1 });
    if (s.category === 'mechanical') {
      if (f.before == null || f.after == null) { failures.push(`${s.name}: mechanical fixture missing before/after`); continue; }
      const got = deterministicFix(diag, f.before);
      if (got !== f.after) failures.push(`${s.name} (TS${code}): deterministic fix mismatch\n  expected: ${JSON.stringify(f.after)}\n  got:      ${JSON.stringify(got)}`);
    } else {
      // semantic: assert we route as declared and carry a non-empty hint
      const routed = routeTarget(diag).rerouted ? 'definer' : 'reporter';
      if (routed !== s.route) failures.push(`${s.name} (TS${code}): route ${routed} ≠ declared ${s.route}`);
      if (!s.hint.trim()) failures.push(`${s.name} (TS${code}): empty hint`);
    }
  }
}
if (failures.length) {
  console.error(`\n✕ oracle generation FAILED — ${failures.length} handler(s) could not be verified:\n- ${failures.join('\n- ')}\n`);
  process.exit(1);
}

// ---- EMIT the frozen table --------------------------------------------------
const byCode: Record<number, { name: string; category: Category; route: string; hint: string }> = {};
for (const s of SPEC) for (const c of s.codes) byCode[c] = { name: s.name, category: s.category, route: s.route, hint: s.hint };
const mechanical = SPEC.filter(s => s.category === 'mechanical').flatMap(s => s.codes);
const semantic = SPEC.filter(s => s.category === 'semantic').flatMap(s => s.codes);

const out = `/**
 * AUTO-GENERATED by gen-oracle.ts — do NOT edit by hand.
 * Frozen, verified diagnostic-oracle table. Every handler below passed its
 * fixtures at generation time; the runtime uses this table with ZERO LLM calls
 * for classification, routing, or mechanical repair.
 *
 * Verified: ${new Date().toISOString()}
 * Coverage: ${Object.keys(byCode).length} codes (${mechanical.length} mechanical, ${semantic.length} semantic).
 */
export interface OracleEntry { name: string; category: 'mechanical' | 'semantic'; route: 'reporter' | 'definer'; hint: string; }

export const ORACLE_BY_CODE: Record<number, OracleEntry> = ${JSON.stringify(byCode, null, 2)};

export const ORACLE_COVERAGE = {
  codes: ${JSON.stringify(Object.keys(byCode).map(Number))},
  mechanical: ${JSON.stringify(mechanical)},
  semantic: ${JSON.stringify(semantic)},
  verifiedAt: ${JSON.stringify(new Date().toISOString())},
} as const;
`;
fs.writeFileSync(path.join(ROOT, 'src', 'core', 'oracle.generated.ts'), out);
console.log(`✓ oracle verified & frozen: ${Object.keys(byCode).length} codes (${mechanical.length} mechanical, ${semantic.length} semantic) → src/core/oracle.generated.ts`);
