import { describe, expect, it } from 'vitest';
import type { FeatureNode, FeatureSpec, LlmClient } from '../src/core/types';
import { substrateFillOne } from '../src/tier1/substrateFill';
import { makeEvalSuiteRunner } from '../src/tier1/testExec';
import { runTier1Job } from '../src/tier1/runner';

const feature: FeatureNode = {
  id: 'F1',
  name: 'widget',
  description: 'creates widgets',
  parentId: null,
  deps: [],
  files: ['src/widget.ts'],
  testFile: 'tests/widget.test.ts',
  edgeCases: [],
  envVars: [],
  exports: [{ file: 'src/widget.ts', symbol: 'WidgetModel' }],
  stubs: [{
    path: 'src/widget.ts',
    content: 'export class WidgetModel { constructor(_value: object) { throw new Error("stub"); } }',
  }],
};

const spec: FeatureSpec = { title: 'Widgets', prompt: 'create widgets', features: [feature] };

describe('SET 97 — substrate-first fill', () => {
  it('accepts a clade rebound only when it is red against stubs', async () => {
    const outcomes: string[] = [];
    const hit = await substrateFillOne({
      spec,
      feature,
      caseGen: { name: 'WidgetModel', kind: 'behavior', body: '', symbol: 'WidgetModel' },
      stubs: feature.stubs,
      runSuite: makeEvalSuiteRunner(),
      imports: [{ from: 'src/widget.ts', symbols: ['WidgetModel'] }],
      onMechanismResult: (mechanism, result) => outcomes.push(`${mechanism}:${result}`),
    });

    expect(hit).toMatchObject({ mechanism: 'rebindCladeBody' });
    expect(outcomes).toContain('rebindCladeBody:red');
  });

  it('rejects a green substrate candidate as vacuous', async () => {
    const outcomes: string[] = [];
    const hit = await substrateFillOne({
      spec,
      feature: {
        ...feature,
        stubs: [{
          path: 'src/widget.ts',
          content: 'export class WidgetModel { constructor(_value: object) {} }',
        }],
      },
      caseGen: { name: 'WidgetModel', kind: 'behavior', body: '', symbol: 'WidgetModel' },
      stubs: [{ path: 'src/widget.ts', content: 'export class WidgetModel { constructor(_value: object) {} }' }],
      runSuite: makeEvalSuiteRunner(),
      imports: [{ from: 'src/widget.ts', symbols: ['WidgetModel'] }],
      onMechanismResult: (mechanism, result) => outcomes.push(`${mechanism}:${result}`),
    });

    expect(hit).toBeNull();
    expect(outcomes).toContain('rebindCladeBody:vacuous');
  });

  it('fills a method anchor before fixTestCases is called', async () => {
    const methodFeature: FeatureNode = {
      ...feature,
      name: 'create user',
      description: 'creates a user object',
      files: ['src/users.ts'],
      testFile: 'tests/users.test.ts',
      exports: [{ file: 'src/users.ts', symbol: 'createUser' }],
      stubs: [{
        path: 'src/users.ts',
        content: 'export function createUser(_value: object): object { throw new Error("stub"); }',
      }],
    };
    const methodSpec: FeatureSpec = {
      title: 'Users',
      prompt: 'create a user',
      features: [methodFeature],
    };
    let fixCalls = 0;
    const llm = {
      async encodeFeatures() { return methodSpec; },
      async generateTests() { return { content: '', parts: { imports: [], cases: [] } }; },
      async generateImplementation() {
        return [{ path: 'src/users.ts', content: 'export function createUser(_value: object): object { return {}; }' }];
      },
      async repair() { return []; },
      async fixTestCases() {
        fixCalls++;
        return { cases: [] };
      },
    } as unknown as LlmClient;

    const result = await runTier1Job({
      prompt: 'create a user',
      llm,
      runSuite: makeEvalSuiteRunner(),
    });

    expect(result.metrics.substrateFillHits).toBeGreaterThanOrEqual(1);
    expect(fixCalls).toBe(0);
    expect(result.log.some(e =>
      e.message === 'case "createUser" filled from substrate: rebindCladeBody (0 tok)',
    )).toBe(true);
  });
});
