"""SmTIAS 照明均一性評価 結果ビューア.

使い方:
    streamlit run scripts/viewer.py
"""

import json
import sys
from pathlib import Path

import matplotlib.pyplot as plt
import numpy
import pandas
import streamlit as st
from PIL import Image, ImageDraw


def _find_project_root() -> Path:
    """CLAUDE.md の存在でプロジェクトルートを特定する．

    Returns:
        プロジェクトルートの Path．

    Raises:
        FileNotFoundError: CLAUDE.md が見つからない場合．
    """
    p = Path(__file__).resolve().parent
    for parent in [p] + list(p.parents):
        if (parent / "CLAUDE.md").exists():
            return parent
    raise FileNotFoundError("CLAUDE.md not found — プロジェクトルートを特定できません")


PROJECT_ROOT = _find_project_root()
sys.path.insert(0, str(PROJECT_ROOT))

RESULTS_DIR = PROJECT_ROOT / "output" / "results"
FIGURES_DIR = PROJECT_ROOT / "output" / "figures"
DATA_DIR = PROJECT_ROOT / "data" / "smtias" / "whiteboard"
ROI_CONFIG = PROJECT_ROOT / "config" / "roi_config.json"
SUMMARY_CSV = RESULTS_DIR / "summary_uniformity.csv"
SPATIAL_CSV = RESULTS_DIR / "summary_spatial.csv"


def load_roi() -> dict | None:
    """ROI 設定を読み込む．"""
    if not ROI_CONFIG.exists():
        return None
    with open(ROI_CONFIG, encoding="utf-8") as f:
        config = json.load(f)
    return config.get("smtias", {}).get("whiteboard")


def overlay_roi(image_path: Path, roi: dict) -> Image.Image:
    """元画像に ROI 矩形をオーバーレイした画像を返す．"""
    img = Image.open(image_path).copy()
    draw = ImageDraw.Draw(img)
    x, y, w, h = roi["x"], roi["y"], roi["width"], roi["height"]
    draw.rectangle([x, y, x + w, y + h], outline="lime", width=6)
    return img


def load_summary() -> pandas.DataFrame:
    """サマリー CSV を読み込む．

    Returns:
        均一性指標の DataFrame．

    Raises:
        FileNotFoundError: summary_uniformity.csv が存在しない場合．
    """
    if not SUMMARY_CSV.exists():
        raise FileNotFoundError(
            f"サマリーファイルが見つかりません: {SUMMARY_CSV}\n"
            "先に run_uniformity.py でバッチ解析を実行してください．"
        )
    return pandas.read_csv(SUMMARY_CSV)


def _build_stats_df(
    df: pandas.DataFrame, cols: list[str]
) -> pandas.DataFrame:
    """指定カラムの列間統計サマリー DataFrame を生成する．

    Args:
        df: 元の DataFrame．
        cols: 統計を集計する列名リスト．

    Returns:
        各列の mean/std/cv/min/max/range を行とした DataFrame．
        index.name は「指標」に設定される．
    """
    stats_data = {}
    for col in cols:
        values = df[col].values
        col_mean = numpy.mean(values)
        col_std = numpy.std(values, ddof=0)
        stats_data[col] = {
            "平均 (Mean)": f"{col_mean:.4f}",
            "標準偏差 (SD)": f"{col_std:.4f}",
            "変動係数 (CV)": (
                f"{col_std / col_mean:.6f}" if col_mean != 0 else "N/A"
            ),
            "最小 (Min)": f"{numpy.min(values):.4f}",
            "最大 (Max)": f"{numpy.max(values):.4f}",
            "範囲 (Range)": f"{numpy.max(values) - numpy.min(values):.4f}",
        }
    result = pandas.DataFrame(stats_data).T
    result.index.name = "指標"
    return result


