#!/usr/bin/env python3
"""
FlyDocs Hook: post-session-wrap-check.py
Triggered: PostToolUse (Bash)
Purpose: Non-blocking hint when a session-wrap / project-update body is
missing the required session-wrap sections (FLY-990).

Belt-and-suspenders to the hard guard in session.py's `wrap` command. This
hook NEVER blocks — it always exits 0. When it can see a `--body` or
`--body-file` argument whose content lacks the required section headers, it
prints a hint pointing at the template.

Exit codes:
  0 - Always (non-blocking)
"""

import json
import re
import shlex
import sys
from pathlib import Path

REQUIRED_WRAP_SECTIONS = ("Accomplished", "Next up", "Blockers", "Progress")
TEMPLATE_PATH = (
    "templates/.claude/skills/flydocs-workflow/templates/session/session-wrap.md"
)


def _missing_sections(body: str) -> list[str]:
    """Return required section headers missing from a wrap body.

    Mirrors session.py:validate_wrap_body — recognizes `##` and `**bold**`
    header styles, matched case-insensitively.
    """
    if not body:
        return list(REQUIRED_WRAP_SECTIONS)
    headers: list[str] = []
    for line in body.splitlines():
        stripped = line.strip()
        md = re.match(r'^#{1,6}\s+(.*)', stripped)
        if md:
            headers.append(md.group(1))
            continue
        bold = re.match(r'^\*\*(.+?)\*\*', stripped)
        if bold:
            headers.append(bold.group(1))
    haystack = '\n'.join(headers).lower()
    return [s for s in REQUIRED_WRAP_SECTIONS if s.lower() not in haystack]


def _read_file_safe(path: str) -> str | None:
    try:
        return Path(path).read_text()
    except (OSError, ValueError):
        return None


def _extract_body(command: str) -> str | None:
    """Pull the body from a --body or --body-file argument, if present."""
    try:
        tokens = shlex.split(command)
    except ValueError:
        return None
    body = None
    for i, tok in enumerate(tokens):
        if tok == '--body' and i + 1 < len(tokens):
            body = tokens[i + 1]
        elif tok.startswith('--body='):
            body = tok[len('--body='):]
        elif tok == '--body-file' and i + 1 < len(tokens):
            body = _read_file_safe(tokens[i + 1])
        elif tok.startswith('--body-file='):
            body = _read_file_safe(tok[len('--body-file='):])
    return body


def _invokes_session_subcommand(command: str) -> bool:
    """True when `command` actually runs `session.py wrap` or `project-update`.

    FLY-1163: this used three substring tests against the whole command string:

        if "session.py" not in command: exit
        if " wrap" not in command and " project-update" not in command: exit
        if "--body" not in command: exit

    A `gh pr create` whose body mentioned `session.py` in a table and contained
    the word "wrapper" — which matches " wrap" — satisfied all three, so the hook
    validated a pull request description against the session-wrap template.
    Anything inside a quoted argument could trigger it.

    Detection is now structural: tokenize, then require a `session.py` token
    immediately followed by the subcommand as a positional. Text inside a quoted
    argument becomes a single token and cannot be read as argv.

    Shell compounds are split first (`cd x && python3 session.py wrap ...`), so a
    real invocation is still found when it is not the first thing on the line,
    while a mention in an unrelated segment is not.
    """
    for segment in re.split(r'&&|\|\||;|\|', command):
        try:
            tokens = shlex.split(segment)
        except ValueError:
            continue
        for i, tok in enumerate(tokens):
            if not tok.endswith("session.py"):
                continue
            # First non-flag token after the script is the subcommand.
            for nxt in tokens[i + 1:]:
                if nxt.startswith("-"):
                    continue
                return nxt in ("wrap", "project-update")
            return False
    return False


def build_output(message: str) -> str:
    return json.dumps({"hookSpecificOutput": {
        "hookEventName": "PostToolUse",
        "additionalContext": message,
    }})


def main() -> None:
    try:
        input_data = json.loads(sys.stdin.read())
    except (json.JSONDecodeError, ValueError):
        print("{}")
        sys.exit(0)

    if input_data.get("tool_name") != "Bash":
        print("{}")
        sys.exit(0)

    command = input_data.get("tool_input", {}).get("command", "")

    # Only act on genuine `session.py wrap` / `project-update` invocations that
    # pass a body. FLY-1163: substring matching read prose in a quoted argument
    # as an invocation.
    if not _invokes_session_subcommand(command):
        print("{}")
        sys.exit(0)
    if "--body" not in command:  # also matches --body-file
        print("{}")
        sys.exit(0)

    body = _extract_body(command)
    if body is None:
        print("{}")
        sys.exit(0)

    missing = _missing_sections(body)
    if missing:
        msg = (
            "Session-wrap body is missing required section(s): "
            + ", ".join(missing)
            + f". Fill {TEMPLATE_PATH} and pass it via --body-file. Required: "
            + ", ".join(REQUIRED_WRAP_SECTIONS)
            + " (Notes optional)."
        )
        print(build_output(msg))
    else:
        print("{}")
    sys.exit(0)


if __name__ == "__main__":
    main()
