"""
GlucoClampTracer (글루코클램프트레이서) — Streamlit MVP
========================================================

하이퍼인슐린혈성-정상혈당 클램프(hyperinsulinemic-euglycemic clamp) 실험으로부터
in vivo 인슐린 감수성을 정량하는 단독 실행형 도구.

도메인: DM(당뇨병)  ·  카테고리: 동물실험 도구

실행:  streamlit run app.py
의존성: streamlit, numpy, scipy, pandas, statsmodels(선택), matplotlib

⚠ 참고용 · 연구용(for reference / research use only).
   임상 진단/치료 목적으로 사용하지 마십시오.
"""

from __future__ import annotations

import io
import os

import numpy as np
import pandas as pd
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import streamlit as st

import clamp_core as cc

APP_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(APP_DIR, "data")

st.set_page_config(
    page_title="GlucoClampTracer",
    page_icon="🩸",
    layout="wide",
)

DISCLAIMER = (
    "⚠ **참고용 · 연구용 도구입니다 (for reference / research use only).** "
    "본 도구의 계산 결과는 동물실험 데이터 분석 보조용이며, "
    "임상 진단·치료 목적으로 사용할 수 없습니다."
)


# ---------------------------------------------------------------------
# 데이터 로딩 유틸
# ---------------------------------------------------------------------
@st.cache_data
def load_demo_data():
    """data/ 폴더의 합성 샘플 CSV 3종을 로드한다."""
    ts = pd.read_csv(os.path.join(DATA_DIR, "clamp_timeseries.csv"))
    tr = pd.read_csv(os.path.join(DATA_DIR, "tracer_enrichment.csv"))
    tg = pd.read_csv(os.path.join(DATA_DIR, "tissue_2dg.csv"))
    return ts, tr, tg


def _read_csv(uploaded):
    if uploaded is None:
        return None
    return pd.read_csv(uploaded)


# ---------------------------------------------------------------------
# 사이드바 — 데이터 소스 + 전역 파라미터
# ---------------------------------------------------------------------
st.sidebar.title("🩸 GlucoClampTracer")
st.sidebar.caption("클램프 인슐린 감수성 정량 — DM 동물실험 도구")

data_mode = st.sidebar.radio(
    "데이터 소스",
    ["데모 데이터 로드", "CSV 업로드"],
    help="오프라인 동작 — 업로드가 없으면 합성 데모 데이터를 사용합니다.",
)

ts_df = tr_df = tg_df = None
if data_mode == "데모 데이터 로드":
    try:
        ts_df, tr_df, tg_df = load_demo_data()
        st.sidebar.success("합성 데모 데이터 로드 완료 (18마리, 3그룹)")
    except FileNotFoundError:
        st.sidebar.error(
            "data/ 폴더에 샘플 CSV가 없습니다. "
            "먼저 `python3 generate_sample_data.py` 를 실행하세요."
        )
else:
    up_ts = st.sidebar.file_uploader("① 클램프 시계열 CSV", type="csv")
    up_tr = st.sidebar.file_uploader("② 트레이서 enrichment CSV", type="csv")
    up_tg = st.sidebar.file_uploader("③ 조직 2-DG CSV", type="csv")
    ts_df = _read_csv(up_ts)
    tr_df = _read_csv(up_tr)
    tg_df = _read_csv(up_tg)
    if ts_df is None:
        st.sidebar.info("CSV 미업로드 시 데모 데이터로 대체됩니다.")
        try:
            ts_df, tr_df, tg_df = load_demo_data()
        except FileNotFoundError:
            pass

st.sidebar.markdown("---")
st.sidebar.subheader("정상상태 탐지 기준")
target_glucose = st.sidebar.number_input("목표 혈당 (mg/dL)", 80.0, 250.0, 150.0, 5.0)
target_tol = st.sidebar.number_input("허용 편차 (± mg/dL)", 5.0, 50.0, 20.0, 5.0)
max_cv = st.sidebar.number_input("최대 혈당 CV (%)", 2.0, 25.0, 10.0, 1.0)
min_window = st.sidebar.number_input("최소 윈도우 길이 (분)", 10.0, 90.0, 30.0, 10.0)

