#!/usr/bin/env python3
"""Feishu/Lark group summary helper for Codex."""

from __future__ import annotations

import argparse
import json
import os
import shutil
import subprocess
import sys
from datetime import date, datetime, time, timedelta
from pathlib import Path
from textwrap import shorten


ALL_DOMAINS = [str(i) for i in range(1, 20)]
SKILL_DIR = Path(__file__).resolve().parents[1]
KAMI_DIR = SKILL_DIR / "vendor" / "kami"
DEFAULT_HEADER_LOGO = SKILL_DIR / "assets" / "header-logo.png"


def ensure_homebrew_library_path() -> None:
    """Help WeasyPrint/cffi find Homebrew's glib/pango libraries on macOS."""
    lib = Path("/opt/homebrew/lib")
    if not lib.exists():
        return
    key = "DYLD_FALLBACK_LIBRARY_PATH"
    parts = [p for p in os.environ.get(key, "").split(":") if p]
    if str(lib) not in parts:
        os.environ[key] = ":".join([str(lib), *parts])


ensure_homebrew_library_path()


def run(cmd: list[str], *, cwd: Path | None = None, check: bool = True, input_text: str | None = None) -> subprocess.CompletedProcess[str]:
    print("+ " + " ".join(cmd), file=sys.stderr)
    return subprocess.run(cmd, cwd=str(cwd) if cwd else None, input=input_text, text=True, capture_output=False, check=check)


def capture(cmd: list[str], *, cwd: Path | None = None, check: bool = True) -> subprocess.CompletedProcess[str]:
    print("+ " + " ".join(cmd), file=sys.stderr)
    return subprocess.run(cmd, cwd=str(cwd) if cwd else None, text=True, capture_output=True, check=check)


def require_lark_cli() -> None:
    if not shutil.which("lark-cli"):
        raise SystemExit("lark-cli not found. Run the setup command first.")


def install_deps(_: argparse.Namespace) -> None:
    """Install the runtime dependencies this skill needs on the local machine."""
    if not shutil.which("lark-cli"):
        run(["npx", "@larksuite/cli@latest", "install"])
    else:
        print("lark-cli already installed.")

    run([
        sys.executable,
        "-m",
        "pip",
        "install",
        "--upgrade",
        "weasyprint",
        "reportlab",
        "pypdf",
        "PyYAML",
        "pillow",
    ])

    if shutil.which("brew"):
        brew_packages = ["glib", "pango", "gdk-pixbuf", "libffi", "cairo"]
        if not shutil.which("pdftoppm"):
            brew_packages.append("poppler")
        run(["brew", "install", *brew_packages], check=False)
    elif not shutil.which("pdftoppm"):
        print("pdftoppm not found and Homebrew is unavailable. Install poppler for render checks.", file=sys.stderr)
    else:
        print("pdftoppm already installed.")

    if not shutil.which("pdftoppm"):
        if shutil.which("brew"):
            run(["brew", "install", "poppler"], check=False)
        else:
            print("pdftoppm not found and Homebrew is unavailable. Install poppler for render checks.", file=sys.stderr)
    else:
        print("pdftoppm already installed.")

    check_deps(argparse.Namespace())


def check_deps(_: argparse.Namespace) -> None:
    checks = {
        "lark-cli": bool(shutil.which("lark-cli")),
        "pdftoppm": bool(shutil.which("pdftoppm")),
    }
    for mod in ["weasyprint", "reportlab", "pypdf", "yaml", "PIL"]:
        try:
            __import__(mod)
            checks[mod] = True
        except Exception:
            checks[mod] = False
    print(json.dumps(checks, ensure_ascii=False, indent=2))


def setup(_: argparse.Namespace) -> None:
    if not shutil.which("lark-cli"):
        run(["npx", "@larksuite/cli@latest", "install"])
    require_lark_cli()
    status = capture(["lark-cli", "doctor"], check=False)
    if status.returncode == 0:
        print("lark-cli is already configured.")
        return
    print(status.stdout or status.stderr)
    print("Starting interactive app configuration. Open the printed URL or scan the QR code.")
    run(["lark-cli", "config", "init", "--new"], check=True)


def auth_all(_: argparse.Namespace) -> None:
    require_lark_cli()
    print("Requesting all domains and all permissions. Confirm in the Feishu/Lark browser page.")
    # The CLI prompt accepts numbers. This selects 1..19, confirms, then chooses option 2: 全部权限.
    run(["lark-cli", "auth", "login"], input_text="\n".join(ALL_DOMAINS + ["0", "2", ""]))
    run(["lark-cli", "auth", "status"], check=False)


