"""Core expression module for therismos query modeling library.

This module provides the Abstract Syntax Tree (AST) implementation for modeling
generic queries and filters in a backend-agnostic way.
"""

from __future__ import annotations

import operator as op
import re
from abc import ABC, abstractmethod
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any, Generic, Protocol, TypeVar

T = TypeVar("T", covariant=True)


@dataclass(frozen=True)
class UnwoundValue:
    """Represents a single unwound field-value pair.

    Used to pass unwound data to expression evaluation methods. Each instance
    represents one primitive value extracted from a potentially nested structure.

    :ivar field_name: The name of the field.
    :vartype field_name: str
    :ivar field_value: The unwound primitive value.
    :vartype field_value: Any
    """

    field_name: str
    field_value: Any


def unwind_data(data: dict[str, Any]) -> list[UnwoundValue]:
    """Unwind nested field values into a flat list of UnwoundValue objects.

    Recursively unwinds all nested lists, creating one UnwoundValue per
    primitive value. Non-list values are preserved as-is.

    :param data: Dictionary with field names as keys and values (possibly nested lists).
    :type data: dict[str, Any]
    :return: A list of UnwoundValue objects.
    :rtype: list[UnwoundValue]

    :Example:
        >>> unwind_data({"a": 5})
        [UnwoundValue(field_name="a", field_value=5)]

        >>> unwind_data({"a": [1, 2, 3]})
        [UnwoundValue(field_name="a", field_value=1),
         UnwoundValue(field_name="a", field_value=2),
         UnwoundValue(field_name="a", field_value=3)]

        >>> unwind_data({"a": [[1, 2], [3, 4]]})
        [UnwoundValue(field_name="a", field_value=1),
         UnwoundValue(field_name="a", field_value=2),
         UnwoundValue(field_name="a", field_value=3),
         UnwoundValue(field_name="a", field_value=4)]

        >>> unwind_data({"a": [1, 2], "b": "x"})
        [UnwoundValue(field_name="a", field_value=1),
         UnwoundValue(field_name="a", field_value=2),
         UnwoundValue(field_name="b", field_value="x")]
    """
    result = []
    for field_name, field_value in data.items():
        result.extend(_unwind_field(field_name, field_value))
    return result


def _unwind_field(field_name: str, field_value: Any) -> list[UnwoundValue]:
    """Recursively unwind a single field's value.

    :param field_name: The field name.
    :type field_name: str
    :param field_value: The value to unwind, which may be a nested list.
    :type field_value: Any
    :return: A list of UnwoundValue objects.
    :rtype: list[UnwoundValue]
    """
    if not isinstance(field_value, list):
        # Base case: primitive value
        return [UnwoundValue(field_name=field_name, field_value=field_value)]

    if not field_value:
        # Empty list: preserve as a single entry
        return [UnwoundValue(field_name=field_name, field_value=[])]

    # Recursive case: unwind each element
    result = []
    for element in field_value:
        result.extend(_unwind_field(field_name, element))
    return result


class ExprVisitor(Protocol, Generic[T]):
    """Visitor interface for traversing expression trees.

    Implementations should provide visit methods for each expression type.
    The generic type parameter :param:`T` specifies the return type of visit methods.
    """

    def visit_eq(self, expr: Eq) -> T: ...
    def visit_ne(self, expr: Ne) -> T: ...
    def visit_lt(self, expr: Lt) -> T: ...
    def visit_le(self, expr: Le) -> T: ...
    def visit_gt(self, expr: Gt) -> T: ...
    def visit_ge(self, expr: Ge) -> T: ...
    def visit_regex(self, expr: Regex) -> T: ...
    def visit_in(self, expr: In) -> T: ...
    def visit_is_null(self, expr: IsNull) -> T: ...
    def visit_true(self, expr: TrueExpr) -> T: ...
    def visit_false(self, expr: FalseExpr) -> T: ...
    def visit_all(self, expr: AllExpr) -> T: ...
    def visit_any(self, expr: AnyExpr) -> T: ...
    def visit_not(self, expr: NotExpr) -> T: ...


