"""Display helpers for the talk command.

Spec source: talk-prompt-toolkit-gate-review-fixes.extract-talk-session-lifecycle
Extracted from talk_cmd.py to keep session and rendering concerns focused.
"""

from __future__ import annotations

import json
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Callable

from rich.console import Console
from rich.panel import Panel


# @invar:allow shell_result: Display helper, no Result needed
# @shell_orchestration: System prompt frame rendering for unforgeable system-level UI
def render_system_prompt(
    console: Console,
    message: str,
    actions: list[str] | None = None,
) -> None:
    """Render a system-level prompt in the unforgeable SYSTEM chrome frame.

    System prompts are visually distinct from agent text: box-drawn frame
    with a SYSTEM label. Agent text is never framed. Users learn one rule:
    "if it has the SYSTEM frame, it's the system talking."

    Args:
        console: Rich console for output.
        message: The system message text (may contain multiple lines).
        actions: Optional list of slash commands (e.g., ["/approve", "/reject"]).
    """
    body_parts: list[str] = [message]
    if actions:
        body_parts.append(" ".join(f"[bold]{a}[/bold]" for a in actions))

    body = "\n".join(body_parts)
    panel = Panel(
        body,
        title="[bold reverse yellow] SYSTEM [/bold reverse yellow]",
        border_style="yellow",
        padding=(0, 1),
    )
    console.print(panel)


# @invar:allow shell_result: Display helper, no Result needed
# @shell_orchestration: Pure formatting function scoped to CLI display module
def format_session_header(
    *,
    model: str | None,
    persona_name: str,
    current_turn: int,
    max_turns: int | None,
) -> str:
    """Format session header line for attach display.

    Format: `{model} · {persona_name} · turn {current_turn}/{max_turns}`
    - Model segment omitted when None
    - If max_turns is None => `turn {current_turn}`
    - Returns plain text only (no Rich markup). Caller applies styling.

    >>> format_session_header(model="gpt-5", persona_name="assistant", current_turn=3, max_turns=10)
    'gpt-5 · assistant · turn 3/10'
    >>> format_session_header(model=None, persona_name="assistant", current_turn=3, max_turns=10)
    'assistant · turn 3/10'
    >>> format_session_header(model="gpt-5", persona_name="assistant", current_turn=3, max_turns=None)
    'gpt-5 · assistant · turn 3'
    >>> format_session_header(model=None, persona_name="assistant", current_turn=1, max_turns=None)
    'assistant · turn 1'
    """
    parts = []
    if model is not None:
        parts.append(model)
    parts.append(persona_name)

    if max_turns is not None:
        parts.append(f"turn {current_turn}/{max_turns}")
    else:
        parts.append(f"turn {current_turn}")

    return " · ".join(parts)


# @invar:allow shell_result: Display helper for formatting, no I/O
# @invar:allow shell_complexity: Branching for optional segments, standard for CLI display formatting
# @shell_orchestration: Display formatting retained in shell for command-local rendering context
def format_turn_metadata(
    turn: int | None,
    total_tokens: int,
    console_width: int = 80,
) -> str:
    """Format turn metadata line for stream_done display.

    Format: `turn N[/M] · N,NNN tok` (right-aligned to console width)
    - Token segment omitted when total_tokens == 0
    - turn segment omitted when turn is None
    - Token count comma-formatted (e.g., 1,247)
    - Returns plain text only (no Rich markup). Caller applies styling.
    - Returns right-aligned plain text; caller applies style and rendering.

    Args:
        turn: Current turn number (None if not available).
        total_tokens: Total tokens used (0 = omit token segment).
        console_width: Console width for right-alignment (default 80).

    Returns:
        Right-aligned plain text metadata string.

    >>> # Content check (plain text, no Rich markup):
    >>> "turn 3" in format_turn_metadata(turn=3, total_tokens=1247)
    True
    >>> "1,247 tok" in format_turn_metadata(turn=3, total_tokens=1247)
    True
    >>> format_turn_metadata(turn=None, total_tokens=5000).strip()
    '5,000 tok'
    >>> format_turn_metadata(turn=5, total_tokens=0).strip()
    'turn 5'
    >>> format_turn_metadata(turn=None, total_tokens=0)
    ''
    >>> # Right-alignment test
    >>> result = format_turn_metadata(turn=3, total_tokens=1247, console_width=80)
    >>> len(result) == 80
    True
    """
    parts: list[str] = []

    # Turn segment
    if turn is not None:
        parts.append(f"turn {turn}")

    # Token segment (omit when total_tokens == 0)
    if total_tokens > 0:
        token_str = f"{total_tokens:,} tok"
        parts.append(token_str)

    if not parts:
        return ""

    content = " · ".join(parts)

    # Right-align to console width
    content_len = len(content)
    if content_len < console_width:
        padding = " " * (console_width - content_len)
        return f"{padding}{content}"

    # No padding needed if content exceeds width
    return content


