"""Internals for the tool-search native tool.

Tool search lets the model discover tools marked with `defer_loading=True` rather
than carrying every deferred tool's full schema in the prompt. The
[`ToolSearch`][pydantic_ai.capabilities.ToolSearch] capability picks between three
modes per provider:

* **Native server-side**: the provider's own tool-search tool (Anthropic
  `bm25`/`regex`, OpenAI server-executed `tool_search`).
* **Native client-executed (custom callable)**: the provider invokes our local
  callable through its native client-execution mode (Anthropic regular function tool
  + `tool_reference` result blocks, OpenAI `ToolSearchToolParam(execution='client')`).
* **Local fallback**: a regular `search_tools` function tool the model can call.
  Used on providers that don't expose any native tool-search surface.

The end-user surface is the [`ToolSearch`][pydantic_ai.capabilities.ToolSearch]
capability; the types in this module are kept here as implementation detail.
"""

from __future__ import annotations

from collections.abc import Awaitable, Callable, Sequence
from dataclasses import dataclass
from typing import TYPE_CHECKING, Literal, Union

from .._run_context import AgentDepsT, RunContext
from ..messages import ToolSearchArgs, ToolSearchMatch, ToolSearchReturnContent
from . import AbstractNativeTool

if TYPE_CHECKING:
    from ..tools import ToolDefinition


__all__ = [
    'ToolSearchArgs',
    'ToolSearchMatch',
    'ToolSearchReturnContent',
    'ToolSearchNativeStrategy',
    'ToolSearchLocalStrategy',
    'ToolSearchFunc',
    'ToolSearchStrategy',
    'TOOL_SEARCH_FUNCTION_TOOL_NAME',
]


ToolSearchNativeStrategy = Literal['bm25', 'regex']
"""Named provider-native tool search strategy.

`'bm25'` and `'regex'` correspond to Anthropic's server-side tool search variants.
OpenAI's Responses API does not expose distinct named native strategies, so these values
are rejected by the OpenAI adapter.
"""

ToolSearchLocalStrategy = Literal['keywords']
"""Named local tool search strategy.

`'keywords'` opts into the built-in keyword-overlap algorithm explicitly — use this
to lock in the current local algorithm rather than the `None` default (which lets
Pydantic AI pick the best algorithm per provider and may change over time).

Future local strategies (e.g. local BM25, TF-IDF, regex) will join this Literal as
they're added; the single-member shape today is forward-compat scaffolding.
"""

ToolSearchFunc = Callable[
    [RunContext[AgentDepsT], Sequence[str], Sequence['ToolDefinition']],
    Sequence[str] | Awaitable[Sequence[str]],
]
"""Custom search function for
[`ToolSearch`][pydantic_ai.capabilities.ToolSearch]'s `strategy` field.

Takes the run context, the list of search queries, and the deferred tool definitions,
and returns the matching tool names ordered by relevance. Both sync and async
implementations are accepted.

Usage `ToolSearchFunc[AgentDepsT]`.
"""

ToolSearchStrategy = Union[ToolSearchFunc[AgentDepsT], ToolSearchLocalStrategy, ToolSearchNativeStrategy]  # noqa: UP007
"""Strategy value accepted by [`ToolSearch.strategy`][pydantic_ai.capabilities.ToolSearch.strategy].

* `'keywords'`: force the local keyword-overlap algorithm regardless of provider.
* `'bm25'` / `'regex'`: force a specific provider-native strategy (Anthropic). The
  request fails on providers that can't honor the choice.
* Callable `(ctx, queries, tools) -> names`: custom search function. Used locally, and also
  by the native "client-executed" surface on providers that support it (Anthropic custom
  tool-reference blocks, OpenAI `ToolSearchToolParam(execution='client')`).

`None` is not part of the union — it's accepted as the default on the
[`ToolSearch.strategy`][pydantic_ai.capabilities.ToolSearch.strategy] field and means
"let Pydantic AI pick"; see that field's docstring for details.
"""


TOOL_SEARCH_FUNCTION_TOOL_NAME = 'search_tools'
"""Name of the local function tool that backs [`ToolSearch`][pydantic_ai.capabilities.ToolSearch]
for keyword-based discovery when native tool search isn't available, and that model adapters
route to for provider-side "client-executed" custom callable modes (Anthropic tool-reference
blocks; OpenAI `execution='client'`)."""


@dataclass(kw_only=True)
class ToolSearchTool(AbstractNativeTool):
    """Framework-internal: users access tool search via the [`ToolSearch`][pydantic_ai.capabilities.ToolSearch] capability — do not construct directly.

    A native tool that enables provider-side tool search.

    Tools marked as part of the search corpus (via `with_native='tool_search'`
    on their [`ToolDefinition`][pydantic_ai.tools.ToolDefinition]) are sent to supporting
    providers with `defer_loading` on the wire; the provider manages their visibility
    and only exposes them once they've been discovered.

    The mode of discovery depends on `strategy`:

    * A named native strategy (or `None` for the provider default): the provider runs
      the search server-side using its own indexing (Anthropic `bm25`/`regex`, OpenAI
      server-executed `tool_search`).
    * `'custom'`: the provider invokes our local search function to answer each search
      request. On Anthropic this goes via a regular function tool whose return value the
      adapter re-formats as `tool_reference` blocks; on OpenAI it goes via
      `ToolSearchToolParam(execution='client')` with our callable's parameter schema.

    When the model doesn't support native tool search at all, the
    [`ToolSearch`][pydantic_ai.capabilities.ToolSearch] capability's local
    implementation handles discovery via its own `search_tools` function tool.

    Supported by:

    * Anthropic (bm25, regex, custom callable) — Sonnet 4.5+, Opus 4.5+, Haiku 4.5+
    * OpenAI Responses (server default, custom callable via `execution='client'`) — GPT-5.4+
      (named strategies `'bm25'`/`'regex'` are not supported).
    """

    strategy: Literal['bm25', 'regex', 'custom'] | None = None
    """The search strategy to use.

    Extends [`ToolSearchNativeStrategy`][pydantic_ai.capabilities.ToolSearchNativeStrategy]
    with `'custom'`, which marks an instance whose discovery is performed by a callable on
    our side. The user-facing [`ToolSearchStrategy`][pydantic_ai.capabilities.ToolSearchStrategy]
    union (in the [`ToolSearch`][pydantic_ai.capabilities.ToolSearch] capability) does not
    include `'custom'` — users pass the callable directly and the capability sets
    `strategy='custom'` on the native tool internally.

    * `None` (default): use the provider's default native search. On Anthropic this is
      `bm25`; on OpenAI it is the server-executed `tool_search` tool.
    * `'bm25'` / `'regex'`: force a specific Anthropic native strategy. Adapters on
      providers that can't honor the choice raise `UserError`.
    * `'custom'`: discovery is performed by a callable on our side; provider adapters
      that support a "client-executed" native surface wire that surface up so the model
      sees a tool search call rather than a regular function tool. Set automatically by
      [`ToolSearch`][pydantic_ai.capabilities.ToolSearch] when its `strategy` is a
      callable; users don't pass `'custom'` directly.
    """

    kind: str = 'tool_search'
    """The kind of tool."""
