#!/usr/bin/env python3
"""
Session start hook that loads crosslink context and auto-starts sessions.
"""

import json
import re
import subprocess
import sys
import os
from datetime import datetime, timezone


# Sessions older than this (in hours) are considered stale and auto-ended
STALE_SESSION_HOURS = 4


def run_crosslink(args):
    """Run a crosslink command and return output."""
    try:
        result = subprocess.run(
            ["crosslink"] + args,
            capture_output=True,
            text=True,
            timeout=5
        )
        return result.stdout.strip() if result.returncode == 0 else None
    except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
        return None


def _is_initialized(candidate):
    """GH#625: only a dir with hook-config.json counts; strays are skipped."""
    return os.path.isfile(os.path.join(candidate, "hook-config.json"))


def check_crosslink_initialized():
    """Check if an INITIALIZED .crosslink directory exists (GH#625-safe).

    Prefers the project root derived from the hook script's own path
    (reliable even when cwd is a subdirectory), falling back to walking
    up from cwd. Stray .crosslink dirs without hook-config.json never
    count as initialized.
    """
    # Primary: resolve from script location (.claude/hooks/ -> project root)
    try:
        root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
        candidate = os.path.join(root, ".crosslink")
        if os.path.isdir(candidate) and _is_initialized(candidate):
            return True
    except (NameError, OSError):
        pass

    # Fallback: walk up from cwd
    current = os.getcwd()
    while True:
        candidate = os.path.join(current, ".crosslink")
        if os.path.isdir(candidate) and _is_initialized(candidate):
            return True
        parent = os.path.dirname(current)
        if parent == current:
            break
        current = parent

    return False


def get_session_age_minutes():
    """Parse session status to get duration in minutes. Returns None if no active session."""
    result = run_crosslink(["session", "status"])
    if not result or "Session #" not in result:
        return None
    match = re.search(r'Duration:\s*(\d+)\s*minutes', result)
    if match:
        return int(match.group(1))
    return None


def has_active_session():
    """Check if there's an active crosslink session."""
    result = run_crosslink(["session", "status"])
    if result and "Session #" in result and "(started" in result:
        return True
    return False


def auto_end_stale_session():
    """End session if it's been open longer than STALE_SESSION_HOURS."""
    age_minutes = get_session_age_minutes()
    if age_minutes is not None and age_minutes > STALE_SESSION_HOURS * 60:
        run_crosslink([
            "session", "end", "--notes",
            f"Session auto-ended (stale after {age_minutes} minutes). No handoff notes provided."
        ])
        return True
    return False


def detect_resume_event():
    """Detect if this is a resume (context compression) vs fresh startup.

    If there's already an active session, this is a resume event.
    """
    return has_active_session()


def get_last_action_from_status(status_text):
    """Extract last action from session status output."""
    if not status_text:
        return None
    match = re.search(r'Last action:\s*(.+)', status_text)
    if match:
        return match.group(1).strip()
    return None


def auto_comment_on_resume(session_status):
    """Add auto-comment on active issue when resuming after context compression."""
    if not session_status:
        return
    # Extract working issue ID
    match = re.search(r'Working on: #(\d+)', session_status)
    if not match:
        return
    issue_id = match.group(1)

    last_action = get_last_action_from_status(session_status)
    if last_action:
        comment = f"[auto] Session resumed after context compression. Last action: {last_action}"
    else:
        comment = "[auto] Session resumed after context compression."

    run_crosslink(["comment", issue_id, comment])


def get_working_issue_id(session_status):
    """Extract the working issue ID from session status text."""
    if not session_status:
        return None
    match = re.search(r'Working on: #(\d+)', session_status)
    return match.group(1) if match else None


def get_issue_labels(issue_id):
    """Get labels for an issue via crosslink issue show --json."""
    output = run_crosslink(["show", issue_id, "--json"])
    if not output:
        return []
    try:
        data = json.loads(output)
        return data.get("labels", [])
    except (json.JSONDecodeError, KeyError):
        return []


def extract_design_doc_slugs(labels):
    """Extract knowledge page slugs from design-doc:<slug> labels."""
    prefix = "design-doc:"
    return [label[len(prefix):] for label in labels if label.startswith(prefix)]