st.sidebar.markdown("---")
st.sidebar.subheader("트레이서 / 동역학")
tracer_type = st.sidebar.selectbox(
    "트레이서 종류",
    ["[3-3H]glucose (방사성, dpm)", "[6,6-2H2]glucose (안정동위원소, MPE%)"],
)
steele_mode = st.sidebar.selectbox(
    "Steele 방정식", ["steady (정상상태)", "non-steady (비정상상태)"]
)
apply_hot_ginf = st.sidebar.checkbox("Hot-GINF 보정 적용", value=False)


# ---------------------------------------------------------------------
# 핵심 계산 — 동물별 클램프 지표 집계
# ---------------------------------------------------------------------
def compute_animal_metrics(ts_df, tr_df):
    """모든 동물에 대해 정상상태 GIR + 트레이서 동역학을 계산한다."""
    use_3h = tracer_type.startswith("[3-3H]")
    mode = "steady" if steele_mode.startswith("steady") else "non-steady"
    records = []
    for aid, sub in ts_df.groupby("animal_id"):
        sub = sub.sort_values("time_min")
        clamp = sub[sub["time_min"] >= 0]
        ss = cc.detect_steady_state(
            clamp["time_min"], clamp["glucose_mg_dL"],
            target_glucose, target_tol, max_cv, min_window,
        )
        bw = float(sub["body_weight_g"].iloc[0])
        lean = float(sub["lean_mass_g"].iloc[0]) if "lean_mass_g" in sub else None
        group = str(sub["group"].iloc[0])
        model = str(sub["model"].iloc[0]) if "model" in sub else None

        rec = {
            "animal_id": aid, "group": group, "model": model,
            "body_weight_g": bw, "lean_mass_g": lean,
            "ss_found": ss["found"],
            "ss_start_min": ss["start_min"], "ss_end_min": ss["end_min"],
            "glucose_cv_pct": ss["cv_pct"],
            "mean_glucose": ss["mean_glucose"],
        }

        if ss["found"]:
            gir_raw = cc.mean_gir_in_window(
                clamp["time_min"], clamp["gir_mL_per_h"],
                ss["start_min"], ss["end_min"],
            )
            gir_pk = cc.gir_to_per_kg(gir_raw, "mL/h", bw)
            gir_lean = cc.normalize_by_mass(gir_pk, bw, lean)
        else:
            gir_pk = gir_lean = float("nan")
        rec["gir_mgkgmin"] = gir_pk
        rec["gir_mgkgmin_lean"] = gir_lean

        # 트레이서 동역학
        if tr_df is not None and aid in set(tr_df["animal_id"]):
            trd = tr_df[tr_df["animal_id"] == aid]
            basal = trd[trd["state"] == "basal"]
            clampr = trd[trd["state"] == "clamp"]
            if len(basal) and len(clampr):
                tir = float(clampr["tracer_3h_infusion_dpm_min"].iloc[0])
                if use_3h:
                    e_b = float(basal["plasma_3h_sa_dpm_per_mg"].iloc[0])
                    e_c = float(clampr["plasma_3h_sa_dpm_per_mg"].iloc[0])
                else:
                    e_b = float(basal["plasma_2h2_enrichment_pct"].iloc[0])
                    e_c = float(clampr["plasma_2h2_enrichment_pct"].iloc[0])
                hot = (float(clampr["hot_ginf_dpm_min"].iloc[0])
                       if apply_hot_ginf and "hot_ginf_dpm_min" in clampr else 0.0)
                kin = cc.compute_kinetics(
                    tir, e_b, e_c,
                    gir_pk if not np.isnan(gir_pk) else 0.0,
                    mode=mode, hot_ginf_rate=hot,
                )
                rec.update({
                    "ra_basal": kin["ra_basal"],
                    "ra_clamp": kin["ra_clamp"],
                    "hgp_basal": kin["hgp_basal"],
                    "hgp_clamp": kin["hgp_clamp"],
                    "rd_clamp": kin["rd_clamp"],
                    "hgp_suppression_pct": kin["hgp_suppression_pct"],
                })
                # 트레이서 CV (basal vs clamp enrichment 안정성 proxy)
                rec["tracer_cv_pct"] = float(
                    np.std([e_b, e_c], ddof=1) / np.mean([e_b, e_c]) * 100.0
                )
        records.append(rec)
    return pd.DataFrame(records)


