"""照明配置スイープ（TEST_18）の発表スライド用の図を英語で生成する.

既存の解析図（build_led_placement_comparison.py / build_led_placement_maps.py）は日本語で
情報量が多いので，スライドに貼れる体裁（題・枠なし，ラベルは英語，右にカラーバー）で
次の 5 枚を作る．

    slide_led_geometry.png        LED 位置の定義（球 φ340 と立方体 L=234 の断面に θ=0〜120° の LED）
    slide_led_position_maps.png   θ=0/30/60/90/120° の輝度マップ（上: 球 φ340，下: 立方体 L=234）
    slide_led_position_curves.png 構造 CoV と min の θ 依存（4 ペア）
    slide_led_count_maps.png      灯数 N=1〜4（θ=90°）の輝度マップ + 軸上 1 灯の参照
    slide_led_count_curves.png    構造 CoV と min の N 依存

輝度マップは build_led_placement_maps.py と同じ処理（メディアン 5 → ガウス σ=4 → 99 パーセンタイル
= 1）で，各マップの下に θ または N と min（ノイズ補正付き最小/最大比）を書く．

実行方法:
    uv run python scripts/build_led_placement_slides.py
    uv run python scripts/build_led_placement_slides.py --material calibrated
"""

from __future__ import annotations

import argparse
import sys
from pathlib import Path

import matplotlib
import numpy as np
import pandas as pd

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

sys.path.insert(0, str(Path(__file__).resolve().parent))
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))

import build_led_placement_maps as maps  # noqa: E402
from sweep_sphere_radius import center_z, cut_radius  # noqa: E402

ROOT = Path(__file__).resolve().parents[1]
THETAS = [0.0, 30.0, 60.0, 90.0, 120.0]
COUNTS = [1, 2, 3, 4]
REP_PAIR = 2  # 球 φ340 / 立方体 L=234
PAIR_LABELS = {
    "sphere": {100.0: "Sphere φ200", 135.0: "Sphere φ270", 170.0: "Sphere φ340",
               250.0: "Sphere φ500"},
    "cube": {141.0: "Cube L=141", 187.0: "Cube L=187", 234.0: "Cube L=234", 340.0: "Cube L=340"},
}
PAIR_COLORS = ["#4b1d6b", "#2c6e9e", "#1fa187", "#8fd744"]
SHAPE_STYLE = {"sphere": ("-", "o"), "cube": ("--", "s")}
VMIN, VMAX = 0.80, 1.00
PLATE_HALF = 50.0  # 評価板の表示半幅 [mm]（orthographic センサの ortho_scale と同程度）
BAFFLE_DIST = 16.0  # 模式図でのバッフルの LED からの距離 [mm]（実際は視線上 10mm）
BAFFLE_HALF = 9.0  # 模式図でのバッフルの半幅 [mm]
COLOR_WALL = "#3f6f95"
COLOR_PLATE = "#d8232a"
COLOR_LED = "#f2a900"

plt.rcParams["font.family"] = ["DejaVu Sans"]  # 英語図．θ・φ は DejaVu に含まれる


# --------------------------------------------------------------------------- 幾何の模式図
def sphere_outline(radius: float) -> tuple[np.ndarray, np.ndarray, float]:
    """球ロフト（similar_radial）キャビティの断面輪郭 (x, z) と球中心 z を返す."""
    cz = center_z(radius, maps.sweep.SPHERE_CAVITY)
    rc = cut_radius(radius, maps.sweep.SPHERE_CAVITY)
    # 接続平面より下の球面（右回りに一周）
    phi0 = np.arcsin(rc / radius)  # 接続円の極角（上向きから）
    phi = np.linspace(phi0, 2 * np.pi - phi0, 400)
    xs = radius * np.sin(phi)
    zs = cz + radius * np.cos(phi)
    # ロフト: 接続円 → 評価板の縁（直線で近似）
    x = np.concatenate([[PLATE_HALF * 1.15], xs, [-PLATE_HALF * 1.15]])
    z = np.concatenate([[0.0], zs, [0.0]])
    return x, z, cz


def cube_outline(size: float) -> tuple[np.ndarray, np.ndarray, float]:
    """立方体キャビティの断面輪郭 (x, z) と中心 z を返す（天井 z=0）."""
    h = size / 2
    x = np.array([-h, h, h, -h, -h])
    z = np.array([0.0, 0.0, -size, -size, 0.0])
    return x, z, -h


