#!/usr/bin/env python3
"""
vault — CLI for Ryan's R/J/F/I Obsidian vault.

Clobber-safe. Dry-run by default; pass --execute to act.
Operates on BOTH trees (/vault map + /data territory) and the topology manifest.

Subcommands:
  vault tree [SYS]              print topology (optionally one system)
  vault search <query>          find IDs/notes by code, title, or content
  vault show <ID>               show an ID's note + its /data home + blob count
  vault create <ID> "<title>"   create ID note (folder+folder-note) in /vault AND /data
  vault move <oldID> <newID>    renumber: rename note+folder, rewrite backlinks, move /data dir
  vault rename <ID> "<title>"   retitle in place, rewrite backlinks
  vault sync-data               reconcile /data skeleton to match manifest (create missing dirs)
  vault manifest                regenerate the topology manifest note
  vault report [--pretty]       deterministic Librarian report (delegates to vault-librarian.py)
  vault analyze [--sonnet|--opus] [-m MODEL] [--provider PROVIDER]
                                spawn a Hermes Librarian pass. DEFAULT: Haiku/Bedrock
                                (cheap+fast — the nightly/weekly tier). --sonnet / --opus
                                step up intelligence & cost for harder judgment calls.
                                -m/--provider override entirely (future/non-Bedrock models).
"""
import os, re, sys, subprocess

# Resolve sibling modules whether running from /var/lib/hermes, the repo, or the Nix store.
_SELF_DIR = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, _SELF_DIR)
import vault_lib as vl

HERMES = "/run/current-system/sw/bin/hermes"
LIBRARIAN = os.path.join(_SELF_DIR, "vault-librarian.py")
PROMPT = os.path.join(_SELF_DIR, "vault-librarian-prompt.md")

# `vault analyze` model tiers. Default is Haiku — cheap+fast for the nightly/weekly
# pass. --sonnet / --opus step up intelligence & cost inline. IDs verified against
# this host's Hermes config + the active Bedrock provisioning.
ANALYZE_TIERS = {
    "haiku":  "us.anthropic.claude-haiku-4-5-20251001-v1:0",
    "sonnet": "global.anthropic.claude-sonnet-5",
    "opus":   "global.anthropic.claude-opus-4-8",
}
# Only the haiku (us.anthropic.*) tier is known to route via an explicit `bedrock`
# provider. The global.anthropic.* IDs use the profile's default provider unless the
# caller passes --provider, to avoid a provider/model-registration mismatch.
ANALYZE_TIER_PROVIDER = {"haiku": "bedrock"}

WIKILINK_RE = re.compile(r'(!?\[\[)([^\]#|]+)((?:#[^\]|]+)?(?:\|[^\]]+)?)(\]\])')


def die(msg, code=1):
    print("ERROR: " + msg, file=sys.stderr)
    sys.exit(code)


def executing(argv):
    return "--execute" in argv


def sys_root(sys_letter):
    """Return abs /vault path of a system root, e.g. 'R' -> /vault/R Ryan."""
    s = vl.walk_structure(vl.VAULT, include_meta=True)["systems"]
    if sys_letter not in s:
        die(f"unknown system '{sys_letter}' (have {sorted(s)})")
    return os.path.join(vl.VAULT, s[sys_letter])


def find_id_folder(root, id_code):
    """Return abs folder path for an ID code under root, or None."""
    for dp, dns, fns in os.walk(root):
        dns[:] = [d for d in dns if d not in vl.SKIP_DIRS]
        if vl.id_code(os.path.basename(dp)) == id_code:
            return dp
    return None


# ---------- read-only commands ----------

def cmd_tree(argv):
    s = vl.walk_structure(vl.VAULT, include_meta=True)
    want = argv[0] if argv else None
    for code in sorted(s["cats"]):
        if want and not code.startswith(want):
            continue
        n = sum(1 for i in s["ids"] if i.startswith(code + "."))
        print(f"  {code:8} {os.path.basename(s['cats'][code])}  ({n} IDs)")