class Expr(ABC):
    """Abstract base class for all expressions.

    All expression nodes are immutable and implement the visitor pattern
    via the `accept` method.
    """

    @abstractmethod
    def accept(self, visitor: ExprVisitor[T]) -> T:
        """Accept a visitor and dispatch to the appropriate visit method.

        :param visitor: The visitor to accept.
        :type visitor: ExprVisitor[T]
        :return: The result of the visitor's corresponding visit method.
        :rtype: T
        """
        ...

    @abstractmethod
    def evaluate(self, data: list[UnwoundValue]) -> bool:
        """Evaluate this expression against the provided data.

        :param data: A list of UnwoundValue objects, each representing one unwound field value.
        :type data: list[UnwoundValue]
        :return: The boolean result of the evaluation.
        :rtype: bool
        :raises KeyError: If a required field is not present in the data.
        :raises TypeError: If a field value cannot be cast to its expected type.
        :raises ValueError: If a field value is invalid for its expected type.

        :Example:
            >>> # Single value
            >>> expr = F("age") > 18
            >>> expr.evaluate([UnwoundValue(field_name="age", field_value=25)])
            True

            >>> # Multiple values (evaluates with 'any' semantics for comparisons)
            >>> expr = F("scores") > 5
            >>> data = [
            ...     UnwoundValue(field_name="scores", field_value=1),
            ...     UnwoundValue(field_name="scores", field_value=6),
            ... ]
            >>> expr.evaluate(data)
            True  # Any score > 5

            >>> # Nested field values (after unwinding)
            >>> expr = F("tags") == "python"
            >>> data = unwind_data({"tags": [["java", "rust"], ["python", "go"]]})
            >>> expr.evaluate(data)
            True
        """
        ...

    @staticmethod
    def _eval_with_list(
        field_value: Any,
        operator: Callable[[Any, Any], bool],
        expected_value: Any,
        aggregation: str = "any",
    ) -> bool:
        """Evaluate an expression with a potential list-valued field.

        :param field_value: The field's value, which can be a single value or a list.
        :type field_value: Any
        :param operator: The comparison operator function (e.g., `op.eq`).
        :type operator: Callable[[Any, Any], bool]
        :param expected_value: The value to compare against.
        :type expected_value: Any
        :param aggregation: The aggregation mode, either "any" or "none".
        :type aggregation: str
        :return: True if the condition holds according to the aggregation mode.
        :rtype: bool
        """
        if isinstance(field_value, list):
            if aggregation == "any":
                return any(operator(v, expected_value) for v in field_value)
            if aggregation == "none":
                return not any(operator(v, expected_value) for v in field_value)
            raise ValueError(f"Invalid aggregation mode: {aggregation}")
        # Single value case
        if aggregation == "none":
            return not operator(field_value, expected_value)
        return operator(field_value, expected_value)

    def __and__(self, other: Expr) -> AllExpr:
        """Combine this expression with another using AND logic."""
        return AllExpr(self, other)

    def __or__(self, other: Expr) -> AnyExpr:
        """Combine this expression with another using OR logic."""
        return AnyExpr(self, other)

    def __invert__(self) -> NotExpr:
        """Negate this expression using NOT logic."""
        return NotExpr(self)


class AtomicExpr(Expr, ABC):
    """Abstract base class for atomic expressions.

    Atomic expressions are the leaf nodes of an expression tree and do not
    contain sub-expressions. They typically operate on fields and values.

    :Example:
        `Eq`, `Ne`, `Lt`, `Le`, `Gt`, `Ge`, `Regex`, `In`, `IsNull`, `TrueExpr`, `FalseExpr`
    """

    @staticmethod
    def _validate_single_value(value: Any, expr_type: str) -> None:
        """Validate that a value is not a collection.

        :param value: The value to validate.
        :type value: Any
        :param expr_type: The name of the expression type, for use in error messages.
        :type expr_type: str
        :raises TypeError: If the value is a list, tuple, set, or frozenset.
        """
        if isinstance(value, (list, tuple, set, frozenset)):
            raise TypeError(
                f"{expr_type} requires a single value, not a {type(value).__name__}. "
                f"Use In expression for multiple values."
            )


