"""Composable metadata filter builders for SearchRequest."""

from __future__ import annotations

from collections.abc import Sequence

from ._generated import kyrodb_pb2 as pb2
from .models import MetadataFilter


def _validate_key(key: str) -> None:
    if not key or not key.strip():
        raise ValueError("filter key must be non-empty")


def exact(key: str, value: str) -> MetadataFilter:
    _validate_key(key)
    if not value:
        raise ValueError("filter value must be non-empty")
    return MetadataFilter.from_proto(pb2.MetadataFilter(exact=pb2.ExactMatch(key=key, value=value)))


def in_values(key: str, values: Sequence[str]) -> MetadataFilter:
    _validate_key(key)
    if not values:
        raise ValueError("values must be non-empty")
    normalized = [value for value in values if value]
    if len(normalized) != len(values):
        raise ValueError("values must not contain empty entries")
    return MetadataFilter.from_proto(
        pb2.MetadataFilter(in_match=pb2.InMatch(key=key, values=normalized))
    )


def range_match(
    key: str,
    *,
    gte: str | None = None,
    lte: str | None = None,
    gt: str | None = None,
    lt: str | None = None,
) -> MetadataFilter:
    _validate_key(key)
    bounds: list[tuple[str, str]] = []
    for label, value in (("gte", gte), ("lte", lte), ("gt", gt), ("lt", lt)):
        if value is not None:
            normalized = value.strip()
            if normalized:
                bounds.append((label, normalized))

    if not bounds:
        raise ValueError("at least one range bound is required")

    if len(bounds) == 1:
        label, value = bounds[0]
        return MetadataFilter.from_proto(
            pb2.MetadataFilter(
                range=pb2.RangeMatch(
                    key=key,
                    **{label: value},
                )
            )
        )

    # RangeMatch uses oneof("bound"), so multi-bound constraints are represented
    # as an explicit AND of single-bound range filters.
    return MetadataFilter.from_proto(
        pb2.MetadataFilter(
            and_filter=pb2.AndFilter(
                filters=[
                    pb2.MetadataFilter(
                        range=pb2.RangeMatch(
                            key=key,
                            **{label: value},
                        )
                    )
                    for label, value in bounds
                ]
            )
        )
    )


def all_of(filters: Sequence[MetadataFilter]) -> MetadataFilter:
    if not filters:
        raise ValueError("filters must be non-empty")
    return MetadataFilter.from_proto(
        pb2.MetadataFilter(and_filter=pb2.AndFilter(filters=[f.to_proto() for f in filters]))
    )


def any_of(filters: Sequence[MetadataFilter]) -> MetadataFilter:
    if not filters:
        raise ValueError("filters must be non-empty")
    return MetadataFilter.from_proto(
        pb2.MetadataFilter(or_filter=pb2.OrFilter(filters=[f.to_proto() for f in filters]))
    )


def negate(filter_value: MetadataFilter) -> MetadataFilter:
    return MetadataFilter.from_proto(
        pb2.MetadataFilter(not_filter=pb2.NotFilter(filter=filter_value.to_proto()))
    )
