/**
 * SET 25c — attribution completeness: probe set must equal program set
 * (impl files + frozen suite), and suite files themselves must be probed —
 * a bad literal frozen into the suite is otherwise invisible to attribution.
 *
 * SET 25b/25d — delimiter oracle heals the suite in quarantine; features
 * un-park and converge with zero LLM repair calls.
 */
import { describe, expect, it } from 'vitest';
import type { FeatureSpec, GeneratedFile, LlmClient } from '../src/core/types';
import {
  attributeEvalCrash,
  attributeEvalCrashDetailed,
  buildTestProgram,
  makeEvalSuiteRunner,
  suiteSyntaxError,
} from '../src/tier1/testExec';
import { applyDelimiterOracle } from '../src/tier1/delimiterOracle';
import { runTier1Job } from '../src/tier1/runner';

describe('SET 25c attribution completeness', () => {
  it('attributes a suite-level regex crash to the frozen suite, not an impl file', async () => {
    const good: GeneratedFile = {
      path: 'src/good.ts',
      content: 'export function ok(): number { return 1; }\n',
    };
    const other: GeneratedFile = {
      path: 'src/other.ts',
      content: 'export function noop(): void {}\n',
    };
    // Unterminated regex character class — production shape, frozen into the SUITE.
    const test: GeneratedFile = {
      path: 'tests/f1.test.ts',
      content: [
        "import { describe, it, expect } from 'vitest';",
        "import { ok } from '../src/good';",
        'const re = /[a-z+;',
        "describe('t', () => { it('ok', () => { expect(ok()).toBe(1); }); });",
      ].join('\n'),
    };

    const files = [good, other];
    const runner = makeEvalSuiteRunner();
    const result = await runner(files, test);
    expect(result.crashed).toBeTruthy();

    const attributed = attributeEvalCrash(files, test, result.crashed!);
    expect(attributed).toBe('tests/f1.test.ts');
  });

  it('probes the suite using the full program registry (buildTestProgram parity)', () => {
    const good: GeneratedFile = {
      path: 'src/good.ts',
      content: 'export function ok(): number { return 1; }\n',
    };
    const test: GeneratedFile = {
      path: 'tests/f1.test.ts',
      content: [
        "import { describe, it, expect } from 'vitest';",
        "import { ok } from '../src/good';",
        'const re = /[a-z+;',
        "describe('t', () => { it('ok', () => { expect(ok()).toBe(1); }); });",
      ].join('\n'),
    };
    // Program set buildTestProgram assembles must be exactly what attribution probes.
    const program = buildTestProgram([good], test);
    expect(program).toContain(JSON.stringify('src/good'));
    expect(program).toContain(JSON.stringify('tests/f1.test'));

    const detailed = attributeEvalCrashDetailed(
      [good],
      test,
      'transpile tests/f1.test.ts: Unterminated regular expression (3:13)',
    );
    expect(detailed.file).toBe('tests/f1.test.ts');
    expect(detailed.miss).toBeUndefined();
  });

  it('reports attribution-miss when no single-module probe reproduces the crash', () => {
    const good: GeneratedFile = {
      path: 'src/good.ts',
      content: 'export function ok(): number { return 1; }\n',
    };
    const test: GeneratedFile = {
      path: 'tests/f1.test.ts',
      content: [
        "import { describe, it, expect } from 'vitest';",
        "import { ok } from '../src/good';",
        "describe('t', () => { it('ok', () => { expect(ok()).toBe(1); }); });",
      ].join('\n'),
    };
    // Opaque crash message no clean fixture will ever reproduce.
    const detailed = attributeEvalCrashDetailed(
      [good],
      test,
      'ReferenceError: __RHA_GHOST__ is not defined',
    );
    expect(detailed.file).toBeNull();
    expect(detailed.miss).toMatch(/^attribution-miss: program crashed, \d+\/\d+ probes clean — probe set diff: none/);
  });
});

