# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/4-ChatApp.ipynb.

# %% auto 0
__all__ = ['ChatAppConfig', 'ChatApp', 'create_default_chat_app', 'example_responder']

# %% ../../nbs/4-ChatApp.ipynb 1
from typing import Optional, Callable, List, Dict, Any
from dataclasses import dataclass, field
from fasthtml.common import *
import asyncio
import inspect

from .session import SessionManager, InMemorySessionManager, ChatSession, Message
from .service import ChatService, Responder, ErrorHandler
from .renderer import ChatRenderer
from .cards import ChatCard

# %% ../../nbs/4-ChatApp.ipynb 3
@dataclass
class ChatAppConfig:
    """Configuration for ChatApp."""

    # App metadata
    app_title: str = "Chat Application"
    page_title: str = "Chat"

    # Initial messages
    initial_messages_factory: Optional[Callable[[], List[Message]]] = None

    # Styling
    bg_color: str = "#1a1a1a"
    header_style: str = "text-align: center; padding: 1em; color: white;"
    container_style: Optional[str] = None

    # WebSocket settings
    ws_endpoint: str = "/ws"

    # FastHTML extensions and headers
    markdown_enabled: bool = True
    syntax_highlighting: bool = True
    highlight_langs: List[str] = field(
        default_factory=lambda: ["python", "javascript", "html", "css"]
    )

    # Spinner configuration
    spinner_css: Optional[str] = None

    def get_default_initial_messages(self) -> List[Message]:
        """Get default initial messages."""
        return [
            Message(role="User", content="Hi"),
            Message(
                role="Assistant",
                content="Hello! I'm your helpful assistant. How can I assist you today?",
            ),
        ]

    def get_spinner_style(self) -> str:
        """Get spinner CSS styles."""
        if self.spinner_css:
            return self.spinner_css

        return """
        .spinner {
            display: inline-block;
            width: 20px;
            height: 20px;
            border: 3px solid rgba(255, 255, 255, 0.3);
            border-top-color: #fff;
            border-radius: 50%;
            animation: spin 1s linear infinite;
        }
        
        @keyframes spin {
            to { transform: rotate(360deg); }
        }
        """

