/**
 * SET 34 — Report diagnostics: every verdict carries claim-bound evidence.
 *
 * Admission rule: a datum enters the report only if it (a) substantiates a
 * specific claim, (b) is bounded (snippets ≤120 / parse ≤80, names not
 * contents, aggregates not dumps), and (c) changes what a reader would DO next.
 *
 * Kept free of `testExec`/sucrase so the web UI can import collectors safely.
 */
import type { FeatureSpec } from '../core/types';
import type { PlannedCase } from './planSuite';
import { edgeCladeFamilyHint, PLANNER_CONSUMABLE_FAMILIES } from './planSuite';
import { TESTGEN_TEMPLATES } from './testgenOracle.generated';
import type { TestgenTemplate } from './testgenOracle.generated';
import { nameTokenOverlap, signatureSimilarity } from './superbuffer';
import { plannedCladeFamily } from './clades';

/**
 * SET 37b — thin strata table type. Stream A owns `solutionStrata.generated.ts`;
 * until that lands, Integration may inject rows or use SEED_SOLUTION_STRATA.
 * 34 admission: render only when it changes router strategy order.
 */
export interface SolutionStrataRow {
  classKey: string;
  strategy: 'mechanical' | 'oracle-hint' | 'surgery' | 'reset' | string;
  findingsShare: number;
  resolutionRate: number;
  n?: number;
}

export type SolutionStrataTable = SolutionStrataRow[];

/** Brief duplicated seed until A's generated module exists (Integration swaps). */
export const SEED_SOLUTION_STRATA: SolutionStrataTable = [
  { classKey: 'assertion', strategy: 'reset', findingsShare: 0.40, resolutionRate: 0.50 },
  { classKey: 'throw-mismatch', strategy: 'oracle-hint', findingsShare: 0.25, resolutionRate: 0.60 },
  { classKey: 'mock-API', strategy: 'mechanical', findingsShare: 0.15, resolutionRate: 0.0 },
  { classKey: 'transpile', strategy: 'oracle-hint', findingsShare: 0.08, resolutionRate: 0.55 },
  { classKey: 'surgery', strategy: 'surgery', findingsShare: 0.12, resolutionRate: 0.70 },
];

/** ±120-char seam window — SET 34a explicit budget. */
export const SEAM_DUMP_RADIUS = 120;
/** Offending parse snippet bound — SET 34b. */
export const PARSE_SNIPPET_MAX = 80;

/** Seam dump shape (produced by `dumpConcatenationSeams` in testExec). */
export interface SeamDump {
  leftPath: string;
  rightPath: string;
  /** Bounded window around the join (≤ 2*radius + 1). */
  snippet: string;
}

export function formatSeamDumpLine(d: SeamDump): string {
  return `seam-dump: ${d.leftPath} ⨝ ${d.rightPath} · «${d.snippet}»`;
}

/**
 * SET 36g — sync fingerprint of a program state for seam-dump dedupe.
 * Not cryptographic; only needs collision resistance within one run.
 */
export function programStateHash(files: { path: string; content: string }[], test: { path: string; content: string }): string {
  const s = [...files, test].map(f => `${f.path}\0${f.content}`).join('\n');
  let h = 2166136261;
  for (let i = 0; i < s.length; i++) {
    h ^= s.charCodeAt(i);
    h = Math.imul(h, 16777619);
  }
  return (h >>> 0).toString(16).padStart(8, '0');
}

/**
 * SET 36g — seam dumps are for genuine parse-class attribution-miss only.
 * Runtime/load crashes (36a position payload) suppress them entirely.
 */
export function shouldEmitSeamDumps(crashMessage: string): boolean {
  const first = (crashMessage.split('\n')[0] ?? '').trim();
  if (/^crash while loading /.test(first)) return false;
  if (/^crash while executing /.test(first)) return false;
  // Position-attributed wrappers already carry the stack; seams add noise.
  if (/\bat\s+.+:\d+/.test(crashMessage) && /Invalid regular expression|is not defined|TypeError|ReferenceError/i.test(crashMessage)) {
    return false;
  }
  return true;
}

