"""Reusable loading spinner component for async operations.

Provides a context manager for displaying a spinner while waiting for
server responses or other async operations.
"""

import os
import sys
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from types import TracebackType

from rich.console import Console
from rich.status import Status
from rich.text import Text


def _stderr_console() -> Console:
    width: int | None = None
    columns = os.getenv("COLUMNS")
    if columns and columns.isdigit():
        width = int(columns)

    stderr = sys.stderr
    is_tty = bool(getattr(stderr, "isatty", lambda: False)())

    return Console(
        file=stderr,
        force_terminal=is_tty,
        no_color=bool(os.getenv("NO_COLOR")),
        width=width,
        legacy_windows=False,
    )


class Spinner:
    """A loading spinner for async operations.

    Usage:
        async with Spinner("Processing...") as spinner:
            result = await some_async_operation()
            spinner.update("Almost done...")

        # Or with the module-level context manager:
        async with spinner("Loading data..."):
            data = await fetch_data()
    """

    def __init__(
        self,
        message: str = "Processing...",
        spinner_style: str = "dots",
        console: Console | None = None,
    ):
        """Initialize the spinner.

        Args:
            message: Initial status message to display
            spinner_style: Rich spinner style (dots, line, dots2, etc.)
            console: Optional console instance (uses global if not provided)
        """
        self.message = message
        self.spinner_style = spinner_style
        self._console = console or _stderr_console()
        self._status: Status | None = None
        self._enabled = self._console.is_terminal

    async def __aenter__(self) -> "Spinner":
        """Start the spinner."""
        if not self._enabled:
            return self

        self._status = Status(
            status=Text(self.message),
            console=self._console,
            spinner=self.spinner_style,
        )
        self._status.start()
        return self

    async def __aexit__(
        self,
        _exc_type: type[BaseException] | None,
        _exc_val: BaseException | None,
        _exc_tb: TracebackType | None,
    ) -> None:
        """Stop the spinner."""
        if self._status:
            self._status.stop()
            self._status = None

    def update(self, message: str) -> None:
        """Update the spinner message.

        Args:
            message: New status message to display
        """
        if self._status:
            self._status.update(Text(message))


@asynccontextmanager
async def spinner(
    message: str = "Processing...",
    spinner_style: str = "dots",
) -> AsyncIterator[Spinner]:
    """Context manager for displaying a loading spinner.

    Usage:
        async with spinner("Fetching data...") as s:
            data = await fetch_data()
            s.update("Processing response...")
            result = process(data)

    Args:
        message: Initial status message to display
        spinner_style: Rich spinner style

    Yields:
        Spinner instance for updating the message
    """
    s = Spinner(message=message, spinner_style=spinner_style)
    async with s:
        yield s