def parse_json_output(text: str) -> dict:
    text = text.strip()
    start = text.find("{")
    if start > 0:
        text = text[start:]
    return json.loads(text)


def collect(args: argparse.Namespace) -> None:
    require_lark_cli()
    out_dir = Path(args.out).resolve()
    out_dir.mkdir(parents=True, exist_ok=True)
    ranges = collect_ranges(args)
    written = []
    for label, start, end in ranges:
        data = collect_one_range(args, start=start, end=end)
        suffix = f".{label}" if label else ""
        path = out_dir / f"messages{suffix}.json"
        path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
        print(path)
        written.append(path)
        resources = []
        for msg in data.get("data", {}).get("messages", []):
            for res in msg.get("resources", []) or []:
                resources.append(res)
        if resources:
            res_path = out_dir / f"resources{suffix}.json"
            res_path.write_text(json.dumps(resources, ensure_ascii=False, indent=2), encoding="utf-8")
            print(res_path)
    if len(written) > 1:
        merged = merge_message_files(written)
        merged_path = out_dir / "messages.json"
        merged_path.write_text(json.dumps(merged, ensure_ascii=False, indent=2), encoding="utf-8")
        print(merged_path)


def collect_ranges(args: argparse.Namespace) -> list[tuple[str, str | None, str | None]]:
    if args.start or args.end or args.period == "custom":
        return [("", args.start, args.end)]
    now = datetime.now().astimezone()
    today_start = datetime.combine(date.today(), time.min).astimezone()
    seven_day_start = now - timedelta(days=7)
    if args.period == "today":
        return [("today", today_start.isoformat(), now.isoformat())]
    if args.period == "7d":
        return [("7d", seven_day_start.isoformat(), now.isoformat())]
    return [
        ("today", today_start.isoformat(), now.isoformat()),
        ("7d", seven_day_start.isoformat(), now.isoformat()),
    ]


def collect_one_range(args: argparse.Namespace, *, start: str | None, end: str | None) -> dict:
    cmd = [
        "lark-cli",
        "im",
        "+chat-messages-list",
        "--chat-id",
        args.chat_id,
        "--page-size",
        str(args.page_size),
        "--order",
        args.order,
        "--download-resources",
        "--format",
        "json",
    ]
    if start:
        cmd += ["--start", start]
    if end:
        cmd += ["--end", end]
    proc = capture(cmd)
    return parse_json_output(proc.stdout)


def merge_message_files(paths: list[Path]) -> dict:
    seen = set()
    messages = []
    base: dict | None = None
    for path in paths:
        data = json.loads(path.read_text(encoding="utf-8"))
        if base is None:
            base = data
        for msg in data.get("data", {}).get("messages", []):
            key = msg.get("message_id") or f"{msg.get('create_time')}:{msg.get('message_position')}"
            if key in seen:
                continue
            seen.add(key)
            messages.append(msg)
    messages.sort(key=lambda m: (m.get("create_time", ""), str(m.get("message_position", ""))), reverse=True)
    if base is None:
        base = {"ok": True, "data": {}}
    base.setdefault("data", {})["messages"] = messages
    base["data"]["total"] = len(messages)
    base["data"]["periods"] = [p.stem.replace("messages.", "") for p in paths]
    return base


def paragraph(text: str, style):
    from reportlab.platypus import Paragraph

    escaped = (
        str(text)
        .replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace("\n", "<br/>")
    )
    return Paragraph(escaped, style)


def html_escape(text: object) -> str:
    return (
        str(text)
        .replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace('"', "&quot;")
    )


def render_list(items: list[str], empty: str = "暂无。") -> str:
    if not items:
        return f"<li>{html_escape(empty)}</li>"
    return "\n".join(f"<li>{html_escape(item)}</li>" for item in items)