metrics_df = None
if ts_df is not None:
    metrics_df = compute_animal_metrics(ts_df, tr_df)


# ---------------------------------------------------------------------
# 헤더
# ---------------------------------------------------------------------
st.title("GlucoClampTracer 🩸")
st.markdown(
    "**하이퍼인슐린혈성-정상혈당 클램프 인슐린 감수성 정량 도구** — "
    "DM(당뇨병) 도메인 · 동물실험 도구"
)
st.info(DISCLAIMER)

if ts_df is None:
    st.warning("데이터가 없습니다. 사이드바에서 데모 데이터를 로드하거나 CSV를 업로드하세요.")
    st.stop()

tab1, tab2, tab3, tab4, tab5 = st.tabs([
    "① 클램프 입력·정상상태",
    "② 트레이서 동역학",
    "③ 조직 2-DG 흡수",
    "④ 모델 참조·QC",
    "⑤ 코호트 통계·리포트",
])


# =====================================================================
# 탭 1 — 클램프 입력 + 정상상태 자동 선택
# =====================================================================
with tab1:
    st.header("① 클램프 원자료 입력 + 정상상태 자동 선택")
    st.caption(
        "펌프 GIR 로그 · 혈당 샘플 · 체중을 입력하면 사용자 정의 기준으로 "
        "정상상태 윈도우를 자동 탐지하고 체중/제지방량(lean mass)으로 정규화합니다."
    )

    animals = sorted(ts_df["animal_id"].unique())
    sel_animal = st.selectbox("동물 선택", animals, key="t1_animal")
    sub = ts_df[ts_df["animal_id"] == sel_animal].sort_values("time_min")
    clamp = sub[sub["time_min"] >= 0]

    ss = cc.detect_steady_state(
        clamp["time_min"], clamp["glucose_mg_dL"],
        target_glucose, target_tol, max_cv, min_window,
    )

    c1, c2 = st.columns([3, 2])
    with c1:
        fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(7, 5.2), sharex=True)
        ax1.plot(sub["time_min"], sub["glucose_mg_dL"], "o-", color="#c0392b")
        ax1.axhline(target_glucose, ls="--", color="gray", lw=1)
        ax1.axhspan(target_glucose - target_tol, target_glucose + target_tol,
                    color="#27ae60", alpha=0.10)
        if ss["found"]:
            ax1.axvspan(ss["start_min"], ss["end_min"], color="#2980b9", alpha=0.18,
                        label="steady-state window")
            ax1.legend(fontsize=8)
        ax1.set_ylabel("Glucose (mg/dL)")
        ax1.set_title(f"{sel_animal} - Glucose & GIR")

        ax2.plot(sub["time_min"], sub["gir_mL_per_h"], "s-", color="#2980b9")
        if ss["found"]:
            ax2.axvspan(ss["start_min"], ss["end_min"], color="#2980b9", alpha=0.18)
        ax2.set_ylabel("GIR (mL/h)")
        ax2.set_xlabel("Time (min, 0 = clamp start)")
        fig.tight_layout()
        st.pyplot(fig)
        plt.close(fig)

    with c2:
        if ss["found"]:
            st.success("정상상태 윈도우 탐지 성공")
            st.metric("윈도우", f"{ss['start_min']:.0f} – {ss['end_min']:.0f} 분")
            st.metric("평균 혈당", f"{ss['mean_glucose']:.1f} mg/dL")
            st.metric("혈당 CV", f"{ss['cv_pct']:.2f} %")
            st.metric("샘플 수", ss["n_points"])
        else:
            st.error("기준을 만족하는 정상상태 윈도우를 찾지 못했습니다.")
            st.caption("사이드바에서 CV 한계나 허용 편차를 완화해 보세요.")

        bw = float(sub["body_weight_g"].iloc[0])
        lean = float(sub["lean_mass_g"].iloc[0])
        st.markdown(f"**체중** {bw:.1f} g · **제지방량** {lean:.1f} g")

        if ss["found"]:
            gir_raw = cc.mean_gir_in_window(
                clamp["time_min"], clamp["gir_mL_per_h"],
                ss["start_min"], ss["end_min"])
            gir_pk = cc.gir_to_per_kg(gir_raw, "mL/h", bw)
            gir_lean = cc.normalize_by_mass(gir_pk, bw, lean)
            st.markdown("**정상상태 GIR**")
            st.write(f"- 원자료: {gir_raw:.3f} mL/h")
            st.write(f"- 체중 정규화: **{gir_pk:.1f} mg/kg/min**")
            st.write(f"- 제지방량 정규화: **{gir_lean:.1f} mg/kg-lean/min**")

    with st.expander("원자료 시계열 보기"):
        st.dataframe(sub, width="stretch")