@dataclass(frozen=True)
class FieldBasedExpr(AtomicExpr, ABC):
    """Base class for atomic expressions that are based on a field.

    All field-based expressions operate on a single named field.

    :ivar field: The field the expression operates on.
    :vartype field: Field
    """

    field: Field


@dataclass(frozen=True)
class SingleValuedFieldExpr(FieldBasedExpr, ABC):
    """Base class for field expressions that operate on a single value.

    These expressions compare a field against a single value, which is
    validated and can be optionally cast.

    :ivar value: The value to compare against.
    :vartype value: Any
    """

    value: Any

    def __post_init__(self) -> None:
        """Validate that the value is not a collection."""
        self._validate_single_value(self.value, self.__class__.__name__)

    def casted_value(self) -> Any:
        """Return the value, cast to the field's declared type.

        :return: The casted value.
        :rtype: Any
        """
        return self.field.cast(self.value)


@dataclass(frozen=True)
class ComparisonExpr(SingleValuedFieldExpr, ABC):
    """Base class for comparison expressions.

    Compares a field's value against an expected value using an operator.
    It supports list-valued fields with aggregation.
    """

    @staticmethod
    @abstractmethod
    def _get_operator() -> Callable[[Any, Any], bool]:
        """Return the comparison operator (e.g., `op.eq`, `op.lt`)."""
        ...

    @staticmethod
    def _get_aggregation() -> str:
        """Return the aggregation mode ('any' or 'none'). Override in `Ne`."""
        return "any"

    def evaluate(self, data: list[UnwoundValue]) -> bool:
        """Evaluate the comparison using the operator and aggregation mode."""
        # Collect all values for this field
        field_values = [item.field_value for item in data if item.field_name == self.field.name]

        # Handle missing field
        if not field_values:
            raise KeyError(f"Field '{self.field.name}' not found in data")

        expected_value = self.casted_value()
        operator = self._get_operator()
        aggregation = self._get_aggregation()

        # Cast and evaluate each value
        results = [operator(self.field.cast(val), expected_value) for val in field_values]

        # Apply aggregation
        if aggregation == "any":
            return any(results)
        if aggregation == "none":
            return not any(results)
        raise ValueError(f"Unknown aggregation: {aggregation}")


@dataclass(frozen=True)
class MultiValuedFieldExpr(FieldBasedExpr, ABC):
    """Base class for field expressions that naturally accept multiple values.

    :Example:
        `In`, `IsNull`
    """

    pass


class CompositeExpr(Expr, ABC):
    """Abstract base class for composite expressions.

    Composite expressions combine multiple sub-expressions using logical
    operators. They form the intermediate and root nodes of an expression tree.

    :Example:
        `AllExpr`, `AnyExpr`
    """

    pass


class DecoratorExpr(Expr, ABC):
    """Abstract base class for decorator expressions.

    Decorator expressions wrap a single sub-expression to modify its behavior,
    following the decorator design pattern.

    :Example:
        `NotExpr`
    """

    pass