def _render_bar_chart(
    data_df: pandas.DataFrame,
    image_names: list[str],
    metrics: list[tuple[str, str, str, str]],
) -> None:
    """2x2 の棒グラフを描画して Streamlit に表示する．

    Args:
        data_df: 各指標の値を持つ DataFrame．
        image_names: X 軸に表示する画像名リスト．
        metrics: (列名, タイトル, Y 軸ラベル, 色) のリスト（4 要素）．
    """
    fig, axes = plt.subplots(2, 2, figsize=(14, 9))
    # 長い名前を短縮してグラフを読みやすくする
    short_names = [name[-8:] for name in image_names]

    for ax, (col, title, ylabel, color) in zip(axes.ravel(), metrics):
        ax.bar(range(len(image_names)), data_df[col], color=color, edgecolor="none")
        ax.set_title(title)
        ax.set_xlabel("Image")
        ax.set_ylabel(ylabel)
        ax.set_xticks(range(len(image_names)))
        ax.set_xticklabels(short_names, rotation=45, ha="right", fontsize=8)

    fig.tight_layout()
    st.pyplot(fig)
    plt.close(fig)


def render_tab_overview(
    df: pandas.DataFrame, spatial_df: pandas.DataFrame | None
) -> None:
    """タブ1「全体比較」を描画する．

    Args:
        df: サマリー DataFrame．
        spatial_df: 空間分析サマリー DataFrame．存在しない場合は None．
    """
    st.header("全体均一性 (Overall Uniformity)")

    # 指標テーブル
    st.subheader("指標テーブル")
    display_df = df.copy()
    # 小数点以下4桁の指標と2桁の指標を一括フォーマットする
    fmt_4f = {"cov": "{:.4f}", "max_min_ratio": "{:.4f}"}
    fmt_2f = {"std": "{:.2f}", "mean": "{:.2f}", "max": "{:.2f}", "min": "{:.2f}"}
    for col, fmt in {**fmt_4f, **fmt_2f}.items():
        display_df[col] = display_df[col].map(fmt.format)
    st.dataframe(display_df, use_container_width=True)

    # 統計サマリー
    st.subheader("統計サマリー")
    numeric_cols = ["mean", "std", "cov", "max_min_ratio", "max", "min"]
    st.dataframe(_build_stats_df(df, numeric_cols), use_container_width=True)
    st.caption("※ 変動係数 (CV) が小さいほど画像間の再現性が高い")

    # 比較グラフ（2x2 棒グラフ）
    st.subheader("指標比較グラフ")
    image_names = df["image_name"].tolist()
    metrics = [
        ("cov", "CoV (Coefficient of Variation)", "CoV", "steelblue"),
        ("std", "Std (Standard Deviation)", "Std", "coral"),
        ("max_min_ratio", "Max/Min Ratio", "Max/Min Ratio", "mediumseagreen"),
        ("mean", "Mean Luminance", "Luminance", "mediumpurple"),
    ]
    _render_bar_chart(df, image_names, metrics)

    # 空間分析セクション
    if spatial_df is not None:
        st.header("空間分析 (Spatial Analysis)")

        # 空間分析指標テーブル
        st.subheader("指標テーブル")
        spatial_display_df = spatial_df[
            [
                "image_name",
                "center_mean",
                "middle_mean",
                "periphery_mean",
                "center_periphery_ratio",
                "gradient_magnitude",
            ]
        ].copy()
        spatial_fmt_2f = {
            "center_mean": "{:.2f}",
            "middle_mean": "{:.2f}",
            "periphery_mean": "{:.2f}",
            "gradient_magnitude": "{:.2f}",
        }
        spatial_fmt_4f = {"center_periphery_ratio": "{:.4f}"}
        for col, fmt in {**spatial_fmt_2f, **spatial_fmt_4f}.items():
            spatial_display_df[col] = spatial_display_df[col].map(fmt.format)
        st.dataframe(spatial_display_df, use_container_width=True)

        # 空間分析 統計サマリー
        st.subheader("統計サマリー")
        spatial_cols = [
            "center_mean",
            "middle_mean",
            "periphery_mean",
            "center_periphery_ratio",
            "gradient_magnitude",
        ]
        st.dataframe(
            _build_stats_df(spatial_df, spatial_cols), use_container_width=True
        )
        st.caption("※ 変動係数 (CV) が小さいほど画像間の再現性が高い")

        # 空間分析 比較グラフ（2x2 棒グラフ）
        st.subheader("指標比較グラフ")
        spatial_metrics = [
            ("center_periphery_ratio", "Center/Periphery Ratio", "Ratio", "steelblue"),
            ("gradient_magnitude", "Gradient Magnitude", "Gradient", "coral"),
            ("center_mean", "Center Mean Luminance", "Luminance", "mediumseagreen"),
            ("periphery_mean", "Periphery Mean Luminance", "Luminance", "mediumpurple"),
        ]
        spatial_image_names = spatial_df["image_name"].tolist()
        _render_bar_chart(spatial_df, spatial_image_names, spatial_metrics)

    # SSIM 再現性評価
    ssim_csv = RESULTS_DIR / "summary_ssim.csv"
    if ssim_csv.exists():
        st.header("再現性評価 (Reproducibility)")
        ssim_stats = pandas.read_csv(ssim_csv, nrows=1)

        # 位置合わせ後の列が存在するか判定
        has_registered = (
            "mean_ssim_registered" in ssim_stats.columns
            and pandas.notna(ssim_stats["mean_ssim_registered"].iloc[0])
            and ssim_stats["mean_ssim_registered"].iloc[0] != ""
        )

        # テーブルデータを構築
        rows: list[dict] = [
            {
                "統計量": "平均",
                "SSIM（位置合わせなし）": f"{ssim_stats['mean_ssim'].iloc[0]:.6f}",
            },
            {
                "統計量": "最小",
                "SSIM（位置合わせなし）": f"{ssim_stats['min_ssim'].iloc[0]:.6f}",
            },
            {
                "統計量": "最大",
                "SSIM（位置合わせなし）": f"{ssim_stats['max_ssim'].iloc[0]:.6f}",
            },
            {
                "統計量": "標準偏差",
                "SSIM（位置合わせなし）": f"{ssim_stats['std_ssim'].iloc[0]:.6f}",
            },
        ]

        if has_registered:
            for i, key in enumerate(
                [
                    "mean_ssim_registered",
                    "min_ssim_registered",
                    "max_ssim_registered",
                    "std_ssim_registered",
                ]
            ):
                rows[i]["SSIM（位置合わせ後）"] = (
                    f"{float(ssim_stats[key].iloc[0]):.6f}"
                )

            # シフト量の行を追加
            rows.append(
                {
                    "統計量": "平均シフト量 (px)",
                    "SSIM（位置合わせなし）": "—",
                    "SSIM（位置合わせ後）": f"{float(ssim_stats['mean_shift_magnitude'].iloc[0]):.4f}",
                }
            )
            rows.append(
                {
                    "統計量": "最大シフト量 (px)",
                    "SSIM（位置合わせなし）": "—",
                    "SSIM（位置合わせ後）": f"{float(ssim_stats['max_shift_magnitude'].iloc[0]):.4f}",
                }
            )

        ssim_table = pandas.DataFrame(rows)
        st.dataframe(ssim_table, use_container_width=True, hide_index=True)
        st.caption("※ SSIM は 1.0 に近いほど画像間の構造的類似度が高く，再現性が良好")


