"""Message rendering system for conversation display.

This module provides comprehensive message rendering for conversation display,
including message formatting, conversation buffer management, and streaming
response support.

Modern UI rendering uses the design system (T, S, Box, TagBox) for themed,
gradient-based message display with proper visual containers.
"""

import logging
import re
import threading
from collections import deque
from dataclasses import dataclass
from enum import Enum
from typing import List, Optional, Dict, Any, Callable, Tuple

# Design system imports for modern rendering
from core.ui.design_system import (
    T, S, Box, TagBox, solid, solid_fg, gradient, gradient_fg, C,
)
from .tool_spinner import get_tool_spinner, is_tool_spinner_enabled
from .terminal_state import get_global_width


logger = logging.getLogger(__name__)


class DisplayFilterRegistry:
    """Registry for message display filters.

    Plugins can register filter functions that transform message content
    before display. This allows plugins to strip/transform their own
    XML commands without hardcoding patterns in core.

    Example:
        # In plugin initialize():
        DisplayFilterRegistry.register(
            "my_plugin",
            self._strip_my_xml,
            message_types=[MessageType.ASSISTANT]
        )

        # Filter function signature:
        def _strip_my_xml(content: str, message_type: 'MessageType') -> str:
            return re.sub(r"<my-tag>.*?</my-tag>", "", content)
    """

    _filters: Dict[str, Dict[str, Any]] = {}
    _sorted_filters_cache: Optional[List[Tuple[str, Dict[str, Any]]]] = None
    _lock = threading.RLock()

    @classmethod
    def register(
        cls,
        name: str,
        filter_fn: Callable[[str, "MessageType"], str],
        message_types: Optional[List["MessageType"]] = None,
        priority: int = 100,
    ) -> None:
        """Register a display filter.

        Args:
            name: Unique name for this filter (usually plugin name).
            filter_fn: Function that takes (content, message_type) and returns transformed content.
            message_types: List of message types to apply filter to (None = all types).
            priority: Execution priority (higher = runs first).
        """
        with cls._lock:
            cls._filters[name] = {
                "fn": filter_fn,
                "message_types": message_types,
                "priority": priority,
            }
            # Invalidate cache when filters change
            cls._sorted_filters_cache = None
            logger.debug(f"Registered display filter: {name}")

    @classmethod
    def unregister(cls, name: str) -> None:
        """Unregister a display filter.

        Args:
            name: Name of the filter to remove.
        """
        with cls._lock:
            if name in cls._filters:
                del cls._filters[name]
                # Invalidate cache when filters change
                cls._sorted_filters_cache = None
                logger.debug(f"Unregistered display filter: {name}")

    @classmethod
    def apply_filters(cls, content: str, message_type: "MessageType") -> str:
        """Apply all registered filters to content.

        Args:
            content: Original message content.
            message_type: Type of message being displayed.

        Returns:
            Transformed content after all applicable filters.
        """
        with cls._lock:
            if not cls._filters:
                return content

            # Build cache if needed (only when filters change, not on every call)
            if cls._sorted_filters_cache is None:
                cls._sorted_filters_cache = sorted(
                    cls._filters.items(),
                    key=lambda x: x[1]["priority"],
                    reverse=True,
                )

            # Use cached sorted filters (defensive copy for iteration safety)
            sorted_filters = list(cls._sorted_filters_cache)

        # Apply filters outside the lock (filter functions may take time)
        for name, filter_info in sorted_filters:
            # Check if filter applies to this message type
            allowed_types = filter_info["message_types"]
            if allowed_types is not None and message_type not in allowed_types:
                continue

            try:
                content = filter_info["fn"](content, message_type)
            except Exception as e:
                logger.error(f"Display filter '{name}' failed: {e}")

        return content

    @classmethod
    def clear(cls) -> None:
        """Clear all registered filters (useful for testing)."""
        with cls._lock:
            cls._filters.clear()
            # Invalidate cache when filters change
            cls._sorted_filters_cache = None


class MessageType(Enum):
    """Types of messages in conversation."""

    USER = "user"
    ASSISTANT = "assistant"
    SYSTEM = "system"
    ERROR = "error"
    INFO = "info"
    DEBUG = "debug"


class MessageFormat(Enum):
    """Message formatting styles."""

    PLAIN = "plain"
    GRADIENT = "gradient"
    HIGHLIGHTED = "highlighted"
    DIMMED = "dimmed"


@dataclass
class ConversationMessage:
    """Represents a message in the conversation."""

    content: str
    message_type: MessageType
    format_style: MessageFormat = MessageFormat.PLAIN
    timestamp: Optional[float] = None
    metadata: Dict[str, Any] = None

    def __post_init__(self):
        if self.metadata is None:
            self.metadata = {}
        if self.timestamp is None:
            import time

            self.timestamp = time.time()


