/**
 * SET 76 — resident agents + fill-ladder authentication (Andy, 2026-07-27):
 *  a. "every time information from supergit needs to be shown to the LLM, we
 *     need to execute the agent synthesis … every stage monitors for a
 *     trigger, which is the need to submit context to LLM" — the watcher is
 *     no longer scheduled at epoch boundaries; it fires on demand, scoped to
 *     the node whose draw is about to happen, and refreshes its model reading
 *     only when the ledger materially moved.
 *  b. "stop looping once something is accepted. we don't need to accumulate
 *     the bodies" — the fill ladder authenticates each draw and breaks.
 *  c. "they aren't informing the metasupercache" — fill bodies are recorded.
 *  d. Candidate records persist for deterministic corpse detection.
 */
import { describe, expect, it } from 'vitest';
import type { FeatureSpec, LlmClient, RepairInput } from '../src/core/types';
import type { MetaCandidateRecord } from '../src/tier1/metaSb';
import { Tier1Supergit } from '../src/tier1/supergit';
import { runTier1Job } from '../src/tier1/runner';
import { makeEvalSuiteRunner } from '../src/tier1/testExec';

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;
}

describe('SET 76a — the agent is resident, not scheduled', () => {
  it('synthesis reuses an unchanged world deterministically', async () => {
    // Andy, 2026-07-27: "Did you make an arbitrary threshold as to how much
    // was substantial enough to matter? It needs to be every single time."
    const prompts: string[] = [];
    const llm = {
      async encodeFeatures() { return makeSpec(); },
      async generateTests() { return { content: '', parts: { imports: [], cases: [] } }; },
      async fixTestCases(_s: unknown, _f: unknown, req: { name: string }[]) {
        const n = req[0]!.name;
        return { cases: [{ name: n, kind: (n === 'zero' ? 'edge' : 'behavior') as 'edge' | 'behavior',
          body: n === 'zero' ? 'expect(add(7, 0)).toBe(7);' : 'expect(add(2, 3)).toBe(5);' }] };
      },
      async generateImplementation() { return [{ path: 'src/adder.ts', content: RIGHT }]; },
      async repair(_i: RepairInput) { return [{ path: 'src/adder.ts', content: RIGHT }]; },
      async watchEpoch(p: string) {
        prompts.push(p);
        return 'IDIOM: steady\nUNION: nothing has changed at all.';
      },
    } as unknown as LlmClient;
    const res = await runTier1Job({ prompt: 'adder', llm, runSuite: makeEvalSuiteRunner() });
    expect(res.metrics.watcherReadings).toBe(prompts.length);
    expect(res.metrics.watcherWorldStaleSkips).toBeGreaterThan(0);
    expect(res.metrics.watcherReadings).toBeLessThan(res.metrics.watcherSyntheses);
    expect(res.log.some(e => /reading reused/.test(e.message))).toBe(false);
    expect(res.log.some(e => /no world advancement since node/.test(e.message))).toBe(true);
  });

  it('synthesis fires per draw and is node-scoped', async () => {
    const prompts: string[] = [];
    const llm = {
      async encodeFeatures() { return makeSpec(); },
      async generateTests() { return { content: '', parts: { imports: [], cases: [] } }; },
      async fixTestCases(_s: unknown, _f: unknown, req: { name: string }[]) {
        const n = req[0]!.name;
        return { cases: [{ name: n, kind: (n === 'zero' ? 'edge' : 'behavior') as 'edge' | 'behavior',
          body: n === 'zero' ? 'expect(add(7, 0)).toBe(7);' : 'expect(add(2, 3)).toBe(5);' }] };
      },
      async generateImplementation() { return [{ path: 'src/adder.ts', content: WRONG }]; },
      async repair(_i: RepairInput) { return [{ path: 'src/adder.ts', content: RIGHT }]; },
      async watchEpoch(p: string) {
        prompts.push(p);
        return 'IDIOM: leans on subtraction\nUNION: one node under repair.';
      },
    } as unknown as LlmClient;

    const res = await runTier1Job({ prompt: 'adder', llm, runSuite: makeEvalSuiteRunner() });
    // Fired on multiple triggers…
    expect(res.metrics.watcherSyntheses).toBeGreaterThanOrEqual(3);
    // Readings occur only after deterministic world advancement.
    expect(res.metrics.watcherReadings).toBeLessThan(res.metrics.watcherSyntheses);
    expect(res.metrics.watcherReadings).toBe(prompts.length);
    // Every reading was scoped to the node whose draw was pending.
    expect(prompts.every(p => /^Supergit state at "\w+" for F1/.test(p))).toBe(true);
    // Each firing is a fresh reading, stated honestly in the log.
    expect(res.log.some(e => /watcher synthesis @ .*fresh reading —/.test(e.message))).toBe(true);
    expect(res.problems).toEqual([]);
  });
});