/** Verbatim crash + seam dumps attached to an attribution-miss claim. */
export function formatAttributionMissEvidence(opts: {
  miss: string;
  crashMessage: string;
  seams: SeamDump[];
  /** SET 36g — program hash; first emission carries dumps, repeats reference it. */
  programHash?: string;
  /** SET 36g — when true, omit dumps (runtime/load position payload replaces them). */
  suppressSeams?: boolean;
  /** SET 36g — prior emission of the same program hash; emit a reference only. */
  seamRefHash?: string;
}): string[] {
  const crash = opts.crashMessage.replace(/\s+/g, ' ').trim().slice(0, 240);
  const lines = [opts.miss, `crash: ${crash}`];
  if (opts.suppressSeams || !shouldEmitSeamDumps(opts.crashMessage)) {
    return lines;
  }
  if (opts.seamRefHash) {
    lines.push(`seam-dump: (see program ${opts.seamRefHash})`);
    return lines;
  }
  if (opts.programHash) {
    lines.push(`seam-dump-program: ${opts.programHash}`);
  }
  lines.push(...opts.seams.map(formatSeamDumpLine));
  return lines;
}

export function formatParseEvidence(
  error: string,
  body: string,
): { firstLine: string; snippet: string } {
  const firstLine = (error.split('\n')[0] ?? 'unparseable').trim().slice(0, 120);
  const pos = /\((\d+):(\d+)\)/.exec(error);
  let snippet: string;
  if (pos) {
    const lines = body.split('\n');
    const lineIdx = Math.max(0, parseInt(pos[1]!, 10) - 1);
    const col = Math.max(0, parseInt(pos[2]!, 10) - 1);
    const line = lines[lineIdx] ?? body;
    const start = Math.max(0, col - Math.floor(PARSE_SNIPPET_MAX / 2));
    snippet = line.slice(start, start + PARSE_SNIPPET_MAX);
  } else {
    snippet = body.replace(/\s+/g, ' ').trim().slice(0, PARSE_SNIPPET_MAX);
  }
  if (!snippet) snippet = '(empty)';
  return { firstLine, snippet };
}

/**
 * Drop reason with parse payload (SET 34b).
 * Full problem line: `case "N" dropped — unparseable: <line> at "<snip>" (coverage reduced)`.
 */
export function formatUnparseableDropReason(error: string, body: string): string {
  const { firstLine, snippet } = formatParseEvidence(error, body);
  return `dropped — unparseable: ${firstLine} at "${snippet}"`;
}

/** Canonical encode-output bytes for the sha256-8 tripwire (SET 34c). */
export function encodeOutputCanonical(spec: FeatureSpec): string {
  return JSON.stringify({
    title: spec.title,
    features: spec.features.map(f => ({
      id: f.id,
      name: f.name,
      description: f.description,
      deps: [...f.deps],
      edgeCases: [...f.edgeCases],
      exports: f.exports.map(e => ({ file: e.file, symbol: e.symbol })),
      files: [...f.files],
      testFile: f.testFile,
    })),
  });
}

/** First 8 hex chars of SHA-256 (Web Crypto; browser + Node). */
export async function sha256Hex8(text: string): Promise<string> {
  const subtle = globalThis.crypto?.subtle;
  if (!subtle) throw new Error('crypto.subtle required for SET 34c spec hash');
  const digest = await subtle.digest('SHA-256', new TextEncoder().encode(text));
  return [...new Uint8Array(digest)]
    .map(b => b.toString(16).padStart(2, '0'))
    .join('')
    .slice(0, 8);
}

/**
 * `spec: <sha256-8> of encode output — matches run <id> | NEW carve`
 */
export function formatSpecHashLine(hash8: string, matchingRunId: string | null): string {
  const tail = matchingRunId ? `matches run ${matchingRunId}` : 'NEW carve';
  return `spec: ${hash8} of encode output — ${tail}`;
}

