"""Copy-on-select widgets for TUI applications.

This module provides widgets that automatically copy selected text to the clipboard.
These are useful for allowing users to select and copy text from the TUI without
needing to use explicit keybindings.

Available widgets:
- CopyOnSelectTextArea: TextArea that copies selected text automatically
- SelectableTextArea: Read-only TextArea for displaying selectable content
"""

import asyncio
import os
import subprocess
from typing import Any, Protocol, cast

import pyperclip
from textual import on, work
from textual.widgets import TextArea


class SupportsClipboard(Protocol):
    """Minimal protocol for app clipboard support."""

    def copy_to_clipboard(self, text: str) -> None: ...


class CopyOnSelectTextArea(TextArea):
    """TextArea that automatically copies selected text to clipboard.

    This widget extends TextArea to provide copy-on-select functionality.
    When the user selects text, it is automatically copied to the clipboard
    after a short debounce period (to avoid copying while the user is still
    making their selection).

    The widget uses pyperclip for clipboard access with a fallback to
    OSC 52 escape sequences for terminals that support it, and pbcopy
    on macOS as a final fallback.

    Attributes:
        show_copy_notification: Whether to show a notification when text is copied.
            Defaults to False to avoid notification spam.
    """

    show_copy_notification: bool = False

    def __init__(
        self,
        *args: Any,
        show_copy_notification: bool = False,
        **kwargs: Any,
    ) -> None:
        """Initialize the copy-on-select TextArea.

        Args:
            *args: Positional arguments passed to TextArea.
            show_copy_notification: Whether to show notifications when copying.
            **kwargs: Keyword arguments passed to TextArea.
        """
        super().__init__(*args, **kwargs)
        self._last_selection = ""
        self._pending_copy: str | None = None
        self.show_copy_notification = show_copy_notification

    @on(TextArea.SelectionChanged)
    def _on_selection_changed(self, event: TextArea.SelectionChanged) -> None:
        """Handle selection changes and schedule copy operation."""
        if event.selection.is_empty:
            self._pending_copy = None
            return

        selected = event.text_area.selected_text
        if selected and selected != self._last_selection:
            self._pending_copy = selected
            self._schedule_copy()

    @work(exclusive=True)
    async def _schedule_copy(self) -> None:
        """Wait for selection to stabilize before copying.

        This debounces the copy operation to avoid copying while the user
        is still making their selection. Waits 150ms after the last
        selection change before actually copying.
        """
        await asyncio.sleep(0.15)
        if self._pending_copy:
            self._copy_to_clipboard(self._pending_copy)
            self._last_selection = self._pending_copy

    def _copy_to_clipboard(self, text: str) -> None:
        """Copy text to clipboard using the best available method.

        Tries pyperclip first, then falls back to OSC 52 or pbcopy
        depending on the terminal environment.
        """
        try:
            pyperclip.copy(text)
            if self.show_copy_notification:
                char_count = len(text)
                label = f"{char_count} char{'s' if char_count != 1 else ''}"
                self.notify(f"Copied {label}", timeout=1)
        except Exception:
            # Fallback to OSC 52 or pbcopy
            self._copy_fallback(text)

    def _copy_fallback(self, text: str) -> None:
        """Fallback clipboard copy methods."""
        term_program = os.environ.get("TERM_PROGRAM", "")

        # Apple Terminal doesn't support OSC 52
        if term_program == "Apple_Terminal":
            self._copy_via_pbcopy(text)
        else:
            # Try OSC 52 (works over SSH in supported terminals)
            try:
                app_obj = getattr(self, "app", None)
                if app_obj is None:
                    raise RuntimeError("App not available")
                app = cast(SupportsClipboard, app_obj)
                app.copy_to_clipboard(text)
            except Exception:
                self._copy_via_pbcopy(text)

    def _copy_via_pbcopy(self, text: str) -> None:
        """Copy text using macOS pbcopy."""
        try:
            subprocess.run("pbcopy", text=True, input=text, check=True)
        except subprocess.SubprocessError:
            pass  # Silent failure - clipboard unavailable


class SelectableTextArea(CopyOnSelectTextArea):
    """Read-only TextArea for displaying selectable content.

    This widget provides a read-only text area that users can select text from.
    Selected text is automatically copied to the clipboard. This is useful for
    displaying content like chat messages or preview panes where users need
    to be able to copy specific portions of text.

    The widget is styled to look like a Static widget but with selection
    capabilities.

    Attributes:
        show_line_numbers: Always False for this widget.
        read_only: Always True for this widget.
    """

    DEFAULT_CSS = """
    SelectableTextArea {
        background: transparent;
        border: none;
        padding: 0;
        min-height: 1;
    }

    SelectableTextArea:focus {
        border: none;
    }

    SelectableTextArea > .text-area--cursor-line {
        background: transparent;
    }
    """

    def __init__(
        self,
        text: str = "",
        *,
        id: str | None = None,
        classes: str | None = None,
        show_copy_notification: bool = False,
    ) -> None:
        """Initialize the selectable text area.

        Args:
            text: Initial text content to display.
            id: The CSS ID of the widget.
            classes: CSS classes for the widget.
            show_copy_notification: Whether to show notifications when copying.
        """
        super().__init__(
            text,
            id=id,
            classes=classes,
            read_only=True,
            show_line_numbers=False,
            show_copy_notification=show_copy_notification,
        )

    def set_content(self, content: str) -> None:
        """Set the text content of the widget.

        This is a convenience method that replaces all text in the widget.

        Args:
            content: The new text content to display.
        """
        self.text = content
