/**
 * SET 39a — TOTAL error taxonomy. Vendors closed emitter catalogs into a frozen
 * classifier: every sandbox error maps to exactly one template (with stratum)
 * or to catalog-drift (tripwire, not shrug).
 *
 *   Run:  npx tsx scripts/gen-error-catalog.ts
 */
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import ts from 'typescript';

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

export type ErrorStratum = 'mechanical-first' | 'playbook' | 'semantic-procedure';
export type ErrorCatalogSource =
  | 'v8-syntax'
  | 'v8-runtime'
  | 'typescript'
  | 'assertion'
  | 'harness';

export interface ErrorCatalogSpec {
  id: string;
  /** Regex source matched against the first line (then full message). */
  pattern: string;
  stratum: ErrorStratum;
  /**
   * classKey family. Use `$1`..`$n` for capture groups from `pattern`.
   * Literal keys (no `$`) are used as-is.
   */
  classKey: string;
  source: ErrorCatalogSource;
  /** Fixture message that must match this template at gen time. */
  fixture: string;
}

// ---------------------------------------------------------------------------
// 1. V8 SyntaxError family → mechanical-first
// ---------------------------------------------------------------------------
const V8_SYNTAX: ErrorCatalogSpec[] = [
  // More-specific "Unexpected *" before the bare Unexpected token catch-all.
  { id: 'v8-invalid-unexpected-token', pattern: 'Invalid or unexpected token', stratum: 'mechanical-first', classKey: 'crash|token|Invalid or unexpected token', source: 'v8-syntax', fixture: 'Invalid or unexpected token' },
  { id: 'v8-unexpected-end', pattern: 'Unexpected end of input', stratum: 'mechanical-first', classKey: 'crash|token|Unexpected end of input', source: 'v8-syntax', fixture: 'Unexpected end of input' },
  { id: 'v8-unexpected-identifier', pattern: 'Unexpected identifier', stratum: 'mechanical-first', classKey: 'crash|token|Unexpected identifier', source: 'v8-syntax', fixture: 'Unexpected identifier' },
  { id: 'v8-unexpected-string', pattern: 'Unexpected string', stratum: 'mechanical-first', classKey: 'crash|token|Unexpected string', source: 'v8-syntax', fixture: 'Unexpected string' },
  { id: 'v8-unexpected-number', pattern: 'Unexpected number', stratum: 'mechanical-first', classKey: 'crash|token|Unexpected number', source: 'v8-syntax', fixture: 'Unexpected number' },
  { id: 'v8-reserved-strict', pattern: 'Unexpected reserved word', stratum: 'mechanical-first', classKey: 'crash|token|Unexpected reserved word', source: 'v8-syntax', fixture: 'Unexpected reserved word' },
  { id: 'v8-unexpected-token', pattern: 'Unexpected token', stratum: 'mechanical-first', classKey: 'crash|token|Unexpected token', source: 'v8-syntax', fixture: 'Unexpected token \'}\'' },
  { id: 'v8-invalid-regex', pattern: 'Invalid regular expression', stratum: 'mechanical-first', classKey: 'regex-literal|$0', source: 'v8-syntax', fixture: 'Invalid regular expression: missing /' },
  { id: 'v8-unterminated-regex', pattern: 'Unterminated regular expression', stratum: 'mechanical-first', classKey: 'regex-literal|$0', source: 'v8-syntax', fixture: 'Unterminated regular expression literal' },
  { id: 'v8-unterminated-template', pattern: 'Unterminated template', stratum: 'mechanical-first', classKey: 'crash|token|Unterminated template', source: 'v8-syntax', fixture: 'Unterminated template' },
  { id: 'v8-unterminated-string', pattern: 'Unterminated string', stratum: 'mechanical-first', classKey: 'crash|token|Unterminated string', source: 'v8-syntax', fixture: 'Unterminated string constant' },
  { id: 'v8-already-declared', pattern: "Identifier '([^']+)' has already been declared", stratum: 'mechanical-first', classKey: 'duplicate-decl|$1', source: 'v8-syntax', fixture: "Identifier 'Foo' has already been declared" },
  { id: 'v8-missing-paren-arglist', pattern: 'missing \\) after argument list', stratum: 'mechanical-first', classKey: 'crash|token|missing ) after argument list', source: 'v8-syntax', fixture: 'missing ) after argument list' },
  { id: 'v8-missing-catch-finally', pattern: 'Missing catch or finally after try', stratum: 'mechanical-first', classKey: 'crash|token|Missing catch or finally', source: 'v8-syntax', fixture: 'Missing catch or finally after try' },
  { id: 'v8-illegal-return', pattern: 'Illegal return statement', stratum: 'mechanical-first', classKey: 'crash|token|Illegal return statement', source: 'v8-syntax', fixture: 'Illegal return statement' },
  { id: 'v8-invalid-lhs', pattern: 'Invalid left-hand side', stratum: 'mechanical-first', classKey: 'crash|token|Invalid left-hand side', source: 'v8-syntax', fixture: 'Invalid left-hand side in assignment' },
  { id: 'v8-missing-initializer', pattern: 'Missing initializer in (const|let) declaration', stratum: 'mechanical-first', classKey: 'crash|token|Missing initializer', source: 'v8-syntax', fixture: 'Missing initializer in const declaration' },
  { id: 'v8-await-outside', pattern: 'await is only valid in async', stratum: 'mechanical-first', classKey: 'crash|token|await outside async', source: 'v8-syntax', fixture: 'await is only valid in async functions' },
  { id: 'v8-octal', pattern: 'Octal literals are not allowed', stratum: 'mechanical-first', classKey: 'crash|token|Octal literals', source: 'v8-syntax', fixture: 'Octal literals are not allowed in strict mode' },
  { id: 'v8-missing-colon', pattern: 'missing : in conditional', stratum: 'mechanical-first', classKey: 'crash|token|missing colon', source: 'v8-syntax', fixture: 'missing : in conditional expression' },
];

