"""Index protocol definitions for vector operations.

This module defines protocols for vector CRUD operations on Pinecone indexes.
"""

from collections.abc import Sequence
from typing import Any, Protocol, TypedDict, runtime_checkable


# Data plane types (used for vector operations)
class SparseValues(TypedDict):
    """Sparse vector representation with indices and values."""

    indices: list[int]
    values: list[float]


# Vector metadata type (flexible dictionary)
Metadata = dict[str, Any]

# Vector input type (supports dict and tuple formats from prior SDK versions)
# Accepts: {"id": "v1", "values": [...]}, ("id", [...]), or ("id", [...], {...})
VectorInput = dict[str, Any] | tuple[str, list[float]] | tuple[str, list[float], dict[str, Any]]


@runtime_checkable
class VectorProtocol(Protocol):
    """Protocol for vector data structure.

    Vectors are the fundamental unit of data in Pinecone, containing an ID,
    dense values, optional sparse values, and optional metadata.

    Example:
        >>> vector = Vector(id="vec1", values=[0.1, 0.2, 0.3], metadata={"category": "example"})
    """

    @property
    def id(self) -> str:
        """Unique identifier for the vector."""
        ...

    @property
    def values(self) -> list[float]:
        """Get the dense vector values."""
        ...

    @property
    def sparse_values(self) -> SparseValues | None:
        """Optional sparse vector values."""
        ...

    @property
    def metadata(self) -> Metadata | None:
        """Get the optional metadata dictionary."""
        ...


@runtime_checkable
class ScoredVectorProtocol(Protocol):
    """Protocol for query result vectors with similarity scores.

    Example:
        >>> for match in results.matches:
        ...     print(f"ID: {match.id}, Score: {match.score}")
    """

    @property
    def id(self) -> str:
        """Vector ID."""
        ...

    @property
    def score(self) -> float:
        """Similarity score."""
        ...

    @property
    def values(self) -> list[float] | None:
        """Get optional dense vector values.

        Only included if include_values=True in the query.
        """
        ...

    @property
    def sparse_values(self) -> SparseValues | None:
        """Optional sparse vector values (if include_values=True)."""
        ...

    @property
    def metadata(self) -> Metadata | None:
        """Get optional metadata.

        Only included if include_metadata=True in the query.
        """
        ...


@runtime_checkable
class QueryResponseProtocol(Protocol):
    """Protocol for query operation responses.

    Example:
        >>> response = index.query(vector=[0.1] * 128, top_k=10)
        >>> print(f"Found {len(response.matches)} matches")
        >>> for match in response.matches:
        ...     print(f"{match.id}: {match.score}")
    """

    @property
    def matches(self) -> list[ScoredVectorProtocol]:
        """List of matching vectors with scores."""
        ...

    @property
    def namespace(self) -> str:
        """Namespace queried."""
        ...


@runtime_checkable
class UpsertResponseProtocol(Protocol):
    """Protocol for upsert operation responses.

    Example:
        >>> response = index.upsert(vectors=vectors)
        >>> print(f"Upserted {response.upserted_count} vectors")
    """

    @property
    def upserted_count(self) -> int:
        """Number of vectors successfully upserted."""
        ...


@runtime_checkable
class FetchResponseProtocol(Protocol):
    """Protocol for fetch operation responses.

    Example:
        >>> response = index.fetch(ids=["vec1", "vec2"])
        >>> for vec_id, vector in response.vectors.items():
        ...     print(f"{vec_id}: {vector.values}")
    """

    @property
    def vectors(self) -> dict[str, VectorProtocol]:
        """Dictionary mapping vector IDs to vector objects."""
        ...

    @property
    def namespace(self) -> str:
        """Namespace fetched from."""
        ...


@runtime_checkable
class DeleteResponseProtocol(Protocol):
    """Protocol for delete operation responses.

    Example:
        >>> response = index.delete(ids=["vec1", "vec2"])
    """

    # Delete operations typically don't return data