def led_point(shape: str, size: float, theta_deg: float) -> tuple[float, float]:
    """キャビティ中心から (sinθ, −cosθ) に伸ばした半直線と壁の交点（断面 x, z）."""
    t = np.radians(theta_deg)
    d = np.array([np.sin(t), -np.cos(t)])
    if shape == "sphere":
        cz = center_z(size, maps.sweep.SPHERE_CAVITY)
        return float(size * d[0]), float(cz + size * d[1])
    h = size / 2
    cz = -h
    # 正方形の壁との交点（底面 or 側壁）
    ts = []
    if d[1] < 0:
        ts.append(h / -d[1])  # 底面 z=-size
    if d[0] > 0:
        ts.append(h / d[0])  # 側壁 x=h
    s = min(ts)
    return float(s * d[0]), float(cz + s * d[1])


def draw_geometry(out: Path) -> None:
    """LED 位置 θ の定義を球と立方体の断面で示す."""
    r_sphere, l_cube = maps.SIZE_PAIRS[REP_PAIR]
    fig, axes = plt.subplots(1, 2, figsize=(9.5, 4.6))
    specs = [("sphere", r_sphere, "Sphere φ340", sphere_outline),
             ("cube", l_cube, "Cube L=234", cube_outline)]
    for ax, (shape, size, title, outline) in zip(axes, specs, strict=True):
        x, z, cz = outline(size)
        ax.plot(x, z, color=COLOR_WALL, lw=2)
        ax.plot([-PLATE_HALF, PLATE_HALF], [0, 0], color=COLOR_PLATE, lw=6, solid_capstyle="butt")
        ax.text(0, -10, "Evaluation plate", ha="center", va="top", fontsize=10)
        ax.plot(0, cz, marker="+", color="k", ms=10, mew=1.5)
        ax.text(-6, cz + 4, "center", fontsize=9, va="bottom", ha="right")
        for th in THETAS:
            px, pz = led_point(shape, size, th)
            ax.plot([0, px], [cz, pz], color="0.6", lw=0.8, ls=":")
            # LED の向き（板中心へ）
            vx, vz = -px, -4.5 - pz
            n = np.hypot(vx, vz)
            ax.annotate("", xy=(px + vx / n * 30, pz + vz / n * 30), xytext=(px, pz),
                        arrowprops={"arrowstyle": "-|>", "color": COLOR_LED, "lw": 1.5})
            ax.plot(px, pz, marker="^", color=COLOR_LED, ms=9, mec="k", mew=0.5)
            # バッフル: LED の視線上（板側）に置いた小円板を，視線に直交する短い線で表す
            ux, uz = vx / n, vz / n
            bx, bz = px + ux * BAFFLE_DIST, pz + uz * BAFFLE_DIST
            ax.plot([bx - uz * BAFFLE_HALF, bx + uz * BAFFLE_HALF],
                    [bz + ux * BAFFLE_HALF, bz - ux * BAFFLE_HALF], color="k", lw=2.5,
                    solid_capstyle="butt", zorder=5)
            if th == 90.0:
                ax.text(bx - 6, bz + 14, "Baffle", fontsize=9, ha="right", va="bottom")
            on_floor = shape == "cube" and th < 45
            if on_floor:
                ax.text(px, pz - 14, f"θ={th:g}°", fontsize=10, ha="center", va="top")
            else:
                off = (12, -4) if th < 100 else (10, 8)
                ax.text(px + off[0], pz + off[1], f"θ={th:g}°", fontsize=10, ha="left",
                        va="center")
        # θ の弧（中心のまわり，0° から 120°）
        arc = np.linspace(0, np.radians(120), 60)
        ax.plot(35 * np.sin(arc), cz - 35 * np.cos(arc), color="0.3", lw=1)
        ax.text(38 * np.sin(np.radians(60)) + 3, cz - 38 * np.cos(np.radians(60)), "θ",
                fontsize=12, ha="left", va="center")
        ax.set_title(title, fontsize=13, pad=14)
        ax.set_aspect("equal")
        ax.axis("off")
        pad = 0.15 * size
        ax.set_xlim(-size - pad if shape == "sphere" else -size / 2 - pad,
                    size + pad + 40 if shape == "sphere" else size / 2 + pad + 40)
        ax.set_ylim((cz - size if shape == "sphere" else -size) - 28, 25)
    fig.tight_layout()
    fig.savefig(out, dpi=200, facecolor="white")
    plt.close(fig)
    print(f"保存: {out}")


