"""6-case KV-sharding tensor diagram (the slide-13 PNG export).

Flat 2-D rectangles, one per sharding case, with:
    Y axis = S_kv (vertical)        — Cube-SP / PE-SP slice it
    X axis = d_head (horizontal)    — Cube-TP-d_head / PE-TP-d_head slice it

Drops the batch axis entirely (decode: B = 1, T_q = 1). Same case set
and visual encoding as slide 13 of GQA_full_deck.pptx; matplotlib
renders it cleanly so the PNG sits next to the other GQA summary
artifacts in 1H_milestone_output/gqa/long_ctx/.
"""
from __future__ import annotations

from pathlib import Path

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt

_C = 8
_P = 8

_GROUP_FILLS = [
    "#A5D8FF", "#B2F2BB", "#FFD8A8", "#FFC9C9",
    "#D0BFFF", "#99E9F2", "#FCC2D7", "#FFEC99",
]

_ACC = {
    "red":    "#E03131",
    "orange": "#FD7E14",
    "blue":   "#1C7ED6",
    "green":  "#37B24D",
}

# (label, accent, kv, comm, overflow, encoding-flags, axis-spec)
#   y_split   = 8 horizontal Y bands  (Cube-SP on S_kv)
#   x_split   = 8 vertical X bands    (Cube-TP-d_head)
#   pe_y      = 7 fine horizontal dividers within each Y band
#   pe_x      = 7 fine vertical dividers within each X band
#   axes      = small annotation under the chip naming the axes
#               that the cube/PE actually shard, so the reader can
#               parse Case 5 (where cube colour fills run X instead
#               of Y, breaking the visual symmetry of the rest).
_CASES = [
    dict(label="Case 1\nCube-Repl / PE-repl", accent=_ACC["red"],
         kv="40 GB", comm="1.2 MB", overflow=True,
         y_split=False, x_split=False, pe_y=False, pe_x=False,
         axes="Cube: replicated     PE: replicated"),
    dict(label="Case 2\nCube-SP / PE-repl",   accent=_ACC["orange"],
         kv="5 GB",  comm="3.8 MB", overflow=True,
         y_split=True,  x_split=False, pe_y=False, pe_x=False,
         axes="Cube → Y (S_kv)     PE: replicated"),
    dict(label="Case 3\nCube-Repl / PE-SP",   accent=_ACC["orange"],
         kv="5 GB",  comm="3.8 MB", overflow=True,
         y_split=False, x_split=False, pe_y=True,  pe_x=False,
         axes="Cube: replicated     PE → Y (S_kv)"),
    dict(label="Case 4\nCube-SP / PE-TP-d_head", accent=_ACC["blue"],
         kv="640 MB", comm="166 MB", overflow=False,
         y_split=True,  x_split=False, pe_y=False, pe_x=True,
         axes="Cube → Y (S_kv)     PE → X (d_head)"),
    dict(label="Case 5\nCube-TP-d_head / PE-SP", accent=_ACC["blue"],
         kv="640 MB", comm="166 MB", overflow=False,
         y_split=False, x_split=True,  pe_y=True,  pe_x=False,
         axes="Cube → X (d_head)     PE → Y (S_kv)"),
    dict(label="Case 6 ★\nCube-SP / PE-SP",   accent=_ACC["green"],
         kv="640 MB", comm="6.2 MB", overflow=False,
         y_split=True,  x_split=False, pe_y=True,  pe_x=False,
         axes="Cube → Y (S_kv)     PE → Y (S_kv)"),
]

_OUT_DIR = (
    Path(__file__).resolve().parents[2]
    / "src" / "kernbench" / "benches"
    / "1H_milestone_output" / "gqa" / "long_ctx"
)