class MessageFormatter:
    """Handles formatting of individual messages."""

    def __init__(self, visual_effects=None):
        """Initialize message formatter.

        Args:
            visual_effects: VisualEffects instance for applying effects.
        """
        self.visual_effects = visual_effects
        self._format_functions: Dict[MessageFormat, Callable] = {
            MessageFormat.PLAIN: self._format_plain,
            MessageFormat.GRADIENT: self._format_gradient,
            MessageFormat.HIGHLIGHTED: self._format_highlighted,
            MessageFormat.DIMMED: self._format_dimmed,
        }

    def format_message(self, message: ConversationMessage) -> str:
        """Format a conversation message for display.

        Args:
            message: ConversationMessage to format.

        Returns:
            Formatted message string.
        """
        formatter = self._format_functions.get(
            message.format_style, self._format_plain
        )
        return formatter(message)

    def _format_plain(self, message: ConversationMessage) -> str:
        """Format message with no special effects.

        Args:
            message: Message to format.

        Returns:
            Plain formatted message.
        """
        return message.content

    def _format_gradient(self, message: ConversationMessage) -> str:
        """Format message with gradient effects.

        Args:
            message: Message to format.

        Returns:
            Gradient formatted message.
        """
        if not self.visual_effects:
            return message.content

        # Apply appropriate gradient based on message type
        if message.message_type == MessageType.USER:
            return self.visual_effects.apply_message_gradient(
                message.content, "white_to_grey"
            )
        elif message.message_type == MessageType.ASSISTANT:
            return self.visual_effects.apply_message_gradient(
                message.content, "white_to_grey"
            )
        else:
            return self.visual_effects.apply_message_gradient(
                message.content, "dim_white"
            )

    def _format_highlighted(self, message: ConversationMessage) -> str:
        """Format message with highlighting effects.

        Args:
            message: Message to format.

        Returns:
            Highlighted formatted message.
        """
        # Apply highlighting based on message type
        if message.message_type == MessageType.ERROR:
            return f"\033[1;31m{message.content}\033[0m"  # Bright red
        elif message.message_type == MessageType.SYSTEM:
            return f"\033[1;33m{message.content}\033[0m"  # Bright yellow
        elif message.message_type == MessageType.INFO:
            return f"\033[1;36m{message.content}\033[0m"  # Bright cyan
        elif message.message_type == MessageType.DEBUG:
            return f"\033[2;37m{message.content}\033[0m"  # Dim white
        else:
            return f"\033[1m{message.content}\033[0m"  # Bright

    def _format_dimmed(self, message: ConversationMessage) -> str:
        """Format message with dimmed appearance.

        Args:
            message: Message to format.

        Returns:
            Dimmed formatted message.
        """
        return f"\033[2m{message.content}\033[0m"


class ConversationBuffer:
    """Manages conversation message history with formatting."""

    def __init__(self, max_messages: int = 1000):
        """Initialize conversation buffer.

        Args:
            max_messages: Maximum messages to keep in buffer.
        """
        self.max_messages = max_messages
        self.messages = deque(maxlen=max_messages)
        self._message_counter = 0

    def add_message(
        self,
        content: str,
        message_type: MessageType,
        format_style: MessageFormat = MessageFormat.PLAIN,
        **metadata,
    ) -> None:
        """Add a message to the conversation buffer.

        Args:
            content: Message content.
            message_type: Type of message.
            format_style: Formatting style to apply.
            **metadata: Additional metadata for the message.
        """
        message = ConversationMessage(
            content=content,
            message_type=message_type,
            format_style=format_style,
            metadata=metadata,
        )

        self.messages.append(message)
        self._message_counter += 1
        logger.debug(f"Added {message_type.value} message to conversation buffer")

    def get_recent_messages(self, count: int) -> List[ConversationMessage]:
        """Get the most recent messages from buffer.

        Args:
            count: Number of recent messages to retrieve.

        Returns:
            List of recent ConversationMessage objects.
        """
        if count <= 0:
            return []

        return list(self.messages)[-count:]

    def get_messages_by_type(
        self, message_type: MessageType
    ) -> List[ConversationMessage]:
        """Get all messages of a specific type.

        Args:
            message_type: Type of messages to retrieve.

        Returns:
            List of ConversationMessage objects of specified type.
        """
        return [msg for msg in self.messages if msg.message_type == message_type]

    def clear(self) -> None:
        """Clear all messages from buffer."""
        self.messages.clear()
        self._message_counter = 0
        logger.debug("Conversation buffer cleared")

    def get_stats(self) -> Dict[str, Any]:
        """Get conversation buffer statistics.

        Returns:
            Dictionary with buffer statistics.
        """
        type_counts = {}
        for msg in self.messages:
            type_counts[msg.message_type.value] = (
                type_counts.get(msg.message_type.value, 0) + 1
            )

        return {
            "total_messages": len(self.messages),
            "max_messages": self.max_messages,
            "messages_added": self._message_counter,
            "type_counts": type_counts,
        }


