/**
 * SET 62 — the promotion flywheel now feeds from whole-file heals: entropy
 * resets and IR-case surgeries mine their before→after delta into
 * handler-candidate promotion (support ≥3, same discipline as ever).
 */
import { describe, expect, it } from 'vitest';
import type { FeatureSpec, GeneratedFile, LlmClient, RepairInput } from '../src/core/types';
import { runTier1Job } from '../src/tier1/runner';
import { makeEvalSuiteRunner } from '../src/tier1/testExec';
import { distillEditShape, promoteLessonsToHandlerCandidates } from '../src/tier1/handlerCandidate';

const WRONG = 'export function add(a: number, b: number): number { return a - b; }';
const RIGHT = 'export function add(a: number, b: number): number { return a + b; }';

function makeSpec(): FeatureSpec {
  return {
    title: 'Adders', prompt: 'adders',
    features: [1, 2, 3].map(i => ({
      id: `F${i}`, name: `add${i}`, description: 'adds two numbers', parentId: null,
      deps: [], files: [`src/adder${i}.ts`], testFile: `tests/f${i}-add.test.ts`,
      edgeCases: ['zero'], envVars: [],
      exports: [{ file: `src/adder${i}.ts`, symbol: 'add' }],
      stubs: [{ path: `src/adder${i}.ts`, content: `export function add(a: number, b: number): number { throw new Error('stub'); }` }],
    })),
  } as unknown as FeatureSpec;
}

function casesFor(i: number) {
  return {
    imports: [{ from: `src/adder${i}`, symbols: ['add'] }],
    cases: [
      { name: 'adds', kind: 'behavior' as const, body: 'expect(add(2, 3)).toBe(5);' },
      { name: 'zero', kind: 'edge' as const, body: 'expect(add(7, 0)).toBe(7);' },
    ],
  };
}

describe('SET 62 — flywheel from reset heals', () => {
  it('distillEditShape is deterministic and clusters identical transforms', () => {
    const s1 = distillEditShape(WRONG, RIGHT);
    const s2 = distillEditShape(WRONG, RIGHT);
    expect(s1).toBe(s2);
    const promoted = promoteLessonsToHandlerCandidates([
      { classKey: 'assertion|toBe', editShape: `reset ${s1}` },
      { classKey: 'assertion|toBe', editShape: `reset ${s1}` },
      { classKey: 'assertion|toBe', editShape: `reset ${s1}` },
    ]);
    expect(promoted.length).toBe(1);
    expect(promoted[0]!.support).toBe(3);
  });

  it('three reset-healed features promote a handler-candidate at done', async () => {
    const featureOf = (path: string) => Number(/adder(\d)/.exec(path)?.[1] ?? '1');
    const llm = {
      async encodeFeatures() { return makeSpec(); },
      async generateTests() { return { content: '', parts: { imports: [], cases: [] } }; },
      async fixTestCases(_s: unknown, f: { id?: string }) {
        const i = Number((f?.id ?? 'F1').slice(1));
        return { cases: casesFor(i).cases };
      },
      async generateImplementation(
        _s: FeatureSpec, f: { files: string[] }, _t: string, _d: GeneratedFile[],
        opts?: { temperature?: number },
      ) {
        const hot = (opts?.temperature ?? 0) >= 1;
        return [{ path: f.files[0]!, content: hot ? RIGHT : WRONG }];
      },
      async repair(input: RepairInput) {
        const f = (input.feature as { files: string[] }).files[0]!;
        return [{ path: f, content: WRONG }]; // circles forever — forces reset
      },
    } as unknown as LlmClient;

    const res = await runTier1Job({ prompt: 'adders', llm, runSuite: makeEvalSuiteRunner() });
    const msgs = res.log.map(e => e.message);
    const resets = msgs.filter(m => /entropy reset @ pass/.test(m)).length;
    const converged = msgs.filter(m => /^converged in [1-9]/.test(m)).length;
    // All three features must have healed via reset with the same delta shape…
    expect(resets).toBeGreaterThanOrEqual(3);
    expect(converged).toBeGreaterThanOrEqual(3);
    // …which is exactly the support the flywheel needs.
    const candidateLines = msgs.filter(m => /^handler-candidate: /.test(m));
    expect(candidateLines.length).toBeGreaterThanOrEqual(1);
    expect(candidateLines[0]).toMatch(/support=3/);
  });
});
