#!/usr/bin/env python3
"""Render `docs/value.md` from the latest `value-v1` JSON report.

Phase 4 Step 1 of `agents/roadmaps/road-to-readable-value-dashboard.md`.

This renderer is **deterministic** — it does not run any bench, only
formats existing reports. Mirrors `render_benchmark_md.py`'s placeholder
discipline: when the report is missing, write a placeholder document
explaining how to produce one. Never errors.

The dashboard has two panels:
  - Panel A — cost ladder (cumulative, min → max)
  - Panel B — behaviour (with vs. without)

Each panel uses plain language, prints `confidence` markers inline,
and ends with a bold NETTO line that lifts the totals out of the
table.
"""
from __future__ import annotations

import argparse
import json
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Optional


REPO_ROOT = Path(__file__).resolve().parent.parent.parent
VALUE_REPORTS_DIR = REPO_ROOT / "internal" / "bench" / "reports" / "value"
LATEST = VALUE_REPORTS_DIR / "latest.json"
OUT_PATH = REPO_ROOT / "docs" / "value.md"

REQUIRED_SECTIONS = (
    "## Reference scale",
    "## Panel A",
    "## Panel B",
    "## Glossary",
    "**NET",
)


def utc_iso() -> str:
    return datetime.now(timezone.utc).isoformat(timespec="seconds")


def safe_load(path: Path) -> Optional[Dict[str, Any]]:
    if not path.exists():
        return None
    try:
        return json.loads(path.read_text())
    except json.JSONDecodeError:
        return None


def fmt_signed_int(value: int) -> str:
    return f"{value:+,}".replace(",", " ")


def fmt_pct(value: float) -> str:
    return f"{value:+.2f}%"


def confidence_badge(level: str) -> str:
    badges = {
        "measured": "✅ measured",
        "estimated": "≈ estimated",
        "vendor-claim": "⚠️ vendor-claim",
        "pending": "⏳ pending",
        "available": "🔁 available (default off)",
    }
    return badges.get(level, level)


def mode_badge(mode: str) -> str:
    if mode == "live":
        return "✅ live"
    if mode == "dry-run":
        return "⚠️ dry-run"
    return mode


def render_intro(report: Dict[str, Any]) -> str:
    ref = report.get("reference_scale", {})
    requests = ref.get("requests", 1000)
    avg_in = ref.get("avg_input_tokens", 8000)
    avg_out = ref.get("avg_output_tokens", 600)
    tier = ref.get("model_tier", "sonnet")
    return (
        f"# Value Dashboard — what does the package cost, what does it bring?\n"
        "\n"
        "> This page answers **one** question in real numbers: "
        "*How many more tokens does the package cost me, and how much "
        "does it save back afterwards?* Generated by "
        "`scripts/render_value_md.py` from the latest `value-v1` report; "
        "source: `internal/bench/reports/value/latest.json`.\n"
        "\n"
        "## How to read this page\n"
        "\n"
        "**Panel A (token ladder)** — read top to bottom. Each "
        "rung states: *what it does*, *how many input tokens it adds or "
        "saves per request*, and *where we stand cumulatively*. "
        "The bold **NET** line at the end is the answer. "
        "Deliberately pure tokens — no € comparison, since subscription "
        "users pay no per-request API prices.\n"
        "\n"
        "**Panel B (behavior)** — four real comparisons, *with* vs. "
        "*without* the package. This is where the non-token value lives: "
        "fitting skill selection, stops on risky actions, fewer "
        "clarifying questions, more completed tasks.\n"
        "\n"
        "**Confidence markers** on each rung: `✅ measured` = a real "
        "value from a report in the repo · `⏳ pending` = not yet "
        "measured, the rung contributes 0 to the sum · `⚠️ vendor-claim` = "
        "a vendor's claim, not measured by us.\n"
        "\n"
        "## Reference scale\n"
        "\n"
        f"- **{requests:,}** requests, on average "
        f"**{avg_in:,}** input tokens and **{avg_out:,}** output tokens "
        "per request\n"
        f"- Model tier (workload assumption): `{tier}`\n"
        "- Anyone running a different workload recomputes it themselves — the "
        "methodology is disclosed; nothing is hardcoded or hidden.\n"
    )