class ConversationRenderer:
    """Handles rendering of conversation messages to terminal."""

    def __init__(self, terminal_state, visual_effects=None):
        """Initialize conversation renderer.

        Args:
            terminal_state: TerminalState instance for output operations.
            visual_effects: VisualEffects instance for formatting.
        """
        self.terminal_state = terminal_state
        self.visual_effects = visual_effects
        self.formatter = MessageFormatter(visual_effects)
        self.buffer = ConversationBuffer()

        # Rendering configuration
        self.auto_format = True
        self.flush_immediately = True

        # State tracking
        self._last_render_count = 0
        
    def _display_sys_msg_block(self, content: str) -> None:
        """Display a compact sys_msg indicator in an orange info box.

        System messages are injected by plugins to guide the LLM (e.g., agent
        orchestrator instructions). These should be hidden from the user with
        only a small indicator shown to avoid cluttering the UI.

        Args:
            content: System message content (used to extract skill name).
        """
        # Extract skill name from first line (title)
        lines = content.split('\n')
        title_line = lines[0].strip() if lines else "system"

        # Clean up title - remove markdown headings, common prefixes
        skill_name = title_line
        skill_name = re.sub(r'^#+\s*', '', skill_name)  # Remove markdown headers
        skill_name = re.sub(r'^\*\*', '', skill_name)  # Remove bold markdown
        skill_name = skill_name.split('\n')[0].strip().lower()

        # Default fallback if title is too long or empty
        if len(skill_name) > 30 or not skill_name:
            skill_name = "system instructions"

        # Use modern rendering with orange tag
        from core.ui.design_system.components import TagBox
        from core.ui.design_system.theme import T
        from .terminal_state import get_global_width

        width = get_global_width()
        orange_tag = (255, 140, 0)  # Orange RGB

        # Single line indicator (compact, doesn't clutter UI)
        message = f"{skill_name} skill triggered"

        # Build the rendered box (single line)
        rendered = TagBox.render(
            lines=[message],
            tag_bg=orange_tag, tag_fg=(255, 255, 255), tag_width=3,
            content_colors=T().dark, content_fg=T().text, content_width=width - 3,
            tag_chars=[" ℹ "],
            use_gradient=False
        )

        # Display immediately
        self._display_message_immediately(rendered, MessageType.SYSTEM, MessageFormat.PLAIN)

    def write_message(
        self,
        content: str,
        message_type: MessageType = MessageType.ASSISTANT,
        format_style: MessageFormat = MessageFormat.GRADIENT,
        immediate_display: bool = True,
        **metadata,
    ) -> None:
        """Write a message to the conversation.

        Args:
            content: Message content to write.
            message_type: Type of message.
            format_style: How to format the message.
            immediate_display: Whether to display immediately.
            **metadata: Additional message metadata.
        """
        # Apply registered display filters (plugins can strip their XML commands)
        display_content = DisplayFilterRegistry.apply_filters(content, message_type)
        display_content = display_content.strip()

        if not display_content:
            return  # Nothing to display after filtering

        # Add to buffer
        self.buffer.add_message(display_content, message_type, format_style, **metadata)

        # Display immediately if requested
        if immediate_display:
            self._display_message_immediately(display_content, message_type, format_style)

    def start_streaming_response(self) -> None:
        """Start a streaming response by setting up the display area."""
        # Use the existing message display infrastructure
        # Write to conversation area using proper positioning
        self._display_message_immediately(
            "\n∴ ", MessageType.ASSISTANT, MessageFormat.PLAIN
        )

    def write_streaming_chunk(self, chunk: str) -> None:
        """Write a streaming chunk directly to the conversation area."""
        # Use the display infrastructure to write in the conversation area
        self._display_chunk_immediately(chunk)

    def write_user_message(self, content: str, **metadata) -> None:
        """Write a user message (convenience method).

        Args:
            content: User message content.
            **metadata: Additional metadata.
        """
        # Extract and display sys_msg blocks first (used for plugin instruction injection)
        sys_msg_pattern = re.compile(r'<sys_msg>(.*?)</sys_msg>', re.DOTALL)
        
        # Find and display all sys_msg blocks
        for match in sys_msg_pattern.finditer(content):
            sys_msg_content = match.group(1).strip()
            self._display_sys_msg_block(sys_msg_content)
        
        # Strip sys_msg blocks from user message content
        display_content = sys_msg_pattern.sub("", content).strip()

        if not display_content:
            return  # Nothing to display after stripping

        self.write_message(
            display_content, MessageType.USER, MessageFormat.HIGHLIGHTED, **metadata
        )

    def write_system_message(self, content: str, **metadata) -> None:
        """Write a system message (convenience method).

        Args:
            content: System message content.
            **metadata: Additional metadata.
        """
        self.write_message(
            content, MessageType.SYSTEM, MessageFormat.HIGHLIGHTED, **metadata
        )

    def write_error_message(self, content: str, **metadata) -> None:
        """Write an error message (convenience method).

        Args:
            content: Error message content.
            **metadata: Additional metadata.
        """
        self.write_message(
            content, MessageType.ERROR, MessageFormat.HIGHLIGHTED, **metadata
        )

    def _display_message_immediately(
        self,
        content: str,
        message_type: MessageType,
        format_style: MessageFormat,
    ) -> None:
        """Display a message immediately to the terminal.

        Args:
            content: Message content.
            message_type: Type of message.
            format_style: Formatting style.
        """
        # Skip display if content is empty (fix for duplicate display issue)
        if not content or not content.strip():
            return

        # Check if we're in pipe mode (no formatting/symbols)
        pipe_mode = getattr(self, 'pipe_mode', False)

        # Store symbol info for later application (after gradient)
        add_symbol = None
        if not pipe_mode:
            if message_type == MessageType.ASSISTANT and content.strip():
                if not content.startswith("∴") and not content.startswith("\033[36m∴"):
                    add_symbol = "llm"
            elif message_type == MessageType.USER:
                add_symbol = "user"

        # Create temporary message for formatting
        temp_message = ConversationMessage(content, message_type, format_style)

        # Format the message (apply gradient first) - skip in pipe mode
        if self.auto_format and format_style != MessageFormat.PLAIN and not pipe_mode:
            formatted_content = self.formatter.format_message(temp_message)
        else:
            formatted_content = content

        # Add symbols AFTER gradient processing - skip in pipe mode
        if add_symbol == "user":
            formatted_content = f"\033[2;33m>\033[0m {formatted_content}"
        elif add_symbol == "llm":
            formatted_content = f"\033[36m∴\033[0m {formatted_content}"

        # Exit raw mode temporarily for writing
        # Handle both enum and string cases for current_mode
        current_mode = getattr(
            self.terminal_state.current_mode,
            "value",
            self.terminal_state.current_mode,
        )
        was_raw = current_mode == "raw"
        if was_raw:
            self.terminal_state.exit_raw_mode()

        try:
            # Write to terminal
            line_count = formatted_content.count('\n') + 1
            logger.info(
                f"DISPLAY: Printing {message_type.value} message: "
                f"{len(content)} chars, {line_count} lines"
            )
            print(formatted_content, flush=self.flush_immediately)
            logger.debug(
                f"Displayed {message_type.value} message: {content[:50]}..."
            )
        finally:
            # Restore raw mode if it was active
            if was_raw:
                self.terminal_state.enter_raw_mode()

    def _display_chunk_immediately(self, chunk: str) -> None:
        """Display a streaming chunk immediately without line breaks.

        Args:
            chunk: Text chunk to display.
        """
        # Exit raw mode temporarily for writing
        current_mode = getattr(
            self.terminal_state.current_mode,
            "value",
            self.terminal_state.current_mode,
        )
        was_raw = current_mode == "raw"
        if was_raw:
            self.terminal_state.exit_raw_mode()

        try:
            # Write chunk without newline and flush immediately
            print(chunk, end="", flush=True)
        finally:
            # Restore raw mode if it was active
            if was_raw:
                self.terminal_state.enter_raw_mode()

    def render_conversation_history(self, count: Optional[int] = None) -> List[str]:
        """Render conversation history as formatted lines.

        Args:
            count: Number of recent messages to render (None for all).

        Returns:
            List of formatted message lines.
        """
        if count is None:
            messages = list(self.buffer.messages)
        else:
            messages = self.buffer.get_recent_messages(count)

        formatted_lines = []
        for message in messages:
            if self.auto_format:
                formatted_content = self.formatter.format_message(message)
            else:
                formatted_content = message.content

            # Split multi-line messages
            lines = formatted_content.split("\n")
            formatted_lines.extend(lines)

        return formatted_lines

    def clear_conversation_area(self) -> None:
        """Clear the conversation display area."""
        # This would typically involve clearing the terminal screen
        # or specific regions, depending on layout management
        if self.terminal_state.is_terminal:
            self.terminal_state.write_raw(
                "\033[2J\033[H"
            )  # Clear screen, move to home
            logger.debug("Cleared conversation area")

    def set_auto_formatting(self, enabled: bool) -> None:
        """Enable or disable automatic message formatting.

        Args:
            enabled: Whether to apply automatic formatting.
        """
        self.auto_format = enabled
        logger.debug(f"Auto formatting {'enabled' if enabled else 'disabled'}")

    def set_visual_effects(self, visual_effects) -> None:
        """Update the visual effects instance.

        Args:
            visual_effects: New VisualEffects instance.
        """
        self.visual_effects = visual_effects
        self.formatter = MessageFormatter(visual_effects)
        logger.debug("Visual effects updated")

    def get_render_stats(self) -> Dict[str, Any]:
        """Get conversation rendering statistics.

        Returns:
            Dictionary with rendering statistics.
        """
        buffer_stats = self.buffer.get_stats()

        return {
            "buffer": buffer_stats,
            "auto_format": self.auto_format,
            "flush_immediately": self.flush_immediately,
            "last_render_count": self._last_render_count,
            "terminal_mode": (
                getattr(
                    self.terminal_state.current_mode,
                    "value",
                    self.terminal_state.current_mode,
                )
                if self.terminal_state
                else "unknown"
            ),
        }