export function resolveSpecHashMatch(
  hash8: string,
  prior: readonly { runId: string; hash: string }[] | undefined,
): string | null {
  if (!prior?.length) return null;
  const hit = prior.find(p => p.hash === hash8);
  return hit?.runId ?? null;
}

export type EfficacyBucket = { cases: number; filled: number };

/**
 * `buffer had templates: N cases, M filled (X%) · no templates: N', M' (X'%)`
 */
export function formatSuperbufferEfficacy(
  withTemplates: EfficacyBucket,
  withoutTemplates: EfficacyBucket,
): string {
  const pct = (m: number, n: number) => (n === 0 ? 0 : Math.round((100 * m) / n));
  const a = withTemplates;
  const b = withoutTemplates;
  return (
    `buffer had templates: ${a.cases} cases, ${a.filled} filled (${pct(a.filled, a.cases)}%)`
    + ` · no templates: ${b.cases} cases, ${b.filled} filled (${pct(b.filled, b.cases)}%)`
  );
}

/** `planner bypass: N matching templates unused` */
export function formatPlannerBypass(n: number): string {
  return `planner bypass: ${n} matching templates unused`;
}

/** Re-export planner-consumable family list (canonical: planSuite). */
export { PLANNER_CONSUMABLE_FAMILIES };

function typesMatchLocal(sigTypes: string[], wanted: string[]): boolean {
  if (sigTypes.length !== wanted.length) return false;
  return sigTypes.every((t, i) => {
    const a = t.replace(/\s+/g, '');
    const b = wanted[i]!.replace(/\s+/g, '');
    return a === b || a.startsWith(b) || b.startsWith(a);
  });
}

/**
 * Pure consumable-match mirror of testgen.matchTemplate (no substrate —
 * web UI must stay free of testExec). Soft buffer-only matches must NOT trip bypass.
 */
export function matchConsumableTemplate(planned: PlannedCase): TestgenTemplate | null {
  const structural = planned.cladeFamily ?? plannedCladeFamily(planned);
  if (structural === 'model-schema' || structural === 'uses-dep') {
    return TESTGEN_TEMPLATES.find(t => t.family === structural) ?? null;
  }
  const edgeFam = planned.cladeFamily ?? edgeCladeFamilyHint(planned.license);
  if (edgeFam && edgeFam !== 'model-schema' && edgeFam !== 'uses-dep') {
    const candidates = TESTGEN_TEMPLATES.filter(t => t.family === edgeFam);
    if (planned.signature) {
      const sig = planned.signature;
      for (const t of candidates) {
        if (sig.params.length === t.paramCount
          && typesMatchLocal(sig.params.map(p => p.type), t.paramTypes)) {
          return t;
        }
      }
    }
    return candidates[0] ?? null;
  }
  // Determinism only when tagged (mirrors testgen.matchDeterminismSmoke).
  const detTagged = planned.cladeFamily === 'determinism'
    || /determin/i.test(planned.license)
    || /determin/i.test(planned.name);
  if (detTagged && planned.signature) {
    const sig = planned.signature;
    for (const t of TESTGEN_TEMPLATES) {
      if (t.family !== 'determinism') continue;
      if (sig.params.length !== t.paramCount) continue;
      if (!typesMatchLocal(sig.params.map(p => p.type), t.paramTypes)) continue;
      const ret = sig.returnType.replace(/\s+/g, '');
      const want = t.returnType.replace(/\s+/g, '');
      if (!ret.startsWith(want) && !want.startsWith(ret)) continue;
      return t;
    }
  }
  if (planned.kind === 'edge' || planned.source === 'edge') {
    for (const t of TESTGEN_TEMPLATES) {
      if (!(PLANNER_CONSUMABLE_FAMILIES as readonly string[]).includes(t.family)) continue;
      if (t.license === 'export' || t.license === 'model-schema' || t.license === 'uses-dep') continue;
      if (!planned.license.toLowerCase().includes(t.license.toLowerCase())) continue;
      if (planned.signature) {
        const sig = planned.signature;
        if (sig.params.length === t.paramCount
          && typesMatchLocal(sig.params.map(p => p.type), t.paramTypes)) {
          return t;
        }
      }
    }
  }
  return null;
}

