from __future__ import annotations

import re
from dataclasses import dataclass, field
from typing import Literal

_THINK_TAG_PATTERN = re.compile(r"<think>(.*?)</think>", re.DOTALL | re.IGNORECASE)
_MAX_TOOL_VALUE_LENGTH = 80
_OMITTED_TOOL_FIELDS = {"content", "old_string", "new_string", "contents", "text", "todos"}

CanonicalEventKind = Literal[
    "plain_text",
    "session_start",
    "thinking",
    "reply",
    "tool_call",
    "tool_result",
    "hook_feedback",
    "completed",
]


@dataclass(frozen=True)
class CanonicalLogEvent:
    kind: CanonicalEventKind
    text: str | None = None
    tool_name: str | None = None
    tool_input: dict[object, object] | None = None
    summary_parts: tuple[str, ...] = field(default_factory=tuple)
    status: str | None = None


def _collapse_whitespace(value: str) -> str:
    return " ".join(value.split())


def _normalize_multiline_text(value: str) -> str:
    lines = [_collapse_whitespace(line) for line in value.splitlines()]
    normalized_lines: list[str] = []
    previous_blank = False
    for line in lines:
        if line:
            normalized_lines.append(line)
            previous_blank = False
            continue
        if not previous_blank:
            normalized_lines.append("")
        previous_blank = True
    while normalized_lines and normalized_lines[0] == "":
        normalized_lines.pop(0)
    while normalized_lines and normalized_lines[-1] == "":
        normalized_lines.pop()
    return "\n".join(normalized_lines)


def _prefix_multiline_text(prefix: str, value: str) -> str:
    normalized = _normalize_multiline_text(value)
    if not normalized:
        return prefix
    lines = normalized.splitlines()
    return "\n".join([f"{prefix} {lines[0]}", *[f"  {line}" if line else "" for line in lines[1:]]])


def _render_reply_text(prefix: str, value: str) -> str:
    normalized = _normalize_multiline_text(value)
    if not normalized:
        return prefix
    if "\n" not in normalized:
        return f"{prefix} {normalized}"
    body = "\n".join(f"  {line}" if line else "" for line in normalized.splitlines())
    return f"{prefix}\n\n{body}\n"


def canonicalize_message_text(message_text: str, *, as_hook_feedback: bool) -> list[CanonicalLogEvent]:
    if as_hook_feedback:
        return [CanonicalLogEvent(kind="hook_feedback", text=message_text)]

    think_matches = [match.strip() for match in _THINK_TAG_PATTERN.findall(message_text) if match.strip()]
    visible_text = _THINK_TAG_PATTERN.sub(" ", message_text).strip()

    events: list[CanonicalLogEvent] = []
    if think_matches:
        events.append(CanonicalLogEvent(kind="thinking", text="\n\n".join(think_matches)))
    if visible_text:
        events.append(CanonicalLogEvent(kind="reply", text=visible_text))
    if events:
        return events

    normalized_text = _normalize_multiline_text(message_text)
    if normalized_text:
        return [CanonicalLogEvent(kind="plain_text", text=normalized_text)]
    return []


def _format_tool_call_name(tool_name: object) -> str | None:
    if isinstance(tool_name, str) and tool_name.strip():
        return tool_name.strip()
    return None


def _truncate_tool_value(value: str, max_length: int = _MAX_TOOL_VALUE_LENGTH) -> str:
    collapsed = _collapse_whitespace(value)
    if len(collapsed) <= max_length:
        return collapsed
    return collapsed[: max_length - 3].rstrip() + "..."


def _format_tool_field(name: str, value: object) -> str | None:
    if isinstance(value, str):
        rendered_value = value if name.endswith("_path") or name in {"path", "command"} else _truncate_tool_value(value)
        if not rendered_value or not rendered_value.strip():
            return None
        if name == "pattern" or any(char.isspace() for char in rendered_value):
            return f'{name}="{rendered_value}"'
        return f"{name}={rendered_value}"
    if isinstance(value, bool):
        return f"{name}={str(value).lower()}"
    if isinstance(value, (int, float)):
        return f"{name}={value}"
    return None