def render_kami_html(summary: dict) -> str:
    title = summary.get("title", "飞书群消息摘要")
    chat_name = summary.get("chat_name", "未知群聊")
    msg_range = summary.get("range", "当前可见消息")
    overview = summary.get("overview", "暂无总述。")
    todos = summary.get("todos") or ["暂无明确待办。"]
    key_points = summary.get("key_points") or ["暂无明显文本讨论。"]
    timeline = summary.get("timeline") or []
    image_notes = summary.get("image_notes") or []
    footer = summary.get("footer", "本摘要仅覆盖当前授权可见消息。")
    generated = summary.get("generated_at") or datetime.now().strftime("%Y-%m-%d %H:%M")
    logo_path = Path(summary.get("header_logo") or DEFAULT_HEADER_LOGO)
    logo_html = ""
    if logo_path.exists():
        logo_html = f'<img class="header-logo" src="{logo_path.resolve().as_uri()}" alt="logo">'

    image_blocks = []
    for note in image_notes:
        if isinstance(note, dict):
            note_summary = note.get("summary", "")
            path = note.get("path", "")
        else:
            note_summary = str(note)
            path = ""
        img_html = ""
        if path and Path(path).exists() and Path(path).suffix.lower() in {".jpg", ".jpeg", ".png", ".webp"}:
            img_html = f'<img class="resource-img" src="{Path(path).resolve().as_uri()}" alt="群聊图片资源">'
        image_blocks.append(f"""
          <div class="image-note">
            <p>{html_escape(note_summary)}</p>
            {img_html}
          </div>
        """)
    images_section = "\n".join(image_blocks) if image_blocks else "<p>暂无图片或文件资源。</p>"

    return f"""<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{html_escape(title)}</title>
<meta name="author" content="Kami">
<meta name="description" content="{html_escape(shorten(overview, width=140, placeholder='...'))}">
<meta name="keywords" content="飞书群聊,消息摘要,PDF,Kami">
<meta name="generator" content="Kami">
<style>
  @page {{ size: A4; margin: 15mm 18mm; background: #f5f4ed; }}
  * {{ box-sizing: border-box; }}
  :root {{
    --parchment: #f5f4ed;
    --ivory: #faf9f5;
    --near-black: #141413;
    --dark-warm: #3d3d3a;
    --olive: #504e49;
    --stone: #6b6a64;
    --brand: #1B365D;
    --border: #e8e6dc;
    --tag-bg: #E4ECF5;
    --serif: "Songti SC", "STSong", "Hiragino Mincho ProN", "Noto Serif CJK SC", "Source Han Serif SC", Georgia, serif;
  }}
  html, body {{ background: var(--parchment); }}
  body {{
    margin: 0;
    color: var(--near-black);
    font-family: var(--serif);
    font-size: 10pt;
    line-height: 1.52;
    letter-spacing: 0.16pt;
  }}
  .header {{
    display: flex;
    justify-content: space-between;
    gap: 18pt;
    padding-bottom: 12pt;
    margin-bottom: 14pt;
    border-bottom: 0.5pt solid var(--border);
  }}
  .eyebrow {{
    color: var(--brand);
    font-size: 9pt;
    letter-spacing: 1.3pt;
    margin-bottom: 5pt;
  }}
  h1 {{ margin: 0 0 5pt; font-size: 24pt; font-weight: 500; line-height: 1.15; }}
  .subtitle {{ color: var(--olive); font-size: 10.5pt; }}
  .meta {{ color: var(--stone); text-align: right; font-size: 9pt; white-space: nowrap; }}
  .header-mark {{
    width: 36mm;
    min-width: 36mm;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 5pt;
  }}
  .header-logo {{
    display: block;
    width: 24mm;
    height: auto;
    object-fit: contain;
  }}
  .summary-card {{
    background: var(--ivory);
    border: 0.5pt solid var(--border);
    border-radius: 8pt;
    padding: 14pt 16pt;
    margin-bottom: 14pt;
  }}
  .label {{
    display: inline-block;
    background: var(--tag-bg);
    color: var(--brand);
    border-radius: 2pt;
    padding: 1pt 5pt;
    font-size: 9pt;
    letter-spacing: 0.4pt;
    margin-bottom: 6pt;
  }}
  h2 {{ color: var(--brand); font-size: 15pt; font-weight: 500; margin: 15pt 0 7pt; }}
  p {{ margin: 0 0 6pt; }}
  ul {{ margin: 0 0 8pt 14pt; padding: 0; }}
  li {{ margin-bottom: 4pt; }}
  .grid {{ display: grid; grid-template-columns: 1fr 1fr; gap: 12pt; }}
  .panel {{
    background: var(--ivory);
    border: 0.5pt solid var(--border);
    border-radius: 8pt;
    padding: 12pt 14pt;
    break-inside: avoid;
  }}
  .timeline li {{ color: var(--dark-warm); }}
  .image-note {{ break-inside: avoid; margin-bottom: 10pt; }}
  .resource-img {{
    display: block;
    max-width: 76mm;
    max-height: 86mm;
    width: auto;
    height: auto;
    border-radius: 6pt;
    margin: 6pt auto 0;
    border: 0.5pt solid var(--border);
  }}
  .footer {{
    margin-top: 14pt;
    padding-top: 8pt;
    border-top: 0.5pt solid var(--border);
    color: var(--stone);
    font-size: 9pt;
  }}
</style>
</head>
<body>
  <header class="header">
    <div>
      <div class="eyebrow">LARK GROUP SUMMARY</div>
      <h1>{html_escape(title)}</h1>
      <div class="subtitle">群聊：{html_escape(chat_name)} · 范围：{html_escape(msg_range)}</div>
    </div>
    <div class="header-mark">
      {logo_html}
      <div class="meta">生成时间<br>{html_escape(generated)}</div>
    </div>
  </header>

  <section class="summary-card">
    <div class="label">OVERVIEW</div>
    <p>{html_escape(overview)}</p>
  </section>

  <section class="grid">
    <div class="panel">
      <h2>关键要点</h2>
      <ul>{render_list(key_points)}</ul>
    </div>
    <div class="panel">
      <h2>待办与结论</h2>
      <ul>{render_list(todos)}</ul>
    </div>
  </section>

  <section>
    <h2>消息时间线</h2>
    <ul class="timeline">{render_list(timeline, '暂无时间线。')}</ul>
  </section>

  <section>
    <h2>图片/文件摘要</h2>
    {images_section}
  </section>

  <div class="footer">{html_escape(footer)}</div>
</body>
</html>
"""