/**
 * Count templates the planner COULD have used — SET 34e / 38d tripwire.
 * Soft buffer-only matches must NOT trip the bypass event.
 */
export function countMatchingTemplatesForPlan(
  planCases: readonly PlannedCase[],
  _edgeTexts: readonly string[] = [],
): number {
  if (!planCases.length) return 0;
  const matched = new Set<string>();
  for (const p of planCases) {
    const t = matchConsumableTemplate(p);
    if (t) matched.add(t.id);
  }
  return matched.size;
}

/** Soft corpus matches (buffer-style) — diagnostics only; not the bypass tripwire. */
export function countSoftMatchingTemplatesForPlan(
  planCases: readonly PlannedCase[],
  edgeTexts: readonly string[] = [],
): number {
  if (!planCases.length) return 0;
  const matched = new Set<string>();
  for (const t of TESTGEN_TEMPLATES) {
    for (const p of planCases) {
      const sig = p.signature
        ? {
          name: p.signature.name,
          params: p.signature.params.map(x => x.type),
          returnType: p.signature.returnType,
        }
        : null;
      const soft = signatureSimilarity(sig, t, p.symbol);
      const nameHit =
        nameTokenOverlap(t.family, p.name) >= 0.25
        || nameTokenOverlap(t.license, p.license) >= 0.25
        || edgeTexts.some(e => nameTokenOverlap(t.license, e) >= 0.25);
      if (soft > 0 || nameHit) {
        matched.add(t.id);
        break;
      }
    }
  }
  return matched.size;
}

/** SET 37f — `concept page: N cases, M filled (X%) · no concept: …` */
export function formatConceptEfficacy(
  withConcept: EfficacyBucket,
  withoutConcept: EfficacyBucket,
): string {
  const pct = (m: number, n: number) => (n === 0 ? 0 : Math.round((100 * m) / n));
  const a = withConcept;
  const b = withoutConcept;
  return (
    `concept page: ${a.cases} cases, ${a.filled} filled (${pct(a.filled, a.cases)}%)`
    + ` · no concept: ${b.cases} cases, ${b.filled} filled (${pct(b.filled, b.cases)}%)`
  );
}

/**
 * SET 37g / 44 — capability exclusion disclosure (not a later revisit).
 * Kept name `formatPermanentDeferral` for call-site stability; copy is "excluded".
 */
export function formatPermanentDeferral(capability: string): string {
  return `excluded — requires ${capability}; not testable in this runtime`;
}

/** Minimal hit shape for TRACEABILITY exclusion rendering (SET 44). */
export type ExcludedCapabilityHit = {
  item: string;
  capability: string;
  line?: number;
  partial?: boolean;
  clause?: string;
};

/**
 * SET 44 — TRACEABILITY.md exclusion section. Full item text + line provenance;
 * one factual footnote (no roadmap).
 */
export function renderExcludedTraceabilitySection(
  hits: readonly ExcludedCapabilityHit[],
): string[] {
  if (!hits.length) return [];
  const lines = [
    '## Excluded (untested — no runtime capability)',
    '',
  ];
  for (const d of hits) {
    const lineBit = d.line != null ? `line ${d.line} · ` : '';
    const partialBit = d.partial ? 'partially excluded · ' : '';
    const body = d.partial && d.clause ? d.clause : d.item;
    lines.push(`- ${lineBit}${partialBit}**${d.capability}**: ${body}`);
  }
  lines.push(
    '',
    'Note: computed-style is the sole excluded capability technically reachable in a browser runtime; others require infrastructure this product does not ship.',
    '',
  );
  return lines;
}

/**
 * SET 37g — continue-project replan ordering note for the report.
 * Deep epoch logic is Integration; this is the claim-bound report string.
 */
export function formatContinueProjectReplanNote(droppedCaseCount: number): string {
  if (droppedCaseCount <= 0) {
    return 'continue-project: no prior dropped cases to replan first';
  }
  return (
    `continue-project: replan ${droppedCaseCount} previously-dropped case`
    + `${droppedCaseCount === 1 ? '' : 's'} FIRST (coverage backfill) before new features`
  );
}

