"""SQL builders: convert ParsedQuery to FTS5 and LIKE SQL fragments."""

from __future__ import annotations

from typing import Any

from ..query import (
    _FTS_COLUMNS,
    _RAW_FILTER_FIELDS,
    Operator,
    ParsedQuery,
    ParsedTerm,
)

_LIKE_ESCAPE = "ESCAPE '!'"


def _escape_fts_phrase(s: str) -> str:
    """Escape user input for safe use inside an FTS5 double-quoted phrase."""
    s = s.replace("\r\n", " ").replace("\n", " ").replace("\r", " ").replace("\t", " ")
    return s.replace('"', '""')


def _escape_like_infix(s: str) -> str:
    """Escape ``!``, ``%`` and ``_`` for LIKE patterns using ``!`` as the escape char."""
    return s.replace("!", "!!").replace("%", "!%").replace("_", "!_")


def _term_to_fts(term: ParsedTerm, exact: bool = False) -> str:
    """Convert a single ParsedTerm to an FTS5 expression."""
    safe_value = _escape_fts_phrase(term.value)
    suffix = "" if exact else "*"
    if not term.fields:
        return f'"{safe_value}"{suffix}'
    cols = sorted(term.fields)
    if len(cols) == 1:
        return f'{cols[0]} : "{safe_value}"{suffix}'
    col_list = " ".join(cols)
    return f'{{{col_list}}} : "{safe_value}"{suffix}'


def _term_to_like_clause(term: ParsedTerm) -> tuple[str, list[Any]]:
    """Convert a single ParsedTerm to a SQL LIKE fragment and bind params."""
    pattern = f"%{_escape_like_infix(term.value)}%"
    cols = sorted(term.fields) if term.fields else sorted(_FTS_COLUMNS)
    parts = " OR ".join(f'"{c}" LIKE ? {_LIKE_ESCAPE}' for c in cols)
    return parts, [pattern] * len(cols)


def _fts_terms(parsed: ParsedQuery) -> list[ParsedTerm]:
    """Terms that belong in the FTS MATCH (exclude raw-only fields like file_path)."""
    return [t for t in parsed.terms if not (t.fields & _RAW_FILTER_FIELDS)]


def get_file_path_from_query(parsed: ParsedQuery) -> str | None:
    """Return the value of the first file_path: term, or None."""
    for t in parsed.terms:
        if "file_path" in t.fields and t.value:
            return t.value
    return None


def get_type_filter_from_query(parsed: ParsedQuery) -> str | None:
    """Return 'signal' or 'frame' if all typed terms agree on a type, else None."""
    type_filters = {t.type_filter for t in parsed.terms if t.type_filter}
    if len(type_filters) == 1:
        return type_filters.pop()
    return None


def to_fts_query(
    parsed: ParsedQuery,
    exact: bool = False,
    operator: Operator = Operator.AND,
) -> str:
    """Convert a ParsedQuery to an FTS5 MATCH expression (excludes raw-only fields)."""
    terms = _fts_terms(parsed)
    if not terms:
        return ""
    parts = [_term_to_fts(t, exact=exact) for t in terms]
    sep = f" {operator.value.upper()} "
    return sep.join(parts)


def to_like_clauses(
    parsed: ParsedQuery,
    operator: Operator = Operator.AND,
) -> tuple[str, list[Any]]:
    """Build a SQL WHERE clause for substring search using LIKE '%term%'.

    Excludes raw-only fields (e.g. file_path); those are applied separately.
    Returns ``("", [])`` when the query has no FTS-like terms.
    """
    terms = _fts_terms(parsed)
    if not terms:
        return "", []
    clauses: list[str] = []
    params: list[Any] = []
    for term in terms:
        clause, term_params = _term_to_like_clause(term)
        clauses.append(clause)
        params.extend(term_params)
    sep = f" {operator.value.upper()} "
    return sep.join(f"({c})" for c in clauses), params
