#!/usr/bin/env python3
"""session_start.py — deterministic SessionStart existence check for os-adr.

NO LONGER WIRED (2026-07-06): the SessionStart entry moved to the os-status
plugin's aggregated check (`adr_system_present` in os-status/hooks/checks.py,
which copies PRESENT_NOTE/ABSENT_NOTE byte-identically). This file stays as
the wording source of record until the add-os-status-plugin change archives.

Pure function of the project's filesystem state; no model, no network, no Ruby.

  present (docs/adr/ + index)  -> additionalContext note naming /os-adr:create
                                  and /os-adr:find (near-zero tokens)
  absent, suppressed           -> silent (.os-adr/suppress flag)
  absent, reminded today       -> silent (.os-adr/last_reminded snooze stamp)
  absent otherwise             -> one-line systemMessage suggesting
                                  /os-adr:init or /os-adr:migrate; stamps snooze
  not a git project            -> silent (never creates state dirs outside
                                  real projects)

Always exits 0 — the hook must never block a session.
"""

from __future__ import annotations

import json
import sys
from datetime import date
from pathlib import Path
from typing import Optional, Tuple

PRESENT_NOTE = (
    "[os-adr] This project records architecture decisions in docs/adr/ "
    "(index: docs/adr/README.md). WHEN your task involves making an "
    "architecture-level choice, or changing/replacing an approach that "
    "already exists in this codebase (persistence, protocols, libraries, "
    "conventions, error handling) -> FIRST run /os-adr:find to check whether "
    "a decision already covers it. Mechanical rule: BEFORE your first edit "
    "to any existing source or config file -> run /os-adr:find on the paths "
    "you are about to touch (one cheap deterministic CLI call; additions "
    "count — a new method can bypass a decided constraint). WHEN "
    "you make such a choice -> record it "
    "with /os-adr:create. If find shows an Accepted ADR covering the approach "
    "you are changing, you are reversing a recorded decision: the task is "
    "NOT COMPLETE until the superseding ADR is created with /os-adr:create "
    "(supersedes: NNNN) — code changed but ADR unrecorded means the decision "
    "history now asserts the opposite of what the code does."
)

ABSENT_NOTE = (
    "[os-adr] No ADR system in this project. /os-adr:init sets one up; "
    "/os-adr:migrate converts existing decision docs. "
    "(touch .os-adr/suppress to silence this permanently)"
)


def find_project_root(cwd: Path) -> Optional[Path]:
    """Nearest ancestor (including cwd) containing .git, else None."""
    for candidate in [cwd, *cwd.parents]:
        if (candidate / ".git").exists():
            return candidate
    return None


def adr_system_present(root: Path) -> bool:
    """The same existence rule as Ruby Adr::Repository#exists?."""
    adr_dir = root / "docs" / "adr"
    return adr_dir.is_dir() and (adr_dir / "README.md").is_file()


def decide(
    present: bool,
    suppressed: bool,
    last_reminded: Optional[date],
    today: date,
) -> Tuple[str, Optional[str]]:
    """Pure decision: ('context', note) | ('banner', note) | ('silent', None)."""
    if present:
        return ("context", PRESENT_NOTE)
    if suppressed:
        return ("silent", None)
    if last_reminded == today:
        return ("silent", None)
    return ("banner", ABSENT_NOTE)


class StateDir:
    """The gitignored per-project .os-adr/ state directory."""

    def __init__(self, root: Path) -> None:
        self._dir = root / ".os-adr"

    def suppressed(self) -> bool:
        return (self._dir / "suppress").exists()

    def last_reminded(self) -> Optional[date]:
        try:
            return date.fromisoformat(
                (self._dir / "last_reminded").read_text().strip()
            )
        except Exception:
            return None

    def stamp_reminded(self, today: date) -> None:
        self._dir.mkdir(exist_ok=True)
        (self._dir / "last_reminded").write_text(today.isoformat())


def run(root: Path, today: date, out) -> str:
    state = StateDir(root)
    outcome, note = decide(
        adr_system_present(root), state.suppressed(), state.last_reminded(), today
    )
    if outcome == "context":
        out.write(json.dumps({
            "hookSpecificOutput": {
                "hookEventName": "SessionStart",
                "additionalContext": note,
            }
        }))
    elif outcome == "banner":
        out.write(json.dumps({"systemMessage": note}))
        state.stamp_reminded(today)
    return outcome


def main() -> int:
    try:
        root = find_project_root(Path.cwd())
        if root is not None:
            run(root, date.today(), sys.stdout)
    except Exception:
        pass  # never block a session
    return 0


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