"""Models for document search operation."""

from typing import Any, cast

from pinecone.models._base import BaseModel
from pinecone.models._display import render_table


class Document:
    """A document returned from search.

    This class wraps a dictionary to provide convenient access to document fields
    while preserving all fields returned by the API.

    Attributes:
        _id: The unique identifier of the document.
        score: The relevance score for this document (if applicable).

    Note:
        Additional fields from the API response are accessible as attributes or
        via dictionary-style access with get().

    Example:
        >>> for doc in response.documents:
        ...     print(f"{doc._id}: {doc.score}")
        ...     if hasattr(doc, "title"):
        ...         print(f"  Title: {doc.title}")
        ...     # Or use get() method
        ...     price = doc.get("price", 0.0)
    """

    __slots__ = ("_data",)

    def __init__(self, data: dict[str, Any]) -> None:
        """Initialize document from dictionary.

        Args:
            data: Raw document data from API.
        """
        self._data = data

    @property
    def _id(self) -> str:
        """The unique identifier of the document."""
        return cast("str", self._data["_id"])

    @property
    def id(self) -> str:
        """Alias for _id to match protocol."""
        return cast("str", self._data["_id"])

    @property
    def score(self) -> float | None:
        """The relevance score for this document (if applicable)."""
        value = self._data.get("score")
        if value is not None:
            return float(value)
        return None

    def get(self, key: str, default: Any = None) -> Any:
        """Get a field value by key.

        Args:
            key: The field name.
            default: Default value if field is not present.

        Returns:
            The field value or default.
        """
        return self._data.get(key, default)

    def __getattr__(self, name: str) -> Any:
        """Allow attribute-style access to document fields.

        Args:
            name: The field name.

        Returns:
            The field value.

        Raises:
            AttributeError: If the field doesn't exist.
        """
        if name.startswith("_"):
            raise AttributeError(f"'Document' object has no attribute '{name}'")
        try:
            return self._data[name]
        except KeyError as e:
            raise AttributeError(f"'Document' object has no attribute '{name}'") from e

    def __repr__(self) -> str:
        """Return string representation."""
        return f"Document(_id={self._id!r}, score={self.score!r}, ...)"

    def __dir__(self) -> list[str]:
        """Return list of available attributes and fields."""
        base_attrs = ["_id", "id", "score", "get"]
        doc_fields = [k for k in self._data if not k.startswith("_") and k != "score"]
        return sorted(base_attrs + doc_fields)


class DocumentSearchResponse(BaseModel, kw_only=True):
    """Response from document search operation.

    Attributes:
        documents: List of matching documents ordered by relevance.
        usage: Usage information (read units consumed).

    Example:
        >>> response = index.documents.search(
        ...     namespace="ns",
        ...     top_k=10,
        ...     score_by=[{"type": "text", "field": "title", "text_query": "hello"}],
        ... )
        >>> print(f"Found {len(response.documents)} documents")
        >>> print(f"Used {response.usage.read_units} read units")
    """

    documents: list[Document]
    usage: "Usage | None" = None

    def __repr__(self) -> str:
        return f"DocumentSearchResponse(documents={len(self.documents)}, usage={self.usage!r})"

    def __dir__(self) -> list[str]:
        """Return public API, hiding internal attributes."""
        attrs = set(super().__dir__())
        public = {name for name in attrs if not name.startswith("_")}
        return sorted(public)

    def _repr_pretty_(self, p: Any, cycle: bool) -> None:
        """Pretty-printer support for IPython."""
        if cycle:
            p.text("DocumentSearchResponse(...)")
            return

        p.text("DocumentSearchResponse(")
        with p.group(2, "", ")"):
            p.breakable()
            p.text(f"documents={len(self.documents)},")
            p.breakable()
            p.text(f"usage={self.usage!r}")

    def _repr_html_(self) -> str:
        """Jupyter notebook HTML representation."""
        rows: list[tuple[str, str | int]] = [
            ("Documents:", len(self.documents)),
        ]

        if self.usage is not None and self.usage.read_units is not None:
            rows.append(("Read Units:", self.usage.read_units))

        return render_table("DocumentSearchResponse", rows)


class Usage(BaseModel, kw_only=True):
    """Usage information for document operations.

    Attributes:
        read_units: Number of read units consumed by the operation.
    """

    read_units: int | None = None