class MessageRenderer:
    """Main message rendering coordinator."""

    def __init__(self, terminal_state, visual_effects=None):
        """Initialize message renderer.

        Args:
            terminal_state: TerminalState instance.
            visual_effects: VisualEffects instance.
        """
        self.conversation_renderer = ConversationRenderer(
            terminal_state, visual_effects
        )
        self.terminal_state = terminal_state
        self.visual_effects = visual_effects

    def write_provider_error(self, provider: str, model: str, error: str) -> None:
        """Write provider-specific error message with context.

        Args:
            provider: Provider name (e.g., "OpenAI", "Anthropic")
            model: Model name
            error: Error message
        """
        error_msg = f"{provider} API error (model={model}): {error}"
        self.conversation_renderer.write_error_message(error_msg)

    def write_provider_timeout(self, provider: str, model: str) -> None:
        """Write provider timeout message with context.

        Args:
            provider: Provider name (e.g., "OpenAI", "Anthropic")
            model: Model name
        """
        timeout_msg = f"{provider} timeout (model={model}): Request timed out"
        self.conversation_renderer.write_error_message(timeout_msg)

    def write_provider_init_error(self, provider: str, error: str) -> None:
        """Write provider initialization error message.

        Args:
            provider: Provider name
            error: Error message
        """
        init_error_msg = f"Provider initialization failed: {provider} - {error}"
        self.conversation_renderer.write_error_message(init_error_msg)

    def write_message(self, content: str, apply_gradient: bool = True) -> None:
        """Write a message with optional gradient (backward compatibility).

        Args:
            content: Message content.
            apply_gradient: Whether to apply gradient effect.
        """
        format_style = (
            MessageFormat.GRADIENT if apply_gradient else MessageFormat.PLAIN
        )
        self.conversation_renderer.write_message(content, format_style=format_style)

    def write_streaming_chunk(self, chunk: str) -> None:
        """Write a streaming chunk without buffering for real-time display.

        Args:
            chunk: Text chunk to write immediately.
        """
        # Initialize streaming state if needed
        if not hasattr(self, "_streaming_buffer"):
            self._streaming_buffer = ""
            # Use the conversation renderer to start streaming properly
            self.conversation_renderer.start_streaming_response()

        # Add chunk to buffer and display it through conversation renderer
        self._streaming_buffer += chunk
        self.conversation_renderer.write_streaming_chunk(chunk)

    def finish_streaming_message(self) -> None:
        """Finish streaming and properly format the complete message."""
        if hasattr(self, "_streaming_buffer"):
            # End the streaming line
            self.terminal_state.write_raw("\n\n")
            # Reset streaming state
            del self._streaming_buffer

    def write_user_message(self, content: str) -> None:
        """Write a user message (backward compatibility).

        Args:
            content: User message content.
        """
        self.conversation_renderer.write_user_message(content)

    def write_system_message(self, content: str, **metadata) -> None:
        """Write a system message (delegated to conversation renderer).

        Args:
            content: System message content.
            **metadata: Additional metadata.
        """
        self.conversation_renderer.write_system_message(content, **metadata)

    def get_conversation_buffer(self) -> ConversationBuffer:
        """Get the conversation buffer for direct access.

        Returns:
            ConversationBuffer instance.
        """
        return self.conversation_renderer.buffer

    def get_stats(self) -> Dict[str, Any]:
        """Get comprehensive message rendering statistics.

        Returns:
            Dictionary with all rendering statistics.
        """
        return {
            "conversation": self.conversation_renderer.get_render_stats(),
            "terminal_state": (
                self.terminal_state.get_status() if self.terminal_state else {}
            ),
            "visual_effects": (
                self.visual_effects.get_effect_stats() if self.visual_effects else {}
            ),
        }


