"""Latency breakdown bar chart for the Case-6 composite-command decode study.

Reads sweep_decode_composite.json and writes a single-figure stacked bar
chart comparing three variants — primitive hand-tiled (16×16×16),
composite GEMM, composite + softmax_merge — at the S_kv = 131 072 point:

  bottom stack: PE_CPU dispatch time (from pe_cpu_dispatch_cycles, ns at
                1 GHz — ADR-0064 Rev2 D3)
  top stack:    engine time (latency_ns − dispatch cycles) — DMA / GEMM /
                MATH / IPCQ work the engine flushes on the critical path

The dispatch/engine split is a first-order breakdown; in reality the
two paths overlap partially. The note on the plot calls this out.

Run (after the composite bench sweep):
    python scripts/paper/paper_plot_gqa_decode_composite_breakdown.py
"""
from __future__ import annotations

import json
from pathlib import Path

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt  # noqa: E402

_REPO_ROOT = Path(__file__).resolve().parents[2]
_FIG_DIR = (
    _REPO_ROOT / "src" / "kernbench" / "benches"
    / "1H_milestone_output" / "gqa" / "long_ctx"
)
_SWEEP_JSON = _FIG_DIR / "sweep_decode_composite.json"
_PAPER_FIG_DIR = (
    _REPO_ROOT / "docs" / "report" / "1H-codesign-paper" / "figures"
)

# The three variants to compare (coarse `primitive` intentionally dropped).
_ORDER = ("primitive_tiled", "composite", "composite_extended")
_LABELS = {
    "primitive_tiled":    "primitive hand-tiled\n(16×16×16)",
    "composite":          "composite\nGEMM",
    "composite_extended": "composite +\nsoftmax_merge",
}
_S_KV_TARGET = 131_072


def main() -> None:
    sweep = json.loads(_SWEEP_JSON.read_text())
    rows = {(r["variant"], r["S_kv"]): r for r in sweep["rows"]}

    dispatch_us = []
    engine_us = []
    totals_us = []
    for v in _ORDER:
        r = rows[(v, _S_KV_TARGET)]
        disp_ns = r["pe_cpu_dispatch_cycles"]
        total_ns = r["latency_ns"]
        eng_ns = max(0.0, total_ns - disp_ns)
        dispatch_us.append(disp_ns / 1e3)
        engine_us.append(eng_ns / 1e3)
        totals_us.append(total_ns / 1e3)

    xs = list(range(len(_ORDER)))
    labels = [_LABELS[v] for v in _ORDER]

    fig, ax = plt.subplots(figsize=(7.5, 5.0))

    bars_eng = ax.bar(
        xs, engine_us,
        color="#4f8a4f", edgecolor="#2a4a2a", label="engine (DMA + GEMM + MATH + IPCQ)",
    )
    bars_disp = ax.bar(
        xs, dispatch_us, bottom=engine_us,
        color="#c0504d", edgecolor="#5a2624", label="PE_CPU dispatch",
    )

    # Segment value labels — engine at mid, dispatch at mid of its stack.
    for i, (eng, disp, tot) in enumerate(zip(engine_us, dispatch_us, totals_us)):
        ax.text(i, eng / 2, f"{eng:.1f} µs",
                ha="center", va="center", fontsize=9, color="white")
        if disp / max(totals_us) > 0.04:  # only label if visible
            ax.text(i, eng + disp / 2, f"{disp:.1f} µs",
                    ha="center", va="center", fontsize=9, color="white")
        else:
            ax.annotate(f"{disp:.2f} µs",
                        xy=(i, tot), xytext=(0, 6),
                        textcoords="offset points",
                        ha="center", va="bottom", fontsize=8,
                        color="#5a2624")
        offset_pts = 14 if disp / max(totals_us) > 0.04 else 20
        ax.annotate(f"total {tot:.1f}",
                    xy=(i, tot), xytext=(0, offset_pts),
                    textcoords="offset points",
                    ha="center", va="bottom", fontsize=9,
                    color="#333", fontweight="bold")

    ax.set_xticks(xs)
    ax.set_xticklabels(labels, fontsize=10)
    ax.set_ylabel("time (µs)")
    ax.set_title(
        f"Case-6 decode latency breakdown at $S_{{kv}}=${_S_KV_TARGET // 1024}K\n"
        "(engine dominates all three — primitive hand-tiled adds "
        f"{dispatch_us[0]:.0f} µs PE_CPU dispatch overhead)",
        fontsize=11,
    )
    ax.legend(loc="upper right", fontsize=9)
    ax.grid(True, axis="y", ls=":", alpha=0.5)
    ax.set_ylim(0, max(totals_us) * 1.15)

    fig.text(
        0.5, 0.02,
        "First-order breakdown: dispatch and engine paths overlap partially on the real "
        "critical path;\ntreat the split as an upper bound on dispatch's contribution.",
        ha="center", fontsize=8, color="#666",
    )
    fig.tight_layout(rect=(0, 0.06, 1, 1))

    out = _FIG_DIR / "gqa_decode_long_ctx_composite_breakdown.png"
    fig.savefig(out, dpi=150)
    plt.close(fig)
    print(f"wrote {out}")

    if _PAPER_FIG_DIR.is_dir():
        dst = _PAPER_FIG_DIR / out.name
        dst.write_bytes(out.read_bytes())
        print(f"copied {dst}")


if __name__ == "__main__":
    main()
