/**
 * SET 2b — testgen template oracle. Each template ships with a conforming
 * reference impl (must PASS) and ≥2 mutants (must FAIL). Nothing unverified
 * is frozen into src/tier1/testgenOracle.generated.ts.
 *
 *   Run:  npx tsx scripts/gen-testgen-oracle.ts
 */
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { makeEvalSuiteRunner } from '../src/tier1/testExec';
import { assembleTestFile } from '../src/core/irScaffold';
import type { FeatureNode } from '../src/core/types';

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

export interface TemplateSpec {
  id: string;
  /** Clade family hint (SET 3 registry). */
  family: string;
  branch: 'algebraic' | 'contract' | 'state' | 'representation' | 'freeform';
  /** Match simple signatures. */
  paramCount: number;
  paramTypes: string[];
  returnType: string;
  /** Edge-case substring that licenses this template, or 'export' for smoke. */
  license: 'export' | string;
  /** Body factory — `SYM` replaced with the export symbol. */
  body: string;
  reference: string;
  mutants: string[];
}

const SPEC: TemplateSpec[] = [
  {
    id: 'smoke-binary-number',
    family: 'determinism',
    branch: 'algebraic',
    paramCount: 2,
    paramTypes: ['number', 'number'],
    returnType: 'number',
    license: 'export',
    body: 'expect(typeof SYM(1, 2)).toBe(\'number\');\nexpect(Number.isFinite(SYM(1, 2))).toBe(true);',
    reference: 'export function add(a: number, b: number): number { return a + b; }\n',
    mutants: [
      'export function add(a: number, b: number): number { return NaN; }\n',
      'export function add(_a: number, _b: number): number { throw new Error(\'no\'); }\n',
    ],
  },
  {
    id: 'smoke-unary-string',
    family: 'determinism',
    branch: 'algebraic',
    paramCount: 1,
    paramTypes: ['string'],
    returnType: 'string',
    license: 'export',
    body: 'expect(typeof SYM(\'x\')).toBe(\'string\');',
    reference: 'export function id(s: string): string { return s; }\n',
    mutants: [
      'export function id(_s: string): string { return 1 as unknown as string; }\n',
      'export function id(_s: string): string { throw new Error(\'no\'); }\n',
    ],
  },
  {
    id: 'rejects-empty-string',
    family: 'rejects-empty',
    branch: 'contract',
    paramCount: 1,
    paramTypes: ['string'],
    returnType: 'string',
    license: 'empty',
    body: 'expect(() => SYM(\'\')).toThrow();',
    reference: 'export function parse(s: string): string { if (!s) throw new Error(\'empty\'); return s; }\n',
    mutants: [
      'export function parse(s: string): string { return s; }\n',
      'export function parse(_s: string): string { return \'ok\'; }\n',
    ],
  },
  {
    id: 'roundtrip-string',
    family: 'roundtrip',
    branch: 'algebraic',
    paramCount: 1,
    paramTypes: ['string'],
    returnType: 'string',
    license: 'roundtrip',
    body: 'expect(SYM(\'  ab  \')).toBe(\'ab\');\nexpect(SYM(\'ab\')).toBe(\'ab\');',
    reference: 'export function norm(s: string): string { return s.trim(); }\n',
    mutants: [
      'export function norm(s: string): string { return s + \'!\'; }\n',
      'export function norm(_s: string): string { return \'x\'; }\n',
    ],
  },
  // SET 31d planner half — *Model class-anchor clade (largest recurring coverage loss).
  {
    id: 'model-schema-construct',
    family: 'model-schema',
    branch: 'state',
    paramCount: 1,
    paramTypes: ['Record<string, unknown>'],
    returnType: 'ArticleModel',
    license: 'model-schema',
    body: 'expect(() => new SYM({})).not.toThrow();\nexpect(new SYM({})).toBeInstanceOf(SYM);',
    reference: 'export class ArticleModel { constructor(_fields: Record<string, unknown>) {} }\n',
    mutants: [
      'export class ArticleModel { constructor(_fields: Record<string, unknown>) { throw new Error(\'no\'); } }\n',
      'export class ArticleModel { constructor(_fields: Record<string, unknown>) { throw new TypeError(\'bad\'); } }\n',
    ],
  },
  // SET 31d planner half — uses <dependency> integration clade.
  // Presence/typeof only: dep symbols may be functions OR classes (*Model);
  // bare SYM() throws on class constructors, so the seam contract is "exported
  // callable/constructable binding exists" — enough to substrate-fill IR.
  {
    id: 'uses-dep-seam',
    family: 'uses-dep',
    branch: 'contract',
    paramCount: 0,
    paramTypes: [],
    returnType: 'unknown',
    license: 'uses-dep',
    body: 'expect(SYM).toBeDefined();\nexpect(typeof SYM).toBe(\'function\');',
    reference: 'export function seam(): number { return 1; }\n',
    mutants: [
      'export const seam = null as unknown as () => number;\n',
      'export const seam = 42 as unknown as () => number;\n',
    ],
  },
];