# --------------------------------------------------------------------------- マップパネル
def map_grid(cells: list[list[tuple[str, str]]], row_labels: list[str], out: Path,
             material: str, label_fs: float = 12) -> None:
    """cells[row][col] = (ラベル, 条件名) のマップ格子を描く（スライド体裁）."""
    n_rows, n_cols = len(cells), len(cells[0])
    fig, axes = plt.subplots(n_rows, n_cols, figsize=(2.55 * n_cols + 1.4, 2.75 * n_rows))
    axes = np.atleast_2d(axes)
    im = None
    for r, row in enumerate(cells):
        for c, (label, name) in enumerate(row):
            ax = axes[r, c]
            data = maps.load_map(name, material)
            if data is None:
                ax.axis("off")
                ax.set_title(f"{label}\n(missing)", fontsize=9)
                continue
            im = ax.imshow(data, cmap=maps.CMAP, vmin=VMIN, vmax=VMAX)
            ratio = maps.min_ratio(name, material)
            ax.set_xlabel(f"{label} (min={ratio:.3f})", fontsize=label_fs, labelpad=5)
            ax.set_xticks([])
            ax.set_yticks([])
            for spine in ax.spines.values():
                spine.set_visible(False)
        axes[r, 0].set_ylabel(row_labels[r], fontsize=13, labelpad=8)
    fig.tight_layout(rect=(0, 0, 0.9, 1.0), h_pad=1.2, w_pad=0.8)
    cbar_ax = fig.add_axes((0.905, 0.25, 0.015, 0.5))
    cbar = fig.colorbar(im, cax=cbar_ax, format="%.2f")
    cbar.set_label("Luminance\n(normalized by max of each)", fontsize=11)
    cbar.ax.tick_params(labelsize=10)
    fig.savefig(out, dpi=200, facecolor="white")
    plt.close(fig)
    print(f"保存: {out}")


def draw_position_maps(out: Path, material: str) -> None:
    """θ=0〜120° の輝度マップ（球 φ340 / 立方体 L=234）."""
    r_sphere, l_cube = maps.SIZE_PAIRS[REP_PAIR]
    cells = [[(f"θ={th:g}°", maps.cond_name(shape, size, th, "board", 1)) for th in THETAS]
             for shape, size in (("sphere", r_sphere), ("cube", l_cube))]
    map_grid(cells, ["Sphere φ340", "Cube L=234"], out, material)


def draw_count_maps(out: Path, material: str) -> None:
    """灯数 N=1〜4（θ=90°）の輝度マップ + 軸上 1 灯（球 φ340 / 立方体 L=234）."""
    r_sphere, l_cube = maps.SIZE_PAIRS[REP_PAIR]
    cells = []
    for shape, size in (("sphere", r_sphere), ("cube", l_cube)):
        row = [("On-axis, N=1", maps.cond_name(shape, size, 0.0, "board", 1))]
        row += [(f"θ=90°, N={n}", maps.cond_name(shape, size, 90.0, "board", n)) for n in COUNTS]
        cells.append(row)
    map_grid(cells, ["Sphere φ340", "Cube L=234"], out, material, label_fs=11)


# --------------------------------------------------------------------------- 曲線
def load_analysis(material: str) -> pd.DataFrame:
    """analysis.csv を読む."""
    return pd.read_csv(maps.output_root(material) / "analysis.csv")


def pair_label(shape: str, size: float) -> str:
    """凡例のラベル."""
    return PAIR_LABELS[shape][float(size)]


