/**
 * SET 43a — mechanical permutation-select; SET 43b(i) — spec-constant repair.
 */
import { describe, expect, it } from 'vitest';
import type { FeatureSpec, GeneratedFile } from '../src/core/types';
import {
  attemptMechanicalFix,
  attemptSpecConstantConformance,
  classifyFailure,
  selectMechanicalCandidate,
} from '../src/tier1/substrate';

const suite: GeneratedFile = {
  path: 'tests/f.test.ts',
  content: `import { describe, it, expect } from 'vitest';\ndescribe('x', () => { it('y', () => { expect(1).toBe(1); }); });\n`,
};

function miniSpec(files: string[]): FeatureSpec {
  return {
    title: 'T', prompt: 't',
    features: [{
      id: 'F1', name: 't', description: 't', parentId: null,
      deps: [], files, testFile: suite.path,
      edgeCases: [], envVars: [],
      exports: files.map(f => ({ file: f, symbol: 'shared' })),
      stubs: files.map(f => ({
        path: f,
        content: `export function shared(): number { throw new Error('stub'); }\n`,
      })),
    }],
  } as unknown as FeatureSpec;
}

describe('SET 43a selectMechanicalCandidate', () => {
  it('picks unique parse-clean winner', () => {
    const r = selectMechanicalCandidate(
      [{ id: 'a', clean: false }, { id: 'b', clean: true }, { id: 'c', clean: true }],
      c => ({
        parseClean: c.clean,
        filesTouched: 1,
        pathOrderKey: c.id,
      }),
    );
    expect(r.status).toBe('picked');
    if (r.status === 'picked') expect(r.winner.id).toBe('b'); // b before c among clean
  });

  it('refuses >4 candidates', () => {
    const cands = [1, 2, 3, 4, 5].map(n => ({ n }));
    const r = selectMechanicalCandidate(cands, c => ({
      parseClean: true, filesTouched: 1, pathOrderKey: String(c.n),
    }));
    expect(r).toMatchObject({ status: 'refused', reason: expect.stringMatching(/>4/) });
  });

  it('refuses fitness tie', () => {
    const r = selectMechanicalCandidate(
      [{ id: 'a' }, { id: 'b' }],
      () => ({ parseClean: true, filesTouched: 1, pathOrderKey: 'same' }),
    );
    expect(r).toMatchObject({ status: 'refused', reason: expect.stringMatching(/tie/) });
  });
});

describe('SET 43a two-import-site fixture', () => {
  it('tries both import sites, applies unique winner, no LLM', () => {
    // One exporter; two consumers that use `shared` without importing it.
    const artifact: GeneratedFile[] = [
      {
        path: 'src/lib.ts',
        content: 'export function shared(): number { return 1; }\n',
      },
      {
        path: 'src/a.ts',
        content: 'export function a(): number { return shared(); }\n',
      },
      {
        path: 'src/b.ts',
        content: 'export function b(): number { return shared() + 1; }\n',
      },
    ];
    const failure = classifyFailure('t', 'shared is not defined');
    expect(failure.kind).toBe('undefined-symbol');
    const attempt = attemptMechanicalFix(failure, artifact, suite, miniSpec(artifact.map(f => f.path)));
    expect(attempt.status).toBe('fixed');
    expect(attempt.fix).toBeTruthy();
    expect(attempt.fix!.content).toMatch(/import \{ shared \} from/);
    // Lexicographic pathOrderKey prefers src/a.ts over src/b.ts
    expect(attempt.fix!.path).toBe('src/a.ts');
  });
});

describe('SET 43b(i) spec-constant conformance', () => {
  it('heals unique wrong-constant 0-tok', () => {
    const artifact: GeneratedFile[] = [{
      path: 'src/status.ts',
      content: 'export function status(): number { return 200; }\n',
    }];
    const failure = classifyFailure(
      't',
      'expect(...).toBe failed — actual: 200, expected: 201',
    );
    expect(failure.classKey).toBe('assertion|toBe');
    expect(failure.detail.received).toBe('200');
    expect(failure.detail.expected).toBe('201');
    const attempt = attemptSpecConstantConformance(failure, artifact);
    expect(attempt?.status).toBe('fixed');
    expect(attempt?.fix?.content).toContain('return 201');
    expect(attempt?.fix?.note).toMatch(/spec-constant/);
  });

  it('refuses ambiguous multi-occurrence literal', () => {
    const artifact: GeneratedFile[] = [{
      path: 'src/status.ts',
      content: 'export function status(): number { return 200 + 200; }\n',
    }];
    const failure = classifyFailure(
      't',
      'expect(...).toBe failed — actual: 200, expected: 201',
    );
    const attempt = attemptSpecConstantConformance(failure, artifact);
    expect(attempt?.status).toBe('refused');
    expect(attempt?.reason).toMatch(/ambiguous literal occurrence/);
  });
});
