/**
 * SET 13 — Vanilla run mode: one raw model draw, no harness contract, no repair.
 * Substrate counterfactual is dry-run only (zero extra LLM calls).
 */
import type { FeatureSpec, GeneratedFile, LlmClient } from '../core/types';
import { applyTemplates } from './testgen';
import { planSuite } from './planSuite';
import { classifyFailure, mechanicalFix, scanCapabilities } from './substrate';
import type { CapabilityHit } from './substrate';
import type { SuiteRunner } from './testExec';
import type { Tier1Event, Tier1Metrics, Tier1Result } from './runner';

export type RunMode = 'vanilla' | 'rha' | 'referee';

export interface VanillaArtifact {
  title: string;
  files: GeneratedFile[];
  tests: GeneratedFile[];
}

export interface CounterfactualReport {
  failuresTotal: number;
  classified: number;
  mechanicalWouldFix: number;
  templatePrevented: number;
  summary: string;
}

export interface VanillaResult extends Tier1Result {
  mode: 'vanilla';
  counterfactual: CounterfactualReport;
  /** Referee cost bucket reserved for optional dual-grade mode (SET 13). */
  refereeCostUsd?: number | null;
}

/**
 * Clients that support a single structured vanilla draw implement this.
 * LocalLlmClient does; MockLlm does for tests.
 */
export interface VanillaCapable {
  vanillaDraw(prompt: string): Promise<VanillaArtifact>;
}

function isVanillaCapable(llm: LlmClient): llm is LlmClient & VanillaCapable {
  return typeof (llm as LlmClient & VanillaCapable).vanillaDraw === 'function';
}

function emptyMetrics(partial?: Partial<Tier1Metrics>): Tier1Metrics {
  return {
    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,
    templatedCases: 0,
    permutationDraws: 0,
    permutationWins: 0,
    permBatches: 0,
    permCandidates: 0,
    permImproved: 0,
    superbufferDraws: 0,
    superbufferWins: 0,
    conceptDraws: 0,
    parsePoisonIncidents: 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,
    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,
    ...partial,
    watcherInfusedCandidates: partial?.watcherInfusedCandidates ?? 0,
  };
}

/** Build a minimal FeatureSpec so exportRepo / planSuite can inspect the artifact. */
export function specFromVanilla(prompt: string, art: VanillaArtifact): FeatureSpec {
  const impl = art.files.filter(f => !/\.test\.(ts|js)$/.test(f.path) && !f.path.startsWith('tests/'));
  const tests = art.tests.length
    ? art.tests
    : art.files.filter(f => /\.test\.(ts|js)$/.test(f.path) || f.path.startsWith('tests/'));
  const files = impl.length ? impl.map(f => f.path) : ['src/index.ts'];
  const testFile = tests[0]?.path || 'tests/vanilla.test.ts';
  return {
    title: art.title || 'Vanilla artifact',
    prompt,
    features: [{
      id: 'V1',
      name: 'vanilla',
      description: prompt.slice(0, 240) || 'vanilla single-draw implementation',
      parentId: null,
      deps: [],
      files,
      testFile,
      edgeCases: [],
      envVars: [],
      exports: [],
      stubs: files.map(path => ({
        path,
        content: impl.find(f => f.path === path)?.content ?? 'export {};\n',
      })),
    }],
  };
}

/**
 * Dry-run substrate over vanilla failures — classify + mechanical candidates
 * + template/plan analysis WITHOUT mutating the artifact. Zero LLM calls.
 */
export function counterfactualSubstrate(opts: {
  prompt: string;
  spec: FeatureSpec;
  files: GeneratedFile[];
  tests: GeneratedFile[];
  failures: { name: string; message: string }[];
}): CounterfactualReport {
  const { prompt, spec, files, tests, failures } = opts;
  let classified = 0;
  let mechanicalWouldFix = 0;
  for (const f of failures) {
    const c = classifyFailure(f.name, f.message);
    classified++;
    for (const suite of tests) {
      if (mechanicalFix(c, files, suite, spec)) {
        mechanicalWouldFix++;
        break;
      }
    }
  }
  let templatePrevented = 0;
  for (const feature of spec.features) {
    try {
      const plan = planSuite(spec, feature);
      const edges = feature.edgeCases?.length
        ? feature.edgeCases
        : prompt.split(/\n/).map(l => l.trim()).filter(Boolean).slice(0, 8);
      templatePrevented += applyTemplates(plan, edges).templated;
    } catch {
      /* sparse vanilla specs may not plan */
    }
  }
  const summary =
    `substrate would have: classified ${classified}/${failures.length}, ` +
    `mechanically fixed ${mechanicalWouldFix}, template-prevented ${templatePrevented}`;
  return {
    failuresTotal: failures.length,
    classified,
    mechanicalWouldFix,
    templatePrevented,
    summary,
  };
}

