"""Chat panel widget for Claude SDK interaction."""

from collections.abc import Callable
from pathlib import Path
from typing import Any

from rich.markdown import Markdown
from rich.syntax import Syntax
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Vertical
from textual.widgets import LoadingIndicator, RichLog, TextArea

from cleave.tui.services.history import InputHistory, get_default_history_file
from cleave.tui.widgets.perf_metrics import PerfMetrics
from cleave.tui.widgets.tool_use import ToolInvocation, ToolUseBlock, ToolUseDisplay


class ChatInput(TextArea):
    """Multi-line input with Shift+Enter for newlines, Enter for submit.

    Supports input history navigation with Up/Down arrows.
    """

    BINDINGS = [
        Binding("enter", "submit", "Submit", show=False),
        Binding("escape", "cancel", "Cancel", show=False),
        Binding("up", "history_prev", "Previous", show=False),
        Binding("down", "history_next", "Next", show=False),
        Binding("ctrl+l", "clear_chat", "Clear", show=False),
    ]

    # Callbacks
    on_submit: Callable[[str], None] | None = None
    on_clear_chat: Callable[[], None] | None = None

    def __init__(
        self,
        history: InputHistory | None = None,
        **kwargs,
    ) -> None:
        super().__init__(**kwargs)
        self.show_line_numbers = False
        self._history = history or InputHistory()
        self._navigating_history = False

    @property
    def input_history(self) -> InputHistory:
        """Access to input history for navigation."""
        return self._history

    def action_submit(self) -> None:
        """Submit on Enter (without Shift)."""
        message = self.text.strip()
        if message and self.on_submit:
            # Add to history before clearing
            self._history.add(message)
            self._history.reset_position()
            self.on_submit(message)
            self.clear()

    def action_cancel(self) -> None:
        """Clear input on Escape."""
        self._history.reset_position()
        self.clear()

    def action_history_prev(self) -> None:
        """Navigate to previous history entry."""
        # Only navigate if cursor is at start or input is single line
        if self.text.count("\n") > 0 and self.cursor_location[0] > 0:
            # Multi-line: let default up behavior work
            return

        prev_entry = self._history.previous(self.text)
        self.text = prev_entry
        # Move cursor to end
        self.move_cursor((len(self.text.split("\n")) - 1, len(self.text.split("\n")[-1])))

    def action_history_next(self) -> None:
        """Navigate to next history entry."""
        # Only navigate if cursor is at end or input is single line
        lines = self.text.split("\n")
        if len(lines) > 1 and self.cursor_location[0] < len(lines) - 1:
            # Multi-line: let default down behavior work
            return

        next_entry = self._history.next()
        self.text = next_entry
        # Move cursor to end
        self.move_cursor((len(self.text.split("\n")) - 1, len(self.text.split("\n")[-1])))

    def action_clear_chat(self) -> None:
        """Trigger chat clear (Ctrl+L)."""
        if self.on_clear_chat:
            self.on_clear_chat()

    def _on_key(self, event) -> None:
        """Handle Shift+Enter for newlines."""
        if event.key == "enter" and event.shift:
            # Insert newline (default behavior)
            self.insert("\n")
            event.prevent_default()
            event.stop()