/** SET 38c — `parse-poison incidents: N (prior run: M)` */
export function formatParsePoisonIncidents(n: number, prior: number): string {
  return `parse-poison incidents: ${n} (prior run: ${prior})`;
}

/**
 * SET 43f — driving metric: substrate hit-rate (fixes/attempts) plus refusal /
 * no-handler tails. Optional per-stage lines when `byStage` is supplied.
 * e.g. `substrate hit-rate: 12/40 fixes (30%) · refusals 10 · no-handler 18`
 */
export function formatSubstrateHitRate(stats: {
  attempts: number;
  fixes: number;
  refusals: number;
  noHandler: number;
  byStage?: Record<string, { attempts: number; fixes: number }>;
}): string {
  const pct = (fixes: number, attempts: number) =>
    (attempts === 0 ? 0 : Math.round((100 * fixes) / attempts));
  const lines = [
    `substrate hit-rate: ${stats.fixes}/${stats.attempts} fixes (${pct(stats.fixes, stats.attempts)}%)`
      + ` · refusals ${stats.refusals} · no-handler ${stats.noHandler}`,
  ];
  if (stats.byStage) {
    for (const stage of Object.keys(stats.byStage).sort()) {
      const s = stats.byStage[stage]!;
      lines.push(
        `  ${stage}: ${s.fixes}/${s.attempts} fixes (${pct(s.fixes, s.attempts)}%)`,
      );
    }
  }
  return lines.join('\n');
}

export type ClassKeyCoverageStratum =
  | 'mechanical'
  | 'mechanical-candidate'
  | 'semantic-impossibility'
  | 'uncovered';

/**
 * SET 43f — standing rule report: every classKey with count≥3 must show
 * mechanical / mechanical-candidate / semantic-impossibility, else UNCOVERED.
 * No third state.
 */
export function formatClassKeyCoverageRule(rows: {
  classKey: string;
  count: number;
  stratum?: ClassKeyCoverageStratum;
  note?: string;
}[]): string[] {
  const eligible = rows
    .filter(r => r.count >= 3)
    .slice()
    .sort((a, b) => b.count - a.count || a.classKey.localeCompare(b.classKey));
  const lines = [
    'classKey coverage: (≥3 sightings → mechanical | mechanical-candidate | semantic-impossibility; else UNCOVERED)',
  ];
  for (const r of eligible) {
    const stratum = r.stratum ?? 'uncovered';
    const label = stratum === 'uncovered' ? 'UNCOVERED' : stratum;
    const note = r.note?.trim() ? ` — ${r.note.trim()}` : '';
    lines.push(`  ${r.classKey}: ${r.count} · ${label}${note}`);
  }
  return lines;
}

/** SET 37b — render strata table lines for Evidence (34 admission: router order). */
export function formatSolutionStrataTable(
  rows: SolutionStrataTable = SEED_SOLUTION_STRATA,
): string[] {
  // Sort by resolution rate desc — this IS the router strategy order hint.
  const sorted = [...rows].sort((a, b) => b.resolutionRate - a.resolutionRate);
  const lines = ['solution-strata (router order by observed resolution rate):'];
  for (const r of sorted) {
    const share = Math.round(r.findingsShare * 100);
    const rate = Math.round(r.resolutionRate * 100);
    lines.push(
      `  ${r.classKey}: prefer ${r.strategy} (${share}% findings @ ${rate}% resolved)`,
    );
  }
  return lines;
}

/** Alias for Integration / tests. */
export const SOLUTION_STRATA_SEED = SEED_SOLUTION_STRATA;
export const formatSolutionStrataLines = formatSolutionStrataTable;

/**
 * Optional dynamic import of Stream A's module; falls back to seed.
 * NOTE: waiting on A's `solutionStrata.generated.ts`.
 */
