#!/usr/bin/env python3
"""SessionStart hook — staleness check + detached graph rebuild. Fail-open.

Must return sub-second; heavy work is detached in the background. Context
injection is handled by session_context.py (UserPromptSubmit hook).
"""
import json
import os
import sys
import time
import subprocess
from datetime import datetime, timezone

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import config as config_mod
import hook_io

HOOK_LOG = "/tmp/memory-hook.log"

# Trigger-conditioned destination ladder (WS2 wording loop, 2026-07-07).
# Injected as additionalContext every session; keep it short — it is a
# per-session token cost in every project.
USAGE_NOTE = (
    "[os-vault] Where knowledge goes — ask in order: "
    "(1) the repo already records it (code, ADR, CLAUDE.md)? -> write it there or "
    "nowhere; never duplicate into memory. "
    "(2) would it change how you act in a DIFFERENT repo next year (tool/API "
    "behavior discovered, client conventions, methodology that worked)? -> the "
    "SecondBrain vault via /os-vault:write — NOT auto-memory; the repo is where "
    "you learned it, not what it's about. "
    "(3) only useful in future sessions of THIS repo? -> auto-memory. "
    "(4) task status / in-flight state -> nothing. "
    "Mechanical triggers — treat these as part of the task itself: "
    "WHEN you discover a tool/API/service behaves differently from what its "
    "docs or config claim (advertised X, actually Y) -> that discrepancy is "
    "vault knowledge, /os-vault:write it. "
    "WHEN the user states a client-specific requirement or standing preference "
    "-> vault note, scope: client. "
    "BEFORE your final reply on any task -> check: did either occur this "
    "session? If yes, the task is NOT COMPLETE until /os-vault:write has "
    "captured it. Ephemeral status and in-progress state never go to the vault."
)


def _emit_usage_note():
    print(json.dumps({
        "hookSpecificOutput": {
            "hookEventName": "SessionStart",
            "additionalContext": USAGE_NOTE,
        }
    }))


def _utc():
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def _log(line):
    try:
        with open(HOOK_LOG, "a") as f:
            f.write(line + "\n")
    except Exception:
        pass


def _pull_repo(repo_dir):
    """Fast-forward-only pull for a sync'd repo. Silent on success; at most a
    one-line stderr note on failure (offline machine must never block or
    error out session start). Mirrors vault_sync.py / memsearch_sync.py push
    style but in the opposite direction (Step 5b multi-machine freshness).
    """
    if not os.path.isdir(os.path.join(repo_dir, ".git")):
        return
    try:
        r = subprocess.run(
            ["timeout", "10", "git", "-C", repo_dir, "pull", "--ff-only", "--quiet", "origin", "main"],
            stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True,
        )
        if r.returncode != 0:
            print(f"[session-start] pull skipped for {repo_dir} (offline or diverged)", file=sys.stderr)
    except Exception:
        print(f"[session-start] pull skipped for {repo_dir}", file=sys.stderr)


def _needs_rebuild(stamp, stale_days):
    if not os.path.exists(stamp):
        return True
    try:
        age_days = (time.time() - os.path.getmtime(stamp)) / 86400
        return age_days > stale_days
    except Exception:
        return True


def main():
    inp = hook_io.read_input()
    source = inp.source or "unknown"
    _log(f"[{_utc()}] session-start fired: source={source} cwd={os.getcwd()}")

    cfg = config_mod.load_config()

    _pull_repo(cfg.vault_path)
    _pull_repo(cfg.memsearch_dir)

    _emit_usage_note()

    if os.environ.get("OS_VAULT_SKIP_REBUILD"):
        _log(f"[{_utc()}] session-start: rebuild skipped (OS_VAULT_SKIP_REBUILD set)")
        return

    cache_dir = os.path.expanduser("~/.cache/graphify")
    stamp = os.path.join(cache_dir, "vault-rebuild.stamp")
    lock = os.path.join(cache_dir, "vault-rebuild.lock")
    logfile = os.path.join(cache_dir, "vault-rebuild.log")
    try:
        os.makedirs(cache_dir, exist_ok=True)
    except Exception:
        pass

    if _needs_rebuild(stamp, cfg.stale_days):
        if os.path.exists(lock):
            try:
                with open(logfile, "a") as f:
                    f.write("[memory/session-start] rebuild already running (lock present), skipping\n")
            except Exception:
                pass
            _log(f"[{_utc()}] session-start: rebuild skipped (lock present)")
        else:
            try:
                open(lock, "a").close()
            except Exception:
                pass
            env = dict(os.environ)
            env.update(cfg.env)
            env["VAULT_PATH"] = cfg.vault_path
            env["MODEL"] = cfg.model
            env["STAMP"] = stamp
            env["LOCK"] = lock
            env["LOGFILE"] = logfile
            cmd = (
                'graphify extract "$VAULT_PATH" --backend ollama --model "$MODEL" --force '
                '>> "$LOGFILE" 2>&1 '
                '&& date > "$STAMP" && rm -f "$LOCK" '
                '|| (rm -f "$LOCK"; echo "rebuild failed" >> "$LOGFILE")'
            )
            try:
                devnull = open(os.devnull, "wb")
                subprocess.Popen(
                    ["bash", "-c", cmd],
                    stdin=subprocess.DEVNULL,
                    stdout=devnull,
                    stderr=devnull,
                    env=env,
                    start_new_session=True,
                )
            except Exception:
                pass
            _log(f"[{_utc()}] session-start: rebuild triggered")
    else:
        _log(f"[{_utc()}] session-start: rebuild not needed")


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print(f"[session-start] error: {e}", file=sys.stderr)
    sys.exit(0)