def render_tab_individual(
    df: pandas.DataFrame, spatial_df: pandas.DataFrame | None
) -> None:
    """タブ2「個別画像」を描画する．

    Args:
        df: サマリー DataFrame．
        spatial_df: 空間分析サマリー DataFrame．存在しない場合は None．
    """
    st.header("個別画像")

    image_names = df["image_name"].tolist()

    # session_state でインデックスを管理
    if "individual_idx" not in st.session_state:
        st.session_state.individual_idx = 0

    def _go_prev() -> None:
        new_idx = max(0, st.session_state.individual_idx - 1)
        st.session_state.individual_idx = new_idx
        st.session_state.individual_select = image_names[new_idx]

    def _go_next() -> None:
        new_idx = min(len(image_names) - 1, st.session_state.individual_idx + 1)
        st.session_state.individual_idx = new_idx
        st.session_state.individual_select = image_names[new_idx]

    def _on_select() -> None:
        st.session_state.individual_idx = image_names.index(
            st.session_state.individual_select
        )

    # ナビゲーションボタン
    btn_prev, btn_next, _ = st.columns([1, 1, 8])
    with btn_prev:
        st.button(
            "← 前へ",
            on_click=_go_prev,
            disabled=st.session_state.individual_idx <= 0,
        )
    with btn_next:
        st.button(
            "次へ →",
            on_click=_go_next,
            disabled=st.session_state.individual_idx >= len(image_names) - 1,
        )

    # ドロップダウン（ボタン操作と同期）
    selected = st.selectbox(
        "画像を選択",
        image_names,
        index=st.session_state.individual_idx,
        key="individual_select",
        on_change=_on_select,
    )

    # 選択行の指標
    row = df[df["image_name"] == selected].iloc[0]

    st.subheader("均一性指標")
    col1, col2, col3, col4 = st.columns(4)
    col1.metric("平均輝度", f"{row['mean']:.2f}")
    col2.metric("標準偏差", f"{row['std']:.2f}")
    col3.metric("CoV", f"{row['cov']:.4f}")
    col4.metric("最大/最小比", f"{row['max_min_ratio']:.4f}")

    # 空間分析 metric カード
    if spatial_df is not None:
        spatial_rows = spatial_df[spatial_df["image_name"] == selected]
        if not spatial_rows.empty:
            spatial_row = spatial_rows.iloc[0]
            st.subheader("空間分析指標")
            scol1, scol2, scol3, scol4 = st.columns(4)
            scol1.metric("中心平均", f"{spatial_row['center_mean']:.2f}")
            scol2.metric("周辺平均", f"{spatial_row['periphery_mean']:.2f}")
            scol3.metric("中心/周辺比", f"{spatial_row['center_periphery_ratio']:.4f}")
            scol4.metric("勾配量(%)", f"{spatial_row['gradient_magnitude']:.2f}")

    # 元画像・輝度マップ・ヒストグラムを横並びで表示
    original_path = DATA_DIR / f"{selected}.png"
    luminance_map_path = FIGURES_DIR / f"{selected}_luminance_map.png"
    histogram_path = FIGURES_DIR / f"{selected}_histogram.png"

    st.subheader("元画像 / 輝度マップ / ヒストグラム")
    orig_col, map_col, hist_col = st.columns([1, 2, 2])

    with orig_col:
        st.caption("元画像（ROI 表示）")
        if original_path.exists():
            roi = load_roi()
            if roi:
                st.image(overlay_roi(original_path, roi), use_container_width=True)
            else:
                st.image(str(original_path), use_container_width=True)
        else:
            st.warning(f"元画像が見つかりません: {original_path.name}")

    with map_col:
        st.caption("輝度マップ")
        if luminance_map_path.exists():
            st.image(str(luminance_map_path), use_container_width=True)
        else:
            st.warning(f"輝度マップが見つかりません: {luminance_map_path.name}")

    with hist_col:
        st.caption("輝度ヒストグラム")
        if histogram_path.exists():
            st.image(str(histogram_path), use_container_width=True)
        else:
            st.warning(f"ヒストグラムが見つかりません: {histogram_path.name}")

    # 放射状プロファイル・ゾーンマップ
    st.subheader("放射状プロファイル / ゾーンマップ")
    radial_profile_path = FIGURES_DIR / f"{selected}_radial_profile.png"
    zone_map_path = FIGURES_DIR / f"{selected}_zone_map.png"
    profile_col, zone_col = st.columns(2)

    with profile_col:
        st.caption("放射状プロファイル")
        if radial_profile_path.exists():
            st.image(str(radial_profile_path), use_container_width=True)
        else:
            st.warning(
                f"放射状プロファイルが見つかりません: {radial_profile_path.name}"
            )

    with zone_col:
        st.caption("ゾーンマップ")
        if zone_map_path.exists():
            st.image(str(zone_map_path), use_container_width=True)
        else:
            st.warning(f"ゾーンマップが見つかりません: {zone_map_path.name}")


def main() -> None:
    """Streamlit アプリのエントリーポイント."""
    st.set_page_config(
        page_title="SmTIAS 照明均一性評価ビューア",
        page_icon=None,
        layout="wide",
    )
    st.title("SmTIAS 照明均一性評価ビューア")

    # サマリー CSV の読み込み
    try:
        df = load_summary()
    except FileNotFoundError as e:
        st.error(str(e))
        return

    # 空間分析 CSV の読み込み（任意）
    spatial_df = None
    if SPATIAL_CSV.exists():
        spatial_df = pandas.read_csv(SPATIAL_CSV)

    tab_overview, tab_individual = st.tabs(["全体比較", "個別画像"])

    with tab_overview:
        render_tab_overview(df, spatial_df)

    with tab_individual:
        render_tab_individual(df, spatial_df)


if __name__ == "__main__":
    main()