def build_pdf_kami(summary: dict, pdf_path: Path) -> bool:
    try:
        from weasyprint import HTML
    except ModuleNotFoundError:
        print("WeasyPrint is not installed; falling back to ReportLab PDF renderer.", file=sys.stderr)
        return False
    html_path = pdf_path.with_suffix(".kami.html")
    html_path.parent.mkdir(parents=True, exist_ok=True)
    html_path.write_text(render_kami_html(summary), encoding="utf-8")
    HTML(filename=str(html_path), base_url=str(html_path.parent)).write_pdf(str(pdf_path))
    return True


def build_pdf(summary: dict, pdf_path: Path) -> None:
    try:
        from reportlab.lib import colors
        from reportlab.lib.enums import TA_CENTER
        from reportlab.lib.pagesizes import A4
        from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
        from reportlab.lib.units import mm
        from reportlab.pdfbase import pdfmetrics
        from reportlab.pdfbase.cidfonts import UnicodeCIDFont
        from reportlab.platypus import Image, SimpleDocTemplate, Spacer, Table, TableStyle
    except ModuleNotFoundError as exc:
        raise SystemExit(
            "Missing Python dependency 'reportlab'. Use the Codex bundled Python at "
            "/Users/macbook/.cache/codex-runtimes/codex-primary-runtime/dependencies/python/bin/python3 "
            "or install reportlab into the active Python environment."
        ) from exc

    pdfmetrics.registerFont(UnicodeCIDFont("STSong-Light"))
    font = "STSong-Light"
    styles = getSampleStyleSheet()
    styles.add(ParagraphStyle(name="CNTitle", parent=styles["Title"], fontName=font, fontSize=22, leading=30, alignment=TA_CENTER, textColor=colors.HexColor("#1f2937")))
    styles.add(ParagraphStyle(name="CNSubtle", parent=styles["Normal"], fontName=font, fontSize=9.5, leading=14, alignment=TA_CENTER, textColor=colors.HexColor("#6b7280")))
    styles.add(ParagraphStyle(name="CNHeading", parent=styles["Heading2"], fontName=font, fontSize=14, leading=20, spaceBefore=8, spaceAfter=5))
    styles.add(ParagraphStyle(name="CNBody", parent=styles["BodyText"], fontName=font, fontSize=10.5, leading=17, spaceAfter=4))
    styles.add(ParagraphStyle(name="CNNote", parent=styles["BodyText"], fontName=font, fontSize=9.5, leading=15, textColor=colors.HexColor("#4b5563")))

    story = [
        paragraph(summary.get("title", "飞书群消息摘要"), styles["CNTitle"]),
        paragraph(f"群聊：{summary.get('chat_name', '未知')} · 范围：{summary.get('range', '当前可见消息')}", styles["CNSubtle"]),
        Spacer(1, 7 * mm),
    ]

    table_data = [
        ["项目", "内容"],
        ["总述", summary.get("overview", "暂无总述。")],
        ["待办/结论", "；".join(summary.get("todos") or ["暂无明确待办。"])],
    ]
    table = Table(table_data, colWidths=[30 * mm, 128 * mm])
    table.setStyle(TableStyle([
        ("FONTNAME", (0, 0), (-1, -1), font),
        ("FONTSIZE", (0, 0), (-1, -1), 9.5),
        ("LEADING", (0, 0), (-1, -1), 14),
        ("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
        ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#2563eb")),
        ("BACKGROUND", (0, 1), (0, -1), colors.HexColor("#eef2ff")),
        ("GRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#d1d5db")),
        ("VALIGN", (0, 0), (-1, -1), "TOP"),
        ("LEFTPADDING", (0, 0), (-1, -1), 8),
        ("RIGHTPADDING", (0, 0), (-1, -1), 8),
        ("TOPPADDING", (0, 0), (-1, -1), 7),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 7),
    ]))
    story.append(table)

    for section, title in [("key_points", "关键要点"), ("timeline", "消息时间线")]:
        items = summary.get(section) or []
        if items:
            story.append(Spacer(1, 5 * mm))
            story.append(paragraph(title, styles["CNHeading"]))
            for item in items:
                story.append(paragraph(f"- {item}", styles["CNBody"]))

    image_notes = summary.get("image_notes") or []
    if image_notes:
        story.append(Spacer(1, 5 * mm))
        story.append(paragraph("图片/文件摘要", styles["CNHeading"]))
        for note in image_notes:
            text = note.get("summary") if isinstance(note, dict) else str(note)
            story.append(paragraph(f"- {text}", styles["CNBody"]))
            path = Path(note.get("path", "")) if isinstance(note, dict) else None
            if path and path.exists() and path.suffix.lower() in {".jpg", ".jpeg", ".png", ".webp"}:
                story.append(Spacer(1, 2 * mm))
                story.append(Image(str(path), width=70 * mm, height=70 * mm, kind="proportional"))

    footer = summary.get("footer", "本摘要仅覆盖当前授权可见消息。")
    story.extend([Spacer(1, 6 * mm), paragraph(footer, styles["CNNote"])])

    pdf_path.parent.mkdir(parents=True, exist_ok=True)
    doc = SimpleDocTemplate(str(pdf_path), pagesize=A4, rightMargin=20 * mm, leftMargin=20 * mm, topMargin=18 * mm, bottomMargin=18 * mm)
    doc.build(story)