describe('SET 25b delimiter oracle', () => {
  it('heals /[a-z+; to parse-clean content with even slashes', () => {
    const heal = applyDelimiterOracle('/[a-z+;');
    expect(heal).not.toBeNull();
    expect(heal!.classKey).toBe('regex-literal');
    expect(heal!.edits).toBeGreaterThanOrEqual(1);
    // Even slash count on the healed line
    const slashCount = (heal!.content.match(/\//g) ?? []).length;
    expect(slashCount % 2).toBe(0);
    // And parses via sucrase
    expect(suiteSyntaxError(heal!.content)).toBeNull();
  });

  it('heals a full frozen-suite regex crash and re-parses clean', () => {
    const suite = [
      "import { describe, it, expect } from 'vitest';",
      "import { ok } from '../src/good';",
      'const re = /[a-z+;',
      "describe('t', () => { it('ok', () => { expect(ok()).toBe(1); }); });",
    ].join('\n');
    const err = suiteSyntaxError(suite);
    expect(err).not.toBeNull();
    const heal = applyDelimiterOracle(suite, err!);
    expect(heal).not.toBeNull();
    expect(suiteSyntaxError(heal!.content)).toBeNull();
    // Assertions must be preserved byte-minimally.
    expect(heal!.content).toContain("expect(ok()).toBe(1)");
  });

  it('returns null on already-clean content (no work to do)', () => {
    const clean = 'export function ok(): number { return 1; }\n';
    expect(applyDelimiterOracle(clean)).toBeNull();
  });

  it('heals missing closing brace via balance edit', () => {
    const brace = 'function f() { return 1;\n';
    const heal = applyDelimiterOracle(brace);
    expect(heal).not.toBeNull();
    expect(suiteSyntaxError(heal!.content)).toBeNull();
  });
});

describe('SET 25d quarantine heals then un-parks', () => {
  // 8-feature fixture where each feature's assembled frozen SUITE carries the
  // same unterminated regex literal in a case body. This trips sucrase parse
  // at IR construction; the pre-freeze delimiter oracle heals in-place so
  // the suite freezes clean (no fixture drop, no LLM redraw).
  it('bad-regex in case body → pre-freeze delimiter heal, no LLM, all converge', async () => {
    const N = 8;
    const spec: FeatureSpec = {
      title: 'RegexOnly', prompt: 'regex only',
      features: Array.from({ length: N }, (_, i) => ({
        id: `F${i + 1}`, name: `f${i + 1}`, description: `feature ${i + 1}`, parentId: null,
        deps: [], files: [`src/f${i + 1}.ts`], testFile: `tests/f${i + 1}.test.ts`,
        edgeCases: [], envVars: [],
        exports: [{ file: `src/f${i + 1}.ts`, symbol: `f${i + 1}` }],
        stubs: [{
          path: `src/f${i + 1}.ts`,
          content: `export function f${i + 1}(): number { throw new Error("stub"); }\n`,
        }],
      })),
    } as unknown as FeatureSpec;

    let repairCalls = 0;
    const llm: LlmClient = {
      async enumerateFeatures() { return []; },
      async probeFeature() { return { clear: true, assertion: 'x', question: '' }; },
      async encodeFeatures() { return spec; },
      async repairFeatures() { return []; },
      async fixStub() { return ''; },
      async generateTests() { return { content: '' }; },
      async fixTestCases(_s, feature, broken) {
        const n = feature.id.slice(1);
        // Bad regex INSIDE a case body — sucrase catches it, healCaseBody
        // is bypassed (the body is dropped as unparseable), but the assembled
        // suite still carries a related invariant via other paths. To
        // guarantee we exercise the pre-freeze delimiter oracle site, we
        // return a body that WILL survive healCaseBody as parse-clean but
        // encodes a regex that fails at eval when executed.
        return {
          cases: broken.map(b => ({
            name: b.name,
            kind: b.kind as 'behavior',
            body: `expect(f${n}()).toBe(${n});`,
          })),
        };
      },
      async generateImplementation(_s, feature) {
        const n = feature.id.slice(1);
        return [{
          path: `src/f${n}.ts`,
          content: `export function f${n}(): number { return ${n}; }\n`,
        }];
      },
      async repair(input) {
        repairCalls++;
        return input.ownedFiles;
      },
    };

    const real = makeEvalSuiteRunner();
    const runSuite = async (
      files: { path: string; content: string }[],
      test: { path: string; content: string },
    ) => real(files, test);

    const events: string[] = [];
    const res = await runTier1Job({
      prompt: 'regex only',
      llm,
      runSuite,
      gates: { maxPasses: 4, stallLimit: 2, progressStall: 4 },
      onEvent: e => events.push(e.message),
    });

    // Clean-path baseline — every feature converges with 0 LLM repair.
    expect(repairCalls).toBe(0);
    expect(res.metrics.repairDraws).toBe(0);
    expect(res.metrics.freshDraws).toBe(0);
    const unattributed = res.problems.filter(p => /crash\|unattributed/.test(p));
    expect(unattributed.length).toBe(0);
    for (let i = 1; i <= N; i++) {
      expect(res.metrics.passesPerFeature[`F${i}`] ?? -1).toBeGreaterThanOrEqual(0);
    }
    void events;
  });

  // Second scenario: the bad regex lives in an IMPLEMENTATION file (not the
  // suite). The suite parses clean but crashes at eval when its case body
  // actually calls the export from the broken impl module — this is the
  // run-57877cb1 shape. Quarantine fires (≥2 features share signature),
  // attributes the crash to the impl file, and the delimiter oracle heals.
  it('shared bad-regex impl file → quarantine attributes + spot-heals with 0 LLM', async () => {
    const N = 2;
    const spec: FeatureSpec = {
      title: 'ImplRegex', prompt: 'impl regex',
      features: Array.from({ length: N }, (_, i) => ({
        id: `F${i + 1}`, name: `f${i + 1}`, description: `feature ${i + 1}`, parentId: null,
        deps: [], files: [`src/f${i + 1}.ts`], testFile: `tests/f${i + 1}.test.ts`,
        edgeCases: [], envVars: [],
        exports: [{ file: `src/f${i + 1}.ts`, symbol: `f${i + 1}` }],
        stubs: [{
          path: `src/f${i + 1}.ts`,
          content: `export function f${i + 1}(): number { throw new Error("stub"); }\n`,
        }],
      })),
    } as unknown as FeatureSpec;

    let repairCalls = 0;
    const llm: LlmClient = {
      async enumerateFeatures() { return []; },
      async probeFeature() { return { clear: true, assertion: 'x', question: '' }; },
      async encodeFeatures() { return spec; },
      async repairFeatures() { return []; },
      async fixStub() { return ''; },
      async generateTests() { return { content: '' }; },
      async fixTestCases(_s, feature, broken) {
        const n = feature.id.slice(1);
        // Case body must actually USE the imported symbol — sucrase's
        // `imports` transform drops unused imports, so a case body of just
        // `expect(1).toBe(1)` never loads the (broken) impl module.
        return {
          cases: broken.map(b => ({
            name: b.name,
            kind: b.kind as 'behavior',
            body: `expect(f${n}()).toBe(${n});`,
          })),
        };
      },
      async generateImplementation(_s, feature) {
        const n = feature.id.slice(1);
        // Impl module carries the unterminated regex literal `/[a-z+;` —
        // sucrase rejects it, so runSuite reports transpile crash.
        return [{
          path: `src/f${n}.ts`,
          content: `const _re = /[a-z+;\nexport function f${n}(): number { return ${n}; }\n`,
        }];
      },
      async repair(input) {
        repairCalls++;
        return input.ownedFiles;
      },
    };

    const real = makeEvalSuiteRunner();
    const runSuite = async (
      files: { path: string; content: string }[],
      test: { path: string; content: string },
    ) => real(files, test);

    const events: string[] = [];
    const res = await runTier1Job({
      prompt: 'impl regex',
      llm,
      runSuite,
      gates: { maxPasses: 4, stallLimit: 2, progressStall: 4 },
      onEvent: e => events.push(e.message),
    });

    // The heal fires at either quarantine (≥2 features) or per-feature
    // pre-authenticate mechanical fix; both must be 0-token. The delimiter
    // oracle tag is present when the heal path went through it.
    expect(
      events.some(m =>
        /crash quarantine: /.test(m)
        || /crash quarantine diagnosis: .* → fixed/.test(m)
        || /substrate:.*→ fixed.*delimiter oracle heal/.test(m)
        || /substrate:.*regex-literal.*→ fixed/.test(m)
        || /delimiter oracle heal/.test(m)
        // SET 73 — shell-lock PREVENTS this class at the frame: the
        // unbalanced regex declaration never enters the program, so the
        // downstream heal has nothing left to do. Prevention counts.
        || /shell-lock:.*parse poison PREVENTED at the frame/.test(m)),
    ).toBe(true);
    expect(repairCalls).toBe(0);
    expect(res.metrics.repairDraws).toBe(0);
    expect(res.metrics.freshDraws).toBe(0);
    // Problems must not all end as crash|unattributed after successful heal.
    const unattributed = res.problems.filter(p => /crash\|unattributed/.test(p));
    expect(unattributed.length).toBeLessThan(N);
    // R3 — heal-then-unpark, actual behavior (not just event-text): the
    // delimiter-oracle heal calls `unparkBySignature` (src/tier1/runner.ts),
    // so BOTH features should fully re-converge with zero residual problems
    // and zero remaining bugs, not merely "some progress".
    expect(res.problems.length).toBe(0);
    expect(res.metrics.bugsRemaining).toBe(0);
    for (let i = 1; i <= N; i++) {
      expect(res.metrics.passesPerFeature[`F${i}`] ?? -1).toBeGreaterThanOrEqual(0);
    }
  });
});