def _draw_panel(ax, cfg):
    """Draw one case's 2-D KV-tensor rectangle into a panel ax."""
    ax.set_xlim(0, 1)
    ax.set_ylim(1, 0)        # Y points down (S_kv ↓)
    ax.set_aspect("auto")
    ax.set_xticks([])
    ax.set_yticks([])

    cube_repl = not cfg["y_split"] and not cfg["x_split"]
    pe_repl   = not cfg["pe_y"]   and not cfg["pe_x"]

    # Cube-level colour fill.
    if cfg["y_split"] and not cfg["x_split"]:
        # 8 horizontal Y bands.
        for c in range(_C):
            ax.add_patch(mpatches.Rectangle(
                (0, c / _C), 1, 1 / _C,
                facecolor=_GROUP_FILLS[c], edgecolor="black", linewidth=0.6))
            ax.text(0.04, c / _C + 0.5 / _C, f"C{c}",
                    ha="left", va="center", fontsize=8,
                    fontweight="bold", color="#333")
    elif cfg["x_split"] and not cfg["y_split"]:
        # 8 vertical X bands.
        for c in range(_C):
            ax.add_patch(mpatches.Rectangle(
                (c / _C, 0), 1 / _C, 1,
                facecolor=_GROUP_FILLS[c], edgecolor="black", linewidth=0.6))
            ax.text(c / _C + 0.5 / _C, 0.04, f"C{c}",
                    ha="center", va="top", fontsize=8,
                    fontweight="bold", color="#333")
    else:
        ax.add_patch(mpatches.Rectangle(
            (0, 0), 1, 1,
            facecolor="#F5F5F5", edgecolor="black", linewidth=0.8))
        ax.text(0.5, 0.5, "× 8 cubes\nfull KV",
                ha="center", va="center",
                fontsize=10, fontweight="bold",
                fontstyle="italic", color="#666")

    # PE-level fine dividers — distinguished from cube boundaries by
    # using a dashed style + slightly stronger contrast. This is what
    # makes Case 5's PE-SP (horizontal lines across vertical cube
    # bands) read as "different axis from the cubes" at a glance.
    if cfg["pe_y"]:
        outer = _C if cfg["y_split"] else 1
        band = 1 / outer
        for o in range(outer):
            for p in range(1, _P):
                y = o * band + band * p / _P
                ax.axhline(y, color="#222", linewidth=0.8,
                           linestyle=(0, (3, 2)), alpha=0.75)
    if cfg["pe_x"]:
        outer = _C if cfg["x_split"] else 1
        band = 1 / outer
        for o in range(outer):
            for p in range(1, _P):
                x = o * band + band * p / _P
                ax.axvline(x, color="#222", linewidth=0.8,
                           linestyle=(0, (3, 2)), alpha=0.75)

    # Heavy outline on top.
    ax.add_patch(mpatches.Rectangle(
        (0, 0), 1, 1, facecolor="none",
        edgecolor="black", linewidth=1.2))

    # Replication badges — small text-only badges in the corners of
    # the rectangle, no ghost-card stacking (which mis-reads as a
    # larger enclosing tensor).
    badges: list[str] = []
    if cube_repl:
        badges.append("× 8 cube copies")
    if pe_repl and (cfg["y_split"] or cfg["x_split"]):
        # Cube is sharded but PEs in each cube replicate that shard.
        badges.append("× 8 PEs / cube replicate")
    elif pe_repl and cube_repl:
        # Both replicated — PE replication adds to the cube one.
        badges.append("× 8 PEs / cube replicate")
    if badges:
        ax.text(0.98, 0.02, "\n".join(badges),
                ha="right", va="top", fontsize=7,
                fontweight="bold", color="#444",
                fontstyle="italic",
                bbox=dict(facecolor="white", edgecolor="#888",
                          boxstyle="round,pad=0.20", linewidth=0.5))


def _make_table_png() -> Path:
    """Slide-14 companion table: per-PE memory + comm for all 6 cases."""
    headers = ["Case", "Sharding", "KV / PE", "Fit",
               "Comm/tok\n(analytical)", "Notes"]
    rows = [
        ("Case 1",   "Cube-Repl  ·  PE-repl",      "40 GB",  "✗",
         "1.2 MB",
         "no sharding —\nfull KV on every PE"),
        ("Case 2",   "Cube-SP    ·  PE-repl",      "5 GB",   "✗",
         "3.8 MB",
         "cube-axis\nsharded only"),
        ("Case 3",   "Cube-Repl  ·  PE-SP",        "5 GB",   "✗",
         "3.8 MB",
         "PE-axis\nsharded only"),
        ("Case 4",   "Cube-SP    ·  PE-TP-d_head", "640 MB", "✓",
         "166 MB",
         "d_head split intra-cube\npartial-score AR ∝ S_kv"),
        ("Case 5",   "Cube-TP-d_head  ·  PE-SP",   "640 MB", "✓",
         "166 MB",
         "d_head split inter-cube\npartial-score AR on UCIe"),
        ("Case 6 ★", "Cube-SP    ·  PE-SP",        "640 MB", "✓",
         "6.2 MB",
         "S_kv split both axes\n(m,ℓ,O) AR only"),
    ]
    accents = [_ACC["red"], _ACC["orange"], _ACC["orange"],
               _ACC["blue"], _ACC["blue"], _ACC["green"]]

    fig, ax = plt.subplots(figsize=(15.0, 5.0))
    ax.set_axis_off()

    cell_data = [headers] + [list(r) for r in rows]
    tbl = ax.table(cellText=cell_data,
                   colWidths=[0.07, 0.20, 0.09, 0.05, 0.14, 0.28],
                   cellLoc="center", loc="center")
    tbl.auto_set_font_size(False)
    tbl.set_fontsize(10.5)
    tbl.scale(1.0, 2.4)

    n_cols = len(headers)
    n_rows = len(rows) + 1   # +1 header
    # Header styling.
    for ci in range(n_cols):
        cell = tbl[(0, ci)]
        cell.set_facecolor("#1F4E79")
        cell.set_text_props(color="white", weight="bold")
        cell.set_edgecolor("#1F4E79")
    # Body styling.
    for ri, row in enumerate(rows, start=1):
        is_pareto = row[0].endswith("★")
        row_fill = "#E8F5E9" if is_pareto else (
            "white" if ri % 2 == 1 else "#F5F5F7")
        # Case-name cell uses accent.
        case_cell = tbl[(ri, 0)]
        case_cell.set_facecolor(accents[ri - 1])
        case_cell.set_text_props(color="white", weight="bold")
        # Remaining cells.
        for ci in range(1, n_cols):
            cell = tbl[(ri, ci)]
            cell.set_facecolor(row_fill)
            txt_kwargs = {"weight": "bold" if is_pareto else "normal",
                          "color": "#333"}
            if ci == 2:                              # KV / PE
                txt_kwargs["color"] = (
                    "#C62828" if row[3] == "✗" else "#2E7D32")
                txt_kwargs["weight"] = "bold"
            if ci == 3:                              # Fit
                txt_kwargs["color"] = (
                    "#C62828" if row[3] == "✗" else "#2E7D32")
                txt_kwargs["weight"] = "bold"
            cell.set_text_props(**txt_kwargs)
        # Last-column (Notes) cells left-aligned for readability.
        tbl[(ri, n_cols - 1)].get_text().set_ha("left")

    # Force left-align on the Notes header too.
    tbl[(0, n_cols - 1)].get_text().set_ha("left")

    fig.suptitle(
        "GQA decode KV-sharding — per-PE memory & communication\n"
        "(LLaMA 70B GQA single KV-head group  ·  S_kv = 1 M, FP16, "
        "80 layers)",
        fontsize=11.5, y=0.94,
    )
    out = _OUT_DIR / "gqa_long_ctx_6cases_kv_sharding_table.png"
    fig.savefig(out, dpi=150, bbox_inches="tight")
    plt.close(fig)
    print(f"wrote {out}")
    return out