export async function loadSolutionStrataTable(): Promise<SolutionStrataTable> {
  try {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const mod: any = await import('./solutionStrata.generated');
    const raw = mod.SOLUTION_STRATA ?? mod.SEED_SOLUTION_STRATA ?? mod.default;
    if (Array.isArray(raw) && raw.length && typeof raw[0]?.classKey === 'string') {
      return raw as SolutionStrataTable;
    }
    // Stream A shape: classFamily + resolutionByStrategy → report rows.
    if (Array.isArray(raw) && raw.length && typeof raw[0]?.classFamily === 'string') {
      const orderFn = mod.strategyOrderForClass as ((k: string) => string[]) | undefined;
      return raw.map((r: {
        classFamily: string;
        findingShare: number;
        resolutionByStrategy: Record<string, number>;
      }) => {
        const order = orderFn?.(r.classFamily) ?? Object.entries(r.resolutionByStrategy ?? {})
          .sort((a, b) => (b[1] ?? 0) - (a[1] ?? 0))
          .map(([k]) => k);
        const strategy = order[0] ?? 'mechanical';
        return {
          classKey: r.classFamily,
          strategy,
          findingsShare: r.findingShare ?? 0,
          resolutionRate: r.resolutionByStrategy?.[strategy] ?? 0,
        };
      });
    }
  } catch {
    // Stream A module not present yet.
  }
  return SEED_SOLUTION_STRATA;
}

/** Evidence prefixes admitted into the report / export evidence section. */
const EVIDENCE_PREFIXES = [
  'attribution-miss:',
  'crash:',
  'seam-dump:',
  'seam-dump-program:',
  'spec:',
  'buffer had templates:',
  'substrate page:',
  'concept page:',
  'planner bypass:',
  'parse-poison incidents:',
  'solution-strata',
  'excluded — requires',
  'continue-project:',
  'hang:',
  'substrate hit-rate:',
  'classKey coverage:',
] as const;

/** Re-export SET 40h hang line formatter (Integration / LocalLlm emit the same shape). */
export { formatHangEvidenceLine } from '../core/providerWatch';
export type { HangResolved } from '../core/providerWatch';

export function isReportEvidenceLine(message: string): boolean {
  const m = message.trim();
  // SET 43f — indented continuations of hit-rate / classKey-coverage blocks.
  if (/^\S.+:\s+\d+\/\d+\s+fixes\s+\(\d+%\)/.test(m)) return true;
  if (/^\S.+:\s+\d+\s+·\s+(mechanical|mechanical-candidate|semantic-impossibility|UNCOVERED)\b/.test(m)) {
    return true;
  }
  return EVIDENCE_PREFIXES.some(p => m.startsWith(p))
    || /dropped — unparseable:/.test(m);
}

export type CollectedReportEvidence = {
  attributionMiss: string[];
  seamDumps: string[];
  crashLines: string[];
  specHash: string | null;
  efficacy: string | null;
  conceptEfficacy: string | null;
  /** SET 45c — substrate handoff page efficacy. */
  substratePageEfficacy: string | null;
  plannerBypass: string[];
  parseDrops: string[];
  parsePoison: string | null;
  solutionStrata: string[];
  /** SET 44 — capability exclusion lines; field name kept for App.tsx (TODO Integration: rename → exclusions). */
  permanentDeferrals: string[];
  continueProjectNote: string | null;
  /** SET 40h — calls that stalled > 3× stage median. */
  hangLines: string[];
  /** SET 43f — substrate hit-rate driving metric. */
  substrateHitRate: string | null;
  /** SET 43f — classKey coverage standing-rule lines. */
  classKeyCoverage: string[];
  /** Flat list in stable report order for rendering / export. */
  lines: string[];
};