# =====================================================================
# 탭 2 — 트레이서 동역학 엔진
# =====================================================================
with tab2:
    st.header("② 트레이서 동역학 엔진 (Steele 방정식)")
    st.caption(
        "[3-3H]glucose(섬광계수 dpm) 또는 [6,6-2H2]glucose(LC-MS/GC-MS enrichment) "
        "지원 · 정상상태/비정상상태 Steele 방정식 선택 · priming/hot-GINF 보정 · "
        "Ra·Rd·HGP·HGP 억제율 계산."
    )
    st.markdown(
        f"현재 설정 — 트레이서: **{tracer_type}** · 방정식: **{steele_mode}** · "
        f"Hot-GINF 보정: **{'적용' if apply_hot_ginf else '미적용'}**"
    )

    if tr_df is None:
        st.warning("트레이서 enrichment 데이터가 없습니다.")
    else:
        kin_cols = ["animal_id", "group", "model", "gir_mgkgmin",
                    "ra_basal", "ra_clamp", "hgp_basal", "hgp_clamp",
                    "rd_clamp", "hgp_suppression_pct"]
        avail = [c for c in kin_cols if c in metrics_df.columns]
        kdf = metrics_df[avail].copy()

        st.subheader("동물별 동역학 지표")
        st.dataframe(
            kdf.style.format({
                c: "{:.1f}" for c in avail
                if c not in ("animal_id", "group", "model")
            }),
            width="stretch",
        )

        st.subheader("정의")
        st.markdown(
            "- **Ra** 포도당 출현율 · **Rd** 포도당 소실율(전신 처리율)\n"
            "- **HGP_basal** = Ra_basal (외인성 포도당 없음)\n"
            "- **HGP_clamp** = Ra_clamp − GIR\n"
            "- **HGP 억제율(%)** = (HGP_basal − HGP_clamp) / HGP_basal × 100\n"
            "- 클램프 **Rd** = Ra_clamp = HGP_clamp + GIR"
        )

        if "hgp_suppression_pct" in kdf.columns:
            grp_means = kdf.groupby("group")[
                ["gir_mgkgmin", "rd_clamp", "hgp_suppression_pct"]
            ].mean()
            fig, ax = plt.subplots(figsize=(7, 3.6))
            grp_means.plot(kind="bar", ax=ax, color=["#2980b9", "#27ae60", "#e67e22"])
            ax.set_ylabel("Value")
            ax.set_title("Group mean - GIR / clamp Rd / HGP suppression")
            ax.legend(fontsize=8)
            fig.tight_layout()
            st.pyplot(fig)
            plt.close(fig)

        with st.expander("비정상상태 Steele 방정식 파라미터(참고)"):
            st.markdown(
                "비정상상태 모드 선택 시 pool fraction(기본 0.65)과 "
                "분포용적(기본 200 mL/kg)을 사용하며, 본 데모는 인접 "
                "basal/clamp 시점을 사용한 단순화 적용입니다. "
                "실제 분석에서는 dense 시계열 enrichment 자료를 권장합니다."
            )