// ---------------------------------------------------------------------------
// 2. V8 ReferenceError / TypeError / RangeError → playbook (mock-API mechanical)
// ---------------------------------------------------------------------------
const V8_RUNTIME: ErrorCatalogSpec[] = [
  // Mock-API mechanical (37c) — before generic not-defined / not-a-function.
  { id: 'v8-not-defined-jest', pattern: '^jest is not defined', stratum: 'mechanical-first', classKey: 'undefined-symbol|jest', source: 'v8-runtime', fixture: 'jest is not defined' },
  { id: 'v8-not-defined-fn', pattern: '^fn is not defined', stratum: 'mechanical-first', classKey: 'undefined-symbol|fn', source: 'v8-runtime', fixture: 'fn is not defined' },
  { id: 'v8-not-a-function-spyOn', pattern: '(?:^|\\.)spyOn is not a function|vi\\.spyOn is not a function|spyOn is not a function', stratum: 'mechanical-first', classKey: 'is-not-a-function|spyOn', source: 'v8-runtime', fixture: 'spyOn is not a function' },
  { id: 'v8-not-defined', pattern: '^([A-Za-z_$][\\w$]*) is not defined', stratum: 'playbook', classKey: 'undefined-symbol|$1', source: 'v8-runtime', fixture: 'slugify is not defined' },
  { id: 'v8-before-init', pattern: 'Cannot access \'([^\']+)\' before initialization', stratum: 'playbook', classKey: 'runtime|before-init|$1', source: 'v8-runtime', fixture: "Cannot access 'x' before initialization" },
  { id: 'v8-not-a-function', pattern: '([A-Za-z_$][\\w$]*(?:\\.[A-Za-z_$][\\w$]*)*) is not a function', stratum: 'playbook', classKey: 'is-not-a-function|$1', source: 'v8-runtime', fixture: 'parse is not a function' },
  { id: 'v8-nullread-undef', pattern: "Cannot read propert(?:y|ies) of undefined \\(reading '([^']+)'\\)", stratum: 'playbook', classKey: 'runtime|nullread|$1', source: 'v8-runtime', fixture: "Cannot read properties of undefined (reading 'id')" },
  { id: 'v8-nullread-null', pattern: "Cannot read propert(?:y|ies) of null \\(reading '([^']+)'\\)", stratum: 'playbook', classKey: 'runtime|nullread|$1', source: 'v8-runtime', fixture: "Cannot read properties of null (reading 'id')" },
  { id: 'v8-set-undef', pattern: "Cannot set propert(?:y|ies) of undefined", stratum: 'playbook', classKey: 'runtime|nullwrite|undefined', source: 'v8-runtime', fixture: "Cannot set properties of undefined (setting 'x')" },
  { id: 'v8-set-null', pattern: "Cannot set propert(?:y|ies) of null", stratum: 'playbook', classKey: 'runtime|nullwrite|null', source: 'v8-runtime', fixture: "Cannot set properties of null (setting 'x')" },
  // SET 98 — missing-`new` / not-a-constructor interop → mechanical-first catalog-drift family.
  { id: 'v8-class-needs-new', pattern: "Class constructor (\\w+) cannot be invoked without 'new'", stratum: 'mechanical-first', classKey: 'catalog-drift|missing-new|$1', source: 'v8-runtime', fixture: "Class constructor Widget cannot be invoked without 'new'" },
  { id: 'v8-interop-not-constructor', pattern: '\\(0\\s*,\\s*_\\w+\\.(\\w+)\\)\\s+is not a constructor', stratum: 'mechanical-first', classKey: 'catalog-drift|missing-new|$1', source: 'v8-runtime', fixture: '(0 , _widget.Widget) is not a constructor' },
  { id: 'v8-not-constructor', pattern: '([A-Za-z_$][\\w$]*) is not a constructor', stratum: 'mechanical-first', classKey: 'catalog-drift|missing-new|$1', source: 'v8-runtime', fixture: 'Foo is not a constructor' },
  { id: 'v8-not-iterable', pattern: '([A-Za-z_$][\\w$]*) is not iterable', stratum: 'playbook', classKey: 'runtime|not-iterable|$1', source: 'v8-runtime', fixture: 'x is not iterable' },
  { id: 'v8-object-not-iterable', pattern: 'object is not iterable|is not iterable \\(cannot read', stratum: 'playbook', classKey: 'runtime|not-iterable|object', source: 'v8-runtime', fixture: 'object is not iterable (cannot read property Symbol(Symbol.iterator))' },
  { id: 'v8-max-stack', pattern: 'Maximum call stack size exceeded', stratum: 'playbook', classKey: 'runtime|max-stack', source: 'v8-runtime', fixture: 'Maximum call stack size exceeded' },
  { id: 'v8-cannot-convert', pattern: 'Cannot convert undefined or null to object', stratum: 'playbook', classKey: 'runtime|cannot-convert', source: 'v8-runtime', fixture: 'Cannot convert undefined or null to object' },
  { id: 'v8-assign-const', pattern: 'Assignment to constant variable', stratum: 'playbook', classKey: 'runtime|assign-const', source: 'v8-runtime', fixture: 'Assignment to constant variable' },
  { id: 'v8-reduce-empty', pattern: 'Reduce of empty array with no initial value', stratum: 'playbook', classKey: 'runtime|reduce-empty', source: 'v8-runtime', fixture: 'Reduce of empty array with no initial value' },
  { id: 'v8-invalid-array-length', pattern: 'Invalid array length', stratum: 'playbook', classKey: 'runtime|invalid-array-length', source: 'v8-runtime', fixture: 'Invalid array length' },
  { id: 'v8-precision-range', pattern: 'precision .* out of range|toFixed\\(\\) digits argument must', stratum: 'playbook', classKey: 'runtime|range|precision', source: 'v8-runtime', fixture: 'toFixed() digits argument must be between 0 and 100' },
  { id: 'v8-in-operator', pattern: 'Cannot use \'in\' operator', stratum: 'playbook', classKey: 'runtime|in-operator', source: 'v8-runtime', fixture: "Cannot use 'in' operator to search for 'x' in undefined" },
  { id: 'v8-spread-non-iterable', pattern: 'Found non-callable @@iterator|is not iterable \\(cannot read property Symbol', stratum: 'playbook', classKey: 'runtime|spread-non-iterable', source: 'v8-runtime', fixture: 'Found non-callable @@iterator' },
  { id: 'v8-instanceof', pattern: 'Right-hand side of \'instanceof\' is not', stratum: 'playbook', classKey: 'runtime|instanceof', source: 'v8-runtime', fixture: "Right-hand side of 'instanceof' is not an object" },
  { id: 'v8-promise-cycle', pattern: 'Chaining cycle detected for promise', stratum: 'playbook', classKey: 'runtime|promise-cycle', source: 'v8-runtime', fixture: 'Chaining cycle detected for promise #<Promise>' },
];