@dataclass(frozen=True)
class Field:
    """Represents a typed field in a query.

    Fields can optionally declare an expected type for automatic casting
    at the conversion level.

    :ivar name: The logical name of the field.
    :vartype name: str
    :ivar type_: An optional Python type or a function to cast values.
    :vartype type_: type[Any] | Callable[[Any], Any] | None
    """

    name: str
    type_: type[Any] | Callable[[Any], Any] | None = None

    def __str__(self) -> str:
        """Return a string representation of the field."""
        return self.name

    def cast(self, value: Any) -> Any:
        """Cast a value to this field's declared type.

        :param value: The value to be cast.
        :type value: Any
        :return: The casted value.
        :rtype: Any
        :raises TypeError: If the cast fails due to a type mismatch.
        :raises ValueError: If the cast fails due to an invalid value.
        """
        if self.type_ is None:
            return value

        # If `type_` is a class, check if `value` is already an instance
        if isinstance(self.type_, type) and isinstance(value, self.type_):
            return value

        try:
            return self.type_(value)
        except (TypeError, ValueError) as e:
            raise type(e)(f"Failed to cast {value!r} to {self.type_}: {e}") from e

    def __eq__(self, other: Any) -> Eq:  # type: ignore[override]
        """Create an equality expression."""
        return Eq(self, other)

    def __ne__(self, other: Any) -> Ne:  # type: ignore[override]
        """Create an inequality expression."""
        return Ne(self, other)

    def __lt__(self, other: Any) -> Lt:
        """Create a less-than expression."""
        return Lt(self, other)

    def __le__(self, other: Any) -> Le:
        """Create a less-than-or-equal expression."""
        return Le(self, other)

    def __gt__(self, other: Any) -> Gt:
        """Create a greater-than expression."""
        return Gt(self, other)

    def __ge__(self, other: Any) -> Ge:
        """Create a greater-than-or-equal expression."""
        return Ge(self, other)

    def matches(self, pattern: str, flags: int | None = None) -> Regex:
        """Create a regex matching expression.

        :param pattern: The regular expression pattern.
        :type pattern: str
        :param flags: Optional regex flags (e.g., `re.IGNORECASE`).
        :type flags: int | None
        :return: A `Regex` expression.
        :rtype: Regex
        """
        return Regex(self, pattern, flags)

    def is_in(self, *values: Any) -> In:
        """Create a membership expression with individual values.

        :param values: The values to check for membership.
        :type values: Any
        :return: An `In` expression.
        :rtype: In
        """
        return In(self, values)

    def is_one_of(self, iterable: tuple[Any, ...] | list[Any]) -> In:
        """Create a membership expression from an iterable.

        :param iterable: An iterable of values to check for membership.
        :type iterable: tuple[Any, ...] | list[Any]
        :return: An `In` expression.
        :rtype: In
        """
        return In(self, tuple(iterable))

    def is_null(self) -> IsNull:
        """Create a null-checking expression.

        :return: An `IsNull` expression with `is_null=True`.
        :rtype: IsNull
        """
        return IsNull(self, is_null=True)

    def is_not_null(self) -> IsNull:
        """Create a not-null-checking expression.

        :return: An `IsNull` expression with `is_null=False`.
        :rtype: IsNull
        """
        return IsNull(self, is_null=False)


def F(name: str, type_: type[Any] | Callable[[Any], Any] | None = None) -> Field:  # noqa: N802
    """A helper function for creating `Field` instances.

    :param name: The name of the field.
    :type name: str
    :param type_: An optional type or a function to cast values.
    :type type_: type[Any] | Callable[[Any], Any] | None
    :return: A `Field` instance.
    :rtype: Field
    """
    return Field(name, type_)


# ============================================================================
# Atomic Expressions
# ============================================================================


@dataclass(frozen=True)
class Eq(ComparisonExpr):
    """An equality comparison expression."""

    @staticmethod
    def _get_operator() -> Callable[[Any, Any], bool]:
        """Return the equality operator."""
        return op.eq

    def __str__(self) -> str:
        """Return a string representation of the equality expression."""
        return f"{self.field} == {self.value!r}"

    def accept(self, visitor: ExprVisitor[T]) -> T:
        """Accept a visitor."""
        return visitor.visit_eq(self)


