#!/usr/bin/env python3
"""PreToolUse guard for Read: consult the hakodesh read wards (קוסטודעס).

Policy (0.5.0): matched+locked -> exit 2; unknown, malformed or empty ward, or a check error
-> exit 2 (fail closed); payload that is not a JSON object -> exit 2 (a crash used to exit 1,
which Claude Code treats as non-blocking); hakodesh not importable -> exit 1 (allow, loudly).
Interpreter is pinned to the venv that has hakodesh installed.

Payload handed to every ward carries two keys:
  file_path       the value Claude Code sent, untouched (existing wards keep their semantics)
  file_path_real  expanduser + realpath + casefold of that value, so a ward on this key
                  cannot be dodged with ~, relative paths, //, /./, .., symlinks or case
                  (APFS is case-insensitive by default)
The wards are named by their dialect tokens; the kernel resolves either spelling.
"""
# Interpreter: the installer (devra קונסטיטוי-האמי) rewrites this shebang to the absolute
# path of the interpreter it is itself running under, which is the one that has hakodesh
# importable. That pin is the point -- a PATH difference between the hook environment and
# an interactive shell must not be able to disable enforcement silently. The portable form
# is what lives in the repository, because an absolute home path may never ship.
import json
import os
import sys
import time
from pathlib import Path

WARDS = ["קוסטוס-סעקרעטום", "קוסטוס-סעקרעטום-ווערום"]  # literal path, then canonical path

job_dir = os.environ.get("CLAUDE_JOB_DIR", "/tmp")
payload_log = Path(job_dir) / "tmp" / "hook_payload.jsonl"


def log(record):
    try:
        payload_log.parent.mkdir(parents=True, exist_ok=True)
        with open(payload_log, "a", encoding="utf-8") as fh:
            fh.write(json.dumps(record, ensure_ascii=False) + "\n")
    except Exception:
        pass


hook_stdin_path = os.environ.get("GROK_HOOK_STDIN")
if not hook_stdin_path and sys.stdin.isatty():
    sys.exit(0)
try:
    if hook_stdin_path:
        with open(hook_stdin_path, encoding="utf-8") as fh:
            hook_input = json.load(fh)
    else:
        hook_input = json.load(sys.stdin)
except Exception as exc:
    log({"ward_enforcement": "MALFORMED", "reason": "payload is not JSON: %s" % exc})
    print("הקודש-קוסטודעס: מטען ההוק אינו JSON; דוחה (סגור-בכשל)", file=sys.stderr)
    sys.exit(2)
if not isinstance(hook_input, dict) or not isinstance(hook_input.get("tool_input", {}), dict):
    log({"ward_enforcement": "MALFORMED", "reason": "payload is not an object"})
    print("הקודש-קוסטודעס: מטען ההוק אינו אובייקט; דוחה (סגור-בכשל)", file=sys.stderr)
    sys.exit(2)

try:
    from hakodesh import ward, home as paths, dialectus
except Exception as exc:
    log({"ward_enforcement": "DISABLED", "reason": "import failed: %s" % exc,
         "python": sys.executable, "ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())})
    print("הקודש-קוסטודעס: לא נאכף (אי אפשר לייבא hakodesh מ-%s: %s)" % (sys.executable, exc), file=sys.stderr)
    sys.exit(1)


def normalize(raw: str) -> str:
    """Canonical, case-folded absolute path. Never raises: on any failure fall back to a
    lexical normalization so a ward on file_path_real still sees a non-empty haystack."""
    try:
        return os.path.realpath(os.path.expanduser(raw)).casefold()
    except Exception:
        try:
            return os.path.normpath(os.path.expanduser(raw)).casefold()
        except Exception:
            return raw.casefold()


tool_input = hook_input.get("tool_input") or {}
file_path = tool_input.get("file_path")
file_path = "" if file_path is None else str(file_path)
payload = {"file_path": file_path, "file_path_real": normalize(file_path) if file_path else ""}

try:
    ward_dir = paths.book_dir("ward")  # honours HAKODESH_HOME
except Exception:
    ward_dir = Path.home() / "Library/Application Support/hakodesh/book/wards"
for ward_name in WARDS:
    try:
        result = ward.check(ward_name, payload)
    except Exception as exc:
        log({"ward_enforcement": "ERROR", "ward": ward_name, "error": str(exc)})
        print("הקודש-קוסטודעס: הבדיקה של %s נכשלה (%s); דוחה (סגור-בכשל)" % (ward_name, exc), file=sys.stderr)
        sys.exit(2)
    if not isinstance(result, dict) or result.get("denied"):
        msg = result.get("error") if isinstance(result, dict) else None
        # canon_enum_v2 (0.7.0): the dialect spelling only -- see claude-shell-guard. The whole
        # lookup is guarded because it only decides WHICH message to print, and a traceback here
        # would exit 1, which Claude Code treats as non-blocking: a deny would become a bypass.
        try:
            if isinstance(result, dict) and dialectus.canon_enum_v2(result.get("error_code"), dialectus.ERROR_CODES) in (None, "ward.denied"):
                purpose = json.loads((ward_dir / ("%s.json" % ward_name)).read_text(encoding="utf-8-sig")).get("purpose")
                if purpose:
                    msg = purpose
        except Exception:
            pass
        print(msg or "הקריאה נדחתה על ידי קוסטוס %s. השתמש ב-`devra פערלעגי-דוקומענטום <path>`." % ward_name, file=sys.stderr)
        sys.exit(2)
sys.exit(0)