@runtime_checkable
class DescribeIndexStatsResponseProtocol(Protocol):
    """Protocol for index statistics responses.

    Example:
        >>> stats = index.describe_index_stats()
        >>> print(f"Total vectors: {stats.total_vector_count}")
        >>> print(f"Dimension: {stats.dimension}")
    """

    @property
    def dimension(self) -> int:
        """Vector dimensionality."""
        ...

    @property
    def index_fullness(self) -> float:
        """Index capacity utilization (0.0 to 1.0)."""
        ...

    @property
    def total_vector_count(self) -> int:
        """Total number of vectors across all namespaces."""
        ...

    @property
    def namespaces(self) -> dict[str, dict[str, int]]:
        """Per-namespace vector counts."""
        ...


@runtime_checkable
class UpdateResponseProtocol(Protocol):
    """Protocol for update operation responses.

    Example:
        >>> response = index.update(id="vec1", values=[0.1, 0.2, 0.3])
        >>> print(f"Matched {response.matched_records} records")
    """

    @property
    def matched_records(self) -> int:
        """Number of records matched by the update."""
        ...


@runtime_checkable
class ListVectorsResponseProtocol(Protocol):
    """Protocol for list vectors operation responses.

    Example:
        >>> response = index.list(prefix="doc#")
        >>> for item in response.vectors:
        ...     print(item.id)
    """

    @property
    def vectors(self) -> list[Any]:
        """List of vector ID items."""
        ...

    @property
    def namespace(self) -> str | None:
        """Namespace listed from."""
        ...

    @property
    def pagination_token(self) -> str | None:
        """Token for fetching the next page, or None if no more pages."""
        ...


@runtime_checkable
class FetchByMetadataResponseProtocol(Protocol):
    """Protocol for fetch-by-metadata operation responses.

    Example:
        >>> response = index.fetch_by_metadata(filter={"genre": "comedy"})
        >>> for vec_id, vector in response.vectors.items():
        ...     print(f"{vec_id}: {vector.metadata}")
    """

    @property
    def vectors(self) -> dict[str, VectorProtocol]:
        """Dictionary mapping vector IDs to vector objects."""
        ...

    @property
    def namespace(self) -> str:
        """Namespace fetched from."""
        ...

    @property
    def pagination_token(self) -> str | None:
        """Token for fetching the next page, or None if no more pages."""
        ...