# =============================================================================
# MODERN RENDERING METHODS (using design system)
# =============================================================================

# Width calculation for modern UI - all elements use global width
# MODERN_WIDTH removed - use get_global_width() instead
# Global width auto-calculates from terminal width via config
INDENT = ""  # No indent - TagBox handles layout


class ModernMessageRenderer:
    """Modern message renderer using design system (TagBox, Box, T, S)."""

    @staticmethod
    def user_message(content: str, width: int = None) -> str:
        """Render user message with themed tag style.

        Args:
            content: User message text (may contain newlines)
            width: Total width of the message (defaults to global width)

        Returns:
            Rendered user message as string
        """
        # Use global width if not specified
        if width is None:
            width = get_global_width()

        # Split content by newlines to handle multi-line content (e.g., shell output)
        content_lines = content.split('\n')
        # Don't add space prefix here - TagBox's continuation_indent handles alignment
        lines = list(content_lines)
        # First line gets the prompt arrow, rest get blank tag
        tag_chars = [" ❯ "] + ["   "] * (len(lines) - 1)

        return TagBox.render(
            lines=lines,
            tag_bg=T().user_tag, tag_fg=T().text_dark, tag_width=3,
            content_colors=T().dark, content_fg=T().text, content_width=width - 3,
            tag_chars=tag_chars
        )

    @staticmethod
    def assistant_message(content: str, width: int = None) -> str:
        """Render assistant response with themed tag style.

        Args:
            content: Response text (may contain newlines)
            width: Total width of the message (defaults to global width)

        Returns:
            Rendered response as string
        """
        # Use global width if not specified
        if width is None:
            width = get_global_width()

        # Split content by newlines to handle multi-line content
        content_lines = content.split('\n')
        # Don't add space prefix here - TagBox's continuation_indent handles alignment
        lines = list(content_lines)
        # First line gets the diamond icon, rest get blank tag
        tag_chars = [" ◆ "] + ["   "] * (len(lines) - 1)

        return TagBox.render(
            lines=lines,
            tag_bg=T().ai_tag, tag_fg=T().text_dark, tag_width=3,
            content_colors=T().response_bg, content_fg=T().text, content_width=width - 3,
            tag_chars=tag_chars
        )

    @staticmethod
    def response_block(lines: List[str], width: int = None) -> str:
        """Render multi-line LLM response with themed AI tag bar.

        Args:
            lines: List of response text lines
            width: Total width of the message

        Returns:
            Rendered response block as string
        """
        # Use global width if not specified
        if width is None:
            width = get_global_width()

        content_lines = [f" {line}" for line in lines]
        tag_chars = [" ◆ "] + ["   "] * (len(lines) - 1)

        return TagBox.render(
            lines=content_lines,
            tag_bg=T().ai_tag, tag_fg=T().text_dark, tag_width=3,
            content_colors=T().response_bg, content_fg=T().text, content_width=width - 3,
            tag_chars=tag_chars
        )

    @staticmethod
    def tool_call(name: str, args: str, status: str = "running",
                  nested_width: int = None, result_summary: str = None) -> str:
        """Render tool call block with themed tag + colored gradient content.

        Args:
            name: Tool function name
            args: Tool arguments string
            status: Status - 'running', 'success', or 'error'
            nested_width: Width for nested content
            result_summary: Optional result summary to display (e.g., "Read 13 lines")

        Returns:
            Rendered tool call as string
        """
        # Use global width if not specified
        if nested_width is None:
            nested_width = get_global_width()

        # Get spinner frame dynamically for running status
        if status == 'running' and is_tool_spinner_enabled():
            spinner = get_tool_spinner()
            running_icon = f' {spinner.get_current_frame()} '
        else:
            running_icon = f" {C['tool_running']} "

        configs = {
            "running": (running_icon, T().tool_tag, T().text, T().secondary, T().text, "running..."),
            "success": (f" {C['tool_success']} ", T().ai_tag, T().text_dark, T().success, T().text_dark, ""),
            "error": (f" {C['tool_error']} ", T().error[0], T().text, T().error, T().text, "error"),
        }
        icon, tag_bg, tag_fg, content_colors, content_fg, status_text = configs.get(status, configs["running"])

        # Build lines - use result_summary if provided, otherwise use status text
        lines = [f" {S.BOLD}{name}({args}){S.RESET_BOLD}"]
        display_text = result_summary if result_summary else status_text
        if display_text:
            lines.append(f" {display_text}")

        return TagBox.render(
            lines=lines,
            tag_bg=tag_bg, tag_fg=tag_fg, tag_width=3,
            content_colors=content_colors, content_fg=content_fg, content_width=nested_width - 3,
            tag_chars=[icon] + ["   "] * (len(lines) - 1),
            indent=INDENT
        )

    @staticmethod
    def tool_result(lines: List[str], nested_width: int = None) -> str:
        """Render tool result block - nested, solid (no gradient).

        Args:
            lines: Result text lines
            nested_width: Width for nested content

        Returns:
            Rendered tool result as string
        """
        # Use global width if not specified
        if nested_width is None:
            nested_width = get_global_width()

        code_bg = T().code_bg
        padded = [f"  {line}" for line in lines]
        # Preserve code formatting - don't wrap tool output
        box = Box.render_solid(padded, code_bg, T().text_dim, nested_width, disable_wrapping=True)
        return "\n".join(INDENT + line for line in box.split("\n"))

    @staticmethod
    def code_block(code_lines: List[str], lang: str = "python",
                   nested_width: int = None) -> str:
        """Render code block with language-colored tag style.

        Args:
            code_lines: List of code lines
            lang: Programming language name
            nested_width: Width for nested content

        Returns:
            Rendered code block as string
        """
        # Use global width if not specified
        if nested_width is None:
            nested_width = get_global_width()

        # Language-specific tag colors
        lang_colors = {
            "python": (130, 80, 180),
            "javascript": (240, 220, 80),
            "typescript": (50, 120, 200),
            "rust": (220, 100, 50),
            "go": (80, 180, 220),
            "bash": (100, 150, 100),
            "shell": (100, 150, 100),
            "json": (200, 150, 50),
            "yaml": (150, 100, 150),
            "markdown": (100, 100, 200),
        }
        lang_bg = lang_colors.get(lang.lower(), (100, 100, 100))
        lang_fg = T().text_dark if lang == "javascript" else T().text

        lines = [f" {S.BOLD}{lang}{S.RESET_BOLD}"] + [f" {line}" for line in code_lines]
        tag_chars = [" # "] + ["   "] * len(code_lines)

        return TagBox.render(
            lines=lines,
            tag_bg=lang_bg, tag_fg=lang_fg, tag_width=3,
            content_colors=T().code_bg, content_fg=T().text, content_width=nested_width - 3,
            tag_chars=tag_chars, use_gradient=False,
            indent=INDENT,
            disable_wrapping=True  # Preserve code formatting
        )

    @staticmethod
    def error_block(title: str, message: str,
                    nested_width: int = None) -> str:
        """Render error block with themed tag style.

        Args:
            title: Error title
            message: Error message (may contain newlines)
            nested_width: Width for nested content

        Returns:
            Rendered error block as string
        """
        # Use global width if not specified
        if nested_width is None:
            nested_width = get_global_width()

        # Split message by newlines to handle multi-line content
        message_lines = message.split('\n')
        lines = [f" {S.BOLD}{title}{S.RESET_BOLD}"] + [f" {line}" for line in message_lines]
        # First line gets the error icon, rest get blank tag
        tag_chars = [" x "] + ["   "] * len(message_lines)

        return TagBox.render(
            lines=lines,
            tag_bg=T().error[0], tag_fg=T().text, tag_width=3,
            content_colors=T().error, content_fg=T().text, content_width=nested_width - 3,
            tag_chars=tag_chars,
            indent=INDENT
        )

    @staticmethod
    def warning_block(message: str, nested_width: int = None) -> str:
        """Render warning block with themed tag style.

        Args:
            message: Warning message (may contain newlines)
            nested_width: Width for nested content

        Returns:
            Rendered warning block as string
        """
        # Use global width if not specified
        if nested_width is None:
            nested_width = get_global_width()

        # Split message by newlines to handle multi-line content
        message_lines = message.split('\n')
        lines = [f" {line}" for line in message_lines]
        # First line gets the warning icon, rest get blank tag
        tag_chars = [" ! "] + ["   "] * (len(lines) - 1)

        return TagBox.render(
            lines=lines,
            tag_bg=T().warning[0], tag_fg=T().text_dark, tag_width=3,
            content_colors=T().warning, content_fg=T().text_dark, content_width=nested_width - 3,
            tag_chars=tag_chars,
            indent=INDENT
        )

    @staticmethod
    def thinking_indicator(seconds: float, tokens: Optional[int] = None,
                           width: int = None) -> str:
        """Render thinking indicator with themed tag style.

        Args:
            seconds: Thinking time in seconds
            tokens: Optional token count
            width: Total width of the indicator

        Returns:
            Rendered thinking indicator as string
        """
        # Use global width if not specified
        if width is None:
            width = get_global_width()

        text = f" thinking {seconds}s  ({tokens} tokens)" if tokens else f" thinking {seconds}s..."
        return TagBox.render(
            lines=[text],
            tag_bg=T().thinking_tag, tag_fg=T().text_dark, tag_width=3,
            content_colors=T().dark, content_fg=T().text_dim, content_width=width - 3,
            tag_chars=[" ~ "]
        )

    @staticmethod
    def info_block(message: str, width: int = None) -> str:
        """Render info block with themed tag style.

        Args:
            message: Info message (may contain newlines)
            width: Total width of the block

        Returns:
            Rendered info block as string
        """
        # Use global width if not specified
        if width is None:
            width = get_global_width()

        # Split message by newlines to handle multi-line content
        message_lines = message.split('\n')
        lines = [f" {line}" for line in message_lines]
        # First line gets the info icon, rest get blank tag
        tag_chars = [" ℹ "] + ["   "] * (len(lines) - 1)

        return TagBox.render(
            lines=lines,
            tag_bg=T().secondary[0], tag_fg=T().text_dark, tag_width=3,
            content_colors=T().secondary, content_fg=T().text_dark, content_width=width - 3,
            tag_chars=tag_chars
        )

    @staticmethod
    def success_block(message: str, width: int = None) -> str:
        """Render success block with themed tag style.

        Args:
            message: Success message (may contain newlines)
            width: Total width of the block

        Returns:
            Rendered success block as string
        """
        # Use global width if not specified
        if width is None:
            width = get_global_width()

        # Split message by newlines to handle multi-line content
        message_lines = message.split('\n')
        lines = [f" {line}" for line in message_lines]
        # First line gets the success icon, rest get blank tag
        tag_chars = [" ✔ "] + ["   "] * (len(lines) - 1)

        return TagBox.render(
            lines=lines,
            tag_bg=T().success[0], tag_fg=T().text_dark, tag_width=3,
            content_colors=T().success, content_fg=T().text_dark, content_width=width - 3,
            tag_chars=tag_chars
        )