@dataclass(frozen=True)
class Ne(ComparisonExpr):
    """An inequality comparison expression."""

    @staticmethod
    def _get_operator() -> Callable[[Any, Any], bool]:
        """Return the equality operator (`Ne` uses `eq` with 'none' aggregation)."""
        return op.eq

    @staticmethod
    def _get_aggregation() -> str:
        """Return 'none' for the aggregation mode of an inequality."""
        return "none"

    def __str__(self) -> str:
        """Return a string representation of the inequality expression."""
        return f"{self.field} != {self.value!r}"

    def accept(self, visitor: ExprVisitor[T]) -> T:
        """Accept a visitor."""
        return visitor.visit_ne(self)


@dataclass(frozen=True)
class Lt(ComparisonExpr):
    """A less-than comparison expression."""

    @staticmethod
    def _get_operator() -> Callable[[Any, Any], bool]:
        """Return the less-than operator."""
        return op.lt

    def __str__(self) -> str:
        """Return a string representation of the less-than expression."""
        return f"{self.field} < {self.value!r}"

    def accept(self, visitor: ExprVisitor[T]) -> T:
        """Accept a visitor."""
        return visitor.visit_lt(self)


@dataclass(frozen=True)
class Le(ComparisonExpr):
    """A less-than-or-equal comparison expression."""

    @staticmethod
    def _get_operator() -> Callable[[Any, Any], bool]:
        """Return the less-than-or-equal operator."""
        return op.le

    def __str__(self) -> str:
        """Return a string representation of the less-than-or-equal expression."""
        return f"{self.field} <= {self.value!r}"

    def accept(self, visitor: ExprVisitor[T]) -> T:
        """Accept a visitor."""
        return visitor.visit_le(self)


@dataclass(frozen=True)
class Gt(ComparisonExpr):
    """A greater-than comparison expression."""

    @staticmethod
    def _get_operator() -> Callable[[Any, Any], bool]:
        """Return the greater-than operator."""
        return op.gt

    def __str__(self) -> str:
        """Return a string representation of the greater-than expression."""
        return f"{self.field} > {self.value!r}"

    def accept(self, visitor: ExprVisitor[T]) -> T:
        """Accept a visitor."""
        return visitor.visit_gt(self)


@dataclass(frozen=True)
class Ge(ComparisonExpr):
    """A greater-than-or-equal comparison expression."""

    @staticmethod
    def _get_operator() -> Callable[[Any, Any], bool]:
        """Return the greater-than-or-equal operator."""
        return op.ge

    def __str__(self) -> str:
        """Return a string representation of the greater-than-or-equal expression."""
        return f"{self.field} >= {self.value!r}"

    def accept(self, visitor: ExprVisitor[T]) -> T:
        """Accept a visitor."""
        return visitor.visit_ge(self)


@dataclass(frozen=True)
class Regex(SingleValuedFieldExpr):
    """A regular expression matching expression.

    The regex pattern is stored in the inherited `value` attribute.

    :ivar flags: Optional regex flags.
    :vartype flags: int | None
    """

    flags: int | None = None

    def __str__(self) -> str:
        """Return a string representation of the regex expression."""
        if self.flags is not None:
            return f"{self.field} MATCHES {self.value!r} (flags={self.flags})"
        return f"{self.field} MATCHES {self.value!r}"

    def accept(self, visitor: ExprVisitor[T]) -> T:
        """Accept a visitor."""
        return visitor.visit_regex(self)

    def evaluate(self, data: list[UnwoundValue]) -> bool:
        """Evaluate the regex pattern matching.

        For list-valued fields, this returns `True` if **any** value matches the pattern.

        :param data: A list of `UnwoundValue` objects.
        :type data: list[UnwoundValue]
        :return: `True` if any field value matches the regex pattern.
        :rtype: bool
        """
        # Collect all values for this field
        field_values = [item.field_value for item in data if item.field_name == self.field.name]

        # Handle missing field
        if not field_values:
            raise KeyError(f"Field '{self.field.name}' not found in data")

        flags = self.flags if self.flags is not None else 0

        # Check if any value matches the pattern
        for val in field_values:
            val_str = str(val) if val is not None else ""
            if re.search(self.value, val_str, flags) is not None:
                return True

        return False


