/**
 * SET 40d — born-red SUMMARY (formatter only, since SET 89).
 *
 * `scoreBornRed` remains as a summary formatter that reports honest counts.
 * It is NOT an acceptance oracle: per SET 89 §3.1, freeze eligibility is
 * decided per-case by `certifyCaseAgainstStubs` and per-suite by the
 * `commitFrozenSuite` gateway. The old `isRed = red >= 1` and
 * `crashed ⇒ isRed: true` rules are DELETED as acceptance semantics — they
 * survive only as `aggregateRedGate` / `crashed`, and no production freeze
 * decision consults them.
 */
import type { FeatureNode, FeatureSpec, GeneratedFile } from '../core/types';
import type { SuiteRunResult } from './testExec';
import { analyzeThrowExpectation, isStubDerivedLiteral } from './caseCertification';

/** Stub-only artifact for every feature (bodies throw by construction). */
export function stubArtifact(spec: FeatureSpec): GeneratedFile[] {
  return spec.features.flatMap(f => f.stubs);
}

export type BornRedMeasure = {
  total: number;
  red: number;
  vacuous: number;
  /**
   * @deprecated Since SET 89 v2, `isRed` is HARD-FALSE. Every production
   * freeze/surgery/anchor decision must use per-case certification
   * (`certifyCaseAgainstStubs`) or `hasRedCase(run)` for the honest
   * "at least one case failed against stubs" predicate. Reading this
   * field cannot authorize any run-affecting decision — it is retained
   * only so legacy formatter call sites keep compiling.
   */
  isRed: false;
  /**
   * @deprecated ALWAYS false. Names the DELETED acceptance rule
   * (`red >= 1 ⇒ freeze-eligible`) so any regression that reads it can
   * be caught by a mutation test.
   */
  aggregateRedGate: false;
  /** True when the program itself crashed. Crash is diagnostic; NEVER red. */
  crashed: boolean;
};

/**
 * Score a suite run against stubs. Since SET 89 this is a SUMMARY only.
 * A crash sets `crashed: true` and leaves `red = 0` — the old
 * "crash counts as red" rule is deleted.
 *
 * `isRed` and `aggregateRedGate` are ALWAYS `false` since SET 89 v2. They
 * name the DELETED acceptance rules so any regression that tries to gate
 * off them is a compile / test failure, not silent misbehavior.
 * Certification is per-case only (SET 89A) — via `certifyCaseAgainstStubs`
 * and the frozen suite gateway. The honest "did any case fail vs stubs?"
 * predicate for surgery / anchor decisions is `hasRedCase(run)`.
 */
export function scoreBornRed(run: SuiteRunResult): BornRedMeasure {
  if (run.crashed) {
    return {
      total: run.total,
      red: 0,
      vacuous: 0,
      isRed: false,
      aggregateRedGate: false,
      crashed: true,
    };
  }
  const total = run.total;
  const red = run.failures.length;
  const vacuous = Math.max(0, total - red);
  return { total, red, vacuous, isRed: false, aggregateRedGate: false, crashed: false };
}

/**
 * SET 89 v2 — the honest per-suite predicate for surgery/anchor/re-enqueue
 * gates that the deleted `isRed` used to try to express. A run has "a red
 * case" only when the oracle COMPLETED (no crash) AND at least one case
 * failed against stubs. Never authorizes freeze — freeze is decided
 * per-case by `certifyCaseAgainstStubs` and per-suite by
 * `commitFrozenSuite`.
 */
export function hasRedCase(run: SuiteRunResult): boolean {
  return !run.crashed && run.failures.length > 0;
}

/** `born-red check: N/M cases red (K vacuous → surgery)` */
export function formatBornRedEvent(m: BornRedMeasure): string {
  return `born-red check: ${m.red}/${m.total} cases red (${m.vacuous} vacuous → surgery)`;
}

/**
 * SET 73 — throw-expecting cases are PASS-by-construction against throwing
 * stubs: `expect(() => f('')).toThrow()` goes green because the stub throws
 * `not implemented`, so green-on-stubs is UNDECIDABLE for them — never
 * vacuous. The rejection templates are fixture/mutation-verified; born-red
 * must retain them, not surgically destroy them every run.
 */
export function isThrowCamouflaged(body: string): boolean {
  return /\.(?:rejects\.)?toThrow\w*\s*\(/.test(body);
}

export function formatThrowCamouflage(n: number): string {
  return `born-red: ${n} throw-expecting case(s) retained (undecidable vs throwing stubs — verified rejection clade)`;
}

/**
 * SET 93 — a case is "stub-derived throw" iff its ENTIRE contract is that
 * a throwing stub throws. Such a case is trivially green while the stub
 * exists and impossible to satisfy once the stub is replaced by a real
 * implementation. Surgery cannot rescue it — drop at born-red.
 */
export function isStubDerivedThrowCase(
  body: string,
  feature: FeatureNode,
  spec: FeatureSpec,
): boolean {
  const ta = analyzeThrowExpectation(body);
  if (!ta.hasThrowAssertion) return false;
  if (!ta.isSoleAssertion) return false;
  if (ta.negated) return false;
  return isStubDerivedLiteral(ta.expectedLiteral, spec, feature);
}

export const VACUOUS_CASE_FINDING =
  'case passes with no implementation — assert observable behavior';