# %% ../../nbs/4-ChatApp.ipynb 4
class ChatApp:
    """
    Complete chat application with full dependency injection.

    This is the main orchestration layer that ties together:
    - Session management (state)
    - Chat service (business logic)
    - Renderer (presentation)
    - FastHTML + WebSocket (infrastructure)
    """

    def __init__(
        self,
        session_manager: SessionManager,
        chat_service: ChatService,
        renderer: ChatRenderer,
        config: Optional[ChatAppConfig] = None,
    ):
        """
        Initialize ChatApp with injected dependencies.

        Args:
            session_manager: Manages chat sessions
            chat_service: Handles message processing
            renderer: Renders UI components
            config: Application configuration
        """
        self.session_manager = session_manager
        self.chat_service = chat_service
        self.renderer = renderer
        self.config = config or ChatAppConfig()

        # Create FastHTML app
        self.app = self._create_fasthtml_app()
        self._register_routes()

    def _create_fasthtml_app(self) -> FastHTML:
        """Create and configure FastHTML application."""
        headers = []

        # Add markdown support
        if self.config.markdown_enabled:
            headers.append(MarkdownJS())

        # Add syntax highlighting
        if self.config.syntax_highlighting:
            headers.append(HighlightJS(langs=self.config.highlight_langs))

        # Add spinner styles
        headers.append(Style(self.config.get_spinner_style()))

        return FastHTML(exts="ws", hdrs=tuple(headers))

    def _get_initial_messages(self) -> List[Message]:
        """Get initial messages for new sessions."""
        if self.config.initial_messages_factory:
            return self.config.initial_messages_factory()
        return self.config.get_default_initial_messages()

    def _register_routes(self):
        """Register HTTP and WebSocket routes."""

        @self.app.route("/")
        def home():
            """Main chat interface."""
            initial_messages = self._get_initial_messages()

            container_style = self.config.container_style or (
                f"font-family: monospace, sans-serif; margin: 0; padding: 0; "
                f"background: {self.config.bg_color}; min-height: 100vh;"
            )

            return (
                Title(self.config.page_title),
                Div(
                    H1(self.config.app_title, style=self.config.header_style),
                    self.renderer.render_messages(initial_messages),
                    self.renderer.render_form(),
                    hx_ext="ws",
                    ws_connect=self.config.ws_endpoint,
                    style=container_style,
                ),
            )

        @self.app.ws(
            self.config.ws_endpoint, conn=self._on_connect, disconn=self._on_disconnect
        )
        async def ws_handler(msg: str, send, ws):
            """WebSocket message handler."""
            await self._handle_websocket_message(msg, send, ws)

    def _on_connect(self, ws, send):
        """Handle WebSocket connection."""
        session_id = str(id(ws))
        initial_messages = self._get_initial_messages()
        self.session_manager.create_session(
            session_id=session_id, initial_messages=initial_messages
        )

    def _on_disconnect(self, ws):
        """Handle WebSocket disconnection."""
        session_id = str(id(ws))
        self.session_manager.delete_session(session_id)

    async def _handle_websocket_message(self, msg: str, send, ws):
        """
        Handle incoming WebSocket message with full lifecycle:
        1. Add user message
        2. Show pending assistant message with spinner
        3. Process with chat service
        4. Update with actual response
        5. Clear input
        """
        session_id = str(id(ws))
        session = self.session_manager.get_session(session_id)

        if session is None:
            # Recreate session if somehow missing
            initial_messages = self._get_initial_messages()
            session = self.session_manager.create_session(
                session_id=session_id, initial_messages=initial_messages
            )

        # Step 1: Add user message and render
        session.add_message("User", msg)
        await send(self.renderer.render_messages(session.get_messages()))

        # Step 2: Add pending assistant message with spinner
        pending_msg = session.add_message("Assistant", "", pending=True)
        await send(self.renderer.render_messages(session.get_messages()))

        # Step 3: Process message with chat service
        try:
            response = await self.chat_service.process_message(msg, session)
        except Exception as e:
            response = f"Error: {str(e)}"

        # Step 4: Update pending message with actual response
        session.update_message(pending_msg.id, content=response, pending=False)
        await send(self.renderer.render_messages(session.get_messages()))

        # Step 5: Clear input field
        await send(self.renderer.render_input())

    def run(
        self, host: str = "0.0.0.0", port: int = 5001, reload: bool = False, **kwargs
    ):
        """
        Run the chat application.

        Args:
            host: Host to bind to
            port: Port to bind to
            reload: Enable auto-reload on code changes
            **kwargs: Additional arguments for uvicorn.run()
        """
        import uvicorn

        print(f'Link: http://{"localhost" if host=="0.0.0.0" else host}:{port}')
        uvicorn.run(self.app, host=host, port=port, reload=reload, **kwargs)

    def get_app(self) -> FastHTML:
        """Get the underlying FastHTML app for advanced usage."""
        return self.app

# %% ../../nbs/4-ChatApp.ipynb 6
def create_default_chat_app(
    responder: Responder,
    config: Optional[ChatAppConfig] = None,
    card: Optional[ChatCard] = None,
    session_manager: Optional[SessionManager] = None,
    error_handler: Optional[ErrorHandler] = None,
    context_provider: Optional[Callable] = None,
) -> ChatApp:
    """
    Factory function to create a ChatApp with sensible defaults.

    Args:
        responder: The AI/LLM responder function
        config: Optional app configuration
        card: Optional custom ChatCard for styling
        session_manager: Optional custom session manager
        error_handler: Optional custom error handler
        context_provider: Optional context provider for responder

    Returns:
        Configured ChatApp instance
    """
    # Use defaults if not provided
    session_manager = session_manager or InMemorySessionManager()
    chat_service = ChatService(
        responder=responder,
        error_handler=error_handler,
        context_provider=context_provider,
    )
    renderer = ChatRenderer(card=card)
    config = config or ChatAppConfig()

    return ChatApp(
        session_manager=session_manager,
        chat_service=chat_service,
        renderer=renderer,
        config=config,
    )

# %% ../../nbs/4-ChatApp.ipynb 8
# Example responder
async def example_responder(message: str, context=None) -> str:
    """Example echo responder with latency."""
    await asyncio.sleep(0.5)  # Simulate processing time
    return f"Echo: {message}"
