"""JSON wire protocol for sidecar <-> Tauri communication.

Inbound (stdin):  One JSON object per line — commands from the desktop shell.
Outbound (stdout): One JSON object per line — events from the agent engine.

Stdout is the ONLY channel for structured data. All logging goes to stderr.

Response types beyond SessionEvent (used by main.py, consumed by Rust):
    ready, done, cancelled, error, hitl_request,
    threads_list, thread_history, thread_deleted, thread_renamed,
    workspace_set, models_list, model_set, default_model_set,
    credentials_info, credentials_set,
    skills_list, skill_created, skill_deleted, skill_content,
    plugins_list, plugin_added, plugin_removed, plugin_skills_list
"""

from __future__ import annotations

import json
import sys
from dataclasses import asdict
from typing import TYPE_CHECKING, Any

from icarus.session.events import EVENT_TYPE_MAP

if TYPE_CHECKING:
    from icarus.session.events import SessionEvent


def serialize_event(event: SessionEvent) -> str:
    """Convert a SessionEvent dataclass to a compact JSON string.

    The ``type`` field is injected from the class-to-name mapping so the
    desktop shell can dispatch on event type without knowing Python classes.

    Returns:
        Single-line JSON string (no trailing newline).
    """
    event_type = EVENT_TYPE_MAP.get(type(event))
    if event_type is None:
        return json.dumps({"type": "unknown", "data": str(event)})
    data = asdict(event)
    data["type"] = event_type
    return json.dumps(data, separators=(",", ":"))


def serialize_message(msg: Any) -> dict[str, Any]:
    """Convert a LangChain message object to a JSON-serializable dict.

    Produces enough detail for the React UI to render chat bubbles,
    tool call boxes, and tool results — matching the existing component
    structure in ``desktop/ui``.
    """
    role = getattr(msg, "type", "unknown")
    content = getattr(msg, "content", "")
    # content can be a list of blocks (multimodal) — stringify for safety
    if isinstance(content, list):
        text_parts = []
        for block in content:
            if isinstance(block, str):
                text_parts.append(block)
            elif isinstance(block, dict) and block.get("type") == "text":
                text_parts.append(block.get("text", ""))
            elif isinstance(block, dict) and block.get("type") == "image_url":
                text_parts.append("[image]")
        content = "\n".join(text_parts) if text_parts else str(content)

    result: dict[str, Any] = {
        "role": role,
        "content": content,
        "id": getattr(msg, "id", None),
    }

    # AIMessage: include tool_calls so React can render ToolCallBox
    tool_calls = getattr(msg, "tool_calls", None)
    if tool_calls:
        result["tool_calls"] = [
            {"id": tc.get("id", ""), "name": tc.get("name", ""), "args": tc.get("args", {})}
            for tc in tool_calls
        ]

    # ToolMessage: include tool_call_id and tool name for pairing
    if role == "tool":
        result["tool_call_id"] = getattr(msg, "tool_call_id", None)
        result["name"] = getattr(msg, "name", None)
        result["status"] = getattr(msg, "status", "success")

    return result


def write_response(data: dict[str, Any]) -> None:
    """Write a JSON response dict as a line to stdout."""
    sys.stdout.write(json.dumps(data, separators=(",", ":")) + "\n")
    sys.stdout.flush()


def parse_command(line: str) -> dict[str, Any]:
    """Parse a single JSON command line from stdin.

    Raises:
        json.JSONDecodeError: If the line is not valid JSON.
    """
    return json.loads(line)