describe('SET 76b/c — fill authenticates, stops, and feeds the cache', () => {
  it('a clean first draw ends the ladder; no bodies accumulated', async () => {
    let fillDraws = 0;
    const llm = {
      async encodeFeatures() { return makeSpec(); },
      async generateTests() { return { content: '', parts: { imports: [], cases: [] } }; },
      async fixTestCases(_s: unknown, _f: unknown, req: { name: string }[]) {
        fillDraws++;
        const n = req[0]!.name;
        return { cases: [{ name: n, kind: (n === 'zero' ? 'edge' : 'behavior') as 'edge' | 'behavior',
          body: n === 'zero' ? 'expect(add(7, 0)).toBe(7);' : 'expect(add(2, 3)).toBe(5);' }] };
      },
      async generateImplementation() { return [{ path: 'src/adder.ts', content: RIGHT }]; },
      async repair(_i: RepairInput) { return [{ path: 'src/adder.ts', content: RIGHT }]; },
    } as unknown as LlmClient;
    const res = await runTier1Job({ prompt: 'adder', llm, runSuite: makeEvalSuiteRunner() });
    // Two planned cases, one accepted draw each — not 3 draws per case.
    expect(fillDraws).toBe(2);
    expect(res.log.some(e => /fill accepted at try 1\/3 .* ladder stopped/.test(e.message))).toBe(true);
    expect(res.problems).toEqual([]);
  });

  it('a failing draw escalates the ladder, and BOTH bodies reach the metasupercache', async () => {
    const recs: MetaCandidateRecord[] = [];
    let n = 0;
    const llm = {
      async encodeFeatures() { return makeSpec(); },
      async generateTests() { return { content: '', parts: { imports: [], cases: [] } }; },
      async fixTestCases(_s: unknown, _f: unknown, req: { name: string }[]) {
        n++;
        // First draw for each case is unusable; second is honest.
        const body = n % 2 === 1 ? '' : 'expect(add(2, 3)).toBe(5);';
        return { cases: [{ name: req[0]!.name, kind: 'behavior' as const, body }] };
      },
      async generateImplementation() { return [{ path: 'src/adder.ts', content: RIGHT }]; },
      async repair(_i: RepairInput) { return [{ path: 'src/adder.ts', content: RIGHT }]; },
    } as unknown as LlmClient;
    const res = await runTier1Job({
      prompt: 'adder', llm, runSuite: makeEvalSuiteRunner(),
      onMetaCandidate: r => recs.push(r),
    });
    // Fill draws are recorded in the cross-run cache — the layer says so.
    const fills = recs.filter(r => r.layer === 'fill');
    expect(fills.length).toBeGreaterThanOrEqual(2);
    // Losers are recorded too — dead shapes are the point of the cache.
    expect(fills.some(r => r.parseStatus === 'unparseable')).toBe(true);
    expect(fills.some(r => r.parseStatus === 'parsed')).toBe(true);
    expect(fills.every(r => !!r.hash)).toBe(true);
    expect(res.problems).toEqual([]);
  });
});