@dataclass(frozen=True)
class In(MultiValuedFieldExpr):
    """A membership (IN) expression.

    :ivar values: The values to check for membership against.
    :vartype values: tuple[Any, ...]
    """

    values: tuple[Any, ...]

    def __str__(self) -> str:
        """Return a string representation of the IN expression."""
        values_str = ", ".join(repr(v) for v in self.values)
        return f"{self.field} IN ({values_str})"

    def casted_values(self) -> tuple[Any, ...]:
        """Return the values, cast to the field's declared type.

        :return: A tuple of casted values.
        :rtype: tuple[Any, ...]
        """
        return tuple(self.field.cast(v) for v in self.values)

    def accept(self, visitor: ExprVisitor[T]) -> T:
        """Accept a visitor."""
        return visitor.visit_in(self)

    def evaluate(self, data: list[UnwoundValue]) -> bool:
        """Evaluate the membership check.

        :param data: A list of `UnwoundValue` objects.
        :type data: list[UnwoundValue]
        :return: `True` if any field value is in the list of allowed values.
        :rtype: bool
        """
        # Collect all values for this field
        field_values = [item.field_value for item in data if item.field_name == self.field.name]

        # Handle missing field
        if not field_values:
            raise KeyError(f"Field '{self.field.name}' not found in data")

        casted_values = self.casted_values()

        # Check if any value is in the allowed set
        return any(self.field.cast(val) in casted_values for val in field_values)


@dataclass(frozen=True)
class IsNull(MultiValuedFieldExpr):
    """A null-checking expression.

    :ivar is_null: If `True`, checks for null; otherwise, checks for not-null.
    :vartype is_null: bool
    """

    is_null: bool = True

    def __str__(self) -> str:
        """Return a string representation of the null-checking expression."""
        return f"{self.field} IS NULL" if self.is_null else f"{self.field} IS NOT NULL"

    def accept(self, visitor: ExprVisitor[T]) -> T:
        """Accept a visitor."""
        return visitor.visit_is_null(self)

    def evaluate(self, data: list[UnwoundValue]) -> bool:
        """Evaluate the null check.

        For list-valued fields, this returns `True` if **all** values satisfy the condition.
        Empty lists are treated as null.

        :param data: A list of `UnwoundValue` objects.
        :type data: list[UnwoundValue]
        :return: `True` if the null condition is met.
        :rtype: bool
        """
        # Collect all values for this field
        field_values = [item.field_value for item in data if item.field_name == self.field.name]

        # Handle missing field
        if not field_values:
            raise KeyError(f"Field '{self.field.name}' not found in data")

        # Special case: an empty list is considered null
        if len(field_values) == 1 and field_values[0] == []:
            return self.is_null

        # Check if all values satisfy the null condition
        for val in field_values:
            is_none = val is None
            if self.is_null and not is_none:
                return False
            if not self.is_null and is_none:
                return False

        return True


@dataclass(frozen=True)
class TrueExpr(AtomicExpr):
    """A logical TRUE constant expression."""

    def __str__(self) -> str:
        """Return a string representation of the TRUE expression."""
        return "TRUE"

    def accept(self, visitor: ExprVisitor[T]) -> T:
        """Accept a visitor."""
        return visitor.visit_true(self)

    def evaluate(self, data: list[UnwoundValue]) -> bool:
        """Evaluate the TRUE constant.

        :param data: A list of `UnwoundValue` objects (unused).
        :type data: list[UnwoundValue]
        :return: Always `True`.
        :rtype: bool
        """
        return True


@dataclass(frozen=True)
class FalseExpr(AtomicExpr):
    """A logical FALSE constant expression."""

    def __str__(self) -> str:
        """Return a string representation of the FALSE expression."""
        return "FALSE"

    def accept(self, visitor: ExprVisitor[T]) -> T:
        """Accept a visitor."""
        return visitor.visit_false(self)

    def evaluate(self, data: list[UnwoundValue]) -> bool:
        """Evaluate the FALSE constant.

        :param data: A list of `UnwoundValue` objects (unused).
        :type data: list[UnwoundValue]
        :return: Always `False`.
        :rtype: bool
        """
        return False


