# utils/fts5_query.py — Shared FTS5 MATCH string builder (unicode / natural queries).
from __future__ import annotations

import re

_FTS_STOPWORDS: frozenset[str] = frozenset(
    "a an the of in on at to for by with from and or but not is are was were "
    "be been being have has had do does did will would shall should may might "
    "can could that this these those it its than so as if then than into through "
    "rather very also just about up out".split()
)


def safe_fts_query(query: str, *, mode: str = "and", literal_tokens_only: bool = False) -> str:
    """Build an FTS5 MATCH expression from a natural-language query.

    *mode* controls how terms are joined:
      - ``"and"`` — all terms must appear (default, strict)
      - ``"and_content"`` — stopwords stripped, remaining content words AND-joined
      - ``"or"`` — any term may appear (broadest recall)

    When *literal_tokens_only* is False (default), queries that contain ``*``
    or uppercase ``AND`` / ``OR`` / ``NOT`` are passed through unchanged so operators
    and prefix globs still work.

    Kanban task search passes ``literal_tokens_only=True``: natural phrases like
    ``task NOT done`` must mean the literal words, not FTS5 exclusion.
    """
    q = (query or "").strip()
    if not q:
        return q
    if not literal_tokens_only and (
        "*" in q or re.search(r"\b(?:AND|OR|NOT)\b", q)
    ):
        return q
    terms = [t for t in re.sub(r"[^\w\s*]", " ", q).split() if t]
    if not terms:
        return q

    if mode == "and_content":
        content_terms = [t for t in terms if t.lower() not in _FTS_STOPWORDS]
        if not content_terms:
            content_terms = terms
        return " AND ".join(f'"{t}"' for t in content_terms[:10])
    if mode == "or":
        return " OR ".join(f'"{t}"' for t in terms[:12])
    return " AND ".join(f'"{t}"' for t in terms[:10])