function fakeFeature(symbol: string, stub: string): FeatureNode {
  return {
    id: 'F1', name: symbol, description: symbol, parentId: null,
    deps: [], files: ['src/m.ts'], testFile: 'tests/f1.test.ts',
    edgeCases: [], envVars: [],
    exports: [{ file: 'src/m.ts', symbol }],
    stubs: [{ path: 'src/m.ts', content: stub }],
  };
}

async function verify(t: TemplateSpec): Promise<void> {
  const run = makeEvalSuiteRunner();
  // Function, class, or const — SET 31d model-schema templates use `export class`.
  const sym = t.reference.match(/(?:function|class|const|let)\s+([A-Za-z_$][\w$]*)/)?.[1];
  if (!sym) throw new Error(`${t.id}: cannot parse symbol from reference`);
  const body = t.body.replace(/SYM/g, sym);
  const feature = fakeFeature(sym, t.reference);
  const kind = t.family === 'uses-dep' ? 'integration' as const : 'behavior' as const;
  const suite = assembleTestFile(
    feature,
    [{ from: 'src/m', symbols: [sym] }],
    [{ name: t.id, kind, body }],
  );
  const ref = await run(
    [{ path: 'src/m.ts', content: t.reference }],
    { path: feature.testFile, content: suite },
  );
  if (ref.crashed || ref.failures.length || ref.total < 1) {
    throw new Error(`${t.id}: reference must PASS — got ${JSON.stringify(ref)}`);
  }
  if (t.mutants.length < 2) throw new Error(`${t.id}: need ≥2 mutants`);
  for (let i = 0; i < t.mutants.length; i++) {
    const mut = await run(
      [{ path: 'src/m.ts', content: t.mutants[i]! }],
      { path: feature.testFile, content: suite },
    );
    if (!mut.crashed && mut.failures.length === 0) {
      throw new Error(`${t.id}: mutant ${i} must FAIL`);
    }
  }
}

async function main() {
  const failures: string[] = [];
  for (const t of SPEC) {
    try { await verify(t); }
    catch (e) { failures.push(e instanceof Error ? e.message : String(e)); }
  }
  if (failures.length) {
    console.error('testgen oracle verification FAILED:\n' + failures.join('\n'));
    process.exit(1);
  }
  const rows = SPEC.map(t => ({
    id: t.id,
    family: t.family,
    branch: t.branch,
    paramCount: t.paramCount,
    paramTypes: t.paramTypes,
    returnType: t.returnType,
    license: t.license,
    body: t.body,
  }));
  const src = `/** AUTO-GENERATED by scripts/gen-testgen-oracle.ts — DO NOT EDIT. */
export interface TestgenTemplate {
  id: string;
  family: string;
  branch: 'algebraic' | 'contract' | 'state' | 'representation' | 'freeform';
  paramCount: number;
  paramTypes: string[];
  returnType: string;
  license: string;
  body: string;
}
export const TESTGEN_TEMPLATES: TestgenTemplate[] = ${JSON.stringify(rows, null, 2)};
`;
  fs.writeFileSync(OUT, src);
  console.log(`froze ${rows.length} verified templates → ${OUT}`);
}

void main();