def render_panel_a(report: Dict[str, Any]) -> str:
    lines = [
        "## Panel A — Cost ladder (cumulative, min → max)\n",
        "Reads top to bottom. Positive Δ values = the package "
        "*costs* tokens (rule load is the honest up-front tax); "
        "negative Δ values = the package *saves* tokens.\n",
        "| Rung | What it does | Δ tokens | Cumulative | Source |",
        "|---|---|---:|---:|---|",
    ]
    for rung in report.get("cost_ladder", []):
        if rung["id"] == "baseline":
            label_cell = f"**{rung['label']}**"
        else:
            label_cell = rung["label"]
        what = rung.get("what_it_does", "")
        token_delta = int(rung.get("token_delta", 0))
        cum = float(rung.get("cumulative_pct", 0.0))
        conf = confidence_badge(rung.get("confidence", "pending"))
        source = rung.get("source_report", "")
        # Honesty stamp: an `up-front-cost` note on the load rung.
        if rung["id"] == "load" and token_delta > 0:
            what = f"{what} ⚠️ costs first"
        lines.append(
            f"| {label_cell} | {what} | "
            f"{fmt_signed_int(token_delta)} | "
            f"{fmt_pct(cum)} | `{source}` · {conf} |"
        )
        if rung.get("footnote"):
            lines.append(
                f"| | _Footnote:_ {rung['footnote']} | | | |"
            )

    totals = report.get("totals", {})
    cum_tokens = int(totals.get("cumulative_token_delta", 0))
    cum_pct = float(totals.get("cumulative_pct", 0.0))
    verdict = totals.get("net_verdict", "—")
    verdict_label = {
        "net-saving": "**NET: saving** ✅",
        "net-cost": "**NET: extra cost** ⚠️",
        "break-even": "**NET: break-even** ⚖️",
    }.get(verdict, f"**NET: {verdict}**")
    lines.extend(
        [
            "",
            f"{verdict_label} — "
            f"**{fmt_signed_int(cum_tokens)} tokens / request**, "
            f"cumulative **{fmt_pct(cum_pct)}** vs. baseline.\n",
        ]
    )
    return "\n".join(lines)


def render_panel_b(report: Dict[str, Any]) -> str:
    lines = [
        "## Panel B — Behavior (with vs. without)\n",
        "Four real comparisons from actual bench runs. This is where the "
        "value lives that tokens alone do not measure: whether the agent "
        "picks the right skill, stops on risky actions, asks fewer "
        "clarifying questions, and completes more tasks.\n",
        "| Metric | What it means | With package | Without package | Δ | Mode |",
        "|---|---|---:|---:|---:|---|",
    ]
    for metric in report.get("behaviour", []):
        label = metric["label"]
        what = metric.get("what_this_means", "")
        unit = metric.get("unit", "")
        mode = mode_badge(metric.get("mode", "dry-run"))

        def _fmt(v: Any) -> str:
            if v is None:
                return "—"
            if unit == "pct" and isinstance(v, (int, float)):
                return f"{float(v) * 100:.1f}%"
            if unit == "count":
                return str(int(v))
            if unit == "ratio" and isinstance(v, (int, float)):
                return f"{float(v):.3f}"
            if unit == "seconds" and isinstance(v, (int, float)):
                return f"{float(v):.1f}s"
            return str(v)

        with_v = _fmt(metric.get("with"))
        without_v = _fmt(metric.get("without"))
        delta_v = _fmt(metric.get("delta"))
        lines.append(
            f"| {label} | {what} | {with_v} | {without_v} | {delta_v} | {mode} |"
        )
    return "\n".join(lines) + "\n"