def send_summary(args: argparse.Namespace) -> None:
    require_lark_cli()
    summary_path = Path(args.summary_json).resolve()
    pdf_path = Path(args.pdf).resolve()
    summary = json.loads(summary_path.read_text(encoding="utf-8"))
    build_and_optionally_render(summary, pdf_path, args.render_check, args.engine)

    cwd = pdf_path.parent
    rel_pdf = pdf_path.name
    send = capture([
        "lark-cli",
        "im",
        "+messages-send",
        "--chat-id",
        args.chat_id,
        "--file",
        rel_pdf,
        "--format",
        "json",
    ], cwd=cwd, check=False)
    print(send.stdout)
    if send.returncode == 0:
        return

    print(send.stderr or send.stdout, file=sys.stderr)
    if not args.fallback_drive:
        raise SystemExit(send.returncode)

    upload = capture([
        "lark-cli",
        "drive",
        "+upload",
        "--as",
        "user",
        "--file",
        str(pdf_path),
        "--name",
        pdf_path.name,
        "--format",
        "json",
    ], check=True)
    print(upload.stdout)
    data = parse_json_output(upload.stdout)
    url = data.get("data", {}).get("url")
    if not url:
        raise SystemExit("Drive upload succeeded but no URL was returned.")
    text = f"{summary.get('title', '群消息摘要 PDF')} 已生成：\n{url}"
    run([
        "lark-cli",
        "im",
        "+messages-send",
        "--chat-id",
        args.chat_id,
        "--text",
        text,
        "--format",
        "json",
    ])


def build_with_engine(summary: dict, pdf_path: Path, engine: str) -> str:
    if engine in {"auto", "kami"}:
        if build_pdf_kami(summary, pdf_path):
            return "kami"
        if engine == "kami":
            raise SystemExit("Kami PDF engine requested, but WeasyPrint is unavailable.")
    build_pdf(summary, pdf_path)
    return "reportlab"


def build_and_optionally_render(summary: dict, pdf_path: Path, render_check: bool, engine: str) -> None:
    used = build_with_engine(summary, pdf_path, engine)
    print(pdf_path)
    print(f"PDF engine: {used}", file=sys.stderr)
    if render_check and shutil.which("pdftoppm"):
        prefix = pdf_path.with_suffix("")
        run(["pdftoppm", "-png", "-singlefile", str(pdf_path), str(prefix)], check=False)


