#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MASLDGuidelineDeltaWatch (매슬드가이드라인델타워치)
=========================================================
Multi-society MASLD/MASH guideline draft/update/correction delta watch
+ recommendation cascade + NIT cutoff/drug label change detection
+ Korean population extrapolation + KASL submission template draft
+ MASLDStager staging logic delta alert.

Domain: MASLD (Metabolic dysfunction-Associated Steatotic Liver Disease)
Category: Research alert (guideline curation)

Usage: python3 main.py --help
"""

from __future__ import annotations

import argparse
import csv
import difflib
import json
import os
import sys
from datetime import datetime, date
from typing import Any, Dict, List, Optional, Tuple

ROOT = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(ROOT, "data")
GUIDELINES_DIR = os.path.join(DATA_DIR, "guidelines")

DISCLAIMER = (
    "[디스클레이머] 본 출력물은 참고용·연구용입니다. "
    "KASL/AASLD/EASL 가이드라인 정식 의견서 제출 전 본인 및 위원회 검토가 필요합니다. "
    "현재 mock data 기반 데모이며 외부 API 호출 없습니다."
)

# ---------- Loaders ----------------------------------------------------------

def load_json(path: str) -> Dict[str, Any]:
    with open(path, "r", encoding="utf-8") as f:
        return json.load(f)


def list_guideline_files(society: Optional[str] = None) -> List[str]:
    out: List[str] = []
    if not os.path.isdir(GUIDELINES_DIR):
        return out
    for fn in sorted(os.listdir(GUIDELINES_DIR)):
        if not fn.endswith(".json"):
            continue
        if society and society != "all":
            if not fn.lower().startswith(society.lower() + "_"):
                continue
        out.append(os.path.join(GUIDELINES_DIR, fn))
    return out


def load_guideline_by_society_version(society: str, version: str) -> Dict[str, Any]:
    target = f"{society.lower()}_{version}.json"
    p = os.path.join(GUIDELINES_DIR, target)
    if not os.path.exists(p):
        for fn in os.listdir(GUIDELINES_DIR):
            if fn.lower() == target:
                p = os.path.join(GUIDELINES_DIR, fn)
                break
    if not os.path.exists(p):
        raise FileNotFoundError(
            f"Guideline file not found for society='{society}', version='{version}' "
            f"(looked for {target})"
        )
    return load_json(p)


def load_cite_papers() -> Dict[str, Dict[str, Any]]:
    p = os.path.join(DATA_DIR, "cite_papers.json")
    raw = load_json(p)
    return {paper["pmid"]: paper for paper in raw.get("papers", [])}


def load_drugs() -> Dict[str, Dict[str, Any]]:
    p = os.path.join(DATA_DIR, "drugs_label.json")
    raw = load_json(p)
    return {d["drug"]: d for d in raw.get("drugs", [])}


def load_nit_cutoffs() -> List[Dict[str, str]]:
    p = os.path.join(DATA_DIR, "nit_cutoffs.csv")
    rows: List[Dict[str, str]] = []
    with open(p, "r", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for r in reader:
            rows.append(r)
    return rows


def load_masldstager_logic() -> Dict[str, Any]:
    return load_json(os.path.join(DATA_DIR, "masldstager_logic.json"))


def load_kasl_template() -> str:
    p = os.path.join(DATA_DIR, "kasl_comment_template.md")
    with open(p, "r", encoding="utf-8") as f:
        return f.read()


def find_recommendation(rec_id: str) -> Optional[Tuple[Dict[str, Any], Dict[str, Any]]]:
    """Returns (guideline, recommendation) or None."""
    for path in list_guideline_files():
        g = load_json(path)
        for rec in g.get("recommendations", []):
            if rec.get("recommendation_id") == rec_id:
                return g, rec
    return None


# ---------- ingest -----------------------------------------------------------

def cmd_ingest(args: argparse.Namespace) -> None:
    society = args.society or "all"
    files = list_guideline_files(society if society != "all" else None)
    if not files:
        print(f"[INGEST] No guideline files found for society='{society}'.")
        return
    summary: List[str] = []
    total_recs = 0
    for fp in files:
        g = load_json(fp)
        n = len(g.get("recommendations", []))
        total_recs += n
        summary.append(
            f"  - {g.get('society','?')} / {g.get('version','?')} "
            f"({g.get('status','?')}) -> {n} recommendations  [{os.path.basename(fp)}]"
        )
    print(f"[INGEST] society={society}  files={len(files)}  total_recommendations={total_recs}")
    print("\n".join(summary))
    print(DISCLAIMER)


# ---------- diff -------------------------------------------------------------

def _index_recs(g: Dict[str, Any]) -> Dict[str, Dict[str, Any]]:
    return {r["recommendation_id"]: r for r in g.get("recommendations", [])}


def _topic_index(g: Dict[str, Any]) -> Dict[str, List[Dict[str, Any]]]:
    out: Dict[str, List[Dict[str, Any]]] = {}
    for r in g.get("recommendations", []):
        out.setdefault(r.get("topic", ""), []).append(r)
    return out


def _match_pair(
    src: Dict[str, Dict[str, Any]],
    dst: Dict[str, Dict[str, Any]],
    src_g: Dict[str, Any],
    dst_g: Dict[str, Any],
) -> List[Tuple[Optional[Dict[str, Any]], Optional[Dict[str, Any]], str]]:
    """Match recs across versions. Use 'supersedes' field, then topic fallback."""
    pairs: List[Tuple[Optional[Dict[str, Any]], Optional[Dict[str, Any]], str]] = []
    used_src: set = set()
    used_dst: set = set()

    # Pass 1: supersedes link in dst -> src
    for did, dr in dst.items():
        sup = dr.get("supersedes") or []
        for sid in sup:
            if sid in src:
                pairs.append((src[sid], dr, "supersedes"))
                used_src.add(sid)
                used_dst.add(did)
                break

    # Pass 2: same recommendation_id (rare across versions)
    for sid, sr in src.items():
        if sid in used_src:
            continue
        if sid in dst:
            pairs.append((sr, dst[sid], "same_id"))
            used_src.add(sid)
            used_dst.add(sid)

    # Pass 3: topic fallback (1:1 only)
    src_by_topic = _topic_index({"recommendations": [r for sid, r in src.items() if sid not in used_src]})
    dst_by_topic = _topic_index({"recommendations": [r for did, r in dst.items() if did not in used_dst]})
    for topic, srecs in src_by_topic.items():
        drecs = dst_by_topic.get(topic, [])
        if len(srecs) == 1 and len(drecs) == 1:
            pairs.append((srecs[0], drecs[0], "topic_match"))
            used_src.add(srecs[0]["recommendation_id"])
            used_dst.add(drecs[0]["recommendation_id"])

    # Remaining: deletions (in src not in dst) and additions (in dst not in src)
    for sid, sr in src.items():
        if sid not in used_src:
            pairs.append((sr, None, "deleted_or_dropped"))
    for did, dr in dst.items():
        if did not in used_dst:
            pairs.append((None, dr, "newly_added"))
    return pairs


def _diff_field(label: str, a: Any, b: Any) -> Optional[str]:
    if a == b:
        return None
    if isinstance(a, list) and isinstance(b, list):
        added = [x for x in b if x not in a]
        removed = [x for x in a if x not in b]
        chunks = []
        if added:
            chunks.append(f"+ {added}")
        if removed:
            chunks.append(f"- {removed}")
        return f"  · {label}: " + "  ".join(chunks)
    if isinstance(a, str) and isinstance(b, str) and (len(a) > 60 or len(b) > 60):
        diff = list(
            difflib.unified_diff(
                a.split(". "), b.split(". "), lineterm="", n=1
            )
        )
        if not diff:
            return f"  · {label}: <text reformat only>"
        compact = "\n".join("    " + d for d in diff if d.strip())
        return f"  · {label}:\n{compact}"
    return f"  · {label}: {a!r}  ->  {b!r}"


def cmd_diff(args: argparse.Namespace) -> None:
    src = load_guideline_by_society_version(args.society, getattr(args, "from_version"))
    dst = load_guideline_by_society_version(args.society, args.to)
    src_idx = _index_recs(src)
    dst_idx = _index_recs(dst)
    pairs = _match_pair(src_idx, dst_idx, src, dst)

    added = [p for p in pairs if p[0] is None]
    deleted = [p for p in pairs if p[1] is None]
    changed = [p for p in pairs if p[0] is not None and p[1] is not None]

    print(f"# Diff: {args.society} {getattr(args,'from_version')} -> {args.to}")
    print(f"  source: {src.get('title')}")
    print(f"  target: {dst.get('title')}")
    print(
        f"  summary: {len(changed)} matched, "
        f"{len(added)} added, {len(deleted)} deleted/dropped"
    )
    print()

    print("## Added recommendations")
    if not added:
        print("  (none)")
    for _src, dr, _why in added:
        print(f"  + [{dr['recommendation_id']}] ({dr['topic']}) {dr['text_kr_summary']}")
    print()

    print("## Deleted/superseded recommendations")
    if not deleted:
        print("  (none)")
    for sr, _dst, why in deleted:
        print(f"  - [{sr['recommendation_id']}] ({sr['topic']}) {sr['text_kr_summary']}  [{why}]")
    print()

    print("## Changed recommendations")
    if not changed:
        print("  (none)")
    for sr, dr, why in changed:
        diffs: List[str] = []
        for f in ("text_en", "strength", "evidence_grade", "cite_pmids", "related_nits", "related_drugs"):
            d = _diff_field(f, sr.get(f), dr.get(f))
            if d:
                diffs.append(d)
        if not diffs:
            continue
        print(f"  ~ [{sr['recommendation_id']} -> {dr['recommendation_id']}] ({dr['topic']})  match={why}")
        for d in diffs:
            print(d)
        print()
    print(DISCLAIMER)


# ---------- evidence-trace ---------------------------------------------------

ALERT_RETRACTION = {"retracted": "RED", "expression_of_concern": "ORANGE", "correction": "YELLOW", "erratum": "YELLOW"}


def cmd_evidence_trace(args: argparse.Namespace) -> None:
    found = find_recommendation(args.recommendation)
    if not found:
        print(f"[EVIDENCE-TRACE] recommendation_id not found: {args.recommendation}")
        return
    g, rec = found
    papers = load_cite_papers()
    print(f"# Evidence trace: {rec['recommendation_id']}")
    print(f"  guideline: {g.get('society')} {g.get('version')}")
    print(f"  topic: {rec.get('topic')}  strength: {rec.get('strength')}  grade: {rec.get('evidence_grade')}")
    print(f"  text(KR): {rec.get('text_kr_summary')}")
    print()

    cites = rec.get("cite_pmids", [])
    if not cites:
        print("  (no cited PMIDs)")
        return

    alerts: List[str] = []
    for pmid in cites:
        p = papers.get(pmid)
        if not p:
            print(f"  - PMID {pmid}: not found in cite_papers.json")
            continue
        rs = p.get("retraction_status", "none")
        ppc = p.get("pubpeer_comment_count", 0)
        ltr = p.get("letter_to_editor_count", 0)
        flag = ALERT_RETRACTION.get(rs, "GREEN")
        line = (
            f"  - PMID {pmid} | {p.get('journal','')} {p.get('year','')} | "
            f"retraction={rs} ({flag}) | pubpeer={ppc} | letter_to_editor={ltr}"
        )
        if rs in ("retracted", "expression_of_concern", "correction", "erratum"):
            alerts.append(f"** ALERT [{flag}]: PMID {pmid} retraction_status={rs} → 권고 영향 검토 필요")
        elif ppc >= 2 or ltr >= 3:
            alerts.append(f"   notice: PMID {pmid} pubpeer/letter 활동 활발 (pubpeer={ppc}, letters={ltr})")
        print(line)

    print()
    if alerts:
        print("## Cascade alerts")
        for a in alerts:
            print(" ", a)
    else:
        print("## Cascade alerts")
        print("  (none)")
    print()
    print(DISCLAIMER)


# ---------- nit-cutoff -------------------------------------------------------

def cmd_nit_cutoff(args: argparse.Namespace) -> None:
    rows = load_nit_cutoffs()
    bm = (args.biomarker or "").upper() if args.biomarker else None
    if bm:
        # normalize FIB-4 etc
        norm = {"FIB4": "FIB-4"}
        bm = norm.get(bm, bm)
    print("# NIT cutoff timeline")
    last_bm: Optional[str] = None
    last_pair: Optional[Tuple[str, str]] = None
    for r in rows:
        if bm and r["biomarker"].upper() != bm.upper():
            continue
        cur_bm = r["biomarker"]
        if cur_bm != last_bm:
            print()
            print(f"## {cur_bm}")
            last_bm = cur_bm
            last_pair = None
        cur_pair = (r.get("low_cutoff", ""), r.get("high_cutoff", ""))
        marker = ""
        if last_pair is not None and cur_pair != last_pair:
            marker = "  <-- CHANGED"
        last_pair = cur_pair
        age = r.get("age_specific", "")
        age_s = f"  (age: {age})" if age else ""
        print(
            f"  {r['society']:6s} | {r['version']:24s} | low={r.get('low_cutoff',''):>5} "
            f"high={r.get('high_cutoff',''):>5} | {r.get('indication','')}{age_s}{marker}"
        )
    print()
    print(DISCLAIMER)


# ---------- drug-update ------------------------------------------------------

def cmd_drug_update(args: argparse.Namespace) -> None:
    drugs = load_drugs()
    targets = [args.drug] if args.drug else list(drugs.keys())
    print("# Drug guideline + label cross-link")
    for dn in targets:
        d = drugs.get(dn)
        if not d:
            print(f"  - {dn}: not found in drugs_label.json")
            continue
        print()
        print(f"## {d.get('drug')}  ({d.get('brand','-')})")
        print(f"  FDA: approval={d.get('FDA_approval_date')} status={d.get('FDA_status','approved')} indication={d.get('FDA_indication','-')}")
        print(f"  EMA: approval={d.get('EMA_approval_date')} status={d.get('EMA_status','-')}")
        print(f"  MFDS: approval={d.get('MFDS_approval_date')} status={d.get('MFDS_status','-')}")
        print(f"  AE warning: {d.get('AE_warning','-')}")
        print(f"  Dosing: {d.get('dosing','-')}")
        # cross-link guidelines mentioning this drug
        mentions: List[str] = []
        for path in list_guideline_files():
            g = load_json(path)
            for rec in g.get("recommendations", []):
                if dn in (rec.get("related_drugs") or []):
                    mentions.append(
                        f"    - {g.get('society')} {g.get('version')}  "
                        f"[{rec['recommendation_id']}] {rec.get('topic')} "
                        f"(strength={rec.get('strength')}, grade={rec.get('evidence_grade')})"
                    )
        if mentions:
            print("  Guideline cross-references:")
            for m in mentions:
                print(m)
        else:
            print("  Guideline cross-references: (none)")
    print()
    print(DISCLAIMER)


# ---------- korean-comment ---------------------------------------------------

def _korean_extrapolation_eval(rec: Dict[str, Any]) -> str:
    related_nits = rec.get("related_nits", [])
    drugs = rec.get("related_drugs", [])
    bullets: List[str] = []
    if "FIB-4" in related_nits:
        bullets.append("FIB-4: 한국 검증 cohort 다수, 65세 이상 cutoff 2.0 적용 권고 (KASL 2025 draft 수용).")
    if "LSM" in related_nits:
        bullets.append("LSM: 한국 NHIS 보험급여(VCTE) 적용 가능, rule-in 12 vs 15 kPa 한국 cohort 검증 필요.")
    if "ELF" in related_nits:
        bullets.append("ELF: MFDS 검토 중, 보험급여 미정 → 비급여 비용 부담 발생 가능.")
    if "CAP" in related_nits or "PDFF" in related_nits:
        bullets.append("CAP/MRI-PDFF: 3차 병원 중심 가용, 1차 의료 접근성 제한.")
    if "resmetirom" in drugs:
        bullets.append("Resmetirom: MFDS 심사 중(2026), 급여 미정. 도입 시 BMI/허리둘레 한국 cutoff 적용 + 간 효소 모니터링 강화 필요.")
    if "semaglutide" in drugs or "tirzepatide" in drugs:
        bullets.append("GLP-1 RA / dual agonist: 한국 보험 급여 비만 indication 제한적, MASLD off-label 가능성. 한국인 GI tolerability 데이터 축적 중.")
    if "pioglitazone" in drugs:
        bullets.append("Pioglitazone: 한국 보험 급여(T2DM) 가능, 체중증가/골절 위험 한국 노년 인구 주의.")
    if "vitamin_e" in drugs:
        bullets.append("Vitamin E: 비급여, 환자 자가구매. 한국 표준 dose 가이드 부재.")
    if not bullets:
        bullets.append("일반: 한국 BMI 23/25 cutoff, 허리둘레 90/85cm 적용을 권고 텍스트에 명시 검토.")
    bullets.append("보험: KASL 의견서에 NHIS 등재 cost-effectiveness 분석 첨부 권고.")
    return "\n".join(f"  - {b}" for b in bullets)


def cmd_korean_comment(args: argparse.Namespace) -> None:
    found = find_recommendation(args.recommendation)
    if not found:
        print(f"[KOREAN-COMMENT] recommendation_id not found: {args.recommendation}")
        return
    g, rec = found
    template = load_kasl_template()
    today = date.today().isoformat()
    filled = template
    replacements = {
        "{{guideline_title}}": g.get("title", ""),
        "{{version}}": g.get("version", ""),
        "{{recommendation_id}}": rec.get("recommendation_id", ""),
        "{{topic}}": rec.get("topic", ""),
        "{{recommendation_text_en}}": rec.get("text_en", ""),
        "{{recommendation_text_kr}}": rec.get("text_kr_summary", ""),
        "{{cite_pmids}}": ", ".join(rec.get("cite_pmids", []) or ["(없음)"]),
        "{{user_input}}": "[작성자 입력 필요]",
    }
    for k, v in replacements.items():
        filled = filled.replace(k, str(v))

    extrap = _korean_extrapolation_eval(rec)
    # Inject auto-generated KR extrapolation block into section 5 (between markers)
    inject_marker = "## 6. 의견 본문"
    auto_block = (
        "\n### 5.4 자동 외삽 평가 (auto-generated)\n"
        f"{extrap}\n\n"
    )
    filled = filled.replace(inject_marker, auto_block + inject_marker, 1)

    print(filled)
    print(f"\n생성일: {today}")
    print(DISCLAIMER)


# ---------- masldstager-impact -----------------------------------------------

def cmd_masldstager_impact(args: argparse.Namespace) -> None:
    logic = load_masldstager_logic()
    rules = logic.get("rules", [])
    print("# MASLDStager staging logic delta impact map")
    print(f"  total rules: {len(rules)}")
    print()

    # Build an index of all current recommendation_ids
    all_recs: Dict[str, Tuple[str, str]] = {}
    for path in list_guideline_files():
        g = load_json(path)
        for rec in g.get("recommendations", []):
            all_recs[rec["recommendation_id"]] = (g.get("society", "?"), g.get("version", "?"))

    for r in rules:
        deps = r.get("depends_on_recommendation_ids", [])
        cutoffs = r.get("depends_on_nit_cutoff", [])
        print(f"## {r['rule_id']}: {r['description']}")
        if deps:
            print("  Linked recommendations:")
            for d in deps:
                if d in all_recs:
                    soc, ver = all_recs[d]
                    print(f"    - {d}  ({soc} {ver})")
                else:
                    print(f"    - {d}  (not in current dataset → IMPACT: rule may need update)")
        if cutoffs:
            print("  Cutoff dependencies:")
            for c in cutoffs:
                print(f"    - {c}")
        # Detect cutoff drift between draft and prior versions
        if cutoffs:
            for c in cutoffs:
                bm = c.get("biomarker")
                drift_versions: List[str] = []
                rows = load_nit_cutoffs()
                for row in rows:
                    if row["biomarker"].upper() == (bm or "").upper():
                        try:
                            row_high = float(row.get("high_cutoff") or 0)
                        except ValueError:
                            row_high = 0
                        if row_high and c.get("high") and row_high != float(c["high"]):
                            drift_versions.append(f"{row['society']}/{row['version']}({row.get('high_cutoff')})")
                if drift_versions:
                    print(f"  ⚠ CUTOFF DRIFT for {bm}: rule expects high={c.get('high')} but observed: {', '.join(drift_versions)}")
                    print(f"    → MASLDStager rule {r['rule_id']} 업데이트 검토 필요.")
        print()
    print(DISCLAIMER)


# ---------- digest -----------------------------------------------------------

def _days_until(d_iso: str) -> Optional[int]:
    try:
        target = datetime.strptime(d_iso, "%Y-%m-%d").date()
    except (ValueError, TypeError):
        return None
    return (target - date.today()).days


def cmd_digest(args: argparse.Namespace) -> None:
    korean = bool(args.korean)
    files = list_guideline_files()
    drafts: List[Tuple[str, str, str, Optional[int]]] = []
    changes: List[str] = []
    for path in files:
        g = load_json(path)
        if g.get("status") == "draft_public_consultation":
            close = g.get("public_consultation_close")
            d_left = _days_until(close) if close else None
            drafts.append((g["society"], g["version"], close or "?", d_left))
        # detect supersedes
        for rec in g.get("recommendations", []):
            if rec.get("supersedes"):
                changes.append(
                    f"{g['society']} {g['version']}: [{rec['recommendation_id']}] "
                    f"({rec['topic']}) supersedes {rec['supersedes']}"
                )

    header = "# 주간 다이제스트 (MASLD Guideline Delta)" if korean else "# Weekly digest (MASLD Guideline Delta)"
    print(header)
    print(f"  생성일: {date.today().isoformat()}")
    print()

    print("## Public consultation drafts (마감일)")
    if not drafts:
        print("  (현재 진행 중 draft 없음)")
    for soc, ver, close, dl in drafts:
        flag = ""
        if dl is not None:
            if dl <= 1:
                flag = "  🔴 D-{}".format(dl)
            elif dl <= 7:
                flag = "  🟠 D-{}".format(dl)
            elif dl <= 30:
                flag = "  🟡 D-{}".format(dl)
            else:
                flag = f"  🟢 D-{dl}"
        print(f"  - {soc} {ver}  | close={close}{flag}")
    print()

    print("## Top emerging recommendation changes")
    if not changes:
        print("  (none)")
    else:
        for c in changes[:10]:
            print(f"  - {c}")
    print()

    print("## Telegram/Slack 알림 시뮬레이션")
    print("  ─────────────────────────────────────")
    print("  📣 [MASLD Guideline Delta Watch]")
    if drafts:
        for soc, ver, close, dl in drafts:
            if dl is not None and dl <= 30:
                print(f"  • {soc} {ver} 의견서 마감 {close} (D-{dl})")
    if changes:
        print(f"  • 주요 권고 변경 {len(changes)}건 발생 (LSM rule-in cutoff 12→15 kPa, FIB-4 65세 임계값 2.0, Resmetirom F2-F3 정식 권고).")
    print("  • 주의: mock data, 실 운용시 PubMed/RetractionWatch live polling 필요.")
    print("  ─────────────────────────────────────")
    print()
    print(DISCLAIMER)


# ---------- argparse setup ---------------------------------------------------

def build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(
        prog="masld-delta-watch",
        description="MASLDGuidelineDeltaWatch — multi-society MASLD/MASH guideline delta + Korean comment + MASLDStager impact alerts",
    )
    sub = p.add_subparsers(dest="cmd", required=True)

    s = sub.add_parser("ingest", help="Load and summarize mock guideline files")
    s.add_argument("--society", choices=["aasld", "easl", "apasl", "kasl", "nice", "aisf", "aleh", "all"], default="all")
    s.set_defaults(func=cmd_ingest)

    s = sub.add_parser("diff", help="Recommendation-level diff between two versions of a society guideline")
    s.add_argument("--society", required=True)
    s.add_argument("--from", dest="from_version", required=True, help="source version")
    s.add_argument("--to", required=True, help="target version")
    s.set_defaults(func=cmd_diff)

    s = sub.add_parser("evidence-trace", help="Trace cited PMIDs of a recommendation; flag retractions/letters")
    s.add_argument("--recommendation", required=True, help="recommendation_id (e.g., AASLD-2024-R08)")
    s.set_defaults(func=cmd_evidence_trace)

    s = sub.add_parser("nit-cutoff", help="NIT cutoff timeline across societies/versions")
    s.add_argument("--biomarker", choices=["fib4", "fib-4", "nfs", "fast", "mast", "mefib", "lsm", "cap", "mre", "pdff", "elf"], default=None)
    s.set_defaults(func=cmd_nit_cutoff)

    s = sub.add_parser("drug-update", help="Drug FDA/EMA/MFDS label + guideline cross-link")
    s.add_argument("--drug", choices=["resmetirom", "pegozafermin", "efruxifermin", "survodutide", "semaglutide", "tirzepatide", "pioglitazone", "vitamin_e", "retatrutide"], default=None)
    s.set_defaults(func=cmd_drug_update)

    s = sub.add_parser("korean-comment", help="Generate KASL submission template draft for one recommendation")
    s.add_argument("--recommendation", required=True)
    s.set_defaults(func=cmd_korean_comment)

    s = sub.add_parser("masldstager-impact", help="Map guideline changes to MASLDStager pinpoint staging rules")
    s.set_defaults(func=cmd_masldstager_impact)

    s = sub.add_parser("digest", help="Weekly digest (top changes + draft deadlines + alert simulation)")
    s.add_argument("--korean", action="store_true", help="한국어 헤더")
    s.set_defaults(func=cmd_digest)

    return p


def main(argv: Optional[List[str]] = None) -> int:
    parser = build_parser()
    args = parser.parse_args(argv)
    try:
        args.func(args)
    except FileNotFoundError as e:
        print(f"[ERROR] {e}", file=sys.stderr)
        return 2
    except Exception as e:
        print(f"[ERROR] {type(e).__name__}: {e}", file=sys.stderr)
        return 1
    return 0


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