def _summarize_generic_tool_input(tool_input: dict[object, object]) -> str | None:
    fields: list[str] = []
    for key, value in tool_input.items():
        if not isinstance(key, str) or key in _OMITTED_TOOL_FIELDS:
            continue
        rendered = _format_tool_field(key, value)
        if rendered is None:
            continue
        fields.append(rendered)
        if len(fields) == 2:
            break
    return " ".join(fields) if fields else None


def _summarize_tool_input(tool_name: str | None, tool_input: object) -> str | None:
    if not isinstance(tool_input, dict):
        return None

    if tool_name == "Read":
        return _format_tool_field("file_path", tool_input.get("file_path") or tool_input.get("path"))
    if tool_name == "Write":
        return _format_tool_field("file_path", tool_input.get("file_path") or tool_input.get("path"))
    if tool_name == "Edit":
        fields: list[str] = []
        file_path = _format_tool_field("file_path", tool_input.get("file_path") or tool_input.get("path"))
        if file_path:
            fields.append(file_path)
        for hint_key in ("replace_all", "old_str", "new_str", "insert_line", "view_range", "instruction"):
            rendered = _format_tool_field(hint_key, tool_input.get(hint_key))
            if rendered:
                fields.append(rendered)
                break
        return " ".join(fields) if fields else None
    if tool_name == "Glob":
        return _format_tool_field("pattern", tool_input.get("pattern"))
    if tool_name == "Grep":
        grep_fields: list[str] = []
        pattern = _format_tool_field("pattern", tool_input.get("pattern"))
        if pattern:
            grep_fields.append(pattern)
        for key in ("path", "dir_path", "include", "include_pattern", "glob"):
            rendered = _format_tool_field(key, tool_input.get(key))
            if rendered:
                grep_fields.append(rendered)
                break
        return " ".join(grep_fields) if grep_fields else None
    if tool_name == "Bash":
        bash_fields: list[str] = []
        description = _format_tool_field("description", tool_input.get("description"))
        if description:
            bash_fields.append(description)
        command = _format_tool_field("command", tool_input.get("command"))
        if command:
            bash_fields.append(command)
        return " ".join(bash_fields) if bash_fields else None
    if tool_name == "TodoWrite":
        todos = tool_input.get("todos")
        if isinstance(todos, list):
            return f"todos={len(todos)}"
        return None

    return _summarize_generic_tool_input(tool_input)


def render_canonical_event(event: CanonicalLogEvent) -> str | None:
    if event.kind == "plain_text":
        return event.text
    if event.kind == "thinking":
        return _prefix_multiline_text("thinking:", event.text or "")
    if event.kind == "reply":
        return _render_reply_text("reply:", event.text or "")
    if event.kind == "hook_feedback":
        return _prefix_multiline_text("hook feedback:", event.text or "")
    if event.kind == "tool_call":
        tool_summary = _summarize_tool_input(event.tool_name, event.tool_input)
        if event.tool_name:
            if tool_summary:
                return f"tool call: {event.tool_name} {tool_summary}"
            return f"tool call: {event.tool_name}"
        return "tool call"
    if event.kind == "tool_result":
        return "tool result received"
    if event.kind == "session_start":
        return "session started" + (f" ({', '.join(event.summary_parts)})" if event.summary_parts else "")
    if event.kind == "completed":
        return "completed" + (f" ({', '.join(event.summary_parts)})" if event.summary_parts else "")
    return None


def render_agent_output_for_stderr(tool_name: str, line: str) -> str | None:
    from cybervisor.adapters.registry import get_adapter

    try:
        adapter = get_adapter(tool_name)
    except ValueError:
        return line

    rendered_events = [render_canonical_event(event) for event in adapter.parse_output_line(line)]
    visible_events = [rendered for rendered in rendered_events if rendered]
    if not visible_events:
        return None
    return "\n".join(visible_events)
