#!/usr/bin/env python3
"""settings-allow-sync — 把 ISEP 的權限白名單寫進**這台機器**的 ~/.claude/settings.json
                        （inkstone/ISEP#130）

━━ 為什麼有這支 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
leo 09-07 親手把四個 Gitea 正門工具（`ticket`／`mainline`／`gate-ok`／`gitea-pr-merge`）
加進本機 `InkStoneCo/.claude/settings.json` 的 `permissions.allow`，auto mode 的分類器
才不擋。**雲端 session 沒有那四條** ⇒ 同樣的動作在雲端照樣被擋
（09-04 progress-guard run log：permission_denials=6）。

雲端真正讀的是薄殼 repo（GitHub）的 settings——推它要 D20 開閘（leo 親跑 github-arm）。
⇒ 白名單不住薄殼、也不住 InkStoneCo，**住 ISEP 一份**（`docs/permissions-allow.json`），
  由本支在那台機器上寫進 `~/.claude/settings.json`（user scope，跟 cwd 無關）。
  雲端：`docs/cloud-setup-script.sh` 裝完 plugin 就跑一次；之後每個 SessionStart 再對一次，
  plugin 更新帶進新規則時不必等快照重拍。本機：同一支、同一份清單。

━━ 規矩 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  · 只加不減：settings.json 裡本來就有的東西一個字不動（別人手加的規則不是本支的地盤）。
  · 冪等：跑第二次 ＋0 條，檔案一個位元組都不變。
  · 清單裡開頭的 `~` 換成這台的家目錄（本機 /Users/…、雲端 /root），其餘照抄。
  · 原子寫：先寫暫存檔再 rename，中途斷掉不會留下半個 JSON。
  · 讀不懂目標（不是 JSON）⇒ **不碰它**、印出來、離開碼 0——這支掛在 SessionStart，
    不准因為它把 session 弄壞；`--check` 才會用離開碼講話。

━━ 用法 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    settings-allow-sync                    寫進 ~/.claude/settings.json
    settings-allow-sync --dry-run          只印會加哪幾條，不寫
    settings-allow-sync --check            對帳：缺任何一條 ⇒ 離開碼 1（測試／驗收用）
    settings-allow-sync --target <檔> --home <家目錄>   測試用：指到別的檔、別的家目錄
    settings-allow-sync --quiet            沒事就閉嘴（SessionStart 用）
"""
import json
import os
import sys
import tempfile

HERE = os.path.dirname(os.path.abspath(__file__))
SOURCE = os.path.join(HERE, "..", "docs", "permissions-allow.json")


def wanted(home):
    with open(SOURCE, encoding="utf-8") as f:
        d = json.load(f)
    out = []
    for rule in d.get("allow") or []:
        if not isinstance(rule, str) or not rule.startswith("Bash(") or not rule.endswith(")"):
            raise ValueError("清單裡有一條不是 Bash(…) 的形狀：%r" % (rule,))
        inner = rule[len("Bash("):-1]
        # 只換「路徑開頭」的 ~：`python3 ~/.claude/…` 這種。`~` 出現在別處不動。
        parts = inner.split(" ")
        parts = [home + p[1:] if p.startswith("~/") else p for p in parts]
        out.append("Bash(" + " ".join(parts) + ")")
    return out


def main(argv):
    target = os.path.join(os.path.expanduser("~"), ".claude", "settings.json")
    home = os.path.expanduser("~")
    dry = check = quiet = False
    i = 0
    while i < len(argv):
        a = argv[i]
        if a == "--target":
            i += 1; target = argv[i]
        elif a == "--home":
            i += 1; home = argv[i].rstrip("/")
        elif a == "--dry-run":
            dry = True
        elif a == "--check":
            check = True
        elif a == "--quiet":
            quiet = True
        elif a in ("-h", "--help"):
            print(__doc__); return 0
        else:
            print("不認得的參數：%s（--help 看用法）" % a, file=sys.stderr); return 2
        i += 1

    try:
        rules = wanted(home)
    except Exception as e:
        print("🔴 讀不了白名單清單 %s：%s" % (SOURCE, e), file=sys.stderr)
        return 1 if check else 0

    cur = {}
    if os.path.exists(target):
        try:
            with open(target, encoding="utf-8") as f:
                cur = json.load(f)
            if not isinstance(cur, dict):
                raise ValueError("最外層不是物件")
        except Exception as e:
            print("🔴 %s 讀不懂（%s）——**沒有動它**。修好它再跑一次。" % (target, e), file=sys.stderr)
            return 1 if check else 0

    perms = cur.get("permissions")
    if not isinstance(perms, dict):
        perms = {}
    allow = perms.get("allow")
    if not isinstance(allow, list):
        allow = []
    have = set(a for a in allow if isinstance(a, str))
    missing = [r for r in rules if r not in have]

    if check:
        if missing:
            print("🔴 %s 缺 %d 條：" % (target, len(missing)))
            for r in missing:
                print("   " + r)
            return 1
        if not quiet:
            print("✅ %s 已有全部 %d 條" % (target, len(rules)))
        return 0

    if not missing:
        if not quiet:
            print("✅ 白名單已齊（%d 條）：%s" % (len(rules), target))
        return 0

    if dry:
        print("🧪 --dry-run：會加 %d 條進 %s：" % (len(missing), target))
        for r in missing:
            print("   " + r)
        return 0

    perms["allow"] = allow + missing
    cur["permissions"] = perms
    try:
        os.makedirs(os.path.dirname(target) or ".", exist_ok=True)
        fd, tmp = tempfile.mkstemp(prefix=".settings-", suffix=".json", dir=os.path.dirname(target) or ".")
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            json.dump(cur, f, ensure_ascii=False, indent=2)
            f.write("\n")
        os.replace(tmp, target)
    except Exception as e:
        print("🔴 寫不進 %s：%s" % (target, e), file=sys.stderr)
        return 0
    print("✅ 白名單 ＋%d 條 → %s（共 %d 條來自 ISEP）" % (len(missing), target, len(rules)))
    if not quiet:
        for r in missing:
            print("   " + r)
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
