/**
 * Human-readable report.md generator for bounded-memory-contracts (issue #1708).
 *
 * The report states what each condition achieved, where typed retrieval wins
 * and loses, and — critically — what claims are NOT yet supported (the issue's
 * "do not hide failures" + "safe claims" acceptance criteria).
 */

import { BOUNDED_MEMORY_CONDITION_LABELS } from "./types.js";
import type {
  BoundedMemoryConditionAggregate,
  BoundedMemoryConditionId,
  BoundedMemoryTask,
} from "./types.js";

const CONDITION_ORDER: readonly BoundedMemoryConditionId[] = [
  "no-memory",
  "raw-transcript",
  "typed-contract",
  "typed-plus-skills",
];

function pct(value: number): string {
  return `${(value * 100).toFixed(1)}%`;
}

function num(value: number): string {
  return value.toFixed(2);
}

export function renderReportMarkdown(
  tasks: readonly BoundedMemoryTask[],
  aggregates: Record<string, BoundedMemoryConditionAggregate>,
): string {
  const lines: string[] = [];
  lines.push("# Bounded Memory Contracts — Benchmark Report");
  lines.push("");
  lines.push("> Generated by `@remnic/bench` (issue #1708). Fully synthetic, offline, deterministic. See the *Safe vs unsupported claims* section before citing any number.");
  lines.push("");

  lines.push("## Configuration");
  lines.push("");
  lines.push("- **Benchmark**: `bounded-memory-contracts` v1.0.0");
  lines.push("- **Model / provider**: none (deterministic offline simulation; no LLM called)");
  lines.push("- **Dataset source**: synthetic smoke fixture (no real user/client data)");
  lines.push(`- **Task count**: ${tasks.length} across 7 families`);
  lines.push("- **Conditions**: C0 no-memory · C1 raw-transcript · C2 typed-contract · C3 typed-plus-skills");
  lines.push("- **Budget**: shared token budget (budget-normalized comparison per the issue's mitigation for an unfair baseline)");
  lines.push("");

  lines.push("## Condition summary");
  lines.push("");
  lines.push("| Condition | Task success | Recall | Stale harm | Wrong-scope | Supersession respected | Citations | Mean tokens | Compression |");
  lines.push("|---|---|---|---|---|---|---|---|---|");
  for (const cond of CONDITION_ORDER) {
    const a = aggregates[cond];
    if (!a) continue;
    lines.push(
      `| ${BOUNDED_MEMORY_CONDITION_LABELS[cond]} | ${pct(a.taskSuccessRate)} | ${pct(a.relevantMemoryRecall)} | ${pct(a.staleMemoryHarmRate)} | ${pct(a.wrongScopeRetrievalRate)} | ${pct(a.supersessionRespectedRate)} | ${pct(a.citationCoverage)} | ${num(a.meanMemoryTokensInjected)} | ${num(a.meanCompressionRatio)}× |`,
    );
  }
  lines.push("");

  lines.push("## Boundary behavior (ask vs act)");
  lines.push("");
  lines.push("| Condition | Should-ask accuracy | Unnecessary clarification | Boundary violation |");
  lines.push("|---|---|---|---|");
  for (const cond of CONDITION_ORDER) {
    const a = aggregates[cond];
    if (!a) continue;
    lines.push(
      `| ${BOUNDED_MEMORY_CONDITION_LABELS[cond]} | ${pct(a.shouldAskAccuracy)} | ${pct(a.unnecessaryClarificationRate)} | ${pct(a.actionBoundaryViolationRate)} |`,
    );
  }
  lines.push("");

  const c3 = aggregates["typed-plus-skills"];
  lines.push("## Skill-trigger behavior (C3 only)");
  lines.push("");
  if (c3) {
    lines.push(`- **Trigger precision**: ${pct(c3.skillTriggerPrecision)}`);
    lines.push(`- **Trigger recall**: ${pct(c3.skillTriggerRecall)}`);
    lines.push(`- **False-positive rate**: ${pct(c3.skillFalsePositiveRate)}`);
    lines.push(`- **False-negative rate**: ${pct(c3.skillFalseNegativeRate)}`);
    lines.push(`- **Helped / harmed / irrelevant**: ${c3.skillHelpedCount} / ${c3.skillHarmedCount} / ${c3.skillIrrelevantCount}`);
  } else {
    lines.push("_C3 not run._");
  }
  lines.push("");

  lines.push("## Biggest wins (typed retrieval vs raw transcript)");
  lines.push("");
  const c1 = aggregates["raw-transcript"];
  const c2 = aggregates["typed-contract"];
  if (c1 && c2) {
    lines.push(`- **Stale-memory harm**: C1 ${pct(c1.staleMemoryHarmRate)} → C2 ${pct(c2.staleMemoryHarmRate)}. The typed contract excludes superseded facts; raw transcript cannot.`);
    lines.push(`- **Wrong-scope leakage**: C1 ${pct(c1.wrongScopeRetrievalRate)} → C2 ${pct(c2.wrongScopeRetrievalRate)}. Scope filtering removes cross-project decoys that raw transcript surfaces.`);
    lines.push(`- **Boundary violations**: C1 ${pct(c1.actionBoundaryViolationRate)} → C2 ${pct(c2.actionBoundaryViolationRate)}. Structured boundary notes are reliably surfaced; buried transcript prose is not.`);
    lines.push(`- **Citation coverage**: C1 ${pct(c1.citationCoverage)} → C2 ${pct(c2.citationCoverage)}. Typed slots carry citations; raw text does not.`);
    lines.push(`- **Compression**: C1 ${num(c1.meanCompressionRatio)}× → C2 ${num(c2.meanCompressionRatio)}× of the raw transcript. Typed packs are smaller for the same budget.`);
  }
  lines.push("");

  lines.push("## Biggest failures / honest weaknesses");
  lines.push("");
  lines.push("- **Recall parity on pure-recall tasks**: raw transcript (C1) is a *fair* baseline — when the needed fact is present and unambiguous, C1 recalls it as well as C2. Typed retrieval's advantage is governance, not brute recall.");
  lines.push("- **C0 no-memory**: fails every recall-dependent task by construction; it is the floor, not a competitor.");
  lines.push("- **Skill overfitting risk**: the smoke fixture is small and hand-authored around two skills; positive/negative trigger balance is preliminary.");
  lines.push("");

  lines.push("## Worked examples");
  lines.push("");
  const staleExample = tasks.find((t) => t.family === "stale-memory-trap");
  if (staleExample) {
    lines.push(`- **Stale-memory trap** (\`${staleExample.id}\`): a superseded fact (more repeated in the transcript) crowds out the one-line correction under C1, while C2 excludes it via \`excludeIfSuperseded\`.`);
  }
  const scopeExample = tasks.find((t) => t.family === "wrong-scope-trap");
  if (scopeExample) {
    lines.push(`- **Wrong-scope trap** (\`${scopeExample.id}\`): a same-subject fact from a different project leaks under C1 (no scope metadata) but is filtered out under C2/C3.`);
  }
  lines.push("");

  lines.push("## Token / cost / latency");
  lines.push("");
  lines.push("- **Estimated cost**: $0 — no model was called. Token counts reflect injected memory only.");
  lines.push("| Condition | Mean injected tokens | Mean retrieved items |");
  lines.push("|---|---|---|");
  for (const cond of CONDITION_ORDER) {
    const a = aggregates[cond];
    if (!a) continue;
    lines.push(`| ${BOUNDED_MEMORY_CONDITION_LABELS[cond]} | ${num(a.meanMemoryTokensInjected)} | ${num(a.meanRetrievedItemCount)} |`);
  }
  lines.push("");

  lines.push("## Safe vs unsupported claims");
  lines.push("");
  lines.push("**Safe to claim from this run:**");
  lines.push("");
  lines.push("- Typed retrieval contracts exclude superseded and wrong-scope memories that raw transcript stuffing surfaces, *under this deterministic simulation*.");
  lines.push("- Structured boundary notes are reliably surfaced by the typed contract and not by raw transcript, *under this simulation*.");
  lines.push("- Typed packs are smaller than the raw transcript for the same task, under the shared budget.");
  lines.push("");
  lines.push("**NOT supported by this run (do not claim):**");
  lines.push("");
  lines.push("- That Remnic beats Mem0 / Zep / Letta or any other system. No cross-system comparison was performed.");
  lines.push("- That these numbers generalize to real users. The fixture is small, synthetic, and hand-authored.");
  lines.push("- Any frontier-model quality figures. No LLM was called; the agent is a deterministic decision procedure.");
  lines.push("- That skill-triggered memory helps on real procedural workloads. Two hand-authored skills are not evidence.");
  lines.push("");

  lines.push("## Recommended next experiment");
  lines.push("");
  lines.push("1. Scale to the full fixture (100+ tasks) with de-identified or synthetic traces.");
  lines.push("2. Swap the deterministic agent for a real responder under a blinded judge (condition names hidden).");
  lines.push("3. Add a `model-context-max` raw-transcript variant as a *separate, non-primary* condition.");
  lines.push("4. Expand the skill library and report false-positive/negative rates over a larger trigger set.");
  lines.push("");

  return lines.join("\n");
}