# Singleton instances for TRUE and FALSE
TRUE = TrueExpr()
FALSE = FalseExpr()


# ============================================================================
# Compound Expressions
# ============================================================================


@dataclass(frozen=True)
class AllExpr(CompositeExpr):
    """An AND expression that automatically flattens nested `AllExpr` nodes.

    `AllExpr([a, AllExpr([b, c]), d])` normalizes to `AllExpr([a, b, c, d])`.

    :ivar exprs: A tuple of expressions to be combined.
    :vartype exprs: tuple[Expr, ...]
    """

    exprs: tuple[Expr, ...]

    def __init__(self, *exprs: Expr) -> None:
        """Initialize an `AllExpr` with automatic flattening.

        :param exprs: The expressions to be combined with AND logic.
        :type exprs: Expr
        """
        # Flatten nested AllExpr nodes
        flattened: list[Expr] = []
        for expr in exprs:
            if isinstance(expr, AllExpr):
                flattened.extend(expr.exprs)
            else:
                flattened.append(expr)

        # Use object.__setattr__ since the class is frozen
        object.__setattr__(self, "exprs", tuple(flattened))

    def __str__(self) -> str:
        """Return a string representation of the AND expression."""
        if not self.exprs:
            return "AND()"
        if len(self.exprs) == 1:
            return f"AND({self.exprs[0]})"
        parts = " AND ".join(str(expr) for expr in self.exprs)
        return f"({parts})"

    def accept(self, visitor: ExprVisitor[T]) -> T:
        """Accept a visitor."""
        return visitor.visit_all(self)

    def evaluate(self, data: list[UnwoundValue]) -> bool:
        """Evaluate the AND expression.

        All sub-expressions must evaluate to `True`.

        :param data: A list of `UnwoundValue` objects.
        :type data: list[UnwoundValue]
        :return: `True` if all sub-expressions evaluate to `True`.
        :rtype: bool
        """
        return all(expr.evaluate(data) for expr in self.exprs)


@dataclass(frozen=True)
class AnyExpr(CompositeExpr):
    """An OR expression that automatically flattens nested `AnyExpr` nodes.

    `AnyExpr([a, AnyExpr([b, c]), d])` normalizes to `AnyExpr([a, b, c, d])`.

    :ivar exprs: A tuple of expressions to be combined.
    :vartype exprs: tuple[Expr, ...]
    """

    exprs: tuple[Expr, ...]

    def __init__(self, *exprs: Expr) -> None:
        """Initialize an `AnyExpr` with automatic flattening.

        :param exprs: The expressions to be combined with OR logic.
        :type exprs: Expr
        """
        # Flatten nested AnyExpr nodes
        flattened: list[Expr] = []
        for expr in exprs:
            if isinstance(expr, AnyExpr):
                flattened.extend(expr.exprs)
            else:
                flattened.append(expr)

        # Use object.__setattr__ since the class is frozen
        object.__setattr__(self, "exprs", tuple(flattened))

    def __str__(self) -> str:
        """Return a string representation of the OR expression."""
        if not self.exprs:
            return "OR()"
        if len(self.exprs) == 1:
            return f"OR({self.exprs[0]})"
        parts = " OR ".join(str(expr) for expr in self.exprs)
        return f"({parts})"

    def accept(self, visitor: ExprVisitor[T]) -> T:
        """Accept a visitor."""
        return visitor.visit_any(self)

    def evaluate(self, data: list[UnwoundValue]) -> bool:
        """Evaluate the OR expression.

        At least one sub-expression must evaluate to `True`.

        :param data: A list of `UnwoundValue` objects.
        :type data: list[UnwoundValue]
        :return: `True` if at least one sub-expression evaluates to `True`.
        :rtype: bool
        """
        return any(expr.evaluate(data) for expr in self.exprs)