# =====================================================================
# 탭 3 — 조직 특이적 2-DG 흡수
# =====================================================================
with tab3:
    st.header("③ 조직 특이적 2-deoxyglucose 흡수 (Rg')")
    st.caption(
        "혈장 [14C/3H]2-DG 소실곡선 AUC 보정 → 골격근(gastrocnemius/soleus/EDL/"
        "tibialis)·심장·BAT·WAT·간의 조직 특이적 포도당 대사지수 Rg' 계산."
    )

    if tg_df is None:
        st.warning("조직 2-DG 데이터가 없습니다.")
    else:
        rg_rows = []
        for aid, sub in tg_df.groupby("animal_id"):
            auc = float(sub["plasma_2dg_auc_dpm_min_per_mL"].iloc[0])
            mglc = float(sub["mean_plasma_glucose_mg_per_mL"].iloc[0])
            panel = cc.compute_tissue_panel(sub, auc, mglc)
            for _, r in panel.iterrows():
                rg_rows.append({
                    "animal_id": aid, "group": r["group"],
                    "model": r.get("model"), "tissue": r["tissue"],
                    "rg_prime": r["rg_prime"],
                })
        rg_df = pd.DataFrame(rg_rows)

        c1, c2 = st.columns([2, 3])
        with c1:
            sel_a = st.selectbox(
                "동물 선택", sorted(rg_df["animal_id"].unique()), key="t3_animal")
            one = rg_df[rg_df["animal_id"] == sel_a]
            st.dataframe(
                one[["tissue", "rg_prime"]].style.format({"rg_prime": "{:.4f}"}),
                width="stretch",
            )
        with c2:
            tissue_means = rg_df.groupby(["group", "tissue"])["rg_prime"].mean().unstack(0)
            fig, ax = plt.subplots(figsize=(7, 4))
            tissue_means.plot(kind="bar", ax=ax)
            ax.set_ylabel("Rg' (tissue glucose metabolic index)")
            ax.set_title("Group mean Rg' by tissue")
            ax.legend(fontsize=8, title="Group")
            fig.tight_layout()
            st.pyplot(fig)
            plt.close(fig)

        st.subheader("조직별 그룹 평균 Rg'")
        st.dataframe(
            tissue_means.style.format("{:.4f}"), width="stretch")

        st.markdown(
            "**Rg' 정의** — Rg' = [조직 2-DG-6-P (dpm/g)] / "
            "[혈장 2-DG AUC (dpm·min/mL)] × [평균 혈장 포도당 (mg/mL)]"
        )


# =====================================================================
# 탭 4 — 모델 참조 + QC 플래그
# =====================================================================
with tab4:
    st.header("④ 모델 참조 범위 + QC 플래그")
    st.caption(
        "C57BL/6·db/db·ob/ob·DIO·ZDF·GK 모델의 전형적 GIR/HGP 참조 범위 내장 · "
        "혈당 비수렴·트레이서 불안정·GINF 보정 누락 자동 경고."
    )

    st.subheader("내장 모델 참조 범위")
    ref_rows = []
    for model, d in cc.MODEL_REFERENCE.items():
        ref_rows.append({
            "model": model,
            "GIR (mg/kg/min)": f"{d['gir'][0]:.0f}–{d['gir'][1]:.0f}",
            "HGP 억제율 (%)": f"{d['hgp_suppression_pct'][0]:.0f}–{d['hgp_suppression_pct'][1]:.0f}",
            "클램프 Rd (mg/kg/min)": f"{d['rd_clamp'][0]:.0f}–{d['rd_clamp'][1]:.0f}",
            "설명": d["note"],
        })
    st.dataframe(pd.DataFrame(ref_rows), width="stretch")

    st.subheader("동물별 QC 점검")
    sel_a = st.selectbox(
        "동물 선택", sorted(metrics_df["animal_id"].unique()), key="t4_animal")
    row = metrics_df[metrics_df["animal_id"] == sel_a].iloc[0]

    flags = cc.qc_flags(
        glucose_cv_pct=row.get("glucose_cv_pct"),
        steady_state_found=bool(row.get("ss_found")),
        tracer_enrichment_cv_pct=row.get("tracer_cv_pct"),
        hot_ginf_applied=apply_hot_ginf,
        model=row.get("model"),
        gir=row.get("gir_mgkgmin"),
        hgp_suppression_pct=row.get("hgp_suppression_pct"),
        rd_clamp=row.get("rd_clamp"),
        max_glucose_cv=max_cv,
    )
    for f in flags:
        if f["level"] == "error":
            st.error(f["message"])
        elif f["level"] == "warning":
            st.warning(f["message"])
        else:
            st.info(f["message"])

    st.subheader("전체 코호트 QC 요약")
    qc_summary = []
    for _, r in metrics_df.iterrows():
        fl = cc.qc_flags(
            glucose_cv_pct=r.get("glucose_cv_pct"),
            steady_state_found=bool(r.get("ss_found")),
            tracer_enrichment_cv_pct=r.get("tracer_cv_pct"),
            hot_ginf_applied=apply_hot_ginf,
            model=r.get("model"),
            gir=r.get("gir_mgkgmin"),
            hgp_suppression_pct=r.get("hgp_suppression_pct"),
            rd_clamp=r.get("rd_clamp"),
            max_glucose_cv=max_cv,
        )
        n_err = sum(1 for x in fl if x["level"] == "error")
        n_warn = sum(1 for x in fl if x["level"] == "warning")
        qc_summary.append({
            "animal_id": r["animal_id"], "group": r["group"],
            "오류": n_err, "경고": n_warn,
            "상태": "❌ 오류" if n_err else ("⚠ 경고" if n_warn else "✅ 정상"),
        })
    st.dataframe(pd.DataFrame(qc_summary), width="stretch")