def build_pdf_command(args: argparse.Namespace) -> None:
    summary_path = Path(args.summary_json).resolve()
    pdf_path = Path(args.pdf).resolve()
    summary = json.loads(summary_path.read_text(encoding="utf-8"))
    build_and_optionally_render(summary, pdf_path, args.render_check, args.engine)


def draft_summary(args: argparse.Namespace) -> None:
    messages_path = Path(args.messages_json).resolve()
    data = json.loads(messages_path.read_text(encoding="utf-8"))
    messages = data.get("data", {}).get("messages", [])
    timeline = []
    key_points = []
    image_notes = []
    for msg in reversed(messages):
        when = msg.get("create_time", "")
        msg_type = msg.get("msg_type", "")
        content = msg.get("content", "")
        item = f"{when} - {msg_type}: {shorten(content, width=120, placeholder='...')}"
        timeline.append(item)
        if msg_type == "text":
            key_points.append(content)
        for res in msg.get("resources", []) or []:
            path = res.get("local_path", "")
            image_notes.append({"path": path, "summary": f"已下载资源：{Path(path).name if path else res.get('key', '')}，请由 Codex 目检后替换为内容摘要。"})
    summary = {
        "title": "飞书群消息摘要",
        "chat_name": args.chat_name or args.chat_id,
        "range": f"最近 {len(messages)} 条消息",
        "overview": "请由 Codex 根据 messages.json 和资源文件改写此总述。",
        "key_points": key_points[:10] or ["暂无明显文本讨论。"],
        "todos": ["暂无明确待办。"],
        "timeline": timeline,
        "image_notes": image_notes,
        "footer": "本摘要仅覆盖当前授权可见消息。",
    }
    out = Path(args.out).resolve()
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8")
    print(out)


def main() -> None:
    parser = argparse.ArgumentParser(description="Feishu/Lark group summary PDF helper")
    sub = parser.add_subparsers(dest="cmd", required=True)

    p = sub.add_parser("install-deps", help="Install lark-cli and Python/PDF dependencies")
    p.set_defaults(func=install_deps)

    p = sub.add_parser("check-deps", help="Check local dependencies for this skill")
    p.set_defaults(func=check_deps)

    p = sub.add_parser("setup", help="Install lark-cli if needed and initialize app config")
    p.set_defaults(func=setup)

    p = sub.add_parser("auth-all", help="Request all CLI domains and all permissions interactively")
    p.set_defaults(func=auth_all)

    p = sub.add_parser("collect", help="Collect group messages and download resources")
    p.add_argument("--chat-id", required=True)
    p.add_argument("--page-size", default=50, type=int)
    p.add_argument("--order", default="desc", choices=["asc", "desc"])
    p.add_argument("--period", default="both", choices=["today", "7d", "both", "custom"], help="Default is both: collect today and last 7 days")
    p.add_argument("--start")
    p.add_argument("--end")
    p.add_argument("--out", default="work/lark-summary")
    p.set_defaults(func=collect)

    p = sub.add_parser("draft-summary", help="Create an editable summary JSON draft from collected messages")
    p.add_argument("--messages-json", required=True)
    p.add_argument("--chat-id", required=True)
    p.add_argument("--chat-name")
    p.add_argument("--out", default="work/lark-summary/summary.json")
    p.set_defaults(func=draft_summary)

    p = sub.add_parser("send-summary", help="Generate PDF from summary JSON and send it to a group")
    p.add_argument("--chat-id", required=True)
    p.add_argument("--summary-json", required=True)
    p.add_argument("--pdf", default="outputs/lark_group_summary.pdf")
    p.add_argument("--render-check", action="store_true")
    p.add_argument("--engine", default="auto", choices=["auto", "kami", "reportlab"])
    p.add_argument("--no-fallback-drive", dest="fallback_drive", action="store_false")
    p.set_defaults(func=send_summary, fallback_drive=True)

    p = sub.add_parser("build-pdf", help="Generate PDF from summary JSON without sending")
    p.add_argument("--summary-json", required=True)
    p.add_argument("--pdf", default="outputs/lark_group_summary.pdf")
    p.add_argument("--render-check", action="store_true")
    p.add_argument("--engine", default="auto", choices=["auto", "kami", "reportlab"])
    p.set_defaults(func=build_pdf_command)

    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()
