#!/usr/bin/env python3
"""session_start.py — aggregated deterministic status pass for the cc-os
plugin family. Runs every registered check in-process (checks.REGISTRY) and
emits at most one warning banner plus any near-zero-token notes.

  ok    -> silent
  note  -> additionalContext line (never snoozed or suppressed)
  warn  -> one line in the single systemMessage banner; snoozed once per day
           and permanently suppressible via .cc-os/suppress-<check>

Outside a git project only environment-scoped checks run and no state is
written. Always exits 0 — the hook must never block a session.
"""
from __future__ import annotations

import json
import os
import signal
import sys
from datetime import date
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))

from checks import DEFAULT_VAULT_PATH, REGISTRY, CheckResult, Ctx  # noqa: E402
from state import StateDir, find_project_root, read_config  # noqa: E402

CHECK_BUDGET_SECONDS = 2
BANNER_HEADER = (
    "[os-status] Session preconditions need attention (run /os-status:fix to"
    " address all of these):"
)


def run_isolated(check, ctx) -> CheckResult:
    """Failure isolation: an exception or a blown time budget becomes a
    generic warn naming the check; the remaining checks keep running."""

    def timed_out(signum, frame):
        raise TimeoutError

    alarmed = hasattr(signal, "SIGALRM")
    if alarmed:
        previous = signal.signal(signal.SIGALRM, timed_out)
        signal.alarm(CHECK_BUDGET_SECONDS)
    try:
        result = check.fn(ctx)
        if not isinstance(result, CheckResult):
            raise TypeError("check returned a non-CheckResult")
        return result
    except Exception:
        return CheckResult("warn", f"status check '{check.name}' failed to run")
    finally:
        if alarmed:
            signal.alarm(0)
            signal.signal(signal.SIGALRM, previous)


def run(ctx: Ctx, state, today: date, out) -> None:
    notes, warns = [], []
    for check in REGISTRY:
        if check.project_scoped and ctx.project_root is None:
            continue
        result = run_isolated(check, ctx)
        if result.status in ("ok", "note") and state:
            # Self-healing (issue #23): a check that flips to ok/note no
            # longer needs a snooze stamp from a prior warn run.
            state.clear_snooze(check.name)
        if result.status == "note":
            notes.append(result.message)
        elif result.status == "warn":
            if state and (
                state.suppressed(check.name) or state.snoozed(check.name, today)
            ):
                continue
            warns.append((check.name, result.message))

    payload = {}
    if warns:
        lines = [BANNER_HEADER]
        lines.extend(f"  - {message}" for _, message in warns)
        if state:
            names = ", ".join(name for name, _ in warns)
            lines.append(
                f"  (silence one permanently: touch .cc-os/suppress-<name>; warned: {names})"
            )
            for name, _ in warns:
                state.stamp(name, today)
        payload["systemMessage"] = "\n".join(lines)
    if notes:
        payload["hookSpecificOutput"] = {
            "hookEventName": "SessionStart",
            "additionalContext": "\n".join(notes),
        }
    if payload:
        out.write(json.dumps(payload))


def main() -> int:
    try:
        root = find_project_root(Path.cwd())
        config = read_config(root) if root else {}
        ctx = Ctx(
            project_root=root,
            settings_path=Path.home() / ".claude" / "settings.json",
            vault_path=Path(
                os.path.expanduser(config.get("vault_path", DEFAULT_VAULT_PATH))
            ),
            config=config,
            environ=dict(os.environ),
        )
        state = StateDir(root) if root else None
        run(ctx, state, date.today(), sys.stdout)
    except Exception:
        pass  # never block a session
    return 0


if __name__ == "__main__":
    sys.exit(main())
