"""Renderer for ``recall_update`` events — the ``schedule_recall`` lifecycle.

The scheduler and orchestrator emit ``RecallUpdate`` (wire ``type =
"recall_update"``) whenever a recall is scheduled, fired, cancelled, skipped,
or rescheduled. Without this renderer those events hit the unknown-event
catch-all and render as a raw dict dump; this turns each lifecycle change into
a compact, color-coded card.

The self-instruction (``additional_context_message``) is not carried on the
wire — it stays visible in the ``schedule_recall`` tool-call card just above.
"""

from __future__ import annotations

from rich.box import ROUNDED
from rich.console import RenderableType
from rich.panel import Panel
from rich.text import Text

from clients.terminal.tui.themes import get_active_theme

from .base import ContentRenderer

# action → (icon, title, theme color attribute)
_ACTIONS: dict[str, tuple[str, str, str]] = {
    "scheduled":   ("⏰", "Recall scheduled",        "info"),
    "fired":       ("▶",  "Recall fired · resuming", "accent"),
    "cancelled":   ("✕", "Recall cancelled",        "error"),
    "skipped":     ("↻", "Recall skipped",          "warning"),
    "rescheduled": ("↻", "Recall rescheduled",      "info"),
}


def _format_trigger(trigger_at: str | None) -> str:
    """Trim an ISO timestamp to a readable ``YYYY-MM-DD HH:MM:SS`` form."""
    if not trigger_at:
        return ""
    return trigger_at.replace("T", " ").split(".")[0]


def _preview_message(message: str | None, max_len: int = 80) -> str | None:
    """First line of the self-instruction plus a ``(+N lines)`` hint, capped.

    Returns ``None`` when there is nothing to show.
    """
    if not message:
        return None
    lines = message.splitlines()
    first = lines[0].strip()
    if len(first) > max_len:
        first = first[: max_len - 1].rstrip() + "…"
    more = len(lines) - 1
    if more > 0:
        return f"{first} …(+{more} lines)"
    return first


class RecallRenderer(ContentRenderer):
    """Render a ``recall_update`` lifecycle event as a compact card."""

    def accepts(self, msg: dict) -> bool:
        return msg.get("type") == "recall_update"

    def render(self, msg: dict) -> RenderableType:
        theme = get_active_theme()
        action = msg.get("action") or "?"
        icon, title, color_attr = _ACTIONS.get(
            action, ("•", f"Recall · {action}", "text_dim")
        )
        color = getattr(theme, color_attr, theme.text_dim)

        panel = Panel(
            self._body(msg, theme),
            title=f"{icon} {title}",
            title_align="left",
            border_style=color.hex,
            box=ROUNDED,
        )
        return panel

    def _body(self, msg: dict, theme) -> RenderableType:
        call_type = msg.get("call_type") or ""
        trigger = _format_trigger(msg.get("trigger_at"))
        message = _preview_message(msg.get("message"))
        dim = theme.text_dim.hex

        body = Text()
        if call_type:
            body.append("type: ", style=dim)
            body.append(call_type, style=theme.text.hex)
        if trigger:
            if body.plain:
                body.append("\n")
            body.append("trigger: ", style=dim)
            body.append(trigger, style=theme.text.hex)
        if message:
            if body.plain:
                body.append("\n")
            body.append("msg: ", style=dim)
            body.append(message, style=theme.text.hex)
        return body