def cmd_search(argv):
    if not argv:
        die("usage: vault search <query>")
    q = " ".join(argv).lower()
    hits = []
    for p in vl.all_notes(vl.VAULT):
        rel = os.path.relpath(p, vl.VAULT)
        stem = os.path.splitext(os.path.basename(p))[0]
        if q in stem.lower() or q in rel.lower():
            hits.append((rel, "name"))
            continue
        try:
            if q in open(p, encoding="utf-8").read().lower():
                hits.append((rel, "content"))
        except Exception:
            pass
    for rel, how in hits[:50]:
        print(f"  [{how}] {rel}")
    print(f"\n{len(hits)} match(es).")


def cmd_show(argv):
    if not argv:
        die("usage: vault show <ID>")
    code = argv[0]
    root = sys_root(code[0])
    folder = find_id_folder(root, code)
    if not folder:
        die(f"ID {code} not found in /vault")
    print("VAULT folder: " + os.path.relpath(folder, vl.VAULT))
    note = os.path.join(folder, os.path.basename(folder) + ".md")
    print("Folder-note:  " + ("present" if os.path.exists(note) else "MISSING"))
    # /data home
    drel = os.path.relpath(folder, vl.VAULT)
    dpath = os.path.join(vl.DATA, drel)
    if os.path.isdir(dpath):
        nblobs = sum(len(fs) for _, _, fs in os.walk(dpath))
        print(f"/data home:   {drel}  ({nblobs} blobs)")
    else:
        print(f"/data home:   MISSING ({drel})")


def main():
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(0)
    cmd, argv = sys.argv[1], sys.argv[2:]
    dispatch = {
        "tree": cmd_tree,
        "search": cmd_search,
        "show": cmd_show,
    }
    # write-commands live in vault_ops (imported lazily to keep this file readable)
    if cmd in dispatch:
        dispatch[cmd](argv)
    elif cmd in ("create", "move", "rename", "sync-data", "manifest"):
        import vault_ops
        vault_ops.run(cmd, argv, executing(sys.argv))
    elif cmd == "report":
        sys.exit(subprocess.run([sys.executable, LIBRARIAN] + argv).returncode)
    elif cmd == "analyze":
        prompt = open(PROMPT, encoding="utf-8").read()
        # Model tier: default Haiku (cheap nightly). --sonnet/--opus step up.
        # Raw -m/--provider override the tier entirely (escape hatch for future models
        # or non-Bedrock providers). Tier flags are last-wins if combined.
        tier = "haiku"
        if "--sonnet" in argv:
            tier = "sonnet"
        if "--opus" in argv:
            tier = "opus"
        model = ANALYZE_TIERS[tier]
        provider = ANALYZE_TIER_PROVIDER.get(tier)  # None for sonnet/opus (use profile default)
        if "-m" in argv:
            i = argv.index("-m")
            if i + 1 >= len(argv):
                die("-m requires a model name")
            model = argv[i + 1]
            tier = "custom"
            provider = None  # custom model: don't force a provider unless asked
        if "--provider" in argv:
            i = argv.index("--provider")
            if i + 1 >= len(argv):
                die("--provider requires a provider name")
            provider = argv[i + 1]
        hcmd = [HERMES, "-z", prompt, "-m", model]
        if provider:
            hcmd += ["--provider", provider]
        # Banner: headless oneshot buffers output, so show it's alive + what it costs.
        print(f"→ spawning Hermes Librarian pass  [tier={tier}  provider={provider or 'profile-default'}]",
              file=sys.stderr)
        print(f"  model: {model}", file=sys.stderr)
        print("  output is buffered; a full pass can take 1–3 min before you see anything.",
              file=sys.stderr)
        if tier == "haiku":
            print("  step up:  vault analyze --sonnet   (or --opus)  for harder judgment calls",
                  file=sys.stderr)
        sys.stderr.flush()
        subprocess.run(hcmd)
    else:
        die(f"unknown command '{cmd}'")


if __name__ == "__main__":
    main()
