/**
 * SET 95c — canary: the primary fill site (runner.ts `fixTestCases:fill`)
 * accepts a hole-only response and stores the EXTRACTED assertion — never
 * the frame around it — as the case body.
 */
import { describe, expect, it } from 'vitest';
import type { FeatureSpec, LlmClient } from '../src/core/types';
import { runTier1Job, TIER1_DEFAULT_GATES } from '../src/tier1/runner';
import { makeEvalSuiteRunner } from '../src/tier1/testExec';

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: [], 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'); }`,
      }],
    }],
  };
}

/** Class-based LlmClient: fixTestCases always answers ⟨HOLE⟩…⟨/END⟩-wrapped. */
class HoleOnlyLlm implements Partial<LlmClient> {
  async encodeFeatures() {
    return makeSpec();
  }
  async generateTests() {
    return { content: '', parts: { imports: [], cases: [] } };
  }
  async generateImplementation() {
    return [{ path: 'src/adder.ts', content: 'export function add(a: number, b: number): number { return a + b; }' }];
  }
  async repair() {
    return [];
  }
  async fixTestCases(
    _spec: unknown,
    _feature: unknown,
    req: { name: string; kind: 'behavior' | 'edge' | 'integration' }[],
  ) {
    const target = req[0]!;
    return {
      cases: [{
        name: target.name,
        kind: target.kind,
        body: '⟨HOLE⟩\nexpect(add(2, 3)).toBe(5);\n⟨/END⟩',
      }],
    };
  }
}

describe('SET 95c — hole-only response accepted at the primary fill site', () => {
  it('ships the canary default-OFF (pre-95 fixtures must not regress)', () => {
    expect(TIER1_DEFAULT_GATES.holeOnlyFill).toBe(false);
  });

  it('extracts the assertion, freezes it as the case body, and does not reject as overreach', async () => {
    const llm = new HoleOnlyLlm() as unknown as LlmClient;
    const res = await runTier1Job({
      prompt: 'adder',
      llm,
      runSuite: makeEvalSuiteRunner(),
      gates: { holeOnlyFill: true, grammarEnabled: false },
    });

    expect(res.metrics.overreachBodyRejections).toBe(0);

    const suite = res.frozenTests.find(t => t.path.includes('f1-add'))?.content ?? '';
    expect(suite).toContain('expect(add(2, 3)).toBe(5);');
    // The frame markers are context only — never stored in the frozen suite.
    expect(suite).not.toContain('⟨HOLE⟩');
    expect(suite).not.toContain('⟨/END⟩');

    expect(res.log.some(e => e.message === 'case "add" filled by model')).toBe(true);
  });
});
