#!/usr/bin/env python3
"""Linear Canal — Cursor sessionStart hook.

Fires when a new composer conversation is created. Fetches pre-indexed project
context from the Linear Canal MCP server and injects it as additional_context
into the session's initial system context.

Stdin:   {"session_id": "...", "workspace_roots": [...], "composer_mode": "..."}
Stdout:  {"additional_context": "<context>"}
          — injected into initial system context for this session

If the MCP server is not running, falls back to reading ARCHITECTURE.ctx directly
from disk so the hook always provides *something* useful.
"""
from __future__ import annotations

import json
import os
import sys
import urllib.error
import urllib.request


_MCP_URL = "http://localhost:3141/mcp/v1/tools/call"
_TIMEOUT = 8


def _try_mcp_server(prompt: str) -> str | None:
    """Call lc_context on the running MCP server and return the text."""
    payload = json.dumps({
        "name": "lc_context",
        "arguments": {"task_description": prompt, "token_budget": 6000},
    }).encode()
    req = urllib.request.Request(
        _MCP_URL,
        data=payload,
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=_TIMEOUT) as resp:
            data = json.loads(resp.read())
        # MCP response: {"content": [{"type": "text", "text": "..."}]}
        content = data.get("content") or data.get("result") or []
        if isinstance(content, list) and content:
            return content[0].get("text") or ""
        if isinstance(content, str):
            return content
    except (urllib.error.URLError, OSError, json.JSONDecodeError):
        pass
    return None


def _try_local_ctx(workspace_roots: list[str]) -> str | None:
    """Fall back: read ARCHITECTURE.ctx + INTERFACES.ctx directly off disk."""
    for root in workspace_roots:
        data_root = os.path.join(root, ".linear_canal_data")
        if not os.path.isdir(data_root):
            continue
        try:
            projects = [
                d for d in os.listdir(data_root)
                if os.path.isdir(os.path.join(data_root, d))
            ]
        except OSError:
            continue
        for pid in projects[:1]:
            ctx_dir = os.path.join(data_root, pid, "ctx")
            chunks: list[str] = []
            for fname in ("ARCHITECTURE.ctx", "INTERFACES.ctx", "DEPENDENCIES.ctx"):
                fpath = os.path.join(ctx_dir, fname)
                if os.path.isfile(fpath):
                    try:
                        text = open(fpath, encoding="utf-8").read()
                        chunks.append(f"### {fname}\n\n{text[:2000]}")
                    except OSError:
                        pass
            if chunks:
                return "\n\n---\n\n".join(chunks)
    return None


def main() -> None:
    try:
        body = json.load(sys.stdin)
    except Exception:
        sys.stdout.write("{}")
        return

    workspace_roots: list[str] = body.get("workspace_roots") or [
        os.environ.get("CURSOR_PROJECT_DIR", os.environ.get("CURSOR_WORKSPACE_ROOT", "."))
    ]

    # Use a generic startup prompt so lc_context returns the architecture overview
    prompt = "session start — give me an overview of this codebase"

    # 1. Try live MCP server (preferred — returns full ranked context bundle)
    ctx = _try_mcp_server(prompt)

    # 2. Fall back to local ctx files if server is not running
    if not ctx:
        ctx = _try_local_ctx(workspace_roots)

    if not ctx:
        sys.stdout.write("{}")
        return

    message = (
        "## Linear Canal — Pre-indexed Project Context\n\n"
        "> Injected automatically at session start by Linear Canal.\n"
        "> Use lc_context / lc_search / lc_impact instead of reading raw files.\n\n"
        + ctx.strip()
    )
    # sessionStart output: additional_context is added to the session's system context
    sys.stdout.write(json.dumps({"additional_context": message}))


if __name__ == "__main__":
    main()