def main() -> Path:
    _OUT_DIR.mkdir(parents=True, exist_ok=True)
    n = len(_CASES)
    fig = plt.figure(figsize=(20.0, 7.0))
    # Three rows per column: case chip · axis-spec annotation · rectangle.
    gs = fig.add_gridspec(3, n,
                          height_ratios=[0.55, 0.32, 8.5],
                          hspace=0.05, wspace=0.20,
                          left=0.04, right=0.99,
                          top=0.93, bottom=0.06)

    for i, cfg in enumerate(_CASES):
        # Top: case chip header.
        ax_chip = fig.add_subplot(gs[0, i])
        ax_chip.set_xticks([])
        ax_chip.set_yticks([])
        for spine in ax_chip.spines.values():
            spine.set_visible(False)
        ax_chip.add_patch(mpatches.Rectangle(
            (0, 0), 1, 1, transform=ax_chip.transAxes,
            facecolor=cfg["accent"], edgecolor=cfg["accent"]))
        ax_chip.text(0.5, 0.5, cfg["label"],
                     ha="center", va="center",
                     fontsize=10, fontweight="bold",
                     color="white")

        # Middle: axis-spec annotation — names which axis the cube
        # shards on and which axis the PE shards on (essential for
        # parsing Case 5 where the cube colour fills run X instead
        # of Y, breaking the visual symmetry of the rest).
        ax_axes = fig.add_subplot(gs[1, i])
        ax_axes.set_xticks([])
        ax_axes.set_yticks([])
        for spine in ax_axes.spines.values():
            spine.set_visible(False)
        ax_axes.add_patch(mpatches.Rectangle(
            (0, 0), 1, 1, transform=ax_axes.transAxes,
            facecolor="#F5F5F7", edgecolor="#CCCCCC",
            linewidth=0.6))
        ax_axes.text(0.5, 0.5, cfg["axes"],
                     ha="center", va="center",
                     fontsize=8.5, fontweight="bold",
                     color="#1F4E79")

        # Bottom: the tensor rectangle.
        ax = fig.add_subplot(gs[2, i])
        _draw_panel(ax, cfg)
        ax.set_xlabel("X : d_head = 128  →",
                      fontsize=9, fontweight="bold",
                      fontstyle="italic", color="#1F4E79")
        ax.set_ylabel("Y : S_kv = 1 M  ↓",
                      fontsize=9, fontweight="bold",
                      fontstyle="italic", color="#1F4E79")

    fig.suptitle(
        "GQA decode KV-tensor sharding — 6 cases  ·  "
        "LLaMA 70B GQA single KV-head group  ·  "
        "C = 8 cubes × P = 8 PEs  ·  S_kv = 1 M, FP16, 80 layers",
        fontsize=12, y=0.99,
    )

    out = _OUT_DIR / "gqa_long_ctx_6cases_kv_sharding_diagram.png"
    fig.savefig(out, dpi=150, bbox_inches="tight")
    plt.close(fig)
    print(f"wrote {out}")

    # Companion table PNG (slide-14 export).
    _make_table_png()
    return out


if __name__ == "__main__":
    main()
