"""Shared helpers for asserting vector-sync visibility in integration tests.

Kept dependency-light (no Playwright) so both the multi-user-basic UI tests and
the single-user sampling tests can import it.
"""

import json
import logging

logger = logging.getLogger(__name__)


async def document_is_searchable(
    mcp_client, search_term: str, note_id: int | None = None
) -> bool:
    """Return True once a freshly-created document is retrievable.

    Polls ``nc_semantic_search`` (hybrid: an exact unique term reliably matches
    on the keyword side) and matches by ``note_id`` when provided, otherwise by
    the term appearing in a result's title/excerpt. Transient errors return
    False so callers can keep polling.
    """
    try:
        search = await mcp_client.call_tool(
            "nc_semantic_search",
            # limit is generous: a fresh note can sit below seed data (e.g. deck
            # cards) in a crowded corpus, and the query is cheap.
            arguments={"query": search_term, "limit": 50, "score_threshold": 0.0},
        )
    except Exception as e:  # transient transport/availability blip — keep polling
        logger.debug("Semantic search poll failed: %s", e)
        return False
    if search.isError:
        logger.debug("Semantic search poll error: %s", search)
        return False

    results = json.loads(search.content[0].text).get("results", [])
    # Token match (not contiguous substring) so multi-word terms work in the
    # note_id-less fallback path.
    tokens = search_term.lower().split()
    for r in results:
        if note_id is not None:
            if r.get("id") == note_id and r.get("doc_type") == "note":
                return True
        else:
            haystack = f"{r.get('title', '')} {r.get('excerpt', '')}".lower()
            if tokens and all(t in haystack for t in tokens):
                return True
    return False