export async function runVanillaJob(opts: {
  prompt: string;
  llm: LlmClient;
  runSuite?: SuiteRunner;
  onEvent?: (e: Tier1Event) => void;
  onMetrics?: (m: Tier1Metrics) => void;
  isCancelled?: () => boolean;
}): Promise<VanillaResult> {
  const started = Date.now();
  const log: Tier1Event[] = [];
  const emit = (phase: Tier1Event['phase'], featureId: string | null, message: string) => {
    const e: Tier1Event = { ts: new Date().toISOString(), phase, featureId, message };
    log.push(e);
    opts.onEvent?.(e);
  };

  if (!isVanillaCapable(opts.llm)) {
    throw new Error('LLM client does not support vanillaDraw');
  }

  emit('build', null, 'vanilla: single draw (no scaffolding, no harness contract, no repair)');
  if (opts.isCancelled?.()) throw new Error('cancelled by user');

  const art = await opts.llm.vanillaDraw(opts.prompt);
  emit('build', null, `vanilla draw complete — ${art.files.length} impl file(s), ${art.tests.length} test file(s)`);

  const files = art.files.filter(f => !f.path.startsWith('tests/') && !/\.test\.(ts|js)$/.test(f.path));
  const tests = art.tests.length
    ? art.tests
    : art.files.filter(f => f.path.startsWith('tests/') || /\.test\.(ts|js)$/.test(f.path));
  const implFiles = files.length ? files : art.files.filter(f => !tests.includes(f));
  const testFiles = tests.length ? tests : [];

  const spec = specFromVanilla(opts.prompt, {
    title: art.title,
    files: implFiles,
    tests: testFiles,
  });
  const deferred: CapabilityHit[] = scanCapabilities(opts.prompt);

  const problems: string[] = [];
  const allFailures: { name: string; message: string }[] = [];
  let bugsObserved = 0;

  if (!testFiles.length) {
    problems.push('vanilla: model returned no tests');
    emit('auth', null, 'vanilla: no tests to execute');
  } else if (!opts.runSuite) {
    problems.push('vanilla: no suite runner — tests not executed');
  } else {
    emit('auth', null, 'vanilla: executing model\'s own tests');
    for (const suite of testFiles) {
      if (opts.isCancelled?.()) throw new Error('cancelled by user');
      const result = await opts.runSuite(implFiles, suite);
      if (result.crashed) {
        allFailures.push({ name: '__suite__', message: result.crashed });
        problems.push(`vanilla suite crashed (${suite.path}): ${result.crashed}`);
        bugsObserved++;
      }
      for (const f of result.failures ?? []) {
        allFailures.push({ name: f.name, message: f.message });
        problems.push(`${suite.path}: ${f.name}: ${f.message}`);
        bugsObserved++;
      }
      emit('auth', 'V1', `${suite.path}: ${result.total - (result.failures?.length ?? 0)}/${result.total} passing`);
    }
  }

  const counterfactual = counterfactualSubstrate({
    prompt: opts.prompt,
    spec,
    files: implFiles,
    tests: testFiles,
    failures: allFailures,
  });
  emit('done', null, counterfactual.summary);

  const metrics = emptyMetrics({
    elapsedMs: Date.now() - started,
    bugsObserved,
    bugsPatched: 0,
    bugsRemaining: bugsObserved,
    freshDraws: 1,
  });
  opts.onMetrics?.(metrics);

  return {
    mode: 'vanilla',
    spec,
    files: implFiles,
    frozenTests: testFiles,
    problems,
    metrics,
    log,
    provenance: [],
    deferred,
    counterfactual,
    refereeCostUsd: null,
  };
}