// ---------------------------------------------------------------------------
// 3. TypeScript diagnostics by code (closed numbered catalog)
// ---------------------------------------------------------------------------
const TS_FOCUS_CODES = new Set([
  1002, 1003, 1005, 1009, 1010, 1012, 1068, 1109, 1128, 1160, 1161, 1434,
  2304, 2305, 2306, 2307, 2322, 2339, 2345, 2459, 2551, 2552, 2554, 7006,
]);

function tsStratum(code: number): ErrorStratum {
  if (code >= 1000 && code < 2000) return 'mechanical-first'; // syntax
  if (code === 2304 || code === 2305 || code === 2307 || code === 2459 || code === 2552) {
    return 'playbook'; // symbol/import rewire
  }
  return 'semantic-procedure';
}

function tsClassKey(code: number): string {
  if (code >= 1000 && code < 2000) return `transpile|TS${code}`;
  if (code === 2304 || code === 2552) return `undefined-symbol|TS${code}`;
  if (code === 2305 || code === 2459) return `unresolved-import|TS${code}`;
  if (code === 2307) return `unresolved-import|TS${code}`;
  return `runtime|TS${code}`;
}

function buildTsSpecs(): ErrorCatalogSpec[] {
  const out: ErrorCatalogSpec[] = [];
  const seen = new Set<number>();
  for (const d of Object.values(ts.Diagnostics)) {
    if (!d || typeof d !== 'object' || !('code' in d)) continue;
    const code = (d as { code: number }).code;
    if (!TS_FOCUS_CODES.has(code) || seen.has(code)) continue;
    seen.add(code);
    const msg = String((d as { message: string }).message ?? '');
    // Prefer code match. Only add message-text alt when the prefix is long enough
    // to be distinctive — a lone `'` from TS1005 (`'{0}' expected`) must never
    // steal `error TS2304: Cannot find name 'Foo'.`
    const prefix = msg.split('{0}')[0]!.slice(0, 40).trim();
    const alt = prefix.length >= 12 ? `|${escapeForAlt(prefix)}` : '';
    const pattern = `TS${code}\\b|error TS${code}\\b${alt}`;
    out.push({
      id: `ts-${code}`,
      pattern,
      stratum: tsStratum(code),
      classKey: tsClassKey(code),
      source: 'typescript',
      fixture: `error TS${code}: ${msg.replace(/\{0\}/g, 'Foo').replace(/\{1\}/g, 'Bar')}`,
    });
  }
  // Ensure observed live codes always present even if Diagnostics shape shifts.
  for (const code of TS_FOCUS_CODES) {
    if (seen.has(code)) continue;
    out.push({
      id: `ts-${code}`,
      pattern: `TS${code}\\b|error TS${code}\\b`,
      stratum: tsStratum(code),
      classKey: tsClassKey(code),
      source: 'typescript',
      fixture: `error TS${code}: diagnostic`,
    });
  }
  return out.sort((a, b) => a.id.localeCompare(b.id));
}

