"""Signal search query parser."""

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum


@dataclass(frozen=True)
class FieldSpec:
    """One searchable/filterable DB column. ``in_fts`` is True if indexed in FTS."""

    name: str
    in_fts: bool


# Single source of truth for all searchable/filterable fields.
# Columns must match the FTS table definition in sqlite/schema.py exactly.
_FIELD_DEFINITIONS: list[FieldSpec] = [
    FieldSpec(name="signal_name", in_fts=True),
    FieldSpec(name="frame_name", in_fts=True),
    FieldSpec(name="frame_id", in_fts=True),
    FieldSpec(name="namespace", in_fts=True),
    FieldSpec(name="channel", in_fts=True),
    FieldSpec(name="description", in_fts=True),
    FieldSpec(name="unit", in_fts=True),
    FieldSpec(name="senders", in_fts=True),
    FieldSpec(name="receivers", in_fts=True),
    FieldSpec(name="named_values", in_fts=True),
    FieldSpec(name="file_path", in_fts=False),  # raw WHERE only, not in FTS
]

_FTS_COLUMNS: frozenset[str] = frozenset(f.name for f in _FIELD_DEFINITIONS if f.in_fts)
_RAW_FILTER_FIELDS: frozenset[str] = frozenset(f.name for f in _FIELD_DEFINITIONS if not f.in_fts)
_ALL_FILTER_FIELDS: frozenset[str] = _FTS_COLUMNS | _RAW_FILTER_FIELDS


def resolve_filter_field(name: str) -> str | None:
    """Resolve a field name (e.g. ``_f_<name>`` suffix) to a DB column name. Returns None if unknown."""
    return name if name in _ALL_FILTER_FIELDS else None


class Operator(str, Enum):
    AND = "and"
    OR = "or"


@dataclass(frozen=True)
class ParsedTerm:
    value: str
    fields: frozenset[str]  # DB column names; empty frozenset = search all FTS columns
    type_filter: str | None = None  # reserved for future typed-query extensions


@dataclass(frozen=True)
class ParsedQuery:
    terms: list[ParsedTerm]
    active_term: ParsedTerm | None = None


def _resolve_fields(prefix: str) -> frozenset[str]:
    """Resolve a field prefix like 'signal_name,description' to DB column names."""
    cols = {c.strip() for c in prefix.split(",") if c.strip()}
    return frozenset(c for c in cols if c in _ALL_FILTER_FIELDS)


def _parse_token(token: str) -> ParsedTerm | None:
    """Parse one query token into a ParsedTerm. Returns None for empty tokens."""
    sanitized = token.replace('"', "")
    if not sanitized:
        return None

    if ":" in sanitized:
        prefix, _, value = sanitized.partition(":")
        if value:
            fields = _resolve_fields(prefix)
            if fields:
                return ParsedTerm(value=value, fields=fields)
        # Unknown field prefix or empty value — treat as plain term
        return ParsedTerm(value=sanitized, fields=frozenset())

    return ParsedTerm(value=sanitized, fields=frozenset())


class Query:
    """Parsed search query. Create via ``Query.parse(s)``."""

    def __init__(self, parsed: ParsedQuery) -> None:
        self._parsed = parsed

    @staticmethod
    def parse(s: str) -> "Query":
        terms: list[ParsedTerm] = []
        for token in s.strip().split():
            if token == "OR":
                continue  # OR is a separate parameter; silently strip from query string
            term = _parse_token(token)
            if term is not None:
                terms.append(term)
        return Query(ParsedQuery(terms=terms, active_term=terms[-1] if terms else None))

    @property
    def parsed(self) -> ParsedQuery:
        return self._parsed
