#!/usr/bin/python3
"""
Context Load Enforcement Hook

Blocks episode generation if /load-context hasn't been run recently.

How it works:
1. When /load-context completes, it writes a timestamp to state/.context_loaded
2. This hook checks that file exists and is recent (within STALE_MINUTES)
3. If missing or stale, blocks the Write and instructs user to run /load-context

This prevents blind generation after context compaction or session resumption.

Usage: Configure in /recoil/.claude/settings.json as a PreToolUse hook
"""

import json
import sys
import re
from pathlib import Path
from datetime import datetime, timedelta

sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent.parent))  # CLAUDE_PROJECTS, for recoil.*
from recoil.core.paths import ProjectPaths

# How long before context is considered stale (in minutes)
STALE_MINUTES = 60

def get_project_root_from_path(filepath):
    """Find project root by walking up from the file path until we find state/ folder"""
    current = Path(filepath).parent
    while current != current.parent:
        if (current / "state").exists():
            return current
        current = current.parent
    return None

def check_context_loaded(project_root):
    """
    Check if context has been loaded recently.
    Returns (is_valid, message)
    """
    context_file = ProjectPaths.from_root(project_root).state_dir / ".context_loaded"

    if not context_file.exists():
        return False, "Context has never been loaded for this project"

    try:
        content = context_file.read_text().strip()
        data = json.loads(content)

        timestamp_str = data.get("timestamp")
        if not timestamp_str:
            return False, "Context file is malformed (no timestamp)"

        loaded_time = datetime.fromisoformat(timestamp_str)
        now = datetime.now()
        age = now - loaded_time

        if age > timedelta(minutes=STALE_MINUTES):
            minutes_ago = int(age.total_seconds() / 60)
            return False, f"Context was loaded {minutes_ago} minutes ago (stale after {STALE_MINUTES} min)"

        return True, f"Context loaded {int(age.total_seconds() / 60)} minutes ago"

    except (json.JSONDecodeError, ValueError) as e:
        return False, f"Context file is corrupted: {e}"

def main():
    # Read hook input from stdin
    try:
        hook_input = json.load(sys.stdin)
    except (json.JSONDecodeError, ValueError, EOFError):
        # If no JSON input, allow (not a hook context)
        sys.exit(0)

    tool_name = hook_input.get("tool_name", "")
    tool_input = hook_input.get("tool_input", {})

    # Only check Write operations
    if tool_name != "Write":
        sys.exit(0)

    file_path = tool_input.get("file_path", "")

    # Only check episode files (pattern: /episodes/ep_XXX.md)
    if "/episodes/ep_" not in file_path or not file_path.endswith(".md"):
        sys.exit(0)

    # Find project root
    project_root = get_project_root_from_path(file_path)

    if project_root is None:
        # Can't find project, allow but warn
        result = {
            "decision": "allow",
            "reason": f"No state/ folder found - proceeding without context enforcement"
        }
        print(json.dumps(result))
        sys.exit(0)

    project_name = project_root.name

    # Check if context was loaded recently
    is_valid, message = check_context_loaded(project_root)

    if not is_valid:
        result = {
            "decision": "block",
            "reason": f"""CONTEXT ENFORCEMENT [{project_name}]: {message}

You MUST run /load-context before generating episodes.

Run: /load-context {project_name} generate

This ensures:
- treatment.md is properly loaded (not summarized)
- characters.md voice patterns are fresh
- format_v12 rules are exact (not paraphrased)
- Current position is known

Generation without proper context causes structural errors and voice drift."""
        }
        print(json.dumps(result))
        sys.exit(2)

    # Context is valid, allow
    sys.exit(0)

if __name__ == "__main__":
    main()