function escapeForAlt(s: string): string {
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/'/g, "\\'").trim() || 'TS';
}

// ---------------------------------------------------------------------------
// 4. Vitest assertion matchers → semantic-procedure
// ---------------------------------------------------------------------------
const MATCHERS = [
  'toBe', 'toEqual', 'toStrictEqual', 'toThrow', 'toThrowError',
  'toMatch', 'toMatchObject', 'toContain', 'toContainEqual',
  'toHaveProperty', 'toBeDefined', 'toBeUndefined', 'toBeNull', 'toBeTruthy',
  'toBeFalsy', 'toBeGreaterThan', 'toBeGreaterThanOrEqual', 'toBeLessThan',
  'toBeLessThanOrEqual', 'toBeCloseTo', 'toBeInstanceOf', 'toHaveLength',
  'toHaveBeenCalled', 'toHaveBeenCalledTimes', 'toHaveBeenCalledWith',
  'toHaveBeenLastCalledWith', 'toHaveReturned', 'toHaveReturnedWith',
  'toSatisfy', 'toMatchSnapshot', 'toMatchInlineSnapshot',
];

function buildAssertionSpecs(): ErrorCatalogSpec[] {
  const out: ErrorCatalogSpec[] = [];
  // Negated matchers first so `not.toBe` does not collapse into `toBe`.
  for (const m of MATCHERS) {
    const kind = m.includes('toThrow') ? 'throw-mismatch' : 'assertion';
    out.push({
      id: `assert-not-${m}`,
      pattern: `expect\\(\\.\\.\\.\\)\\.not\\.${m}\\b failed`,
      stratum: 'semantic-procedure',
      classKey: `${kind}|not.${m}`,
      source: 'assertion',
      fixture: `expect(...).not.${m} failed — actual: 1, expected: 2`,
    });
  }
  for (const m of MATCHERS) {
    const kind = m.includes('toThrow') ? 'throw-mismatch' : 'assertion';
    out.push({
      id: `assert-${m}`,
      pattern: `expect\\(\\.\\.\\.\\)\\.${m}\\b failed`,
      stratum: 'semantic-procedure',
      classKey: `${kind}|${m}`,
      source: 'assertion',
      fixture: `expect(...).${m} failed — actual: 1, expected: 2`,
    });
  }
  // Generic vitest assertion fallback (must be last among assertion entries).
  out.push({
    id: 'assert-generic',
    pattern: 'expect\\(\\.\\.\\.\\)\\.((?:not\\.)?[A-Za-z]+) failed',
    stratum: 'semantic-procedure',
    classKey: 'assertion|$1',
    source: 'assertion',
    fixture: 'expect(...).customMatcher failed — actual: false, expected: true',
  });
  return out;
}