# Add modern rendering methods to existing classes for backward compatibility
def _add_modern_methods():
    """Add modern rendering methods to existing renderer classes."""

    # Add modern methods to MessageRenderer
    def render_modern_user(self, content: str) -> None:
        """Render user message using modern design system."""
        rendered = ModernMessageRenderer.user_message(content)
        self._display_raw(rendered)

    def render_modern_assistant(self, content: str) -> None:
        """Render assistant message using modern design system."""
        rendered = ModernMessageRenderer.assistant_message(content)
        self._display_raw(rendered)

    def render_modern_response_block(self, lines: List[str]) -> None:
        """Render multi-line response using modern design system."""
        rendered = ModernMessageRenderer.response_block(lines)
        self._display_raw(rendered)

    def render_modern_tool_call(self, name: str, args: str, status: str = "running") -> None:
        """Render tool call using modern design system."""
        rendered = ModernMessageRenderer.tool_call(name, args, status)
        self._display_raw(rendered)

    def render_modern_tool_result(self, lines: List[str]) -> None:
        """Render tool result using modern design system."""
        rendered = ModernMessageRenderer.tool_result(lines)
        self._display_raw(rendered)

    def render_modern_code_block(self, code_lines: List[str], lang: str = "python") -> None:
        """Render code block using modern design system."""
        rendered = ModernMessageRenderer.code_block(code_lines, lang)
        self._display_raw(rendered)

    def render_modern_error(self, title: str, message: str) -> None:
        """Render error block using modern design system."""
        rendered = ModernMessageRenderer.error_block(title, message)
        self._display_raw(rendered)

    def render_modern_warning(self, message: str) -> None:
        """Render warning block using modern design system."""
        rendered = ModernMessageRenderer.warning_block(message)
        self._display_raw(rendered)

    def render_modern_thinking(self, seconds: float, tokens: Optional[int] = None) -> None:
        """Render thinking indicator using modern design system."""
        rendered = ModernMessageRenderer.thinking_indicator(seconds, tokens)
        self._display_raw(rendered)

    def _display_raw(self, content: str) -> None:
        """Display pre-rendered content directly to terminal."""
        if not content or not content.strip():
            return

        # Exit raw mode temporarily for writing
        current_mode = getattr(
            self.conversation_renderer.terminal_state.current_mode,
            "value",
            self.conversation_renderer.terminal_state.current_mode,
        )
        was_raw = current_mode == "raw"
        if was_raw:
            self.conversation_renderer.terminal_state.exit_raw_mode()

        try:
            # Write to terminal
            print(content, flush=True)
            print("", flush=True)  # Blank line for separation
        finally:
            # Restore raw mode if it was active
            if was_raw:
                self.conversation_renderer.terminal_state.enter_raw_mode()

    # Add methods to MessageRenderer
    MessageRenderer.render_modern_user = render_modern_user
    MessageRenderer.render_modern_assistant = render_modern_assistant
    MessageRenderer.render_modern_response_block = render_modern_response_block
    MessageRenderer.render_modern_tool_call = render_modern_tool_call
    MessageRenderer.render_modern_tool_result = render_modern_tool_result
    MessageRenderer.render_modern_code_block = render_modern_code_block
    MessageRenderer.render_modern_error = render_modern_error
    MessageRenderer.render_modern_warning = render_modern_warning
    MessageRenderer.render_modern_thinking = render_modern_thinking
    MessageRenderer._display_raw = _display_raw


# Monkey-patch the methods at import time
_add_modern_methods()