SLASH_HELP_LINES = (
    "Available slash commands:",
    "  /help                       Show this help",
    "  /stop                       Cancel current agent turn (agent keeps running)",
    "  /approve [child_id] [note]  Approve pending action (auto-targets if unambiguous)",
    "  /reject [child_id] [reason] Reject pending action (agent continues)",
    "  /switch <persona>           Switch to a different persona (hot-swap)",
    "  /quit                       Detach from agent",
    "",
    "Keyboard:",
    "  Enter                       Send message",
    "  Shift+Enter                 New line (Ghostty/Kitty/WezTerm)",
    "  Esc then Enter              New line (universal fallback)",
)


# @invar:allow shell_result: Prompt coordination wrapper, no Result needed
# @shell_orchestration: Delegates redraw safety to prompt_toolkit patch_stdout
def emit_with_prompt_coordination(
    *,
    emit: Callable[[], None],
    console: Console | None = None,
    json_output: bool | None = None,
) -> None:
    """Render output while prompt_toolkit manages prompt redraw."""
    _ = console
    _ = json_output
    emit()


# @invar:allow shell_result: Display helper, no Result needed
def print_help(
    *, json_output: bool, console: Console, slash_help_lines: tuple[str, ...]
) -> None:
    """Print slash-command help in the appropriate output format."""
    if json_output:
        # Use pure json.dumps() for machine-readable output - no ANSI escapes.
        # console.print_json() emits ANSI codes when force_terminal=True.
        console.file.write(
            json.dumps(
                {
                    "event": "help",
                    "commands": [
                        "/help",
                        "/stop",
                        "/approve [child_id] [note]",
                        "/reject [child_id] [reason]",
                        "/switch <persona>",
                        "/quit",
                    ],
                }
            )
            + "\n"
        )
    else:
        console.print("\n".join(slash_help_lines))


# @invar:allow shell_result: Display helper, no Result needed
def print_detach(
    *,
    agent_id: str,
    json_output: bool,
    console: Console,
    newline_prefix: bool = False,
) -> None:
    """Print detach message in the appropriate output format."""
    if json_output:
        # Use pure json.dumps() for machine-readable output - no ANSI escapes.
        # console.print_json() emits ANSI codes when force_terminal=True.
        console.file.write(json.dumps({"event": "detach", "agent_id": agent_id}) + "\n")
    else:
        prefix = "\n" if newline_prefix else ""
        console.print(f"{prefix}[dim]Detaching from agent (agent continues)[/dim]")


def print_inline_command_error(
    *,
    json_output: bool,
    console: Console,
    code: str,
    numeric_code: int,
    message: str,
    details: dict[str, str] | None = None,
) -> None:
    """Print a talk-session command error without terminating the REPL."""
    if json_output:
        # Use pure json.dumps() for machine-readable output - no ANSI escapes.
        # console.print_json() emits ANSI codes when force_terminal=True.
        error_payload: dict[str, object] = {
            "code": code,
            "numeric_code": numeric_code,
            "message": message,
        }
        if details is not None:
            error_payload["details"] = details
        console.file.write(json.dumps({"error": error_payload}) + "\n")
        return

    console.print(f"[red]Error: {message}[/red]")


# @invar:allow shell_result: MCP connection error handler, no Result needed
def handle_connection_error(
    *,
    error_code: str,
    numeric_code: int,
    message: str,
    socket_path: Path,
    json_output: bool,
    console: Console,
    human_message: str,
) -> None:
    """Print connection error and exit with code 1."""
    if json_output:
        # Use pure json.dumps() for machine-readable output - no ANSI escapes.
        # console.print_json() emits ANSI codes when force_terminal=True.
        console.file.write(
            json.dumps(
                {
                    "error": {
                        "code": error_code,
                        "numeric_code": numeric_code,
                        "message": message,
                        "details": {"socket_path": str(socket_path)},
                    }
                }
            )
            + "\n"
        )
    else:
        console.print(f"[red]{human_message}[/red]")
    sys.exit(1)
