/**
 * SET 102 — unterminated regex heals at program-parse write-acceptance gates
 * (build + RHA acceptWrite) via applyDelimiterOracle at 0 tokens — no hotter re-draw.
 */
import { describe, expect, it } from 'vitest';
import type { FeatureSpec, LlmClient } from '../src/core/types';
import { runTier1Job } from '../src/tier1/runner';
import { makeEvalSuiteRunner } from '../src/tier1/testExec';

/** Known healUnterminatedRegexLiteral / delimiterOracle fixture (odd `/`). */
const BAD_IMPL = [
  'export function match(s: string): boolean {',
  '  const re = /[a-z+',
  '  return re.test(s);',
  '}',
  '',
].join('\n');

function makeSpec(): FeatureSpec {
  return {
    title: 'Re',
    prompt: 'regex match',
    features: [{
      id: 'F1', name: 'match', description: 'match lowercase', parentId: null,
      deps: [], files: ['src/re.ts'], testFile: 'tests/re.test.ts',
      edgeCases: [], envVars: [],
      exports: [{ file: 'src/re.ts', symbol: 'match' }],
      stubs: [{
        path: 'src/re.ts',
        content: 'export function match(s: string): boolean { throw new Error("stub"); }\n',
      }],
    }],
  } as unknown as FeatureSpec;
}

describe('SET 102 — regex heal at program-parse write gates', () => {
  it('build gate heals unterminated regex; throwing LLM proves no re-draw', async () => {
    let implCalls = 0;
    const llm = {
      async encodeFeatures() { return makeSpec(); },
      async generateTests() {
        return {
          content: '',
          parts: {
            imports: [{ from: 'src/re.ts', symbols: ['match'] }],
            cases: [{
              name: 'match',
              kind: 'behavior' as const,
              body: 'expect(match("abc")).toBe(true);',
            }],
          },
        };
      },
      async fixTestCases(
        _s: unknown,
        _f: unknown,
        req: { name: string; kind: 'behavior' | 'edge' | 'integration' }[],
      ) {
        return {
          cases: req.map(r => ({
            name: r.name,
            kind: r.kind,
            body: 'expect(match("abc")).toBe(true);',
          })),
        };
      },
      async generateImplementation() {
        implCalls++;
        if (implCalls > 1) {
          throw new Error('SET 102: build re-draw must not run after delimiterOracle heal');
        }
        return [{ path: 'src/re.ts', content: BAD_IMPL }];
      },
      async repair() {
        throw new Error('SET 102: LLM repair must not run after delimiterOracle heal');
      },
    } as unknown as LlmClient;

    const events: string[] = [];
    const res = await runTier1Job({
      prompt: 'regex match',
      llm,
      runSuite: makeEvalSuiteRunner(),
      // shellLock would rewrite bodies and erase the unterminated-literal fixture.
      gates: { maxPasses: 1, stallLimit: 1, progressStall: 1, shellLock: false },
      onEvent: e => events.push(e.message),
    });

    expect(events.some(m =>
      /program-parse healed via delimiterOracle \(regex-literal, 0 tok\)/.test(m),
    )).toBe(true);
    expect(res.metrics.regexLiteralGateHeals).toBeGreaterThanOrEqual(1);
    expect(res.metrics.mechanicalFixes).toBeGreaterThanOrEqual(1);
    expect(implCalls).toBe(1);
    const landed = res.files.find(f => f.path === 'src/re.ts')?.content ?? '';
    expect(landed).toMatch(/\/\[a-z\+\]\//);
    expect(landed).not.toMatch(/const re = \/\[a-z\+\n/);
  });

  it('RHA acceptWrite heals unterminated regex on repair trial write', async () => {
    // Multi-case so substrate toBe-flip cannot satisfy the suite alone — forces LLM repair.
    const spec: FeatureSpec = {
      title: 'Re',
      prompt: 'regex match',
      features: [{
        id: 'F1', name: 'match', description: 'match lowercase', parentId: null,
        deps: [], files: ['src/re.ts'], testFile: 'tests/re.test.ts',
        edgeCases: ['empty'], envVars: [],
        exports: [{ file: 'src/re.ts', symbol: 'match' }],
        stubs: [{
          path: 'src/re.ts',
          content: 'export function match(s: string): boolean { throw new Error("stub"); }\n',
        }],
      }],
    } as unknown as FeatureSpec;

    let sawBadRepair = false;
    const llm = {
      async encodeFeatures() { return spec; },
      async generateTests() {
        return {
          content: '',
          parts: {
            imports: [{ from: 'src/re.ts', symbols: ['match'] }],
            cases: [
              { name: 'match', kind: 'behavior' as const, body: 'expect(match("abc")).toBe(true);' },
              { name: 'empty', kind: 'edge' as const, body: 'expect(match("")).toBe(false);' },
              { name: 'upper', kind: 'behavior' as const, body: 'expect(match("ABC")).toBe(false);' },
            ],
          },
        };
      },
      async fixTestCases(
        _s: unknown,
        _f: unknown,
        req: { name: string; kind: 'behavior' | 'edge' | 'integration' }[],
      ) {
        return {
          cases: req.map(r => ({
            name: r.name,
            kind: r.kind,
            body: r.name === 'empty' ? 'expect(match("")).toBe(false);'
              : r.name === 'upper' ? 'expect(match("ABC")).toBe(false);'
              : 'expect(match("abc")).toBe(true);',
          })),
        };
      },
      async generateImplementation() {
        // Always-true cannot pass empty/upper — ladder must enter repair.
        return [{
          path: 'src/re.ts',
          content: 'export function match(s: string): boolean { return true; }\n',
        }];
      },
      async repair() {
        sawBadRepair = true;
        // First (and subsequent) repair draws carry the healable regex poison.
        // After SET 102, acceptWrite heals at 0 tok instead of program-parse reject.
        return [{ path: 'src/re.ts', content: BAD_IMPL }];
      },
    } as unknown as LlmClient;

    const events: string[] = [];
    const res = await runTier1Job({
      prompt: 'regex match',
      llm,
      runSuite: makeEvalSuiteRunner(),
      gates: { maxPasses: 2, stallLimit: 2, progressStall: 2, shellLock: false },
      onEvent: e => events.push(`${e.phase}: ${e.message}`),
    });

    expect(sawBadRepair).toBe(true);
    expect(events.some(m =>
      /^rha: program-parse healed via delimiterOracle \(regex-literal, 0 tok\)$/.test(m),
    )).toBe(true);
    expect(res.metrics.regexLiteralGateHeals).toBeGreaterThanOrEqual(1);
    // Must not fall through to an unhealed Invalid-regular-expression reject for this write.
    expect(events.some(m =>
      /program-parse regression:.*Invalid regular expression/i.test(m),
    )).toBe(false);
  });
});

