#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
AntiObesityOffTargetMech-Kor (안티오베시티오프타겟메크코어)
차세대 항비만 multi-target agonist off-target mechanism hypothesis generator

⚠️ 본 도구는 연구용·참고용 mechanism hypothesis 생성기입니다.
   임상의사결정·차세대 항비만약 처방·승인 결정에 사용 금지.
"""

import argparse
import csv
import json
import math
import os
import sys
from collections import defaultdict

# ----- 경로 설정 -----
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(BASE_DIR, "data")

DISCLAIMER = (
    "⚠️ 본 도구는 연구용·참고용 mechanism hypothesis 생성기입니다. "
    "임상의사결정·차세대 항비만약 처방·승인 결정에 사용 금지."
)


# ----- CSV 로더 -----
def load_csv(path):
    rows = []
    with open(path, "r", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for r in reader:
            rows.append(r)
    return rows


def load_all():
    return {
        "drugs": load_csv(os.path.join(DATA_DIR, "drugs_selectivity.csv")),
        "ae": load_csv(os.path.join(DATA_DIR, "ae_pattern.csv")),
        "expr": load_csv(os.path.join(DATA_DIR, "receptor_expression.csv")),
        "faers": load_csv(os.path.join(DATA_DIR, "faers_kor.csv")),
        "hyp": load_csv(os.path.join(DATA_DIR, "mechanism_hypotheses.csv")),
    }


# ----- 유틸 -----
def fnum(x, default=0.0):
    try:
        return float(x)
    except (ValueError, TypeError):
        return default


def fmt_ki(ki):
    v = fnum(ki, 9999)
    if v >= 9999:
        return "—"
    return f"{v:.3f}"


def get_drug(drugs, drug_id_or_name):
    key = drug_id_or_name.lower()
    for d in drugs:
        if d["drug_id"].lower() == key or d["drug_name"].lower() == key:
            return d
    return None


# ----- 기능 1: Selectivity Matrix -----
def cmd_selectivity_matrix(db):
    print()
    print("=" * 100)
    print(" 항비만 Multi-Target Agonist Selectivity Matrix (Ki, nM)")
    print("=" * 100)
    header = f"{'Drug':<16}{'Class':<32}{'GLP-1R':>10}{'GIPR':>10}{'GCGR':>10}{'AMYR':>10}{'MC4R':>10}{'Phase':>10}"
    print(header)
    print("-" * 100)
    for d in db["drugs"]:
        line = (
            f"{d['drug_name']:<16}"
            f"{d['drug_class'][:30]:<32}"
            f"{fmt_ki(d['glp1r_ki_nm']):>10}"
            f"{fmt_ki(d['gipr_ki_nm']):>10}"
            f"{fmt_ki(d['gcgr_ki_nm']):>10}"
            f"{fmt_ki(d['amyr_ki_nm']):>10}"
            f"{fmt_ki(d['mc4r_ki_nm']):>10}"
            f"{d['phase']:>10}"
        )
        print(line)
    print("-" * 100)
    print(" '—' = no binding 또는 inactive (Ki>9999 nM)")
    print(f" 총 {len(db['drugs'])} 약물 / 5 receptor 분석")
    print("=" * 100)
    print(DISCLAIMER)
    print()


# ----- 기능 2: Tissue heat-map (텍스트) -----
def tpm_to_bar(tpm, max_tpm):
    if max_tpm <= 0:
        return ""
    width = int(round((tpm / max_tpm) * 30))
    return "█" * width


def cmd_tissue_heatmap(db, receptor):
    rec = receptor.upper().replace(" ", "")
    # 사용자 입력 정규화
    norm = {
        "GLP1R": "GLP-1R", "GLP-1R": "GLP-1R",
        "GIPR": "GIPR",
        "GCGR": "GCGR",
        "AMYR": "AMYR", "AMY": "AMYR",
        "MC4R": "MC4R", "MC4": "MC4R",
    }.get(rec, receptor)

    rows = [r for r in db["expr"] if r["receptor"] == norm]
    if not rows:
        print(f"⚠️ receptor '{receptor}' (normalized: {norm}) 에 대한 발현 데이터가 없습니다.")
        return

    rows.sort(key=lambda r: fnum(r["gtex_tpm"]), reverse=True)
    max_tpm = max(fnum(r["gtex_tpm"]) for r in rows)

    print()
    print("=" * 110)
    print(f" {norm} : GTEx 54-tissue 발현 + Tabula Sapiens cell-type + IMPC KO 표현형 Heat-map")
    print("=" * 110)
    header = f"{'Tissue':<32}{'TPM':>8}  {'Bar':<32}{'Cell type':<28}{'%cells':>8}  {'KO phenotype':<30}"
    print(header)
    print("-" * 110)
    for r in rows:
        tpm = fnum(r["gtex_tpm"])
        bar = tpm_to_bar(tpm, max_tpm)
        line = (
            f"{r['tissue'][:30]:<32}"
            f"{tpm:>8.1f}  "
            f"{bar:<32}"
            f"{r['tabula_sapiens_cell_type'][:26]:<28}"
            f"{fnum(r['ts_pct_expressing_cells']):>7.1f}% "
            f"{r['impc_ko_phenotype'][:28]:<30}"
        )
        print(line)
    print("-" * 110)
    print(f" 총 {len(rows)} tissue entries / max TPM={max_tpm:.1f}")
    print("=" * 110)
    print(DISCLAIMER)
    print()


# ----- 기능 3: FAERS/KIDS-KD disproportionality -----
def faers_for_drug(db, drug_id):
    return [r for r in db["faers"] if r["drug_id"] == drug_id]


# ----- 기능 4: Mechanism ranking (Bayesian weighting) -----
def bayesian_score(hyp_row, db):
    """
    AE pattern × selectivity × tissue/cell-type expression × KO phenotype
    4축 cross-reference Bayesian posterior 가중합
    """
    # base prior (from hypothesis_csv bayesian_posterior)
    prior = fnum(hyp_row["bayesian_posterior"], 0.5)
    evidence = fnum(hyp_row["evidence_score"], 5.0)

    drug = next((d for d in db["drugs"] if d["drug_id"] == hyp_row["drug_id"]), None)
    if not drug:
        return prior, evidence, 0.0, 0.0, 0.0, 0.0

    # axis 1: selectivity (lower Ki = higher binding affinity = more likely off-target if expressed)
    rec = hyp_row["receptor"]
    rec_to_col = {
        "GLP-1R": "glp1r_ki_nm",
        "GIPR": "gipr_ki_nm",
        "GCGR": "gcgr_ki_nm",
        "AMYR": "amyr_ki_nm",
        "MC4R": "mc4r_ki_nm",
    }
    ki = fnum(drug.get(rec_to_col.get(rec, ""), 9999), 9999)
    if ki >= 9999:
        sel_score = 0.05
    else:
        # log-scaled inverse Ki
        sel_score = 1.0 / (1.0 + math.log10(max(ki * 10, 1.0)))
        sel_score = max(0.05, min(1.0, sel_score))

    # axis 2: tissue expression match
    expr_rows = [r for r in db["expr"] if r["receptor"] == rec and r["tissue"] == hyp_row["tissue"]]
    if expr_rows:
        tpm = fnum(expr_rows[0]["gtex_tpm"])
        tissue_score = min(1.0, tpm / 100.0 + 0.2)
    else:
        tissue_score = 0.2

    # axis 3: AE pattern strength (FAERS)
    faers_hits = [
        r for r in db["faers"]
        if r["drug_id"] == hyp_row["drug_id"] and hyp_row["ae_pattern"].lower() in r["ae_pt"].lower()
    ]
    if faers_hits:
        ror = fnum(faers_hits[0]["ror"], 1.0)
        ae_score = min(1.0, math.log10(max(ror, 1.0)) / 1.5)
    else:
        ae_score = 0.3

    # axis 4: KO phenotype concordance (heuristic: presence of relevant phenotype text)
    ko_score = 0.4
    if hyp_row["ko_phenotype"]:
        ko_score = 0.75

    # Bayesian update (multiplicative + normalized)
    composite = (
        0.30 * sel_score
        + 0.25 * tissue_score
        + 0.30 * ae_score
        + 0.15 * ko_score
    )
    # Combine with prior
    posterior = 0.5 * prior + 0.5 * composite
    return posterior, evidence, sel_score, tissue_score, ae_score, ko_score


def cmd_rank_mechanism(db, top, drug_filter=None):
    print()
    print("=" * 120)
    print(" 항비만 Off-Target Mechanism 가설 Bayesian Ranking")
    if drug_filter:
        print(f" Filter: drug = {drug_filter}")
    print("=" * 120)

    drug_id_filter = None
    if drug_filter:
        d = get_drug(db["drugs"], drug_filter)
        if d:
            drug_id_filter = d["drug_id"]
        else:
            print(f"⚠️ '{drug_filter}' 약물을 찾을 수 없습니다.")
            print(DISCLAIMER)
            return

    scored = []
    for h in db["hyp"]:
        if drug_id_filter and h["drug_id"] != drug_id_filter:
            continue
        posterior, evidence, sel, tissue, ae, ko = bayesian_score(h, db)
        scored.append((posterior, evidence, sel, tissue, ae, ko, h))

    scored.sort(key=lambda x: (x[0], x[1]), reverse=True)
    scored = scored[:top]

    print(
        f"{'Rank':<5}{'HypID':<6}{'Drug':<14}{'Rec':<8}"
        f"{'AE':<24}{'Tissue':<26}{'Post':>6}{'Sel':>6}{'Tis':>6}{'AE':>6}{'KO':>6}"
    )
    print("-" * 120)
    for i, (post, ev, sel, tis, ae, ko, h) in enumerate(scored, 1):
        drug_name = next(
            (d["drug_name"] for d in db["drugs"] if d["drug_id"] == h["drug_id"]),
            h["drug_id"],
        )
        line = (
            f"{i:<5}"
            f"{h['hypothesis_id']:<6}"
            f"{drug_name[:12]:<14}"
            f"{h['receptor']:<8}"
            f"{h['ae_pattern'][:22]:<24}"
            f"{h['tissue'][:24]:<26}"
            f"{post:>6.3f}"
            f"{sel:>6.2f}"
            f"{tis:>6.2f}"
            f"{ae:>6.2f}"
            f"{ko:>6.2f}"
        )
        print(line)
    print("-" * 120)
    print(" 가중치: Selectivity 0.30 · Tissue 0.25 · AE 0.30 · KO 0.15 + prior 0.50 blend")
    print(f" 총 {len(scored)} 가설 출력 (전체 {len(db['hyp'])} 중)")
    print("=" * 120)
    print(DISCLAIMER)
    print()


# ----- 기능 5: 한국어 hypothesis card -----
def find_hypothesis(db, query):
    q = query.lower().strip()

    # 1) 정확 매칭 (hypothesis_id / narrative / ae_pattern)
    for h in db["hyp"]:
        if (
            q == h["hypothesis_id"].lower()
            or q in h["mechanism_narrative_kor"].lower()
            or q in h["ae_pattern"].lower()
        ):
            return h

    # 2) 다중 토큰 부분 매칭 — 모든 토큰이 narrative/ae/tissue/receptor에 포함되면 일치
    tokens = [t for t in q.replace("-", " ").replace("·", " ").split() if t]
    if not tokens:
        return None

    best = None
    best_hits = 0
    for h in db["hyp"]:
        blob = (
            h["mechanism_narrative_kor"].lower()
            + " | " + h["ae_pattern"].lower()
            + " | " + h["tissue"].lower()
            + " | " + h["receptor"].lower()
            + " | " + h["cell_type"].lower()
        )
        hits = sum(1 for t in tokens if t in blob)
        if hits > best_hits and hits >= max(1, len(tokens) // 2):
            best_hits = hits
            best = h
    return best


def cmd_card(db, query):
    h = find_hypothesis(db, query)
    if not h:
        print(f"⚠️ '{query}' 와 일치하는 mechanism 가설을 찾을 수 없습니다.")
        print(DISCLAIMER)
        return

    drug = next((d for d in db["drugs"] if d["drug_id"] == h["drug_id"]), None)
    if not drug:
        print("⚠️ 연결된 약물 정보 누락")
        return

    posterior, evidence, sel, tis, ae_s, ko_s = bayesian_score(h, db)

    # rank
    all_scores = []
    for hh in db["hyp"]:
        p, _, _, _, _, _ = bayesian_score(hh, db)
        all_scores.append((p, hh["hypothesis_id"]))
    all_scores.sort(reverse=True)
    rank = next(
        (i + 1 for i, (_, hid) in enumerate(all_scores) if hid == h["hypothesis_id"]),
        len(all_scores),
    )
    total = len(all_scores)

    # AE pattern
    ae_rows = [
        a for a in db["ae"]
        if a["drug_id"] == h["drug_id"]
        and h["ae_pattern"].lower() in a["meddra_pt"].lower()
    ]
    if ae_rows:
        ae0 = ae_rows[0]
        trial = ae0["trial_name"]
        soc = ae0["meddra_soc"]
        pt = ae0["meddra_pt"]
        inc = ae0["incidence_pct"]
        plac = ae0["placebo_pct"]
        ror = ae0["ror"]
        n = ae0["sub_group_n"]
    else:
        trial = "—"
        soc = "—"
        pt = h["ae_pattern"]
        inc = "—"
        plac = "—"
        ror = "—"
        n = "—"

    # tissue/cell-type
    expr_rows = [
        e for e in db["expr"]
        if e["receptor"] == h["receptor"] and e["tissue"] == h["tissue"]
    ]
    if expr_rows:
        e0 = expr_rows[0]
        tissue_expr_summary = f"GTEx TPM={e0['gtex_tpm']} ({e0['tissue']})"
        cell_type = e0["tabula_sapiens_cell_type"]
        pct = e0["ts_pct_expressing_cells"]
        ko_gene = e0["impc_ko_gene"]
        ko_pheno = e0["impc_ko_phenotype"]
    else:
        tissue_expr_summary = h["tissue"]
        cell_type = h["cell_type"]
        pct = "—"
        ko_gene = "—"
        ko_pheno = h["ko_phenotype"]

    # FAERS
    faers_rows = [
        f for f in db["faers"]
        if f["drug_id"] == h["drug_id"]
        and h["ae_pattern"].lower() in f["ae_pt"].lower()
    ]
    if faers_rows:
        f0 = faers_rows[0]
        ror_f = f0["ror"]
        ebgm_f = f0["ebgm"]
        kids = f0["kor_kids_kd_signal"]
        hira = f0["hira_signal"]
        kcd = f0["kcd_code"]
    else:
        ror_f = "—"
        ebgm_f = "—"
        kids = "—"
        hira = "—"
        kcd = "—"

    # Receptor Ki for selectivity ratio
    rec_to_col = {
        "GLP-1R": "glp1r_ki_nm",
        "GIPR": "gipr_ki_nm",
        "GCGR": "gcgr_ki_nm",
        "AMYR": "amyr_ki_nm",
        "MC4R": "mc4r_ki_nm",
    }
    main_ki = fmt_ki(drug.get(rec_to_col.get(h["receptor"], ""), 9999))
    selectivity_ratio = (
        f"GLP-1R={fmt_ki(drug['glp1r_ki_nm'])} / "
        f"GIPR={fmt_ki(drug['gipr_ki_nm'])} / "
        f"GCGR={fmt_ki(drug['gcgr_ki_nm'])} / "
        f"AMYR={fmt_ki(drug['amyr_ki_nm'])} / "
        f"MC4R={fmt_ki(drug['mc4r_ki_nm'])}"
    )

    print()
    print(f"# Mechanism 가설: {h['mechanism_narrative_kor']}")
    print()
    print("## 약물·Receptor 정보")
    print(f"- 약물: {drug['drug_name']} ({drug['drug_class']}, sponsor: {drug['sponsor']}, phase: {drug['phase']})")
    print(f"- 관련 receptor: {h['receptor']} (Ki: {main_ki} nM)")
    print(f"- 선택성 프로파일 (Ki nM): {selectivity_ratio}")
    print()
    print("## 관련 AE pattern")
    print(f"- Trial: {trial}")
    print(f"- MedDRA SOC/PT: {soc} / {pt}")
    print(f"- 발생률: {inc}% (placebo {plac}%)")
    print(f"- ROR: {ror}")
    print(f"- Sub-group n: {n}")
    print()
    print("## Tissue/Cell-type 근거")
    print(f"- GTEx 54-tissue 발현: {tissue_expr_summary}")
    print(f"- Tabula Sapiens cell-type: {cell_type} ({pct}% expressing)")
    print(f"- IMPC KO mouse: {ko_gene}-/- {ko_pheno}")
    print()
    print("## Korean RWE")
    print(f"- FAERS ROR: {ror_f}, EBGM: {ebgm_f}")
    print(f"- KIDS-KD signal: {kids}")
    print(f"- HIRA: {hira}, KCD: {kcd}")
    print()
    print(f"## Bayesian Posterior: {posterior:.3f} (rank {rank}/{total}, evidence score {evidence:.1f})")
    print()
    print("## Grant proposal suggestion")
    print("- KDDF 차세대 항비만약 안전성 분과 IIT / NRF 중견연구 / KSSO 소형 IIT")
    print("- Korean cohort PoC: KOGES + NHIS-HEALS 백본 + HIRA claim linkage")
    print("- KFDA/EMA REMS 호환 모니터링: GLP-1/GIP/GCG triple agonist 안전성 layer")
    print(f"- 참고 PMID: {h['references_pmid']}")
    print()
    print("## OpenClaw 호환 layer")
    print("- 약물 재조합 안전성 layer JSON ready (--export-openclaw 로 출력)")
    print(f"- Hypothesis ID: {h['hypothesis_id']} / Drug ID: {h['drug_id']} / Receptor: {h['receptor']}")
    print()
    print("## ⚠️ 디스클레이머")
    print(DISCLAIMER)
    print()


# ----- 기능 5b: OpenClaw export -----
def cmd_export_openclaw(db, out_path):
    payload = {
        "schema_version": "1.0",
        "tool": "AntiObesityOffTargetMech-Kor",
        "disclaimer": DISCLAIMER,
        "drugs": [],
        "mechanism_hypotheses": [],
        "receptor_expression": [],
        "faers_kor": [],
    }

    for d in db["drugs"]:
        payload["drugs"].append({
            "drug_id": d["drug_id"],
            "drug_name": d["drug_name"],
            "drug_class": d["drug_class"],
            "sponsor": d["sponsor"],
            "phase": d["phase"],
            "indication": d["indication"],
            "selectivity_ki_nm": {
                "GLP-1R": fnum(d["glp1r_ki_nm"], None),
                "GIPR": fnum(d["gipr_ki_nm"], None),
                "GCGR": fnum(d["gcgr_ki_nm"], None),
                "AMYR": fnum(d["amyr_ki_nm"], None),
                "MC4R": fnum(d["mc4r_ki_nm"], None),
            },
        })

    for h in db["hyp"]:
        post, ev, sel, tis, ae_s, ko_s = bayesian_score(h, db)
        payload["mechanism_hypotheses"].append({
            "hypothesis_id": h["hypothesis_id"],
            "drug_id": h["drug_id"],
            "receptor": h["receptor"],
            "ae_pattern": h["ae_pattern"],
            "tissue": h["tissue"],
            "cell_type": h["cell_type"],
            "ko_phenotype": h["ko_phenotype"],
            "mechanism_narrative_kor": h["mechanism_narrative_kor"],
            "bayesian_posterior_recomputed": round(post, 4),
            "evidence_score": fnum(h["evidence_score"]),
            "axis_scores": {
                "selectivity": round(sel, 3),
                "tissue_expression": round(tis, 3),
                "ae_pattern": round(ae_s, 3),
                "ko_phenotype": round(ko_s, 3),
            },
            "references_pmid": h["references_pmid"],
        })

    for e in db["expr"]:
        payload["receptor_expression"].append({
            "receptor": e["receptor"],
            "tissue": e["tissue"],
            "gtex_tpm": fnum(e["gtex_tpm"]),
            "cell_type": e["tabula_sapiens_cell_type"],
            "pct_expressing": fnum(e["ts_pct_expressing_cells"]),
            "impc_ko_gene": e["impc_ko_gene"],
            "impc_ko_phenotype": e["impc_ko_phenotype"],
        })

    for f in db["faers"]:
        payload["faers_kor"].append({
            "drug_id": f["drug_id"],
            "ae_pt": f["ae_pt"],
            "kcd_code": f["kcd_code"],
            "exposure_n": fnum(f["exposure_n"]),
            "event_n": fnum(f["event_n"]),
            "ror": fnum(f["ror"]),
            "prr": fnum(f["prr"]),
            "ebgm": fnum(f["ebgm"]),
            "kor_kids_kd_signal": f["kor_kids_kd_signal"],
            "hira_signal": f["hira_signal"],
        })

    with open(out_path, "w", encoding="utf-8") as fp:
        json.dump(payload, fp, ensure_ascii=False, indent=2)

    print(f"✅ OpenClaw 호환 JSON export 완료: {out_path}")
    print(f"   - drugs: {len(payload['drugs'])}")
    print(f"   - mechanism_hypotheses: {len(payload['mechanism_hypotheses'])}")
    print(f"   - receptor_expression: {len(payload['receptor_expression'])}")
    print(f"   - faers_kor: {len(payload['faers_kor'])}")
    print(DISCLAIMER)


# ----- 기능: summary -----
def cmd_summary(db):
    print()
    print("=" * 90)
    print(" AntiObesityOffTargetMech-Kor (안티오베시티오프타겟메크코어)")
    print(" 차세대 항비만 multi-target agonist off-target mechanism 가설 생성기")
    print("=" * 90)
    print()
    print("[데이터 요약]")
    print(f"  - 약물: {len(db['drugs'])} 종 (multi-target agonist 9+ + liraglutide)")
    print(f"  - AE pattern rows: {len(db['ae'])} (STEP/SURMOUNT/TRIUMPH/MARITIDE post-hoc)")
    print(f"  - Receptor expression rows: {len(db['expr'])} (GTEx + Tabula Sapiens + IMPC KO)")
    print(f"  - FAERS/KIDS-KD/HIRA rows: {len(db['faers'])}")
    print(f"  - Mechanism 가설: {len(db['hyp'])}")
    print()
    print("[5대 기능]")
    print("  1. --selectivity-matrix     약물 × 5 receptor Ki 매트릭스")
    print("  2. --tissue-heatmap <REC>   GTEx + Tabula Sapiens + IMPC KO heat-map")
    print("  3. --rank-mechanism --top N off-target 가설 Bayesian ranking")
    print("  4. --card '<query>'         한국어 hypothesis card (grant proposal 포함)")
    print("  5. --export-openclaw <path> OpenClaw 약물 재조합 DB JSON export")
    print()
    print("[Ranking 가중치]")
    print("  Selectivity 0.30 + Tissue 0.25 + AE 0.30 + KO 0.15 (+ prior 0.50 blend)")
    print()
    print("[데이터 소스(시뮬레이션)]")
    print("  - STEP/SURMOUNT/TRIUMPH post-hoc AE")
    print("  - GTEx 54-tissue + Tabula Sapiens cell atlas + IMPC KO mouse")
    print("  - BindingDB receptor selectivity (Ki/EC50)")
    print("  - FAERS + KIDS-KD + HIRA Korean claims (KCD codes)")
    print()
    print(DISCLAIMER)
    print("=" * 90)
    print()


# ----- argparse -----
def build_parser():
    parser = argparse.ArgumentParser(
        prog="AntiObesityOffTargetMech-Kor",
        description=(
            "AntiObesityOffTargetMech-Kor (안티오베시티오프타겟메크코어): "
            "차세대 항비만 multi-target agonist off-target mechanism 가설 생성기. "
            + DISCLAIMER
        ),
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=(
            "예시:\n"
            "  python3 main.py --summary\n"
            "  python3 main.py --selectivity-matrix\n"
            "  python3 main.py --tissue-heatmap GLP-1R\n"
            "  python3 main.py --rank-mechanism --top 15\n"
            "  python3 main.py --rank-mechanism --top 10 --drug retatrutide\n"
            "  python3 main.py --card 'GCG-mediated transient ALT elevation'\n"
            "  python3 main.py --export-openclaw output.json\n"
            "\n" + DISCLAIMER
        ),
    )
    parser.add_argument("--summary", action="store_true", help="도구 개요 요약 출력")
    parser.add_argument("--selectivity-matrix", action="store_true",
                        help="9+ 약물 × 5 receptor selectivity matrix")
    parser.add_argument("--tissue-heatmap", metavar="RECEPTOR",
                        help="receptor (GLP-1R/GIPR/GCGR/AMYR/MC4R) GTEx 54-tissue heat-map")
    parser.add_argument("--rank-mechanism", action="store_true",
                        help="off-target mechanism 가설 Bayesian ranking")
    parser.add_argument("--top", type=int, default=15,
                        help="ranking 상위 N (default 15)")
    parser.add_argument("--drug", metavar="DRUG",
                        help="특정 약물 필터 (drug_id 또는 drug_name)")
    parser.add_argument("--card", metavar="QUERY",
                        help="hypothesis_id 또는 narrative/ae 키워드로 한국어 card 출력")
    parser.add_argument("--export-openclaw", metavar="PATH",
                        help="OpenClaw 약물 재조합 DB JSON export")
    return parser


def main():
    parser = build_parser()
    args = parser.parse_args()

    # default: summary
    if not any([
        args.summary,
        args.selectivity_matrix,
        args.tissue_heatmap,
        args.rank_mechanism,
        args.card,
        args.export_openclaw,
    ]):
        parser.print_help()
        return 0

    try:
        db = load_all()
    except FileNotFoundError as e:
        print(f"❌ 데이터 파일 누락: {e}")
        return 1

    if args.summary:
        cmd_summary(db)
    if args.selectivity_matrix:
        cmd_selectivity_matrix(db)
    if args.tissue_heatmap:
        cmd_tissue_heatmap(db, args.tissue_heatmap)
    if args.rank_mechanism:
        cmd_rank_mechanism(db, args.top, args.drug)
    if args.card:
        cmd_card(db, args.card)
    if args.export_openclaw:
        cmd_export_openclaw(db, args.export_openclaw)

    return 0


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