// ---------------------------------------------------------------------------
// 5. Harness-domain errors
// ---------------------------------------------------------------------------
const HARNESS: ErrorCatalogSpec[] = [
  { id: 'harness-unresolved-import', pattern: '^unresolved import (.+?) \\(from (.+?)\\)$', stratum: 'mechanical-first', classKey: 'unresolved-import|$1', source: 'harness', fixture: 'unresolved import ./addr (from src/calc)' },
  { id: 'harness-transpile', pattern: '^transpile ([^:]+):', stratum: 'mechanical-first', classKey: 'transpile|$1', source: 'harness', fixture: 'transpile src/a.ts: Unexpected token' },
  { id: 'harness-dynamic-eval', pattern: '^dynamic-eval:', stratum: 'playbook', classKey: 'dynamic-eval|$0', source: 'harness', fixture: 'dynamic-eval: eval( is forbidden in frozen tests/impls' },
  { id: 'harness-stub', pattern: 'stub: not implemented', stratum: 'playbook', classKey: 'runtime|stub-not-implemented', source: 'harness', fixture: 'stub: not implemented' },
  { id: 'harness-placeholder', pattern: 'PLACEHOLDER|__RHA_PLACEHOLDER__', stratum: 'mechanical-first', classKey: 'transpile|placeholder', source: 'harness', fixture: '__RHA_PLACEHOLDER__ unresolved' },
  { id: 'harness-crash-loading', pattern: '^crash while loading ', stratum: 'mechanical-first', classKey: 'crash|load|$0', source: 'harness', fixture: 'crash while loading src/a.ts:1' },
  { id: 'harness-crash-exec', pattern: '^crash while executing ', stratum: 'mechanical-first', classKey: 'crash|exec|$0', source: 'harness', fixture: 'crash while executing case "adds"' },
  { id: 'harness-empty-draw', pattern: 'unparseable at "\\(empty\\)"|unparseable:.*at "\\(empty\\)"|^\\(empty\\)$', stratum: 'mechanical-first', classKey: 'empty-draw', source: 'harness', fixture: 'dropped — unparseable: Unexpected end of input at "(empty)"' },
  { id: 'harness-provider-desync', pattern: 'provider desync|desync:|model returned empty', stratum: 'mechanical-first', classKey: 'empty-draw', source: 'harness', fixture: 'model returned empty' },
  { id: 'harness-boundary-fault', pattern: 'boundary-fault|seam (?:fault|mismatch)', stratum: 'mechanical-first', classKey: 'boundary-fault|$0', source: 'harness', fixture: 'boundary-fault seam mismatch' },
  // SET 40e — micro-harness missing vitest surface (.each/.skip/.call) before generic is-not-a-function.
  { id: 'harness-vitest-surface', pattern: '_vitest\\.(?:it|test|describe)\\.call\\(\\.\\.\\.\\) is not a function|(?:it|test|describe)\\.(?:each|skip|todo|only) is not a function|_vitest\\.(?:it|test|describe)\\.(?:each|skip|todo|only)', stratum: 'mechanical-first', classKey: 'harness-vitest-surface', source: 'harness', fixture: '_vitest.it.call(...) is not a function' },
];

