/**
 * SET 64 — L2 schema-guided interpretation in the bounded loop:
 * classified-but-unhandled findings get K=1 procedure-execution draws with the
 * full dossier (schema + strata + superbuffer + 64c exhibit + 64d trace)
 * BEFORE any L3 permutation batch; corpse draws are rejected without
 * execution; layer attribution and metaSb candidate records flow.
 */
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';
import { drawHashForFiles } from '../src/tier1/metaSb';
import type { MetaCandidateRecord } from '../src/tier1/metaSb';

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: '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(opts: {
  repairBodies: string[]; // consumed per repair call; last repeats
  onRepair?: (input: RepairInput, purpose: string | undefined) => void;
}): LlmClient {
  let repairCalls = 0;
  return {
    async encodeFeatures() { return makeSpec(); },
    async generateTests() { return { content: '', parts: { imports: [], cases: [] } }; },
    async fixTestCases(_s: unknown, _f: unknown, req: { name: string }[]) {
      const name = req[0]!.name;
      return {
        cases: [{
          name,
          kind: (name === 'zero' ? 'edge' : 'behavior') as 'edge' | 'behavior',
          body: name === 'zero' ? 'expect(add(7, 0)).toBe(7);' : 'expect(add(2, 3)).toBe(5);',
        }],
      };
    },
    async generateImplementation() { return [{ path: 'src/adder.ts', content: WRONG }]; },
    async repair(input: RepairInput, o?: { purpose?: string }) {
      opts.onRepair?.(input, o?.purpose);
      const body = opts.repairBodies[Math.min(repairCalls, opts.repairBodies.length - 1)]!;
      repairCalls++;
      return [{ path: 'src/adder.ts', content: body }];
    },
  } as unknown as LlmClient;
}

describe('SET 64 — L2 rung + corpse + attribution', () => {
  it('L2 bullseye: dossier-conditioned K=1 draw resolves the class before any L3 batch', async () => {
    const purposes: (string | undefined)[] = [];
    const dossiers: string[] = [];
    const metaRecs: MetaCandidateRecord[] = [];
    const res = await runTier1Job({
      prompt: 'adder',
      llm: makeLlm({
        repairBodies: [RIGHT],
        onRepair: (input, purpose) => {
          purposes.push(purpose);
          dossiers.push(input.failureOutput ?? '');
        },
      }),
      runSuite: makeEvalSuiteRunner(),
      onMetaCandidate: r => metaRecs.push(r),
    });
    // Healed by L2, never reached L3.
    expect(res.problems).toEqual([]);
    expect(res.metrics.l2Draws).toBe(1);
    expect(res.metrics.l2Wins).toBe(1);
    expect(res.metrics.l2Bullseyes).toBe(1);
    expect(res.metrics.fixesL2).toBe(1);
    expect(res.metrics.fixesL3).toBe(0);
    // No L3 repair batch ever ran (permBatches counts fill batches too, so
    // the honest proxy is the absence of any L3 repair-pass event).
    const allMsgs = res.log.map(e => e.message);
    expect(allMsgs.some(m => /^repair pass \d+:/.test(m))).toBe(false);
    // The one repair call was the L2 procedure draw with the full dossier.
    expect(purposes[0]).toMatch(/^L2 procedure: assertion/);
    // SET 80 — the FIRST L2 loop is now node-compliant repair: schema + the
    // node itself + all three substrata, with a repair-in-place instruction.
    expect(dossiers[0]).toContain('REPAIR THIS NODE SO THAT IT COMPLIES WITH THE SCHEMA');
    expect(dossiers[0]).toContain('SCHEMA — the node at src/adder.ts');
    expect(dossiers[0]).toContain('NODE UNDER REPAIR');
    expect(dossiers[0]).toContain('return a - b;'); // the actual node, in full
    expect(dossiers[0]).toContain('SUPERGIT');
    expect(dossiers[0]).toContain('SUPERCACHE');
    expect(dossiers[0]).toContain('SUBSTRATE');
    expect(res.metrics.nodeRepairDraws).toBeGreaterThanOrEqual(1);
    // Event narrative + metaSb record.
    const msgs = res.log.map(e => e.message);
    expect(msgs.some(m => /^L2 procedure loop 1\/3 \[assertion/.test(m))).toBe(true);
    expect(msgs.some(m => /^L2 bullseye: assertion/.test(m))).toBe(true);
    expect(metaRecs.some(r => r.layer === 'L2' && r.exhibitHash)).toBe(true);
    // Classification floor accounting saw the assertion failures.
    expect(res.metrics.classifyAttempts).toBeGreaterThan(0);
    expect(res.metrics.classifyCataloged).toBe(res.metrics.classifyAttempts);
  });

  it('corpse / SET 101c: pass-1 no-improvement short-circuits L2; escalate without passes 2/3', async () => {
    const res = await runTier1Job({
      prompt: 'adder',
      llm: makeLlm({ repairBodies: [WRONG] }), // always the incumbent, verbatim
      runSuite: makeEvalSuiteRunner(),
      gates: { maxPasses: 6, stallLimit: 5, progressStall: 5 },
    });
    const msgs = res.log.map(e => e.message);
    // SET 101c — pass 1 no-improvement + unchanged finding hash skips loops 2/3.
    expect(res.metrics.l2Draws).toBe(1);
    expect(res.metrics.l2ShortCircuits).toBeGreaterThanOrEqual(1);
    expect(res.metrics.l2Wins).toBe(0);
    expect(msgs.some(m => /L2 short-circuit: world unchanged — escalating/.test(m))).toBe(true);
    // Feature stays red honestly; nothing was accepted at any layer.
    expect(res.metrics.fixesL2).toBe(0);
    expect(res.metrics.fixesL3).toBe(0);
    expect(res.problems.length).toBeGreaterThan(0);
  });

  it('cross-run corpse: metaSb hash pre-seeds the rejection (metasupercache source)', async () => {
    const res = await runTier1Job({
      prompt: 'adder',
      llm: makeLlm({ repairBodies: [WRONG] }),
      runSuite: makeEvalSuiteRunner(),
      gates: { maxPasses: 3, stallLimit: 2, progressStall: 2 },
      metaSb: { corpseHashes: [drawHashForFiles([{ path: 'src/adder.ts', content: WRONG }])] },
    });
    const msgs = res.log.map(e => e.message);
    expect(msgs.some(m => /^corpse detected \[assertion.*metasupercache/.test(m))).toBe(true);
    expect(res.metrics.corpseRejections).toBeGreaterThanOrEqual(1);
  });
});