@runtime_checkable
class IndexProtocol(Protocol):
    """Protocol for vector index operations.

    This protocol defines all vector CRUD operations available on a Pinecone index.

    Example:
        >>> from pinecone import Pinecone
        >>> pc = Pinecone(api_key="...")
        >>> index = pc.index(host="my-index-abc123.svc.pinecone.io")
        >>>
        >>> # Upsert vectors
        >>> index.upsert(
        ...     vectors=[
        ...         {"id": "vec1", "values": [0.1, 0.2, 0.3]},
        ...         {"id": "vec2", "values": [0.4, 0.5, 0.6]},
        ...     ]
        ... )
        >>>
        >>> # Query similar vectors
        >>> results = index.query(vector=[0.1, 0.2, 0.3], top_k=10, include_metadata=True)
    """

    def upsert(
        self,
        vectors: Sequence[VectorInput] | Sequence[VectorProtocol],
        *,
        namespace: str = "",
    ) -> UpsertResponseProtocol:
        """Insert or update vectors.

        Args:
            vectors: Sequence of vectors to upsert. Each vector can be:
                - Dict with 'id', 'values', optional 'sparse_values', and optional 'metadata'
                - 2-tuple of (id, values) for simple vectors
                - 3-tuple of (id, values, metadata) for vectors with metadata
            namespace: Optional namespace to upsert into.

        Returns:
            UpsertResponse containing the count of upserted vectors.

        Raises:
            PineconeException: If upsert operation fails.

        Example:
            >>> index.upsert(
            ...     vectors=[
            ...         {"id": "v1", "values": [0.1, 0.2], "metadata": {"key": "value"}},
            ...         {"id": "v2", "values": [0.3, 0.4]},
            ...     ]
            ... )
        """
        ...

    def query(
        self,
        *,
        vector: list[float] | None = None,
        id: str | None = None,
        top_k: int = 10,
        namespace: str = "",
        filter: dict[str, Any] | None = None,
        include_values: bool = False,
        include_metadata: bool = True,
        sparse_vector: SparseValues | None = None,
    ) -> QueryResponseProtocol:
        """Query vectors by similarity.

        Args:
            vector: Dense query vector values. Required if id is not provided.
            id: Query by vector ID. Required if vector is not provided.
            top_k: Number of results to return.
            namespace: Optional namespace to query.
            filter: Metadata filter to apply.
            include_values: Include vector values in results.
            include_metadata: Include metadata in results.
            sparse_vector: Optional sparse query vector.

        Returns:
            QueryResponse containing matching vectors with scores.

        Raises:
            PineconeException: If query operation fails.

        Example:
            >>> results = index.query(
            ...     vector=[0.1, 0.2, 0.3],
            ...     top_k=10,
            ...     filter={"category": "example"},
            ...     include_metadata=True,
            ... )
            >>> for match in results.matches:
            ...     print(f"{match.id}: {match.score}")
        """
        ...

    def fetch(
        self,
        ids: list[str],
        *,
        namespace: str = "",
    ) -> FetchResponseProtocol:
        """Fetch vectors by ID.

        Args:
            ids: List of vector IDs to fetch.
            namespace: Optional namespace to fetch from.

        Returns:
            FetchResponse containing the fetched vectors.

        Raises:
            PineconeException: If fetch operation fails.

        Example:
            >>> response = index.fetch(ids=["vec1", "vec2", "vec3"])
            >>> for vec_id, vector in response.vectors.items():
            ...     print(f"{vec_id}: {len(vector.values)} dimensions")
        """
        ...

    def delete(
        self,
        *,
        ids: list[str] | None = None,
        delete_all: bool = False,
        namespace: str = "",
        filter: dict[str, Any] | None = None,
    ) -> DeleteResponseProtocol:
        """Delete vectors.

        Args:
            ids: List of vector IDs to delete.
            delete_all: Delete all vectors in the namespace.
            namespace: Optional namespace to delete from.
            filter: Delete vectors matching the metadata filter.

        Returns:
            DeleteResponse (typically empty).

        Raises:
            PineconeException: If delete operation fails.

        Example:
            >>> # Delete specific vectors
            >>> index.delete(ids=["vec1", "vec2"])
            >>>
            >>> # Delete by filter
            >>> index.delete(filter={"category": "old"})
            >>>
            >>> # Delete all vectors in namespace
            >>> index.delete(delete_all=True, namespace="test")
        """
        ...

    def update(
        self,
        id: str,
        *,
        values: list[float] | None = None,
        sparse_values: SparseValues | None = None,
        set_metadata: Metadata | None = None,
        namespace: str = "",
    ) -> UpdateResponseProtocol:
        """Update a vector by ID.

        Args:
            id: Vector ID to update.
            values: Optional new dense vector values.
            sparse_values: Optional new sparse vector values.
            set_metadata: Optional metadata fields to set or overwrite.
            namespace: Optional namespace containing the vector.

        Returns:
            UpdateResponse with the number of matched records.

        Example:
            >>> index.update(
            ...     id="vec1",
            ...     set_metadata={"category": "updated"},
            ... )
        """
        ...

    def list(
        self,
        *,
        prefix: str | None = None,
        limit: int | None = None,
        pagination_token: str | None = None,
        namespace: str = "",
    ) -> ListVectorsResponseProtocol:
        """List vector IDs in a namespace.

        Args:
            prefix: Filter to IDs starting with this prefix.
            limit: Maximum number of IDs to return.
            pagination_token: Token to resume from a previous call.
            namespace: Namespace to list from.

        Returns:
            ListVectorsResponse with vector IDs and pagination info.

        Example:
            >>> response = index.list(prefix="doc#", limit=100)
            >>> for item in response.vectors:
            ...     print(item.id)
        """
        ...

    def fetch_by_metadata(
        self,
        *,
        filter: dict[str, Any] | None = None,
        namespace: str = "",
        limit: int | None = None,
        pagination_token: str | None = None,
    ) -> FetchByMetadataResponseProtocol:
        """Fetch vectors matching a metadata filter.

        Args:
            filter: Metadata filter expression.
            namespace: Namespace to fetch from.
            limit: Maximum number of vectors to return.
            pagination_token: Token to resume from a previous call.

        Returns:
            FetchByMetadataResponse with matching vectors.

        Example:
            >>> response = index.fetch_by_metadata(
            ...     filter={"genre": "comedy"},
            ...     limit=50,
            ... )
            >>> for vec_id, vector in response.vectors.items():
            ...     print(f"{vec_id}: {vector.metadata}")
        """
        ...

    def describe_index_stats(
        self,
        *,
        filter: dict[str, Any] | None = None,
    ) -> DescribeIndexStatsResponseProtocol:
        """Get index statistics.

        Args:
            filter: Optional metadata filter to get filtered stats.

        Returns:
            DescribeIndexStatsResponse with index statistics.

        Raises:
            PineconeException: If operation fails.

        Example:
            >>> stats = index.describe_index_stats()
            >>> print(f"Total vectors: {stats.total_vector_count}")
            >>> print(f"Dimension: {stats.dimension}")
            >>> print(f"Fullness: {stats.index_fullness:.2%}")
        """
        ...


