/**
 * SET 59b — HIL approval gates: the runner awaits onPhaseGate('ir-contract')
 * after every freeze and before any build, and onPhaseGate('build') before
 * anything executes. Omitted hook = straight through (prior behavior).
 */
import { describe, expect, it } from 'vitest';
import type { FeatureSpec, GeneratedFile, 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; }';

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;
}
const CASES = {
  imports: [{ from: 'src/adder', symbols: ['add'] }],
  cases: [
    { name: 'adds', kind: 'behavior' as const, body: 'expect(add(2, 3)).toBe(5);' },
    { name: 'zero', kind: 'edge' as const, body: 'expect(add(7, 0)).toBe(7);' },
  ],
};
const client: LlmClient = {
  async encodeFeatures() { return makeSpec(); },
  async generateTests() { return { content: '', parts: CASES }; },
  async generateImplementation() { return [{ path: 'src/adder.ts', content: RIGHT }]; },
  async repair(_i: RepairInput) { return [{ path: 'src/adder.ts', content: RIGHT }]; },
  async fixTestCases() { return { cases: CASES.cases }; },
} as unknown as LlmClient;

describe('SET 59b — phase gates', () => {
  it('awaits ir-contract before any build, then build before any execution', async () => {
    const trace: string[] = [];
    const res = await runTier1Job({
      prompt: 'adder',
      llm: {
        ...client,
        async generateImplementation(...args: unknown[]) {
          trace.push('impl-draw');
          return (client.generateImplementation as (...a: unknown[]) => Promise<GeneratedFile[]>)(...args);
        },
      } as unknown as LlmClient,
      runSuite: async (files, test) => {
        trace.push('suite-exec');
        return makeEvalSuiteRunner()(files, test);
      },
      onPhaseGate: async gate => { trace.push(`gate:${gate}`); },
    });
    expect(res.files.length).toBeGreaterThan(0);
    const gIr = trace.indexOf('gate:ir-contract');
    const gBuild = trace.indexOf('gate:build');
    const firstImpl = trace.indexOf('impl-draw');
    const firstExecAfterBuild = trace.slice(gBuild).indexOf('suite-exec');
    expect(gIr).toBeGreaterThanOrEqual(0);
    expect(gBuild).toBeGreaterThan(gIr);
    expect(firstImpl).toBeGreaterThan(gIr); // no impl before contract approval
    expect(firstExecAfterBuild).toBeGreaterThan(0); // execution follows the build gate
  });

  it('omitted hook runs straight through (prior behavior)', async () => {
    const res = await runTier1Job({ prompt: 'adder', llm: client, runSuite: makeEvalSuiteRunner() });
    expect(res.files.length).toBeGreaterThan(0);
  });
});
