import { describe, expect, it } from 'vitest';
import { selectPermutation, permutationEvent } from '../src/tier1/permutation';
import { classifyCaseClade, cladeRatio, rebindCladeBody } from '../src/tier1/clades';
import { buildRepo } from '../src/tier1/exportRepo';
import type { Tier1Result } from '../src/tier1/runner';
import { runTier1Job } from '../src/tier1/runner';
import { makeEvalSuiteRunner } from '../src/tier1/testExec';
import type { FeatureSpec, LlmClient } from '../src/core/types';

describe('SET 3a permutation-select', () => {
  it('picks highest score, then fewest findings, then lowest temp', () => {
    const r = selectPermutation([
      { value: 'a', temp: 1.0, score: 2, findings: 1 },
      { value: 'b', temp: 0.7, score: 5, findings: 0, label: '5/6 passing' },
      { value: 'c', temp: 0.3, score: 5, findings: 0 },
    ]);
    expect(r.picked.value).toBe('c'); // tie on score+findings → lowest temp
    expect(permutationEvent(r)).toMatch(/permutation-select: 3 candidates, picked t=0\.3/);
  });
});

describe('SET 3b clades', () => {
  it('tags template-like roundtrip bodies and unmatched as freeform', () => {
    const rt = classifyCaseClade({
      name: 'round-trips values', kind: 'behavior',
      body: 'expect(decode(encode(1))).toBe(1);',
    }, 'encode');
    expect(rt.family).toBe('roundtrip');
    expect(rt.branch).toBe('algebraic');
    const free = classifyCaseClade({
      name: 'mystery', kind: 'behavior', body: 'expect(foo()).toBe(1);',
    });
    expect(free.branch).toBe('freeform');
    expect(cladeRatio([
      { name: 'a', kind: 'behavior', body: '', clade: rt },
      { name: 'b', kind: 'behavior', body: '', clade: free },
    ])).toEqual({ schemad: 1, freeform: 1 });
  });

  it('rebinds schema\'d cases before model', () => {
    const body = rebindCladeBody({
      name: 'empty', kind: 'edge', body: 'expect(true).toBe(false);',
      clade: { branch: 'contract', family: 'rejects-empty', bindings: { symbol: 'parse' } },
    });
    expect(body).toBe("expect(() => parse('')).toThrow();");
    expect(rebindCladeBody({
      name: 'x', kind: 'behavior', body: '',
      clade: { branch: 'freeform', family: 'freeform', bindings: {} },
    })).toBeNull();
  });
});

