#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
DMRepurpRank-Kor (디엠리퍼프랭크코어)
-----------------------------------
비당뇨 약물 → 당뇨 효능 재포지셔닝 후보 자동 통합 ranking +
한국어 IIT/grant proposal card 생성 standalone Python CLI.

7+ source 통합:
  - DrugBank / OpenTargets  (target, mechanism)
  - UK Biobank / KoGES GWAS (Mendelian Randomization)
  - CMAP / L1000            (transcriptomic signature reversal)
  - FAERS / HIRA / KIDS-KD  (claims / RWE disproportionality)
  - IMPC                    (KO mouse phenotype)
  - KCD-7/8                 (E11.x T2DM code signal)
  - Viability               (generic / patent / KFDA / HIRA coverage)

⚠️ 본 도구는 연구용·참고용 hypothesis 생성기입니다.
   임상의사결정·환자 처방·실제 약물 재포지셔닝 승인 결정에 사용 금지.
"""

from __future__ import annotations
import argparse
import csv
import json
import math
import os
import sys
from typing import Any

# ---------------------------------------------------------------------------
# Paths
# ---------------------------------------------------------------------------
HERE = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(HERE, "data")

DISCLAIMER = (
    "본 도구는 연구용·참고용 hypothesis 생성기입니다. "
    "임상의사결정·환자 처방·실제 약물 재포지셔닝 승인 결정에 사용 금지."
)

# ---------------------------------------------------------------------------
# CSV loaders (stdlib csv — robust without pandas dependency)
# ---------------------------------------------------------------------------
def _load_csv(path: str) -> list[dict[str, str]]:
    with open(path, "r", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        return list(reader)


def load_all() -> dict[str, list[dict[str, str]]]:
    return {
        "drugs":         _load_csv(os.path.join(DATA_DIR, "drugs.csv")),
        "gwas_mr":       _load_csv(os.path.join(DATA_DIR, "gwas_mr.csv")),
        "cmap":          _load_csv(os.path.join(DATA_DIR, "cmap_signatures.csv")),
        "faers":         _load_csv(os.path.join(DATA_DIR, "faers_rwe.csv")),
        "target":        _load_csv(os.path.join(DATA_DIR, "target_pathway.csv")),
    }


def _index(rows: list[dict[str, str]], key: str = "drug_id") -> dict[str, dict[str, str]]:
    return {r[key]: r for r in rows}


# ---------------------------------------------------------------------------
# Statistical primitives (no rpy2; reproduces Wald ratio + IVW + Cochran Q)
# ---------------------------------------------------------------------------
def wald_ratio(beta_x: float, beta_y: float, se_y: float, se_x: float) -> tuple[float, float]:
    """Single-SNP MR Wald ratio with first-order delta-method SE.
       β_MR = β_y / β_x;  SE(β_MR) ≈ sqrt( SE_y²/β_x² + β_y² SE_x² / β_x⁴ )
    """
    if beta_x == 0:
        return float("nan"), float("inf")
    bmr = beta_y / beta_x
    var = (se_y ** 2) / (beta_x ** 2) + (beta_y ** 2 * se_x ** 2) / (beta_x ** 4)
    return bmr, math.sqrt(max(var, 0.0))


def ivw_meta(beta_mrs: list[float], ses: list[float]) -> tuple[float, float, float]:
    """Inverse-variance-weighted meta-analytic MR estimate + Cochran's Q.
       Returns (β_IVW, SE_IVW, Q)."""
    if not beta_mrs:
        return float("nan"), float("inf"), float("nan")
    weights = [1.0 / (s * s) if s > 0 else 0.0 for s in ses]
    sw = sum(weights)
    if sw == 0:
        return float("nan"), float("inf"), float("nan")
    beta_ivw = sum(b * w for b, w in zip(beta_mrs, weights)) / sw
    se_ivw = math.sqrt(1.0 / sw)
    # Cochran's Q (heterogeneity)
    Q = sum(w * (b - beta_ivw) ** 2 for b, w in zip(beta_mrs, weights))
    return beta_ivw, se_ivw, Q


def spearman_rho(a: list[float], b: list[float]) -> float:
    """Spearman rank correlation (ties handled with average rank)."""
    if len(a) != len(b) or len(a) < 2:
        return 0.0
    def _rank(xs: list[float]) -> list[float]:
        order = sorted(range(len(xs)), key=lambda i: xs[i])
        ranks = [0.0] * len(xs)
        i = 0
        while i < len(xs):
            j = i
            while j + 1 < len(xs) and xs[order[j + 1]] == xs[order[i]]:
                j += 1
            avg = (i + j) / 2.0 + 1.0
            for k in range(i, j + 1):
                ranks[order[k]] = avg
            i = j + 1
        return ranks
    ra, rb = _rank(a), _rank(b)
    n = len(a)
    mean_a = sum(ra) / n
    mean_b = sum(rb) / n
    num = sum((ra[i] - mean_a) * (rb[i] - mean_b) for i in range(n))
    den_a = math.sqrt(sum((ra[i] - mean_a) ** 2 for i in range(n)))
    den_b = math.sqrt(sum((rb[i] - mean_b) ** 2 for i in range(n)))
    if den_a == 0 or den_b == 0:
        return 0.0
    return num / (den_a * den_b)


# ---------------------------------------------------------------------------
# 5-axis scoring (each axis normalized to 0~100)
# ---------------------------------------------------------------------------
# T2DM "reference" disease signature direction per tissue (higher = more T2DM-like)
T2DM_DISEASE_SIG = {
    "liver":    1.0,
    "muscle":   1.0,
    "adipose":  1.0,
    "pancreas": 1.0,
}


def score_mr(row: dict[str, str]) -> tuple[float, dict[str, Any]]:
    """Score MR axis. Lower β_outcome_t2dm + higher F-stat + more SNPs = stronger
       protective effect → higher score."""
    beta_x = float(row["beta_exposure"])
    se_x   = float(row["se_exposure"])
    beta_y = float(row["beta_outcome_t2dm"])
    se_y   = float(row["se_outcome"])
    beta_y_hba1c = float(row["beta_outcome_hba1c"])
    se_y_hba1c   = float(row["se_outcome_hba1c"])
    f_stat = float(row["f_statistic"])
    n_snps = int(row["n_snps"])

    # Wald ratios (T2DM + HbA1c) — naive meta to demonstrate IVW
    bmr_t2dm, se_t2dm = wald_ratio(beta_x, beta_y, se_y, se_x)
    bmr_hba1c, se_hba1c = wald_ratio(beta_x, beta_y_hba1c, se_y_hba1c, se_x)
    beta_ivw, se_ivw, Q = ivw_meta([bmr_t2dm, bmr_hba1c], [se_t2dm, se_hba1c])

    # Score: more negative β_ivw → better; weight by sqrt(F) and log(n_snps)
    raw = -beta_ivw * math.sqrt(max(f_stat, 1.0)) * math.log1p(n_snps)
    # Normalize: empirically map raw [−6, +12] → [0, 100]
    score = max(0.0, min(100.0, (raw + 6.0) * 100.0 / 18.0))

    return score, {
        "beta_ivw_t2dm_hba1c": round(beta_ivw, 4),
        "se_ivw": round(se_ivw, 4),
        "cochran_Q": round(Q, 4),
        "f_statistic": f_stat,
        "n_snps": n_snps,
        "wald_t2dm": round(bmr_t2dm, 4),
        "wald_hba1c": round(bmr_hba1c, 4),
    }


def score_cmap(row: dict[str, str]) -> tuple[float, dict[str, Any]]:
    """Score CMAP axis: drug signature ↔ disease signature inverse correlation."""
    drug_sig = [
        float(row["signature_corr_t2dm_liver"]),
        float(row["signature_corr_t2dm_muscle"]),
        float(row["signature_corr_t2dm_adipose"]),
        float(row["signature_corr_t2dm_pancreas"]),
    ]
    disease_sig = [T2DM_DISEASE_SIG["liver"],
                   T2DM_DISEASE_SIG["muscle"],
                   T2DM_DISEASE_SIG["adipose"],
                   T2DM_DISEASE_SIG["pancreas"]]
    rho = spearman_rho(drug_sig, disease_sig)
    mean_corr = sum(drug_sig) / len(drug_sig)
    # Reversal: more negative mean_corr → better; map [-0.7, +0.3] → [100, 0]
    score = max(0.0, min(100.0, (0.3 - mean_corr) * 100.0))
    return score, {
        "spearman_rho_drug_vs_disease": round(rho, 4),
        "mean_tissue_correlation": round(mean_corr, 4),
        "per_tissue": {
            "liver": drug_sig[0],
            "muscle": drug_sig[1],
            "adipose": drug_sig[2],
            "pancreas": drug_sig[3],
        },
    }


def score_target(row: dict[str, str]) -> tuple[float, dict[str, Any]]:
    """Score target-pathway concordance axis using -log10(p) of GWAS+KEGG overlap
       and a +/- adjustment for IMPC KO phenotype."""
    p_gwas = float(row["t2dm_gwas_overlap_p"])
    p_kegg = float(row["kegg_pathway_overlap_p"])
    p_combined_log = -math.log10(p_gwas) + -math.log10(p_kegg)

    glucose_pheno = row["impc_ko_glucose_phenotype"]
    insulin_pheno = row["impc_ko_insulin_phenotype"]
    pheno_bonus = 0.0
    if "decreased_glucose" in glucose_pheno or "improved" in insulin_pheno:
        pheno_bonus += 10
    if "strongly_improved" in insulin_pheno or "decreased_glucose" == glucose_pheno:
        pheno_bonus += 5
    if "increased_glucose" in glucose_pheno or "decreased_sensitivity" in insulin_pheno:
        pheno_bonus -= 15
    if "strongly_increased" in glucose_pheno:
        pheno_bonus -= 10
    # Map [0, 16] → [0, 80], add bonus
    score = max(0.0, min(100.0, p_combined_log * 5.0 + pheno_bonus + 20.0))
    return score, {
        "t2dm_gwas_overlap_p": p_gwas,
        "kegg_pathway_overlap_p": p_kegg,
        "impc_glucose_phenotype": glucose_pheno,
        "impc_insulin_phenotype": insulin_pheno,
        "fisher_combined_neg_log10p": round(p_combined_log, 4),
    }


def score_rwe(row: dict[str, str]) -> tuple[float, dict[str, Any]]:
    """Score RWE disproportionality axis. RR/ROR/PRR/EBGM < 1 → protective."""
    rr = float(row["rr"])
    ror = float(row["ror"])
    prr = float(row["prr"])
    ebgm = float(row["ebgm"])
    mean_signal = (rr + ror + prr + ebgm) / 4.0
    # Map [0.5, 2.5] → [100, 0]
    score = max(0.0, min(100.0, (2.5 - mean_signal) * 50.0))
    return score, {
        "rr": rr,
        "ror": ror,
        "prr": prr,
        "ebgm": ebgm,
        "kcd_e11_signal_kor": row["kcd_e11_signal_kor"],
        "exposure_n": int(row["exposure_n"]),
        "t2dm_event_n": int(row["t2dm_event_n"]),
    }


def score_viability(drug_row: dict[str, str]) -> tuple[float, dict[str, Any]]:
    """Score viability axis (generic·patent·KFDA·HIRA)."""
    s = 0
    s += 25 if drug_row["generic_status"] == "generic" else 10
    s += 25 if drug_row["patent_status"] == "expired" else 5
    s += 25 if drug_row["kfda_approval"] == "approved" else 0
    if drug_row["hira_coverage"] == "covered":
        s += 25
    elif drug_row["hira_coverage"] == "partial":
        s += 15
    else:
        s += 5
    return float(s), {
        "generic": drug_row["generic_status"],
        "patent": drug_row["patent_status"],
        "kfda": drug_row["kfda_approval"],
        "hira": drug_row["hira_coverage"],
    }


# ---------------------------------------------------------------------------
# Composite ranking
# ---------------------------------------------------------------------------
DEFAULT_WEIGHTS = {"mr": 0.30, "cmap": 0.20, "target": 0.20, "rwe": 0.20, "viability": 0.10}


def compute_ranking(data: dict[str, list[dict[str, str]]],
                    weights: dict[str, float] | None = None
                    ) -> list[dict[str, Any]]:
    weights = weights or DEFAULT_WEIGHTS
    drugs    = _index(data["drugs"])
    gwas_mr  = _index(data["gwas_mr"])
    cmap     = _index(data["cmap"])
    faers    = _index(data["faers"])
    target   = _index(data["target"])

    out = []
    for drug_id, drug_row in drugs.items():
        # Reference (metformin) is informative but excluded from ranking
        is_reference = drug_id == "DR001"

        mr_s,   mr_det   = score_mr(gwas_mr[drug_id])     if drug_id in gwas_mr  else (0.0, {})
        cmap_s, cmap_det = score_cmap(cmap[drug_id])      if drug_id in cmap     else (0.0, {})
        tgt_s,  tgt_det  = score_target(target[drug_id])  if drug_id in target   else (0.0, {})
        rwe_s,  rwe_det  = score_rwe(faers[drug_id])      if drug_id in faers    else (0.0, {})
        via_s,  via_det  = score_viability(drug_row)

        composite = (
            weights["mr"]        * mr_s   +
            weights["cmap"]      * cmap_s +
            weights["target"]    * tgt_s  +
            weights["rwe"]       * rwe_s  +
            weights["viability"] * via_s
        )
        out.append({
            "drug_id": drug_id,
            "drug_name_en": drug_row["drug_name_en"],
            "drug_name_kr": drug_row["drug_name_kr"],
            "atc_code": drug_row["atc_code"],
            "drugbank_id": drug_row["drugbank_id"],
            "original_indication": drug_row["original_indication"],
            "target_gene": drug_row["target_gene"],
            "target_pathway": drug_row["target_pathway"],
            "scores": {
                "mr":        round(mr_s, 2),
                "cmap":      round(cmap_s, 2),
                "target":    round(tgt_s, 2),
                "rwe":       round(rwe_s, 2),
                "viability": round(via_s, 2),
                "composite": round(composite, 2),
            },
            "details": {
                "mr": mr_det,
                "cmap": cmap_det,
                "target": tgt_det,
                "rwe": rwe_det,
                "viability": via_det,
            },
            "is_reference": is_reference,
            "drug_record": drug_row,
        })
    # Exclude reference from main ranking but keep at end (for context)
    ranked = sorted([o for o in out if not o["is_reference"]],
                    key=lambda r: r["scores"]["composite"], reverse=True)
    for i, r in enumerate(ranked, start=1):
        r["rank"] = i
    # Append reference (metformin) marked
    ref = [o for o in out if o["is_reference"]]
    for r in ref:
        r["rank"] = 0  # 0 = reference
    return ranked + ref


# ---------------------------------------------------------------------------
# Pretty printers
# ---------------------------------------------------------------------------
def print_table(rows: list[dict[str, Any]], top: int) -> None:
    print()
    print(f"DMRepurpRank-Kor — Top {top} 비당뇨 → 당뇨 재포지셔닝 후보")
    print("=" * 110)
    header = (
        f"{'순위':>4} {'약물(KR)':<14} {'약물(EN)':<22} {'ATC':<10} "
        f"{'MR':>6} {'CMAP':>6} {'TGT':>6} {'RWE':>6} {'VIA':>6} {'COMP':>7}"
    )
    print(header)
    print("-" * 110)
    shown = 0
    for r in rows:
        if r.get("is_reference"):
            continue
        if shown >= top:
            break
        s = r["scores"]
        print(
            f"{r['rank']:>4} {r['drug_name_kr']:<14} {r['drug_name_en']:<22} "
            f"{r['atc_code']:<10} "
            f"{s['mr']:>6.1f} {s['cmap']:>6.1f} {s['target']:>6.1f} "
            f"{s['rwe']:>6.1f} {s['viability']:>6.1f} {s['composite']:>7.2f}"
        )
        shown += 1
    print("-" * 110)
    # Reference line
    for r in rows:
        if r.get("is_reference"):
            s = r["scores"]
            print(
                f"{'REF':>4} {r['drug_name_kr']:<14} {r['drug_name_en']:<22} "
                f"{r['atc_code']:<10} "
                f"{s['mr']:>6.1f} {s['cmap']:>6.1f} {s['target']:>6.1f} "
                f"{s['rwe']:>6.1f} {s['viability']:>6.1f} {s['composite']:>7.2f}  ← 당뇨 표준치료 (참조)"
            )
    print()
    print(f"⚠️  {DISCLAIMER}")
    print()


def format_card(r: dict[str, Any]) -> str:
    s = r["scores"]
    d = r["details"]
    mr = d["mr"]
    cmap = d["cmap"]
    tgt = d["target"]
    rwe = d["rwe"]
    via = d["viability"]
    drug = r["drug_record"]

    pheno_kr = ""
    glu = tgt.get("impc_glucose_phenotype", "")
    ins = tgt.get("impc_insulin_phenotype", "")
    if "decreased_glucose" in glu:
        pheno_kr += "포도당 ↓ "
    if "improved" in ins:
        pheno_kr += "/ 인슐린 감수성 ↑"
    if "increased_glucose" in glu:
        pheno_kr += "포도당 ↑ (역방향 시그널) "

    narrative = (
        f"{r['drug_name_kr']}({r['drug_name_en']})은(는) 현재 "
        f"{drug['original_indication']} 적응증으로 사용 중이며, "
        f"표적 {r['target_gene']} ({r['target_pathway']}) 활성이 T2DM/HbA1c "
        f"표현형에 미치는 MR-기반 인과 추정치는 β_IVW={mr.get('beta_ivw_t2dm_hba1c')}, "
        f"F={mr.get('f_statistic')}, n_SNP={mr.get('n_snps')} 수준으로 관찰됨. "
        f"CMAP 4-tissue 평균 reversal correlation은 ρ={cmap.get('mean_tissue_correlation')} "
        f"(disease signature와 음의 상관 → reversal). "
        f"FAERS/HIRA disproportionality는 RR={rwe.get('rr')} (ROR={rwe.get('ror')}, "
        f"PRR={rwe.get('prr')}, EBGM={rwe.get('ebgm')}), "
        f"KCD E11.x 시그널: {rwe.get('kcd_e11_signal_kor')}. "
        f"IMPC KO 표현형: {pheno_kr or '특기 없음'}."
    )

    grant_block = (
        "## KDDF / IIT / Grant proposal template suggestion\n"
        f"- 제안 grant 후보: KDDF 재포지셔닝 트랙, 국가신약개발사업단(KDDF), "
        f"보건복지부 의료기술연구개발(R&D), 한국연구재단 중견연구\n"
        f"- 제안 study design: 1) Korean cohort PoC (HIRA claims + KoGES GWAS proxy) "
        f"target {r['target_gene']} stratified, 2) 12주 randomized open-label PoC trial "
        f"(primary: ΔHbA1c, secondary: HOMA-IR / fasting glucose), "
        f"3) safety: KFDA-approved {drug['kfda_approval']}, HIRA coverage {drug['hira_coverage']}\n"
        f"- 산학협력: 순천향대 부천병원(닥터앤서 3.0 컨소시엄) + KoGES 코호트\n"
        f"- 산업적 관점: generic={drug['generic_status']}, patent={drug['patent_status']} → "
        f"{'low-cost 재포지셔닝 적합' if drug['patent_status']=='expired' else 'IP 회피 설계 필요'}"
    )

    card = (
        f"# {r['drug_name_kr']} ({r['drug_name_en']}) 당뇨 재포지셔닝 가설\n"
        f"- 기존 적응증: {drug['original_indication']}\n"
        f"- Target: {r['target_gene']} ({r['target_pathway']})\n"
        f"- ATC: {r['atc_code']} | DrugBank: {r['drugbank_id']}\n\n"
        f"## 5축 점수 (각 0~100)\n"
        f"- **MR (Mendelian randomization)**: {s['mr']:.1f}/100 — "
        f"β_IVW={mr.get('beta_ivw_t2dm_hba1c')}, F={mr.get('f_statistic')}, "
        f"n_SNP={mr.get('n_snps')}, Q={mr.get('cochran_Q')}\n"
        f"- **CMAP transcriptomic reversal**: {s['cmap']:.1f}/100 — "
        f"mean ρ={cmap.get('mean_tissue_correlation')} "
        f"(liver={cmap.get('per_tissue',{}).get('liver')}, "
        f"muscle={cmap.get('per_tissue',{}).get('muscle')}, "
        f"adipose={cmap.get('per_tissue',{}).get('adipose')}, "
        f"pancreas={cmap.get('per_tissue',{}).get('pancreas')})\n"
        f"- **Target-pathway concordance**: {s['target']:.1f}/100 — "
        f"GWAS overlap p={tgt.get('t2dm_gwas_overlap_p')}, "
        f"KEGG overlap p={tgt.get('kegg_pathway_overlap_p')}, "
        f"IMPC KO glucose={tgt.get('impc_glucose_phenotype')}, "
        f"insulin={tgt.get('impc_insulin_phenotype')}\n"
        f"- **RWE disproportionality**: {s['rwe']:.1f}/100 — "
        f"RR={rwe.get('rr')}, ROR={rwe.get('ror')}, PRR={rwe.get('prr')}, "
        f"EBGM={rwe.get('ebgm')}, KCD: {rwe.get('kcd_e11_signal_kor')}, "
        f"n_exposure={rwe.get('exposure_n')}\n"
        f"- **Viability**: {s['viability']:.1f}/100 — "
        f"generic={via.get('generic')}, patent={via.get('patent')}, "
        f"KFDA={via.get('kfda')}, HIRA={via.get('hira')}\n\n"
        f"## Composite: {s['composite']:.2f}/100 (Top {r.get('rank','-')})\n\n"
        f"## 가설 narrative\n{narrative}\n\n"
        f"{grant_block}\n\n"
        f"## ⚠️ 디스클레이머\n{DISCLAIMER}\n"
    )
    return card


# ---------------------------------------------------------------------------
# Weight parser
# ---------------------------------------------------------------------------
def parse_weights(spec: str) -> dict[str, float]:
    out = dict(DEFAULT_WEIGHTS)
    if not spec:
        return out
    parts = [p.strip() for p in spec.split(",") if p.strip()]
    for p in parts:
        if "=" not in p:
            raise ValueError(f"잘못된 가중치 형식: {p}")
        k, v = p.split("=", 1)
        k = k.strip().lower()
        if k not in DEFAULT_WEIGHTS:
            raise ValueError(f"알 수 없는 axis: {k} (허용: {list(DEFAULT_WEIGHTS.keys())})")
        out[k] = float(v)
    # Renormalize so they sum to 1
    total = sum(out.values())
    if total <= 0:
        raise ValueError("가중치 합이 0보다 커야 합니다.")
    return {k: v / total for k, v in out.items()}


# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
def print_summary() -> None:
    drug_n = len(load_all()["drugs"])
    body = (
        "\n"
        "DMRepurpRank-Kor (디엠리퍼프랭크코어)\n"
        "─────────────────────────────────────\n"
        "도메인       : DM (당뇨병)\n"
        "카테고리     : 연구 아이디어 생성 / drug repurposing ranker\n"
        "형식         : Python CLI (argparse)\n"
        "빌드 일자    : 2026-05-15\n\n"
        "핵심 기능 (5):\n"
        "  1) Multi-source ingest + entity 정규화 (DrugBank, OpenTargets, GWAS,\n"
        "     CMAP/L1000, FAERS, HIRA, IMPC, KCD-7/8 등 7+ source 합성 통합)\n"
        "  2) Mendelian randomization 자동 계산 (Wald ratio, IVW, F-statistic,\n"
        "     Cochran's Q — rpy2 없이 numpy/stdlib로 구현)\n"
        "  3) Transcriptomic signature reversal (Spearman ρ) + target-pathway\n"
        "     concordance (GWAS+KEGG Fisher combined p)\n"
        "  4) RWE disproportionality (RR / ROR / PRR / EBGM) + KCD E11.x 시그널\n"
        "  5) Composite ranking + 한국어 hypothesis card / IIT-grant proposal 출력\n\n"
        "CLI:\n"
        "  python3 main.py --top 10\n"
        "  python3 main.py --top 10 --weights mr=0.3,cmap=0.2,target=0.2,rwe=0.2,viability=0.1\n"
        "  python3 main.py --card 메트포르민        # 또는 영문 이름 / drug_id\n"
        "  python3 main.py --export-json out.json\n"
        "  python3 main.py --summary\n\n"
        "데이터 (합성):\n"
        f"  data/drugs.csv             {drug_n}개 비당뇨 약물 후보\n"
        "  data/gwas_mr.csv           target gene 별 GWAS IV\n"
        "  data/cmap_signatures.csv   4-tissue (liver, muscle, adipose, pancreas) reversal\n"
        "  data/faers_rwe.csv         FAERS/HIRA RR/ROR/PRR/EBGM + KCD signal\n"
        "  data/target_pathway.csv    GWAS+KEGG overlap + IMPC KO phenotype\n\n"
        f"⚠️  {DISCLAIMER}\n"
    )
    print(body)


# ---------------------------------------------------------------------------
# Export
# ---------------------------------------------------------------------------
def export_json(rows: list[dict[str, Any]], path: str) -> None:
    # 닥터앤서 3.0 호환 envelope
    envelope = {
        "tool": "DMRepurpRank-Kor",
        "tool_name_kr": "디엠리퍼프랭크코어",
        "version": "1.0.0",
        "build_date": "2026-05-15",
        "domain": "DM",
        "category": "research_idea_generation",
        "consortium": "닥터앤서 3.0 / 순천향대 부천병원",
        "disclaimer": DISCLAIMER,
        "weights": DEFAULT_WEIGHTS,
        "sources": [
            "DrugBank", "OpenTargets", "UK Biobank GWAS", "KoGES GWAS",
            "CMAP/L1000", "FAERS", "HIRA claims (KIDS-KD)",
            "IMPC KO", "KCD-7/8",
        ],
        "candidates": [
            {k: v for k, v in r.items() if k != "drug_record"}
            for r in rows
        ],
    }
    with open(path, "w", encoding="utf-8") as f:
        json.dump(envelope, f, ensure_ascii=False, indent=2)


# ---------------------------------------------------------------------------
# Lookup helper
# ---------------------------------------------------------------------------
def find_drug(rows: list[dict[str, Any]], query: str) -> dict[str, Any] | None:
    q = query.strip().lower()
    for r in rows:
        if (r["drug_id"].lower() == q or
            r["drug_name_en"].lower() == q or
            r["drug_name_kr"].lower() == q or
            r["drug_name_en"].lower().startswith(q) or
            q in r["drug_name_kr"].lower()):
            return r
    return None


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def build_argparser() -> argparse.ArgumentParser:
    epilog = (
        "예시:\n"
        "  python3 main.py --top 10\n"
        "  python3 main.py --top 5 --weights mr=0.4,cmap=0.2,target=0.2,rwe=0.15,viability=0.05\n"
        "  python3 main.py --card 텔미사르탄\n"
        "  python3 main.py --export-json result.json\n"
        "  python3 main.py --summary\n\n"
        f"⚠️  {DISCLAIMER}"
    )
    p = argparse.ArgumentParser(
        prog="DMRepurpRank-Kor",
        description=(
            "DMRepurpRank-Kor (디엠리퍼프랭크코어) — 비당뇨 약물 → 당뇨 효능 "
            "재포지셔닝 후보 자동 통합 ranking + 한국어 IIT/grant card 생성 CLI. "
            f"⚠️ {DISCLAIMER}"
        ),
        epilog=epilog,
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    p.add_argument("--top", type=int, default=None,
                   help="상위 N개 후보 ranking 표 출력")
    p.add_argument("--weights", type=str, default=None,
                   help="가중치 (예: mr=0.3,cmap=0.2,target=0.2,rwe=0.2,viability=0.1)")
    p.add_argument("--card", type=str, default=None,
                   help="특정 약물 한국어 hypothesis card 출력 (drug_id / EN / KR 이름)")
    p.add_argument("--export-json", type=str, default=None, metavar="PATH",
                   help="닥터앤서 3.0 호환 JSON으로 export")
    p.add_argument("--summary", action="store_true",
                   help="도구 개요 요약 출력")
    return p


def main(argv: list[str] | None = None) -> int:
    parser = build_argparser()
    args = parser.parse_args(argv)

    if not (args.top or args.card or args.export_json or args.summary):
        parser.print_help()
        return 0

    if args.summary:
        print_summary()
        return 0

    weights = parse_weights(args.weights) if args.weights else DEFAULT_WEIGHTS
    data = load_all()
    ranking = compute_ranking(data, weights)

    did_any = False
    if args.top:
        print_table(ranking, args.top)
        did_any = True

    if args.card:
        target = find_drug(ranking, args.card)
        if target is None:
            print(f"❌ '{args.card}' 와 일치하는 후보를 찾을 수 없습니다.", file=sys.stderr)
            return 2
        print(format_card(target))
        did_any = True

    if args.export_json:
        export_json(ranking, args.export_json)
        print(f"✅ JSON export 완료: {args.export_json} ({len(ranking)}개 후보)")
        did_any = True

    return 0 if did_any else 0


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