/**
 * SET 60 → SET 71 — per-NODE K-draws (Andy: "there should be 9").
 *
 * History: SET 60 added cross-candidate salvage for multi-case batches; SET 71
 * made the bounded unit of every fill batch the CASE itself, which subsumes
 * salvage at fill (selection is per-case natively) and makes empty-tail
 * truncation structurally impossible. This suite pins the per-node contract.
 */
import { describe, expect, it } from 'vitest';
import type { FeatureSpec, LlmClient, RepairInput } from '../src/core/types';
import { runTier1Job } from '../src/tier1/runner';
import { makeEvalSuiteRunner } from '../src/tier1/testExec';

const RIGHT = 'export function add(a: number, b: number): number { return a + b; }';
const GOOD_A = 'expect(add(2, 3)).toBe(5);';
const GOOD_B = 'expect(add(7, 0)).toBe(7);';

function makeSpec(): FeatureSpec {
  return {
    title: 'Adder', prompt: 'adder',
    features: [{
      id: 'F1', name: 'add', description: 'adds two numbers', parentId: null,
      deps: [], files: ['src/adder.ts'], testFile: 'tests/f1-add.test.ts',
      edgeCases: ['zero'], envVars: [],
      exports: [{ file: 'src/adder.ts', symbol: 'add' }],
      stubs: [{ path: 'src/adder.ts', content: `export function add(a: number, b: number): number { throw new Error('stub'); }` }],
    }],
  } as unknown as FeatureSpec;
}

describe('SET 71 — per-node K-draws', () => {
  it('every fill batch targets exactly ONE case; both cases fill without drops', async () => {
    const reqSizes: number[] = [];
    const llm = {
      async encodeFeatures() { return makeSpec(); },
      async generateTests() { return { content: '', parts: { imports: [], cases: [] } }; },
      async generateImplementation() { return [{ path: 'src/adder.ts', content: RIGHT }]; },
      async repair(_i: RepairInput) { return [{ path: 'src/adder.ts', content: RIGHT }]; },
      async fixTestCases(_s: unknown, _f: unknown, req: { name: string }[]) {
        reqSizes.push(req.length);
        const name = req[0]!.name;
        return {
          cases: [{
            name,
            kind: (name === 'zero' ? 'edge' : 'behavior') as 'edge' | 'behavior',
            body: name === 'zero' ? GOOD_B : GOOD_A,
          }],
        };
      },
    } as unknown as LlmClient;

    const res = await runTier1Job({ prompt: 'adder', llm, runSuite: makeEvalSuiteRunner() });
    const msgs = res.log.map(e => e.message);

    // The per-node contract: no fill request ever bundles multiple cases.
    expect(reqSizes.length).toBeGreaterThan(0);
    expect(Math.max(...reqSizes)).toBe(1);
    // Both cases land; nothing drops, nothing re-enqueues.
    const suite = res.frozenTests[0]?.content ?? '';
    expect(suite).toContain(GOOD_A);
    expect(suite).toContain(GOOD_B);
    expect(msgs.some(m => /dropped|re-enqueued/.test(m))).toBe(false);
    // Freeze ledger states the fill tally (SET 71).
    expect(msgs.some(m => /frozen suite .*\(\d+ planned → \d+ filled/.test(m))).toBe(true);
  });
});
