#!/usr/bin/env python3
"""
Claude Board TODO Sync Hook

PostToolUse hook that syncs task tool calls to the Claude Board server.
This allows the web UI to display the current TODO list.

Handles both:
- Legacy TodoWrite (full list replacement)
- New TaskCreate/TaskUpdate (incremental operations)

This hook is non-blocking and doesn't interfere with Claude Code
if the server is unavailable.
"""

import contextlib
import json
import os
import sys
from pathlib import Path

import httpx

# Server configuration - can be overridden by environment variables
SERVER_HOST = os.environ.get("CLAUDE_BOARD_HOST", "127.0.0.1")
SERVER_PORT = int(os.environ.get("CLAUDE_BOARD_PORT", "8765"))
SERVER_BASE = f"http://{SERVER_HOST}:{SERVER_PORT}"
TIMEOUT = 5  # Short timeout - don't block Claude Code

# Tool names we handle
TASK_TOOLS = {"TaskCreate", "TaskUpdate", "TodoWrite"}


def is_claude_probe(project_path: str) -> bool:
    """Check if this is a ClaudeProbe project (should not be recorded)."""
    if not project_path:
        return False
    name = Path(project_path).resolve().name
    return "ClaudeProbe" in name or "ClaudeProbe" in project_path


def send_to_server(url: str, payload: dict) -> None:
    """Send JSON payload to server, silently ignoring errors."""
    with contextlib.suppress(httpx.ConnectError, httpx.TimeoutException, OSError):
        httpx.post(url, json=payload, timeout=TIMEOUT)


def handle_todo_write(input_data: dict) -> None:
    """Handle legacy TodoWrite - sends full list."""
    tool_input = input_data.get("tool_input", {})
    todos = tool_input.get("todos", [])
    if not todos:
        return

    project_path = input_data.get("cwd", "") or str(Path.cwd())
    session_id = input_data.get("session_id", "")

    send_to_server(
        f"{SERVER_BASE}/api/todos",
        {
            "todos": todos,
            "project_path": project_path,
            "session_id": session_id,
        },
    )


def handle_task_create(input_data: dict) -> None:
    """Handle TaskCreate - sends incremental add."""
    tool_input = input_data.get("tool_input", {})
    subject = tool_input.get("subject", "")
    if not subject:
        return

    project_path = input_data.get("cwd", "") or str(Path.cwd())
    session_id = input_data.get("session_id", "")

    send_to_server(
        f"{SERVER_BASE}/api/todos/task",
        {
            "action": "create",
            "subject": subject,
            "description": tool_input.get("description", ""),
            "activeForm": tool_input.get("activeForm", ""),
            "project_path": project_path,
            "session_id": session_id,
        },
    )


def handle_task_update(input_data: dict) -> None:
    """Handle TaskUpdate - sends incremental update."""
    tool_input = input_data.get("tool_input", {})
    task_id = tool_input.get("taskId", "")
    if not task_id:
        return

    project_path = input_data.get("cwd", "") or str(Path.cwd())
    session_id = input_data.get("session_id", "")

    payload: dict[str, object] = {
        "action": "update",
        "taskId": task_id,
        "project_path": project_path,
        "session_id": session_id,
    }

    # Include only fields that are being updated
    for field in ("status", "subject", "description", "activeForm"):
        if field in tool_input:
            payload[field] = tool_input[field]

    send_to_server(f"{SERVER_BASE}/api/todos/task", payload)


def main() -> None:
    try:
        input_data = json.load(sys.stdin)

        hook_event = input_data.get("hook_event_name", "")
        tool_name = input_data.get("tool_name", "")

        if hook_event != "PostToolUse" or tool_name not in TASK_TOOLS:
            sys.exit(0)

        # ClaudeProbe projects are not recorded
        project_path = input_data.get("cwd", "") or str(Path.cwd())
        if is_claude_probe(str(project_path)):
            sys.exit(0)

        if tool_name == "TodoWrite":
            handle_todo_write(input_data)
        elif tool_name == "TaskCreate":
            handle_task_create(input_data)
        elif tool_name == "TaskUpdate":
            handle_task_update(input_data)
        else:
            # Should not happen due to earlier check, but just in case
            pass

    except (OSError, json.JSONDecodeError, KeyError, TypeError):
        # Don't interfere with Claude Code on any error
        pass

    sys.exit(0)


if __name__ == "__main__":
    main()