def render_glossary() -> str:
    return (
        "## Glossary\n"
        "\n"
        "Plain-language definitions for the non-developer reader.\n"
        "\n"
        "- **Token** — the unit a language model bills in. "
        "Rule of thumb: one token ≈ 4 characters of German/English prose. "
        "1,000 tokens ≈ 750 words.\n"
        "- **Input tokens** — everything the model reads per turn "
        "(system prompt, always-active rules, your message, earlier "
        "conversation). The package adds rules here — installation "
        "costs input tokens.\n"
        "- **Output tokens** — what the model writes back. Usually "
        "fewer than input. More expensive per token than input.\n"
        "- **condense** — a build step that shrinks the rule files "
        "before shipping (`.agent-src.uncondensed` → "
        "`dist/agent-src`). Saves input tokens on every request.\n"
        "- **rtk** — the *Rust Token Killer*, a CLI wrapper that "
        "filters verbose output (`git status`, lint output, test "
        "runners) before the model reads it. Saves input tokens on "
        "tool calls.\n"
        "- **terse / telegraph** — a style (short phrases, "
        "dropped articles) the agent uses for terser answers. "
        "Saves output tokens — when the corpus rewards it.\n"
        "- **Without package / With package** — the two arms of the "
        "A/B comparison.\n"
        "- **Δ tokens** — input-token difference per request against the "
        "baseline. Deliberately the only cost unit: a € comparison "
        "would imply per-request API prices that subscription users do "
        "not pay.\n"
    )


def render_methodology(report: Dict[str, Any]) -> str:
    notes = report.get("notes", [])
    lines = [
        "## Methodology & sources\n",
        "This page is a **derived** view — not its own "
        "measurement. It summarizes three existing bench surfaces "
        "(see the 'Source' column in Panel A). The machine-readable "
        "raw reports remain the source of truth:\n",
        "- `internal/bench/reports/telegraph-v1.json` / `telegraph-v2.json` "
        "— Telegraph/condense measurements.\n",
        "- `agents/runtime/frugality/baseline.jsonl` — the package load "
        "(Metric A footprint).\n",
        "- `internal/bench/reports/rtk/latest.json` — the rtk measurement "
        "(new, Phase 2).\n",
        "- `internal/bench/reports/ab/*-ab-trackb-{with,without}.json` "
        "— A/B Track B (behavior).\n",
        "- `internal/bench/reports/*-dev.json` — dev-corpus selection "
        "accuracy.\n",
        "",
        "**A/B technical appendix:** [`docs/benchmark.md`](benchmark.md) "
        "carries the cache-key, integrity, and methodology details of "
        "the A/B bench — anyone who wants to see the variant-axis proof "
        "reads on there.\n",
        "",
    ]
    if notes:
        lines.append("**Notes from the report:**\n")
        for note in notes:
            lines.append(f"- {note}")
        lines.append("")
    lines.append(f"_Last rendered: `{utc_iso()}`_\n")
    return "\n".join(lines)


def render_placeholder() -> str:
    return (
        "# Value Dashboard — placeholder\n"
        "\n"
        "_No `value-v1` report exists yet at "
        "`internal/bench/reports/value/latest.json`._\n"
        "\n"
        "Generate one with:\n"
        "\n"
        "```sh\n"
        "task value\n"
        "```\n"
        "\n"
        "The methodology of this dashboard is described in "
        "`docs/contracts/value-dashboard-spec.md` and the associated "
        "roadmap `agents/roadmaps/road-to-readable-value-dashboard.md`.\n"
        "\n"
        f"_Last rendered: {utc_iso()}_\n"
    )


def render(quiet: bool = False) -> int:
    report = safe_load(LATEST)
    OUT_PATH.parent.mkdir(parents=True, exist_ok=True)
    if not report:
        OUT_PATH.write_text(render_placeholder())
        if not quiet:
            sys.stdout.write(
                f"render_value_md: no report — wrote placeholder to "
                f"{OUT_PATH.relative_to(REPO_ROOT)}\n"
            )
        return 0
    parts = [
        render_intro(report),
        render_panel_a(report),
        render_panel_b(report),
        render_glossary(),
        render_methodology(report),
    ]
    OUT_PATH.write_text("\n".join(parts))
    if not quiet:
        sys.stdout.write(
            f"render_value_md: wrote {OUT_PATH.relative_to(REPO_ROOT)}\n"
        )
    return 0


def parse_args(argv: list[str]) -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Render docs/value.md from the latest value-v1 report."
    )
    parser.add_argument("--quiet", action="store_true", help="Suppress stdout.")
    return parser.parse_args(argv)


def main(argv: list[str] | None = None) -> int:
    args = parse_args(argv if argv is not None else sys.argv[1:])
    return render(quiet=args.quiet)


if __name__ == "__main__":
    raise SystemExit(main())