# =====================================================================
# 탭 5 — 코호트 통계 + 리포트
# =====================================================================
with tab5:
    st.header("⑤ 코호트 통계 + 리포트")
    st.caption(
        "그룹별 GIR/Rd/HGP 억제율/조직 Rg' 의 일원배치 ANOVA · 쌍별 비교 · "
        "혼합효과모형(statsmodels 설치 시) · 한국어/영어 method+result 리포트 export."
    )

    metric_options = {
        "GIR (mg/kg/min)": "gir_mgkgmin",
        "클램프 Rd (mg/kg/min)": "rd_clamp",
        "HGP 억제율 (%)": "hgp_suppression_pct",
    }
    avail_metrics = {k: v for k, v in metric_options.items()
                     if v in metrics_df.columns}
    sel_metric_label = st.selectbox("분석 지표", list(avail_metrics.keys()))
    sel_metric = avail_metrics[sel_metric_label]

    anova = cc.one_way_anova(metrics_df, "group", sel_metric)
    summ = cc.group_summary(metrics_df, "group", sel_metric)

    c1, c2 = st.columns(2)
    with c1:
        st.subheader("그룹 요약")
        st.dataframe(
            summ.style.format({
                c: "{:.2f}" for c in summ.columns if c not in ("group", "n")
            }),
            width="stretch",
        )
        st.subheader("일원배치 ANOVA")
        if not np.isnan(anova["p_value"]):
            st.metric("F 통계량", f"{anova['f_stat']:.3f}")
            st.metric("p-value", f"{anova['p_value']:.4g}")
            if anova["p_value"] < 0.05:
                st.success("그룹 간 유의한 차이 (p < 0.05)")
            else:
                st.info("그룹 간 유의한 차이 없음 (p ≥ 0.05)")
        else:
            st.warning("ANOVA 계산 불가 — 그룹/표본 수 부족.")

    with c2:
        st.subheader(f"{sel_metric_label} 그룹 분포")
        fig, ax = plt.subplots(figsize=(6, 4))
        groups = sorted(metrics_df["group"].unique())
        data = [metrics_df[metrics_df["group"] == g][sel_metric].dropna()
                for g in groups]
        ax.boxplot(data, tick_labels=groups)
        for i, d in enumerate(data, 1):
            ax.scatter(np.random.normal(i, 0.05, len(d)), d,
                       alpha=0.6, color="#2980b9", s=20)
        ax.set_ylabel("Value")
        ax.set_title("Metric distribution by group")
        fig.tight_layout()
        st.pyplot(fig)
        plt.close(fig)

    st.subheader("쌍별 비교 (Welch t-검정 + Bonferroni 보정)")
    pw = cc.tukey_pairwise(metrics_df, "group", sel_metric)
    if not pw.empty:
        st.dataframe(
            pw.style.format({
                "mean_diff": "{:.2f}", "t_stat": "{:.3f}",
                "p_raw": "{:.4g}", "p_bonferroni": "{:.4g}",
            }),
            width="stretch",
        )

    st.subheader("혼합효과모형 (개체 random effect)")
    if cc.mixed_effects_available():
        mm = cc.mixed_effects_model(metrics_df, sel_metric, "group", "animal_id")
        if mm:
            st.text(mm["summary_text"])
    else:
        st.info(
            "statsmodels가 설치되어 있지 않아 혼합효과모형은 생략됩니다. "
            "`pip install statsmodels` 후 사용 가능합니다. "
            "(ANOVA·쌍별 비교는 scipy 기반으로 정상 동작합니다.)"
        )

    # ----- 리포트 생성 -----
    st.subheader("리포트 export")
    lang = st.radio("언어", ["한국어", "English"], horizontal=True)

    def build_report(lang):
        lines = []
        if lang == "한국어":
            lines.append("# GlucoClampTracer 분석 리포트\n")
            lines.append("## 방법 (Methods)\n")
            lines.append(
                f"하이퍼인슐린혈성-정상혈당 클램프 데이터를 분석하였다. "
                f"정상상태 윈도우는 목표 혈당 {target_glucose:.0f}±{target_tol:.0f} mg/dL, "
                f"혈당 CV ≤ {max_cv:.0f}%, 최소 윈도우 길이 {min_window:.0f}분 기준으로 "
                f"자동 선택하였다. 포도당 출현율(Ra)/소실율(Rd)은 {tracer_type} 트레이서와 "
                f"{steele_mode} Steele 방정식으로 계산하였다. HGP 억제율은 기저 대비 "
                f"클램프 HGP 감소율로 산출하였다. 그룹 비교는 일원배치 ANOVA 및 "
                f"Welch t-검정(Bonferroni 보정)으로 수행하였다.\n")
            lines.append("## 결과 (Results)\n")
            lines.append(f"분석 지표: {sel_metric_label}\n")
            for _, r in summ.iterrows():
                lines.append(
                    f"- {r['group']} (n={r['n']}): "
                    f"{r[sel_metric+'_mean']:.2f} ± {r[sel_metric+'_sem']:.2f} (mean±SEM)")
            if not np.isnan(anova["p_value"]):
                lines.append(
                    f"\n일원배치 ANOVA: F = {anova['f_stat']:.3f}, "
                    f"p = {anova['p_value']:.4g}.")
            lines.append(
                "\n*본 리포트는 참고용·연구용입니다. 임상 진단 목적 사용 금지.*")
        else:
            lines.append("# GlucoClampTracer Analysis Report\n")
            lines.append("## Methods\n")
            lines.append(
                f"Hyperinsulinemic-euglycemic clamp data were analyzed. "
                f"The steady-state window was auto-selected with target glucose "
                f"{target_glucose:.0f}±{target_tol:.0f} mg/dL, glucose CV "
                f"≤ {max_cv:.0f}%, and minimum window length {min_window:.0f} min. "
                f"Glucose rate of appearance (Ra) / disappearance (Rd) were computed "
                f"using {tracer_type} tracer and the {steele_mode} Steele equation. "
                f"HGP suppression was calculated as the clamp-vs-basal reduction in HGP. "
                f"Group comparisons used one-way ANOVA and Welch t-tests "
                f"(Bonferroni-corrected).\n")
            lines.append("## Results\n")
            lines.append(f"Analyzed metric: {sel_metric_label}\n")
            for _, r in summ.iterrows():
                lines.append(
                    f"- {r['group']} (n={r['n']}): "
                    f"{r[sel_metric+'_mean']:.2f} ± {r[sel_metric+'_sem']:.2f} (mean±SEM)")
            if not np.isnan(anova["p_value"]):
                lines.append(
                    f"\nOne-way ANOVA: F = {anova['f_stat']:.3f}, "
                    f"p = {anova['p_value']:.4g}.")
            lines.append(
                "\n*For reference / research use only. Not for clinical diagnosis.*")
        return "\n".join(lines)

    report = build_report(lang)
    st.text_area("리포트 미리보기", report, height=320)

    c1, c2 = st.columns(2)
    with c1:
        st.download_button(
            "리포트 다운로드 (.md)", report,
            file_name="glucoclamptracer_report.md", mime="text/markdown")
    with c2:
        buf = io.StringIO()
        metrics_df.to_csv(buf, index=False)
        st.download_button(
            "동물별 지표 다운로드 (.csv)", buf.getvalue(),
            file_name="glucoclamptracer_metrics.csv", mime="text/csv")


st.markdown("---")
st.caption(
    "GlucoClampTracer · DM 동물실험 도구 · 2026 metabolic daily idea pipeline · "
    "참고용·연구용(for reference / research use only)"
)