def draw_position_curves(out: Path, material: str) -> None:
    """構造 CoV と min の θ 依存（4 ペア・球は実線・立方体は破線）."""
    df = load_analysis(material)
    sel = df[(df["group"] == "position") & (df["aim"] == "board") & (df["n_led"] == 1)
             & (df["rep"] == 1) & (df["azimuth_deg"] == 0)]
    fig, axes = plt.subplots(1, 2, figsize=(11, 4.3))
    for pair, (r_sphere, l_cube) in enumerate(maps.SIZE_PAIRS):
        for shape, size in (("sphere", r_sphere), ("cube", l_cube)):
            d = sel[(sel["shape"] == shape) & (sel["size_mm"] == size)].sort_values("theta_deg")
            if d.empty:
                continue
            ls, mk = SHAPE_STYLE[shape]
            kw = {"color": PAIR_COLORS[pair], "ls": ls, "marker": mk, "ms": 5, "lw": 1.6,
                  "label": pair_label(shape, size)}
            axes[0].plot(d["theta_deg"], d["structure_cov"], **kw)
            axes[1].plot(d["theta_deg"], d["fullimage_min_max"], **kw)
    axes[0].set_ylabel("Structural CoV (lower = more uniform)", fontsize=11)
    axes[1].set_ylabel("min (max = 1, higher = more uniform)", fontsize=11)
    axes[0].set_ylim(bottom=0)
    for ax in axes:
        ax.set_xlabel("LED polar angle θ [deg]", fontsize=11)
        ax.set_xticks(range(0, 121, 30))
        ax.grid(alpha=0.3)
    axes[0].legend(fontsize=8, ncol=2, title="solid: sphere, dashed: cube", title_fontsize=8)
    fig.suptitle("θ = 0: bottom center (on-axis),  90: side wall,  120: just below the plate",
                 fontsize=10, y=0.995)
    fig.tight_layout()
    fig.savefig(out, dpi=200, facecolor="white")
    plt.close(fig)
    print(f"保存: {out}")


def draw_count_curves(out: Path, material: str) -> None:
    """構造 CoV と min の N 依存（θ=90°・板向き・総光束一定）."""
    df = load_analysis(material)
    sel = df[(df["theta_deg"] == 90.0) & (df["aim"] == "board") & (df["rep"] == 1)
             & (df["azimuth_deg"] == 0) & (df["n_led"].isin(COUNTS))]
    fig, axes = plt.subplots(1, 2, figsize=(11, 4.3))
    for pair, (r_sphere, l_cube) in enumerate(maps.SIZE_PAIRS):
        for shape, size in (("sphere", r_sphere), ("cube", l_cube)):
            d = sel[(sel["shape"] == shape) & (sel["size_mm"] == size)].sort_values("n_led")
            if len(d) < 2:
                continue
            ls, mk = SHAPE_STYLE[shape]
            kw = {"color": PAIR_COLORS[pair], "ls": ls, "marker": mk, "ms": 5, "lw": 1.6,
                  "label": pair_label(shape, size)}
            axes[0].plot(d["n_led"], d["structure_cov"], **kw)
            axes[1].plot(d["n_led"], d["fullimage_min_max"], **kw)
    axes[0].set_ylabel("Structural CoV (lower = more uniform)", fontsize=11)
    axes[1].set_ylabel("min (max = 1, higher = more uniform)", fontsize=11)
    axes[0].set_ylim(bottom=0)
    for ax in axes:
        ax.set_xlabel("Number of LEDs N  (θ=90°, equally spaced, total flux constant)",
                      fontsize=10)
        ax.set_xticks(COUNTS)
        ax.grid(alpha=0.3)
    axes[0].legend(fontsize=8, ncol=2, title="solid: sphere, dashed: cube", title_fontsize=8)
    fig.tight_layout()
    fig.savefig(out, dpi=200, facecolor="white")
    plt.close(fig)
    print(f"保存: {out}")


def main() -> int:
    """5 枚の図を出力する."""
    parser = argparse.ArgumentParser(description="TEST_18 のスライド用図（英語）")
    parser.add_argument("--material", choices=("legacy", "calibrated"), default="legacy")
    parser.add_argument("--output", type=Path, default=None)
    args = parser.parse_args()
    out_dir = args.output or maps.output_root(args.material) / "slides"
    out_dir.mkdir(parents=True, exist_ok=True)
    draw_geometry(out_dir / "slide_led_geometry.png")
    draw_position_maps(out_dir / "slide_led_position_maps.png", args.material)
    draw_position_curves(out_dir / "slide_led_position_curves.png", args.material)
    draw_count_maps(out_dir / "slide_led_count_maps.png", args.material)
    draw_count_curves(out_dir / "slide_led_count_curves.png", args.material)
    return 0


if __name__ == "__main__":
    sys.exit(main())