describe('SET 3c TRACEABILITY.md', () => {
  it('writes full provenance chains into the export zip', async () => {
    const result: Tier1Result = {
      spec: {
        title: 'Demo', prompt: 'build a demo',
        features: [{
          id: 'F1', name: 'demo', description: 'demo feature', parentId: null,
          deps: [], files: ['src/a.ts'], testFile: 'tests/f1.test.ts',
          edgeCases: [], envVars: [], exports: [], stubs: [],
        }],
      },
      files: [{ path: 'src/a.ts', content: 'export const a = 1;\n' }],
      frozenTests: [{
        path: 'tests/f1.test.ts',
        content: "import { describe, it, expect } from 'vitest';\ndescribe('x', () => { it('y', () => { expect(1).toBe(1); }); });\n",
      }],
      problems: [],
      metrics: {
        elapsedMs: 0, passesTotal: 0, passesPerFeature: {},
        repairDraws: 0, freshDraws: 0, irSurgeries: 0, irGenAttempts: 0, mechanicalFixes: 0,
        catalogDriftMechanicalFixes: 0,
        regexLiteralGateHeals: 0,
        bornRedStubDerivedDrops: 0,
        substrateAttempts: 0, substrateFixes: 0, substrateRefusals: 0, substrateNoHandler: 0,
        substrateEditsApplied: 0, deterministicEditsOutsideSubstrate: 0,
        substrateFillHits: 0, substrateFillEscalations: 0,
        substrateFillEmptyMechanisms: 0, substrateFillVacuousMechanisms: 0,
        overreachBodyRejections: 0,
        conceptDraws: 0, parsePoisonIncidents: 0,
        permBatches: 0, permCandidates: 0, permImproved: 0,
        superbufferDraws: 0, superbufferWins: 0,
        templatedCases: 0, permutationDraws: 0, permutationWins: 0,
        bugsObserved: 0, bugsPatched: 0, bugsRemaining: 0,
        artifactFaults: 0, artifactFaultsRemaining: 0, artifactFaultsAffectedFeatures: 0,
        featuresTotal: 0, featuresFrozenWithoutRedraw: 0, irRedraws: 0,
        nodesTotal: 0, nodesBuildable: 0, nodesBuilt: 0, nodesBuiltCleanFirstPass: 0,
        boundedLoopNodes: 0, boundedLoopIterations: 0,
        candidatesDrawn: 0, candidatesAccepted: 0,
        fillCandidatesDrawn: 0, repairCandidatesDrawn: 0,
        fillCandidatesAccepted: 0, repairCandidatesAccepted: 0,
        salvagedCases: 0,
        sickRedCases: 0, testRhaSubstrateFixes: 0, testRhaDraws: 0,
        testRhaSuperbufferAssemblies: 0, testRhaDrawThrew: 0, testRhaDraftRejected: 0,
        testRhaDraftAcceptedCleared: 0, testRhaDraftAcceptedStillSick: 0,
        testRhaSkippedNoHandle: 0, testRhaSkippedAlreadyRedrawn: 0,
        testRhaCaseExhausted: 0, testRhaCaseCleared: 0,
        irSurgeriesInRha: 0,
        reEnqueuedCases: 0, recoveredCases: 0,
        l2Draws: 0, l2Wins: 0, l2Bullseyes: 0,
        fixesL1: 0, fixesL2: 0, fixesL3: 0, fixesL4: 0,
        corpseRejections: 0, classifyAttempts: 0, classifyCataloged: 0,
        grammarConformant: 0, grammarFallbacks: 0,
        shellLockApplied: 0, shellLockFallbacks: 0,
        judgmentGates: 0, judgmentOperator: 0, judgmentLlm: 0, judgmentDropped: 0,
        watcherSyntheses: 0, watcherReadings: 0, watcherObservations: 0,
        watcherCacheHits: 0, watcherCacheMisses: 0, watcherWorldStaleSkips: 0,
        watcherInfusedCandidates: 0,
        corpseRejectionCeilingBreaches: 0,
    watcherEngineFailures: 0, observatoryEngineFailures: 0,
        nodeRepairDraws: 0, nodeRepairWins: 0,
        observationPolls: 0, observationsNew: 0, observationsCorroborated: 0,
        stubRouteShortcuts: 0, observatoryPollsSkipped: 0, l2ShortCircuits: 0, quarantineSuspensions: 0,
        errorsTotal: 0, errorsParse: 0, errorsConformance: 0, errorsExecution: 0, errorsCrash: 0,
        findingOccurrencesTotal: 0, findingOccurrencesParse: 0,
        findingOccurrencesConformance: 0, findingOccurrencesExecution: 0,
        findingOccurrencesCrash: 0, findingOccurrencesTestFailure: 0,
        uniqueDefectsObserved: 0, uniqueDefectsResolved: 0, uniqueDefectsUnresolved: 0,
        actionStarts: 0, actionTerminals: 0, actionDecisions: 0, actionPending: 0, actionFailed: 0,
        actionFailedNonfatal: 0,
        deferredTotal: 0, deferredRelitigated: 0, deferredResolvedAfterRelitigation: 0,
        deferredPermanent: 0,
        nodesHealed: 0, fixesUnsuccessful: 0, fixesUnparseable: 0,
      },
      log: [],
      provenance: [{
        caseName: 'demo', kind: 'behavior', license: 'demo', source: 'export',
        featureId: 'F1', featureDescription: 'demo feature', prompt: 'build a demo',
      }],
    };
    const repo = await buildRepo(result);
    const text = await repo.fs.promises.readFile('/repo/TRACEABILITY.md', 'utf8') as string;
    expect(text).toContain('**behavior:** `demo`');
    expect(text).toContain('license ← `export`: demo');
    expect(text).toContain('feature.description ← demo feature');
    expect(text).toContain('prompt ← build a demo');
  });

  it('run records provenance and permutation metrics', async () => {
    const spec: FeatureSpec = {
      title: 'Adder', prompt: 'adder please',
      features: [{
        id: 'F1', name: 'add', description: 'adds two numbers', parentId: null,
        deps: [], files: ['src/adder.ts'], testFile: 'tests/f1.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"); }\n',
        }],
      }],
    } as unknown as FeatureSpec;
    const llm = {
      async encodeFeatures() { return spec; },
      async generateTests() { return { content: 'x', parts: { imports: [], cases: [] } }; },
      async fixTestCases(
        _s: unknown, _f: unknown, broken: { name: string; kind: string }[],
      ) {
        return {
          cases: broken.map(b => ({
            name: b.name, kind: b.kind as 'behavior' | 'edge' | 'integration',
            body: b.kind === 'edge' ? 'expect(add(0, 5)).toBe(5);' : 'expect(add(2, 3)).toBe(5);',
          })),
        };
      },
      async generateImplementation() {
        return [{ path: 'src/adder.ts', content: 'export function add(a: number, b: number): number { return a + b; }\n' }];
      },
      async repair() { return []; },
      async enumerateFeatures() { return []; },
      async probeFeature() { return { clear: true, assertion: '', question: '' }; },
      async repairFeatures() { return []; },
      async fixStub() { return ''; },
    } as unknown as LlmClient;
    const res = await runTier1Job({ prompt: 'adder please', llm, runSuite: makeEvalSuiteRunner() });
    expect(res.provenance.length).toBeGreaterThanOrEqual(2);
    expect(res.provenance[0]?.prompt).toBe('adder please');
    // SET 76 — the fill ladder authenticates each draw and STOPS at the first
    // fully-accepted one, so a clean first try costs 1 draw, not 3.
    expect(res.metrics.permutationDraws).toBeGreaterThanOrEqual(1);
    expect(res.metrics.permutationWins).toBeGreaterThanOrEqual(1);
    expect(res.metrics.cladeRatio).toMatch(/^\d+:\d+$/);
  });
});