class ChatPanel(Vertical):
    """Chat interface for Claude SDK interaction.

    Supports both interrogation mode (Calf Raises) and execution output.

    Interface for sibling integration:
        add_message(role: str, content: str) - Add message to display
        add_streaming_token(token: str) - Append token during streaming
        finalize_streaming() - Complete streaming message
        on_submit: Callable[[str], None] - Callback when user submits
        clear_input() - Clear the input field
        set_loading(bool) - Show/hide loading indicator
        add_tool_use(name, inputs) - Display tool invocation
        update_tool_use(tool_id, ...) - Update tool status/output
    """

    DEFAULT_CSS = """
    ChatPanel {
        height: 100%;
    }

    ChatPanel RichLog {
        height: 1fr;
        border: solid $panel;
        background: $surface;
        padding: 0 1;
        scrollbar-gutter: stable;
    }

    ChatPanel ToolUseDisplay {
        height: auto;
        max-height: 15;
        display: none;
    }

    ChatPanel ToolUseDisplay.has-tools {
        display: block;
    }

    ChatPanel ChatInput {
        dock: bottom;
        height: 4;
        min-height: 3;
        max-height: 10;
        margin-top: 1;
        border: solid $panel;
    }

    ChatPanel ChatInput:focus {
        border: solid $accent;
    }

    ChatPanel LoadingIndicator {
        dock: bottom;
        height: 1;
        display: none;
    }

    ChatPanel LoadingIndicator.visible {
        display: block;
    }

    ChatPanel PerfMetrics {
        dock: bottom;
        height: 1;
        display: none;
        margin-top: 0;
    }

    ChatPanel PerfMetrics.streaming-active {
        display: block;
    }
    """

    # Callback for when user submits a message
    on_submit: Callable[[str], None] | None = None

    # Streaming state
    _streaming_buffer: str = ""
    _is_streaming: bool = False

    def __init__(
        self,
        history_file: Path | None = None,
        name: str | None = None,
        id: str | None = None,
        classes: str | None = None,
    ) -> None:
        super().__init__(name=name, id=id, classes=classes)
        self._log_entries: list[tuple[str, str]] = []
        # Initialize history (will be used by ChatInput)
        self._history = InputHistory(
            max_size=500,
            history_file=history_file or get_default_history_file(),
        )
        # Performance metrics widget (tracks streaming stats)
        self._perf_metrics = PerfMetrics(id="perf-metrics")

    def compose(self) -> ComposeResult:
        """Compose the chat log, tool display, loading indicator, and input field."""
        yield RichLog(id="chat-log", wrap=True, highlight=True, markup=True)
        yield self._perf_metrics
        yield ToolUseDisplay(id="tool-display")
        yield LoadingIndicator(id="chat-loading")
        yield ChatInput(id="chat-input", history=self._history)

    def on_mount(self) -> None:
        """Wire up input submission callback."""
        chat_input = self.query_one("#chat-input", ChatInput)
        chat_input.on_submit = self._handle_submit
        chat_input.on_clear_chat = self.clear_history

    def _handle_submit(self, message: str) -> None:
        """Handle user input submission."""
        if not message:
            return

        # Clear perf metrics on new submission
        self._perf_metrics.hide()

        # Display user message
        self.add_message("user", message)

        # Invoke callback if registered
        if self.on_submit is not None:
            self.on_submit(message)

    def add_message(self, role: str, content: str) -> None:
        """Add a message to the chat display with markdown rendering.

        Args:
            role: Message author - "user", "assistant", or "system"
            content: Message text (may contain markdown)
        """
        self._log_entries.append((role, content))
        log = self.query_one("#chat-log", RichLog)

        if role == "user":
            log.write(f"[bold cyan]You:[/]")
            log.write(content)
        elif role == "assistant":
            log.write(f"[bold green]Claude:[/]")
            # Render markdown for assistant messages
            self._render_markdown(content)
        elif role == "system":
            log.write(f"[dim italic]{content}[/]")
        else:
            log.write(f"[bold]{role}:[/]")
            log.write(content)

    def _render_markdown(self, content: str) -> None:
        """Render markdown content with syntax highlighting for code blocks.

        Args:
            content: Markdown text to render.
        """
        log = self.query_one("#chat-log", RichLog)

        # Check if content has code blocks
        if "```" in content:
            # Parse and render code blocks with syntax highlighting
            self._render_with_code_blocks(content)
        else:
            # Simple markdown rendering
            try:
                md = Markdown(content)
                log.write(md)
            except Exception:
                # Fallback to plain text
                log.write(content)

    def _render_with_code_blocks(self, content: str) -> None:
        """Render content with syntax-highlighted code blocks.

        Args:
            content: Markdown content with code blocks.
        """
        log = self.query_one("#chat-log", RichLog)
        import re

        # Pattern for fenced code blocks: ```lang\ncode\n```
        pattern = r"```(\w*)\n(.*?)```"
        last_end = 0

        for match in re.finditer(pattern, content, re.DOTALL):
            # Render text before code block
            before = content[last_end : match.start()].strip()
            if before:
                try:
                    md = Markdown(before)
                    log.write(md)
                except Exception:
                    log.write(before)

            # Render code block with syntax highlighting
            lang = match.group(1) or "text"
            code = match.group(2).rstrip()

            try:
                syntax = Syntax(
                    code,
                    lang,
                    theme="monokai",
                    line_numbers=True,
                    word_wrap=True,
                )
                log.write(syntax)
            except Exception:
                # Fallback: render as plain text in code style
                log.write(f"[dim]```{lang}[/]")
                log.write(code)
                log.write("[dim]```[/]")

            last_end = match.end()

        # Render any remaining text after last code block
        after = content[last_end:].strip()
        if after:
            try:
                md = Markdown(after)
                log.write(md)
            except Exception:
                log.write(after)

    # --- Streaming Support ---

    def start_streaming(self, role: str = "assistant") -> None:
        """Begin a streaming message.

        Args:
            role: Message role (usually "assistant").
        """
        self._streaming_buffer = ""
        self._is_streaming = True
        self._perf_metrics.start()

        log = self.query_one("#chat-log", RichLog)
        if role == "assistant":
            log.write(f"[bold green]Claude:[/]")

    def add_streaming_token(self, token: str) -> None:
        """Append a token to the streaming buffer and display.

        Args:
            token: Token text to append.
        """
        if not self._is_streaming:
            self.start_streaming()

        self._streaming_buffer += token
        self._perf_metrics.on_token(token)
        log = self.query_one("#chat-log", RichLog)

        # For streaming, we append raw text (markdown rendered at finalize)
        # Use write with end="" to avoid newlines per token
        log.write(token, expand=True, scroll_end=True)

    def finalize_streaming(self) -> str:
        """Complete the streaming message and render final markdown.

        Returns:
            The complete message content.
        """
        content = self._streaming_buffer
        self._streaming_buffer = ""
        self._is_streaming = False
        self._perf_metrics.stop()
        self._log_entries.append(("assistant", content))

        # Re-render the complete message with proper markdown
        # (The streaming display was raw; now we'd ideally replace it)
        # For now, add a newline to separate from next message
        log = self.query_one("#chat-log", RichLog)
        log.write("")  # Blank line separator

        return content

    # --- Loading Indicator ---

    def set_loading(self, loading: bool) -> None:
        """Show or hide the loading indicator.

        Args:
            loading: True to show, False to hide.
        """
        indicator = self.query_one("#chat-loading", LoadingIndicator)
        if loading:
            indicator.add_class("visible")
        else:
            indicator.remove_class("visible")

    def clear_input(self) -> None:
        """Clear the input field."""
        input_widget = self.query_one("#chat-input", ChatInput)
        input_widget.clear()

    def clear_history(self) -> None:
        """Clear the chat history."""
        log = self.query_one("#chat-log", RichLog)
        log.clear()
        self._streaming_buffer = ""
        self._is_streaming = False
        self._log_entries.clear()

        # Also clear tool display
        tool_display = self.query_one("#tool-display", ToolUseDisplay)
        tool_display.clear()
        tool_display.remove_class("has-tools")

    # --- Tool Use Display ---

    def add_tool_use(
        self,
        tool_name: str,
        inputs: dict[str, Any] | None = None,
        tool_id: str | None = None,
    ) -> str:
        """Add a tool invocation to the display.

        Args:
            tool_name: Name of the tool being invoked.
            inputs: Tool input parameters.
            tool_id: Optional ID (auto-generated if not provided).

        Returns:
            The tool ID for later updates.
        """
        tool_display = self.query_one("#tool-display", ToolUseDisplay)
        tool_display.add_class("has-tools")

        block = tool_display.add_tool(tool_name, inputs, tool_id)
        return block._invocation.tool_id

    def update_tool_use(
        self,
        tool_id: str,
        output: str | None = None,
        status: str | None = None,
        error: str | None = None,
        duration_ms: int | None = None,
    ) -> None:
        """Update an existing tool invocation.

        Args:
            tool_id: ID of the tool to update.
            output: Tool output to display.
            status: New status (pending/running/success/error).
            error: Error message if failed.
            duration_ms: Execution duration in milliseconds.
        """
        tool_display = self.query_one("#tool-display", ToolUseDisplay)
        tool_display.update_tool(
            tool_id,
            output=output,
            status=status,
            error=error,
            duration_ms=duration_ms,
        )

    def clear_tool_display(self) -> None:
        """Clear all tool invocations."""
        tool_display = self.query_one("#tool-display", ToolUseDisplay)
        tool_display.clear()
        tool_display.remove_class("has-tools")
