/**
 * SET 70 — dropped is a scheduling decision made too early: fill-dropped
 * cases are re-enqueued and re-prosecuted at end-of-run against the warm
 * world; only a failed WARM retry produces the final (lineage-parseable) drop.
 */
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: not implemented'); }` }],
    }],
  } as unknown as FeatureSpec;
}

function makeLlm(warmBody: string | null): LlmClient {
  return {
    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 }[], _imp: unknown, _cur: unknown,
      opts?: { purpose?: string },
    ) {
      if (opts?.purpose?.startsWith('case-relay:')) {
        // The warm-world draw — the whole point of the relay.
        return { cases: [{ name: req[0]!.name, kind: 'edge' as const, body: warmBody ?? '' }] };
      }
      // SET 71 per-node draws: cold fills succeed for "adds", fail for
      // "zero" — forcing the re-enqueue regardless of batch shape.
      const name = req[0]!.name;
      return {
        cases: [{
          name,
          kind: (name === 'zero' ? 'edge' : 'behavior') as 'edge' | 'behavior',
          body: name === 'zero' ? '' : GOOD_A,
        }],
      };
    },
  } as unknown as LlmClient;
}

describe('SET 70 — end-of-run case-relay', () => {
  it('a cold-dropped case is re-enqueued and RECOVERED against the warm world', async () => {
    const res = await runTier1Job({ prompt: 'adder', llm: makeLlm(GOOD_B), runSuite: makeEvalSuiteRunner() });
    const msgs = res.log.map(e => e.message);
    expect(msgs.some(m => /re-enqueued to end-of-run stack/.test(m))).toBe(true);
    expect(msgs.some(m => /case-relay: case "zero" recovered at end-of-run/.test(m))).toBe(true);
    expect(res.metrics.reEnqueuedCases).toBe(1);
    expect(res.metrics.recoveredCases).toBe(1);
    // The recovered case lives in the FINAL frozen contract; no drop problem.
    const suite = res.frozenTests[0]?.content ?? '';
    expect(suite).toContain(GOOD_B);
    expect(res.problems.some(p => /dropped/.test(p))).toBe(false);
  });

  it('a failed warm retry produces the final drop with the lineage-parseable message', async () => {
    const res = await runTier1Job({ prompt: 'adder', llm: makeLlm(null), runSuite: makeEvalSuiteRunner() });
    const msgs = res.log.map(e => e.message);
    expect(msgs.some(m => /re-enqueued to end-of-run stack/.test(m))).toBe(true);
    expect(msgs.some(m => /case-relay: case "zero" final drop/.test(m))).toBe(true);
    expect(res.metrics.recoveredCases).toBe(0);
    // Lineage shape preserved for cross-epoch replan (37g).
    expect(res.problems.some(p => /planned case "zero" unfilled after .* — dropped/.test(p))).toBe(true);
  });
});