@runtime_checkable
class AsyncIndexProtocol(Protocol):
    """Async protocol for vector index operations.

    This protocol mirrors IndexProtocol but with async methods.

    Example:
        >>> from pinecone import AsyncPinecone
        >>> pc = AsyncPinecone(api_key="...")
        >>> index = pc.index(host="my-index-abc123.svc.pinecone.io")
        >>>
        >>> # Async upsert
        >>> await index.upsert(
        ...     vectors=[
        ...         {"id": "vec1", "values": [0.1, 0.2, 0.3]},
        ...     ]
        ... )
        >>>
        >>> # Async query
        >>> results = await index.query(vector=[0.1, 0.2, 0.3], top_k=10)
    """

    async def upsert(
        self,
        vectors: Sequence[VectorInput] | Sequence[VectorProtocol],
        *,
        namespace: str = "",
    ) -> UpsertResponseProtocol:
        """Async insert or update vectors."""
        ...

    async def query(
        self,
        *,
        vector: list[float] | None = None,
        id: str | None = None,
        top_k: int = 10,
        namespace: str = "",
        filter: dict[str, Any] | None = None,
        include_values: bool = False,
        include_metadata: bool = True,
        sparse_vector: SparseValues | None = None,
    ) -> QueryResponseProtocol:
        """Async query vectors by similarity."""
        ...

    async def fetch(
        self,
        ids: list[str],
        *,
        namespace: str = "",
    ) -> FetchResponseProtocol:
        """Async fetch vectors by ID."""
        ...

    async def delete(
        self,
        *,
        ids: list[str] | None = None,
        delete_all: bool = False,
        namespace: str = "",
        filter: dict[str, Any] | None = None,
    ) -> DeleteResponseProtocol:
        """Async delete vectors."""
        ...

    async def update(
        self,
        id: str,
        *,
        values: list[float] | None = None,
        sparse_values: SparseValues | None = None,
        set_metadata: Metadata | None = None,
        namespace: str = "",
    ) -> UpdateResponseProtocol:
        """Async update a vector by ID."""
        ...

    async def list(
        self,
        *,
        prefix: str | None = None,
        limit: int | None = None,
        pagination_token: str | None = None,
        namespace: str = "",
    ) -> ListVectorsResponseProtocol:
        """Async list vector IDs in a namespace."""
        ...

    async def fetch_by_metadata(
        self,
        *,
        filter: dict[str, Any] | None = None,
        namespace: str = "",
        limit: int | None = None,
        pagination_token: str | None = None,
    ) -> FetchByMetadataResponseProtocol:
        """Async fetch vectors matching a metadata filter."""
        ...

    async def describe_index_stats(
        self,
        *,
        filter: dict[str, Any] | None = None,
    ) -> DescribeIndexStatsResponseProtocol:
        """Async get index statistics."""
        ...