/** Pull claim-bound evidence lines from pipeline events (+ optional problems). */
export function collectReportEvidence(
  events: readonly { message: string }[],
  problems: readonly string[] = [],
  opts?: { solutionStrata?: SolutionStrataTable | null },
): CollectedReportEvidence {
  const attributionMiss: string[] = [];
  const seamDumps: string[] = [];
  const crashLines: string[] = [];
  const plannerBypass: string[] = [];
  const parseDrops: string[] = [];
  const permanentDeferrals: string[] = [];
  const hangLines: string[] = [];
  const classKeyCoverage: string[] = [];
  let specHash: string | null = null;
  let efficacy: string | null = null;
  let conceptEfficacy: string | null = null;
  let substratePageEfficacy: string | null = null;
  let parsePoison: string | null = null;
  let continueProjectNote: string | null = null;
  let substrateHitRate: string | null = null;
  const solutionStrataFromEvents: string[] = [];

  const consider = (raw: string) => {
    const m = raw.trim();
    if (m.startsWith('attribution-miss:')) attributionMiss.push(m);
    else if (m.startsWith('seam-dump:')) seamDumps.push(m);
    else if (m.startsWith('crash:')) crashLines.push(m);
    else if (m.startsWith('spec:') && m.includes('of encode output')) specHash = m;
    else if (m.startsWith('buffer had templates:')) efficacy = m;
    else if (m.startsWith('substrate page:')) substratePageEfficacy = m;
    else if (m.startsWith('concept page:')) conceptEfficacy = m;
    else if (m.startsWith('planner bypass:')) plannerBypass.push(m);
    else if (m.startsWith('parse-poison incidents:')) parsePoison = m;
    else if (m.startsWith('solution-strata')) solutionStrataFromEvents.push(m);
    else if (m.startsWith('excluded — requires')) permanentDeferrals.push(m);
    else if (m.startsWith('continue-project:')) continueProjectNote = m;
    else if (m.startsWith('hang:')) hangLines.push(m);
    else if (m.startsWith('substrate hit-rate:')) substrateHitRate = m;
    else if (
      !m.startsWith('substrate hit-rate:')
      && /^\S.+:\s+\d+\/\d+\s+fixes\s+\(\d+%\)/.test(m)
    ) {
      // SET 43f — per-stage continuation when Integration emits lines separately.
      // (consider() trims, so match without leading whitespace.)
      const staged = `  ${m}`;
      substrateHitRate = substrateHitRate ? `${substrateHitRate}\n${staged}` : m;
    }
    else if (m.startsWith('classKey coverage:')) classKeyCoverage.push(m);
    else if (/^\S.+:\s+\d+\s+·\s+(mechanical|mechanical-candidate|semantic-impossibility|UNCOVERED)\b/.test(m)) {
      // Preserve indent in the collected block for report readability.
      classKeyCoverage.push(m.startsWith('  ') ? m : `  ${m}`);
    }
    else if (/dropped — unparseable:/.test(m)) parseDrops.push(m);
  };

  for (const e of events) consider(e.message);
  for (const p of problems) consider(p);

  // SET 37b — inject strata table when provided (changes router — 34 admission).
  const solutionStrata = solutionStrataFromEvents.length
    ? solutionStrataFromEvents
    : (opts?.solutionStrata
      ? formatSolutionStrataTable(opts.solutionStrata)
      : []);

  const lines = [
    ...attributionMiss,
    ...crashLines,
    ...seamDumps,
    ...(specHash ? [specHash] : []),
    ...(efficacy ? [efficacy] : []),
    ...(substratePageEfficacy ? [substratePageEfficacy] : []),
    ...(conceptEfficacy ? [conceptEfficacy] : []),
    ...plannerBypass,
    ...(parsePoison ? [parsePoison] : []),
    ...solutionStrata,
    ...permanentDeferrals,
    ...(continueProjectNote ? [continueProjectNote] : []),
    ...hangLines,
    ...(substrateHitRate ? [substrateHitRate] : []),
    ...classKeyCoverage,
    ...parseDrops,
  ];

  return {
    attributionMiss,
    seamDumps,
    crashLines,
    specHash,
    efficacy,
    conceptEfficacy,
    substratePageEfficacy,
    plannerBypass,
    parseDrops,
    parsePoison,
    solutionStrata,
    permanentDeferrals,
    continueProjectNote,
    hangLines,
    substrateHitRate,
    classKeyCoverage,
    lines,
  };
}