@dataclass(frozen=True)
class NotExpr(DecoratorExpr):
    """A NOT expression.

    :ivar expr: The expression to be negated.
    :vartype expr: Expr
    """

    expr: Expr

    def __str__(self) -> str:
        """Return a string representation of the NOT expression."""
        return f"NOT ({self.expr})"

    def accept(self, visitor: ExprVisitor[T]) -> T:
        """Accept a visitor."""
        return visitor.visit_not(self)

    def evaluate(self, data: list[UnwoundValue]) -> bool:
        """Evaluate the NOT expression (negation of the sub-expression).

        :param data: A list of `UnwoundValue` objects.
        :type data: list[UnwoundValue]
        :return: `True` if the sub-expression evaluates to `False`.
        :rtype: bool
        """
        return not self.expr.evaluate(data)


# ============================================================================
# Union Type Definitions (Legacy - for backward compatibility)
# ============================================================================
# Note: The class-based hierarchy (FieldBasedExpr, SingleValuedFieldExpr, etc.)
# should be preferred for new code. These Union types are kept for compatibility.

# Comparison expressions (Eq, Ne, Lt, Le, Gt, Ge)
# Now available as ComparisonExpr base class
ComparisonExprUnion = Eq | Ne | Lt | Le | Gt | Ge

# Single-valued atomic expressions (require single values, not lists)
# Now available as SingleValuedFieldExpr base class
SingleValuedAtomicExprUnion = Eq | Ne | Lt | Le | Gt | Ge | Regex

# Multi-valued atomic expressions (accept lists naturally)
# Now available as MultiValuedFieldExpr base class
MultiValuedAtomicExprUnion = In | IsNull

# Field-based atomic expressions (all expressions that reference fields)
# Now available as FieldBasedExpr base class
FieldBasedAtomicExprUnion = Eq | Ne | Lt | Le | Gt | Ge | Regex | In | IsNull

# Constant expressions (TrueExpr, FalseExpr)
ConstantExpr = TrueExpr | FalseExpr

# All atomic expression types
AtomicExprType = Eq | Ne | Lt | Le | Gt | Ge | Regex | In | IsNull | TrueExpr | FalseExpr

# Composite expression types (AllExpr, AnyExpr)
CompositeExprType = AllExpr | AnyExpr

# Decorator expression type (NotExpr)
DecoratorExprType = NotExpr

# Logical expressions (AllExpr, AnyExpr, NotExpr)
LogicalExpr = AllExpr | AnyExpr | NotExpr

# Boolean logic expressions (all logical operators)
BooleanLogicExpr = AllExpr | AnyExpr | NotExpr


__all__ = [
    # Base classes
    "Expr",
    "AtomicExpr",
    "FieldBasedExpr",
    "SingleValuedFieldExpr",
    "ComparisonExpr",
    "MultiValuedFieldExpr",
    "CompositeExpr",
    "DecoratorExpr",
    "Field",
    "F",
    "ExprVisitor",
    # Data structures and utilities
    "UnwoundValue",
    "unwind_data",
    # Atomic expressions
    "Eq",
    "Ne",
    "Lt",
    "Le",
    "Gt",
    "Ge",
    "Regex",
    "In",
    "IsNull",
    "TrueExpr",
    "FalseExpr",
    "TRUE",
    "FALSE",
    # Compound expressions
    "AllExpr",
    "AnyExpr",
    "NotExpr",
    # Union types
    "ComparisonExprUnion",
    "SingleValuedAtomicExprUnion",
    "MultiValuedAtomicExprUnion",
    "FieldBasedAtomicExprUnion",
    "ConstantExpr",
    "AtomicExprType",
    "CompositeExprType",
    "DecoratorExprType",
    "LogicalExpr",
    "BooleanLogicExpr",
]