def build_design_context(session_status):
    """Build auto-injected design context from issue labels.

    Returns a formatted string block, or None if no design docs found.
    """
    issue_id = get_working_issue_id(session_status)
    if not issue_id:
        return None

    labels = get_issue_labels(issue_id)
    slugs = extract_design_doc_slugs(labels)
    if not slugs:
        return None

    parts = ["## Design Context (auto-injected)"]

    # Limit to 3 pages to respect hook timeout
    for slug in slugs[:3]:
        content = run_crosslink(["knowledge", "show", slug])
        if not content:
            parts.append(f"### {slug}\n*Page not found. Run `crosslink knowledge show {slug}` to check.*")
            continue

        if len(content) <= 8000:
            parts.append(f"### {slug}\n{content}")
        else:
            # Too large — inject summary only
            meta = run_crosslink(["knowledge", "show", slug, "--json"])
            if meta:
                try:
                    data = json.loads(meta)
                    title = data.get("title", slug)
                    tags = ", ".join(data.get("tags", []))
                    parts.append(
                        f"### {slug}\n"
                        f"**{title}** (tags: {tags})\n"
                        f"*Content too large for auto-injection ({len(content)} chars). "
                        f"View with: `crosslink knowledge show {slug}`*"
                    )
                except json.JSONDecodeError:
                    parts.append(
                        f"### {slug}\n"
                        f"*Content too large ({len(content)} chars). "
                        f"View with: `crosslink knowledge show {slug}`*"
                    )
            else:
                parts.append(
                    f"### {slug}\n"
                    f"*Content too large ({len(content)} chars). "
                    f"View with: `crosslink knowledge show {slug}`*"
                )

    if len(parts) == 1:
        return None

    return "\n\n".join(parts)


def main():
    if not check_crosslink_initialized():
        # No crosslink repo, skip
        sys.exit(0)

    context_parts = ["<crosslink-session-context>"]

    is_resume = detect_resume_event()

    # Check for stale session and auto-end it
    stale_ended = False
    if is_resume:
        stale_ended = auto_end_stale_session()
        if stale_ended:
            is_resume = False
            context_parts.append(
                "## Stale Session Warning\nPrevious session was auto-ended (open > "
                f"{STALE_SESSION_HOURS} hours). Handoff notes may be incomplete."
            )

    # Get handoff notes from previous session before starting new one
    last_handoff = run_crosslink(["session", "last-handoff"])

    # Auto-start session if none active
    if not has_active_session():
        run_crosslink(["session", "start"])

    # If resuming, add breadcrumb comment and context
    if is_resume:
        session_status = run_crosslink(["session", "status"])
        auto_comment_on_resume(session_status)

        last_action = get_last_action_from_status(session_status)
        if last_action:
            context_parts.append(
                f"## Context Compression Breadcrumb\n"
                f"This session resumed after context compression.\n"
                f"Last recorded action: {last_action}"
            )
        else:
            context_parts.append(
                "## Context Compression Breadcrumb\n"
                "This session resumed after context compression.\n"
                "No last action was recorded. Use `crosslink session action \"...\"` to track progress."
            )

    # Include previous session handoff notes if available
    if last_handoff and "No previous" not in last_handoff:
        context_parts.append(f"## Previous Session Handoff\n{last_handoff}")

    # Try to get session status
    session_status = run_crosslink(["session", "status"])
    if session_status:
        context_parts.append(f"## Current Session\n{session_status}")

    # Show agent identity if in multi-agent mode
    agent_status = run_crosslink(["agent", "status"])
    if agent_status and "No agent configured" not in agent_status:
        context_parts.append(f"## Agent Identity\n{agent_status}")

    # Sync lock state and hydrate shared issues (best-effort, non-blocking)
    sync_result = run_crosslink(["sync"])
    if sync_result:
        context_parts.append(f"## Coordination Sync\n{sync_result}")

    # Show lock assignments
    locks_result = run_crosslink(["locks", "list"])
    if locks_result and "No locks" not in locks_result:
        context_parts.append(f"## Active Locks\n{locks_result}")

    # Show knowledge repo summary
    knowledge_list = run_crosslink(["knowledge", "list", "--quiet"])
    if knowledge_list is not None:
        # --quiet outputs one slug per line; count non-empty lines
        page_count = len([line for line in knowledge_list.splitlines() if line.strip()])
        if page_count > 0:
            context_parts.append(
                f"## Knowledge Repo\n{page_count} page(s) available. "
                "Search with `crosslink knowledge search '<query>'` before researching a topic."
            )

    # Auto-inject design docs from issue labels
    design_context = build_design_context(session_status)
    if design_context:
        context_parts.append(design_context)

    # Get ready issues (unblocked work)
    ready_issues = run_crosslink(["ready"])
    if ready_issues:
        context_parts.append(f"## Ready Issues (unblocked)\n{ready_issues}")

    # Get open issues summary
    open_issues = run_crosslink(["list", "-s", "open"])
    if open_issues:
        context_parts.append(f"## Open Issues\n{open_issues}")

    context_parts.append("""
## Crosslink Workflow Reminder
- Use `crosslink session start` at the beginning of work
- Use `crosslink session work <id>` to mark current focus
- Use `crosslink session action "..."` to record breadcrumbs before context compression
- Add comments as you discover things: `crosslink issue comment <id> "..."`
- End with handoff notes: `crosslink session end --notes "..."`
- Use `crosslink locks list` to see which issues are claimed by agents
- Use `crosslink sync` to refresh lock state from the coordination branch
</crosslink-session-context>""")

    print("\n\n".join(context_parts))
    sys.exit(0)


if __name__ == "__main__":
    main()
