"""In-memory stub backends for the ACMS Backend Abstraction Layer.

These stubs satisfy the :class:`TextBackend`, :class:`VectorBackend`,
and :class:`GraphBackend` protocols with minimal, zero-dependency
implementations that return empty results.  They serve as:

1. **Development placeholders** while physical store integrations are
   built.
2. **Test doubles** for strategy and pipeline tests that need a
   functioning backend without external infrastructure.
3. **Reference implementations** documenting the expected behaviour of
   each protocol method, including argument validation.

Production backends (Tantivy, FAISS, Blazegraph, etc.) will replace
these stubs via DI container provider selection at startup.

Based on ``docs/specification.md`` > ACMS > Backend Abstraction Layer.
"""

from __future__ import annotations

from cleveragents.domain.models.acms.backends import (
    GraphResult,
    TextResult,
    VectorResult,
)


class InMemoryTextBackend:
    """Stub :class:`TextBackend` that validates inputs and returns no results.

    All searches return an empty list.  This implementation validates
    arguments to ensure callers conform to the protocol contract.
    """

    def search(
        self,
        query: str,
        *,
        scope: frozenset[str],
        max_results: int = 20,
    ) -> list[TextResult]:
        """Search for text matching *query* within *scope*.

        Args:
            query: Natural-language or keyword query string.
            scope: Frozenset of resource ULIDs to restrict results to.
            max_results: Maximum number of results to return.

        Returns:
            An empty list (stub implementation).

        Raises:
            ValueError: If *query* is empty or *max_results* < 1.
        """
        if not query:
            raise ValueError("query must be a non-empty string")
        if max_results < 1:
            raise ValueError(f"max_results must be positive, got {max_results}")
        return []


class InMemoryVectorBackend:
    """Stub :class:`VectorBackend` that validates inputs and returns no results.

    All similarity searches return an empty list.
    """

    def similarity_search(
        self,
        embedding: list[float],
        *,
        scope: frozenset[str],
        top_k: int = 20,
    ) -> list[VectorResult]:
        """Find vectors most similar to *embedding* within *scope*.

        Args:
            embedding: Query embedding vector.
            scope: Frozenset of resource ULIDs to restrict results to.
            top_k: Maximum number of results to return.

        Returns:
            An empty list (stub implementation).

        Raises:
            ValueError: If *embedding* is empty or *top_k* < 1.
        """
        if not embedding:
            raise ValueError("embedding must be a non-empty list")
        if top_k < 1:
            raise ValueError(f"top_k must be positive, got {top_k}")
        return []


class InMemoryGraphBackend:
    """Stub :class:`GraphBackend` that validates inputs and returns empty results.

    All queries and traversals return a :class:`GraphResult` with an
    empty triple list.
    """

    def sparql_query(
        self,
        query: str,
        *,
        scope: frozenset[str],
    ) -> GraphResult:
        """Execute a SPARQL query within *scope*.

        Args:
            query: SPARQL query string.
            scope: Frozenset of resource ULIDs to restrict results to.

        Returns:
            A :class:`GraphResult` with no triples (stub).

        Raises:
            ValueError: If *query* is empty.
        """
        if not query:
            raise ValueError("query must be a non-empty string")
        return GraphResult()

    def get_triples(
        self,
        subject: str,
    ) -> GraphResult:
        """Retrieve all triples for *subject*.

        Args:
            subject: The UKO URI of the subject node.

        Returns:
            A :class:`GraphResult` with no triples (stub).

        Raises:
            ValueError: If *subject* is empty.
        """
        if not subject:
            raise ValueError("subject must be a non-empty string")
        return GraphResult()

    def traverse(
        self,
        start: str,
        *,
        depth: int = 2,
    ) -> GraphResult:
        """Traverse the graph from *start* up to *depth* hops.

        Args:
            start: UKO URI to begin traversal from.
            depth: Maximum number of hops.

        Returns:
            A :class:`GraphResult` with no triples (stub).

        Raises:
            ValueError: If *start* is empty or *depth* is negative.
        """
        if not start:
            raise ValueError("start must be a non-empty string")
        if depth < 0:
            raise ValueError(f"depth must be non-negative, got {depth}")
        return GraphResult()
