"""Pagination utilities for Pinecone SDK list operations.

This module provides lazy iterators for paginated API responses, enabling
memory-efficient iteration over large result sets.

Example:
    >>> # Simple iteration (most common)
    >>> for index in pc.list_indexes():
    ...     print(index.name)
    >>>
    >>> # Page-level access
    >>> for page in pc.list_indexes().pages():
    ...     print(f"Got {len(page.items)} items")
    >>>
    >>> # Convert to list
    >>> all_indexes = pc.list_indexes().to_list()
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Generic, TypeVar

from pinecone.models._base import BaseModel


if TYPE_CHECKING:
    from collections.abc import AsyncIterator, Awaitable, Callable, Iterator

T = TypeVar("T")


class Page(BaseModel, Generic[T], kw_only=True):  # pyright: ignore[reportCallIssue]
    """A single page of results from a paginated API.

    Attributes:
        items: List of items in this page.
        pagination_token: Token to fetch the next page, or None if this is the last page.

    Example:
        >>> for page in pc.list_indexes().pages():
        ...     print(f"Page has {len(page.items)} items")
        ...     if page.has_more:
        ...         print(f"More pages available")
    """

    items: list[T]
    pagination_token: str | None = None

    @property
    def has_more(self) -> bool:
        """Return True if there are more pages to fetch."""
        return self.pagination_token is not None


class Paginator(Generic[T]):
    """Lazy iterator over paginated API results.

    Iterates over individual items by default. Use `.pages()` for page-level
    iteration when you need access to pagination metadata or batch processing.

    Attributes:
        pagination_token: Current pagination token (for resuming later).

    Example:
        >>> # Item-level iteration (default)
        >>> for index in pc.list_indexes():
        ...     print(index.name)
        >>>
        >>> # Page-level iteration
        >>> for page in pc.list_indexes().pages():
        ...     process_batch(page.items)
        >>>
        >>> # Convert to list
        >>> all_indexes = pc.list_indexes().to_list()
        >>>
        >>> # Resume from token
        >>> iterator = pc.list_indexes(pagination_token="...")
    """

    def __init__(
        self,
        fetch_page: Callable[[str | None], Page[T]],
        initial_token: str | None = None,
        limit: int | None = None,
        item_type: str | None = None,
    ) -> None:
        """Initialize the paginator.

        Args:
            fetch_page: Function that fetches a page given a pagination token.
            initial_token: Token to resume pagination from a previous call.
            limit: Maximum number of items to yield (across all pages).
            item_type: Optional type name for display purposes (e.g., "IndexModel").
        """
        self._fetch_page = fetch_page
        self._current_token = initial_token
        self._limit = limit
        self._item_type = item_type
        self._exhausted = False
        self._items_yielded = 0

    def __iter__(self) -> Iterator[T]:
        """Iterate over individual items across all pages.

        Yields:
            Individual items from each page.
        """
        for page in self.pages():
            for item in page.items:
                if self._limit is not None and self._items_yielded >= self._limit:
                    self._exhausted = True
                    return
                yield item
                self._items_yielded += 1

    def pages(self) -> Iterator[Page[T]]:
        """Iterate over pages for batch processing.

        Use this when you need access to pagination metadata or want to
        process items in batches.

        Yields:
            Page objects containing items and pagination_token.

        Example:
            >>> for page in pc.list_indexes().pages():
            ...     print(f"Processing {len(page.items)} items")
            ...     for index in page.items:
            ...         process(index)
        """
        while not self._exhausted:
            page = self._fetch_page(self._current_token)
            self._current_token = page.pagination_token
            if not page.pagination_token:
                self._exhausted = True
            yield page

    def to_list(self) -> list[T]:
        """Fetch all items and return as a list.

        This loads all items into memory. For large result sets, consider
        iterating directly for memory efficiency.

        Returns:
            List of all items.

        Example:
            >>> indexes = pc.list_indexes().to_list()
            >>> print(f"Found {len(indexes)} indexes")
        """
        return list(self)

    @property
    def pagination_token(self) -> str | None:
        """Current pagination token for resuming later.

        Returns None if iteration hasn't started or has completed.
        """
        return self._current_token

    def __repr__(self) -> str:
        """Return informative representation."""
        type_label = f"Paginator[{self._item_type}]" if self._item_type else "Paginator"

        if self._exhausted:
            return f"<{type_label} (exhausted)>"
        if self._items_yielded > 0:
            hint = f"yielded {self._items_yielded} items, use in for-loop for more"
            if self._limit is not None:
                hint = (
                    f"yielded {self._items_yielded}/{self._limit} items, use in for-loop for more"
                )
            return f"<{type_label} ({hint})>"
        hint = "use in for-loop or call .to_list()"
        if self._limit is not None:
            hint = f"limit={self._limit}, {hint}"
        return f"<{type_label} ({hint})>"


class AsyncPaginator(Generic[T]):
    """Async lazy iterator over paginated API results.

    Async version of Paginator for use with AsyncPinecone client.

    Example:
        >>> # Item-level iteration
        >>> async for index in async_pc.list_indexes():
        ...     print(index.name)
        >>>
        >>> # Page-level iteration
        >>> async for page in async_pc.list_indexes().pages():
        ...     await process_batch(page.items)
        >>>
        >>> # Convert to list
        >>> all_indexes = await async_pc.list_indexes().to_list()
    """

    def __init__(
        self,
        fetch_page: Callable[[str | None], Awaitable[Page[T]]],
        initial_token: str | None = None,
        limit: int | None = None,
        item_type: str | None = None,
    ) -> None:
        """Initialize the async paginator.

        Args:
            fetch_page: Async function that fetches a page given a pagination token.
            initial_token: Token to resume pagination from a previous call.
            limit: Maximum number of items to yield (across all pages).
            item_type: Optional type name for display purposes (e.g., "IndexModel").
        """
        self._fetch_page = fetch_page
        self._current_token = initial_token
        self._limit = limit
        self._item_type = item_type
        self._exhausted = False
        self._items_yielded = 0

    async def __aiter__(self) -> AsyncIterator[T]:
        """Iterate over individual items across all pages.

        Yields:
            Individual items from each page.
        """
        async for page in self.pages():
            for item in page.items:
                if self._limit is not None and self._items_yielded >= self._limit:
                    self._exhausted = True
                    return
                yield item
                self._items_yielded += 1

    async def pages(self) -> AsyncIterator[Page[T]]:
        """Iterate over pages for batch processing.

        Yields:
            Page objects containing items and pagination_token.
        """
        while not self._exhausted:
            page = await self._fetch_page(self._current_token)
            self._current_token = page.pagination_token
            if not page.pagination_token:
                self._exhausted = True
            yield page

    async def to_list(self) -> list[T]:
        """Fetch all items and return as a list.

        This loads all items into memory. For large result sets, consider
        iterating directly for memory efficiency.

        Returns:
            List of all items.
        """
        return [item async for item in self]

    @property
    def pagination_token(self) -> str | None:
        """Current pagination token for resuming later."""
        return self._current_token

    def __repr__(self) -> str:
        """Return informative representation."""
        type_label = f"AsyncPaginator[{self._item_type}]" if self._item_type else "AsyncPaginator"

        if self._exhausted:
            return f"<{type_label} (exhausted)>"
        if self._items_yielded > 0:
            hint = f"yielded {self._items_yielded} items, use in async for-loop for more"
            if self._limit is not None:
                hint = f"yielded {self._items_yielded}/{self._limit} items, use in async for-loop for more"
            return f"<{type_label} ({hint})>"
        hint = "use in async for-loop or call .to_list()"
        if self._limit is not None:
            hint = f"limit={self._limit}, {hint}"
        return f"<{type_label} ({hint})>"
