"""Finds the governing procedure/document for a turn, from the EvidenceStore.

Two independent lookups, because the two real procedures answer different questions:
  - `governing_procedure` picks the complaint-handling procedure by what the representative
    has actually confirmed (complaint type + preconditions), not by keyword guessing — the
    same reason agent_core.stage_for keeps turn/fact-tracking in the harness rather than
    asking the model to self-report it.
  - `search_documents` is a plain keyword search over citable prose (the Vulnerable Customer
    Process document) for when the model's tool call asks a free-text question rather than
    matching a structured precondition.
"""
import re


def governing_procedure(store, complaint_type=None, preconditions=None):
    """The one procedure whose `applies_to` matches these facts, or None.

    `preconditions` is an iterable of fact names already confirmed true (e.g.
    "direct_debit_rejected"). A procedure with an empty `applies_to` (like
    PROC-VULNERABILITY-FLOW, which is an interrupt, not a complaint-type procedure) never
    matches here — it's surfaced separately by `vulnerability_interrupt`.
    """
    preconditions = set(preconditions or [])
    best = None
    for procedure in store.procedures.values():
        applies_to = procedure.get("applies_to") or {}
        types = applies_to.get("complaint_types") or []
        preqs = applies_to.get("preconditions") or []
        if not types and not preqs:
            continue
        if complaint_type and types and complaint_type not in types:
            continue
        if preqs and not set(preqs).issubset(preconditions):
            continue
        if best is None or procedure.get("specificity_rank", 0) > best.get("specificity_rank", 0):
            best = procedure
    return best


def vulnerability_interrupt(store):
    """The vulnerability-handling procedure, which applies regardless of complaint type."""
    return store.procedure("PROC-VULNERABILITY-FLOW")


_WORD = re.compile(r"[a-z0-9']+")


def _tokens(text):
    return set(_WORD.findall((text or "").lower()))


def search_documents(store, query, top_k=3):
    """Keyword-overlap ranking over citable documents — good enough for a two-document corpus.

    Swap for the gateway's embedding model (see knowledge_base/README.md and the gateway
    integration plan) if/when the corpus grows past a handful of documents and keyword
    overlap stops being discriminating enough.
    """
    query_tokens = _tokens(query)
    if not query_tokens:
        return []
    scored = []
    for article_id, doc in store.documents().items():
        doc_tokens = _tokens(doc["text"])
        overlap = len(query_tokens & doc_tokens)
        if overlap:
            scored.append((overlap, article_id, doc))
    scored.sort(key=lambda row: row[0], reverse=True)
    return [doc for _, _, doc in scored[:top_k]]
