/**
 * SET 38 — Incremental program-parse validation, ddmin, parse-poison helpers.
 */
import { describe, expect, it } from 'vitest';
import type { GeneratedFile } from '../src/core/types';
import {
  ddmin,
  ddminFailureEvent,
  ddminForQuarantine,
  ddminHealWithOracle,
  ddminMinimalFailingSet,
  ddminTriggerHolds,
  detectRegexLiteralPoisonPatterns,
  distillProgramParseLessonKey,
  formatParsePoisonIncidents,
  programParseError,
  vetProgramParse,
  vetProgramParseAfterWrite,
} from '../src/tier1/testExec';

function cleanMod(i: number): GeneratedFile {
  return {
    path: `src/m${i}.ts`,
    content: `export function m${i}(): number { return ${i}; }\n`,
  };
}

describe('SET 38a incremental whole-program parse validation', () => {
  it('9 clean modules + 1 program-poisoning write → poisoner rejected at its step', () => {
    const clean = Array.from({ length: 9 }, (_, i) => cleanMod(i + 1));
    const test: GeneratedFile = {
      path: 'tests/f1.test.ts',
      content: [
        "import { describe, it, expect } from 'vitest';",
        "import { m1 } from '../src/m1';",
        "describe('t', () => { it('x', () => { expect(m1()).toBe(1); }); });",
      ].join('\n'),
    };

    const artifact: GeneratedFile[] = [];
    // Write clean modules one by one — each accepted.
    for (const f of clean) {
      artifact.push(f);
      const trial = [...artifact, test];
      const v = vetProgramParseAfterWrite(f, trial);
      expect(v.ok, `clean ${f.path} should parse`).toBe(true);
    }
    // Suite write accepted.
    {
      const trial = [...artifact, test];
      expect(vetProgramParse(trial, test.path).ok).toBe(true);
      artifact.push(test);
    }

    // Program-poisoning write: sucrase-clean, V8 construct-fail.
    const poison: GeneratedFile = {
      path: 'src/poison.ts',
      content: 'export const re = /(/;\n',
    };
    const poisoned = [...artifact.filter(f => !/\.test\./.test(f.path)), poison, test];
    const rejected = vetProgramParseAfterWrite(poison, poisoned);
    expect(rejected.ok).toBe(false);
    if (!rejected.ok) {
      expect(rejected.event).toBe(
        `program-parse regression: ${poison.path} rejected (crash: ${rejected.message})`,
      );
      expect(rejected.event).toMatch(/^program-parse regression: src\/poison\.ts rejected \(crash: /);
      expect(distillProgramParseLessonKey(poison.path, rejected.message)).toMatch(
        /^program-parse\|src\/poison\.ts\|/,
      );
    }

    // Clean files remain accepted if we keep the incumbent (no poison).
    const incumbent = [...clean, test];
    expect(vetProgramParse(incumbent, clean[0]!.path).ok).toBe(true);
  });

  it('vetProgramParse accepts a fully clean artifact', () => {
    const files = [cleanMod(1), cleanMod(2)];
    const test: GeneratedFile = {
      path: 'tests/f1.test.ts',
      content: [
        "import { describe, it, expect } from 'vitest';",
        "import { m1 } from '../src/m1';",
        "describe('t', () => { it('x', () => { expect(m1()).toBe(1); }); });",
      ].join('\n'),
    };
    expect(vetProgramParse([...files, test], 'src/m2.ts')).toEqual({ ok: true });
    expect(programParseError(files, test)).toBeNull();
  });
});

describe('SET 38b ddmin minimal failing set', () => {
  it('ddmin pure finds 1-minimal interesting subset', () => {
    const universe = ['A', 'B', 'C', 'D', 'E'];
    // Interesting iff contains both C and E (non-adjacent — prefix bisection unsound).
    const interesting = (s: string[]) => s.includes('C') && s.includes('E');
    const minimal = ddmin(universe, interesting);
    expect(minimal.sort()).toEqual(['C', 'E']);
  });

  it('ddminMinimalFailingSet names the poison module', () => {
    const files: GeneratedFile[] = [
      cleanMod(1),
      cleanMod(2),
      { path: 'src/bad.ts', content: 'export const re = /(/;\n' },
      cleanMod(3),
    ];
    const r = ddminMinimalFailingSet(files);
    expect(r.event).toMatch(/^minimal failing set: \[/);
    expect(r.set).toContain('src/bad.ts');
    expect(r.set.length).toBe(1);
    expect(r.event).toBe(`minimal failing set: [src/bad.ts]`);
  });

  it('tripwire: all-probes-clean MUST emit minimal failing set OR ddmin-failure', () => {
    // All modules parse/load clean — trigger still holds when crashMessage present.
    const files: GeneratedFile[] = [cleanMod(1), cleanMod(2)];
    const test: GeneratedFile = {
      path: 'tests/f1.test.ts',
      content: [
        "import { describe, it, expect } from 'vitest';",
        "import { m1 } from '../src/m1';",
        "describe('t', () => { it('x', () => { expect(m1()).toBe(1); }); });",
      ].join('\n'),
    };
    const crash = 'opaque quarantine crash with clean probes';
    expect(ddminTriggerHolds(files, test, crash)).toBe(true);
    const emitted = ddminForQuarantine(files, test, crash);
    expect(emitted).not.toBeNull();
    expect(emitted!.event).toMatch(/^(minimal failing set:|ddmin-failure)/);
    // Silent non-fire would be null — forbidden when trigger holds.
    expect(emitted).not.toBeNull();

    // When a probe is dirty, trigger does not hold → null is correct (not silent miss).
    const dirty: GeneratedFile[] = [
      ...files,
      { path: 'src/bad.ts', content: 'export const re = /(/;\n' },
    ];
    expect(ddminTriggerHolds(dirty, test, crash)).toBe(false);
    expect(ddminForQuarantine(dirty, test, crash)).toBeNull();

    expect(ddminFailureEvent('boom')).toBe('ddmin-failure: boom');
    expect(ddminFailureEvent()).toBe('ddmin-failure');
  });

  it('ddminHealWithOracle applies full-text delimiter oracle to minimal set', () => {
    const bad: GeneratedFile = {
      path: 'src/bad.ts',
      content: 'export const re = /(/;\n',
    };
    const files = [cleanMod(1), bad];
    const r = ddminMinimalFailingSet(files);
    expect(r.set).toEqual(['src/bad.ts']);
    expect(r.event).toBe('minimal failing set: [src/bad.ts]');
    // Injected oracle over FULL text (not ±120 seam).
    const oracle = (content: string) =>
      content.includes('/(/')
        ? { content: content.replace('/(/', '/(?:)/'), classKey: 'unterminated-regex', edits: 1 }
        : null;
    const fixes = ddminHealWithOracle(files, r.set, oracle, r.event);
    expect(fixes).toHaveLength(1);
    expect(fixes[0]!.note).toMatch(/ddmin delimiter-oracle full-text heal/);
    expect(fixes[0]!.path).toBe('src/bad.ts');
    expect(programParseError([{ path: fixes[0]!.path, content: fixes[0]!.content }])).toBeNull();
  });
});

describe('SET 38c parse-poison helpers', () => {
  it('formatParsePoisonIncidents exact shape', () => {
    expect(formatParsePoisonIncidents(3, 7)).toBe('parse-poison incidents: 3 (prior run: 7)');
    expect(formatParsePoisonIncidents(0, 0)).toBe('parse-poison incidents: 0 (prior run: 0)');
  });

  it('detectRegexLiteralPoisonPatterns flags unterminated regex shapes', () => {
    expect(detectRegexLiteralPoisonPatterns('export const re = /(/;\n')).toContain(
      'unterminated-regex-group',
    );
    expect(detectRegexLiteralPoisonPatterns('export const re = /[a-z;\n')).toContain(
      'unterminated-regex-charclass',
    );
    expect(detectRegexLiteralPoisonPatterns('export const ok = /abc/;\n')).toEqual([]);
  });
});