/** First-match order: harness → assertion → v8-syntax → v8-runtime → typescript. */
function allSpecs(): ErrorCatalogSpec[] {
  // More-specific assertion entries before generic; harness before V8 so our
  // structured messages win over bare V8 substrings.
  const assertions = buildAssertionSpecs();
  // Put assert-generic last among assertions.
  const specificAssert = assertions.filter(a => a.id !== 'assert-generic');
  const genericAssert = assertions.filter(a => a.id === 'assert-generic');
  return [
    ...HARNESS,
    ...specificAssert,
    ...genericAssert,
    ...V8_SYNTAX,
    ...V8_RUNTIME,
    ...buildTsSpecs(),
  ];
}

function applyClassKey(template: string, match: RegExpExecArray, firstLine: string): string {
  let out = template;
  for (let i = 1; i < match.length; i++) {
    out = out.replace(new RegExp(`\\$${i}`, 'g'), match[i] ?? '');
  }
  out = out.replace(/\$0/g, firstLine.slice(0, 40));
  // is-not-a-function: keep leaf symbol only (matches substrate classifyFailure).
  if (out.startsWith('is-not-a-function|') && match[1]) {
    out = `is-not-a-function|${match[1].split('.').pop()!}`;
  }
  return out;
}

function verify(specs: ErrorCatalogSpec[]): void {
  const failures: string[] = [];
  for (const s of specs) {
    const re = new RegExp(s.pattern, 'i');
    const first = s.fixture.split('\n')[0]!;
    if (!re.test(first) && !re.test(s.fixture)) {
      failures.push(`${s.id}: fixture does not match /${s.pattern}/`);
    }
  }
  if (failures.length) {
    console.error('error catalog verification FAILED:\n' + failures.join('\n'));
    process.exit(1);
  }
}

function main() {
  const specs = allSpecs();
  verify(specs);
  const rows = specs.map(s => ({
    id: s.id,
    pattern: s.pattern,
    stratum: s.stratum,
    classKey: s.classKey,
    source: s.source,
    fixture: s.fixture,
  }));

  const src = `/** AUTO-GENERATED by scripts/gen-error-catalog.ts — DO NOT EDIT. */
export type ErrorStratum = 'mechanical-first' | 'playbook' | 'semantic-procedure';
export type ErrorCatalogSource =
  | 'v8-syntax'
  | 'v8-runtime'
  | 'typescript'
  | 'assertion'
  | 'harness';

export interface ErrorCatalogTemplate {
  id: string;
  pattern: string;
  stratum: ErrorStratum;
  classKey: string;
  source: ErrorCatalogSource;
  fixture: string;
}

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

export interface CatalogClassification {
  templateId: string;
  stratum: ErrorStratum | 'catalog-drift';
  classKey: string;
}

function applyClassKey(template: string, match: RegExpExecArray, firstLine: string): string {
  let out = template;
  for (let i = 1; i < match.length; i++) {
    out = out.split('$' + i).join(match[i] ?? '');
  }
  out = out.split('$0').join(firstLine.slice(0, 40));
  if (out.startsWith('is-not-a-function|') && match[1]) {
    out = 'is-not-a-function|' + (match[1].split('.').pop() ?? match[1]);
  }
  return out;
}

/**
 * SET 39a — TOTAL classifier. Every message maps to exactly one template or
 * to catalog-drift (tripwire).
 */
export function classifyCataloged(message: string): CatalogClassification {
  const text = message ?? '';
  if (text.trim() === '') {
    return { templateId: 'harness-empty-draw', stratum: 'mechanical-first', classKey: 'empty-draw' };
  }
  const first = text.split('\\n')[0] ?? '';
  for (const t of ERROR_CATALOG) {
    const re = new RegExp(t.pattern, 'i');
    let m = re.exec(first);
    if (!m) m = re.exec(text);
    if (!m) continue;
    return {
      templateId: t.id,
      stratum: t.stratum,
      classKey: applyClassKey(t.classKey, m, first),
    };
  }
  return {
    templateId: 'catalog-drift',
    stratum: 'catalog-drift',
    classKey: 'catalog-drift',
  };
}

/** Runtime tripwire string for unmatched errors (SET 39b). */
export function formatCatalogDriftTripwire(message: string): string {
  const first = ((message ?? '').split('\\n')[0] ?? '').slice(0, 120);
  return 'catalog-drift: "' + first.replace(/"/g, "'") + '" matches no template';
}
`;
  const fixed = src
    .replace("text.split('\\\\n')", "text.split('\\n')")
    .replace("((message ?? '').split('\\\\n')", "((message ?? '').split('\\n')");
  fs.writeFileSync(OUT, fixed);
  console.log(`froze ${rows.length} verified error-catalog templates → ${OUT}`);
}

void main();
