"""Filter parsing and application for dicom_search."""

from typing import Any, Dict, Optional

# Text operators (case-insensitive matching on string tag values)
_TEXT_OPERATORS = {
    "is": lambda actual, expected: actual.lower() == expected.lower(),
    "is not": lambda actual, expected: actual.lower() != expected.lower(),
    "contains": lambda actual, expected: expected.lower() in actual.lower(),
    "starts with": lambda actual, expected: actual.lower().startswith(expected.lower()),
    "ends with": lambda actual, expected: actual.lower().endswith(expected.lower()),
}

# Symbolic operators (numeric with string fallback)
_SYMBOLIC_OPERATORS = {
    ">=": lambda a, b: a >= b,
    "<=": lambda a, b: a <= b,
    "!=": lambda a, b: a != b,
    ">": lambda a, b: a > b,
    "<": lambda a, b: a < b,
    "=": lambda a, b: a == b,
}

# Presence operators (no value needed)
_PRESENCE_OPERATORS = {
    "exists": lambda actual: actual != "N/A" and actual.strip() != "",
    "missing": lambda actual: actual == "N/A" or actual.strip() == "",
}


def _parse_filter(filter_str: str) -> Optional[Dict[str, Any]]:
    """Parse a filter string into a structured filter dict.

    Supported formats:
      Text:     "SeriesDescription contains MOST"
      Symbolic: "EchoTime > 10"
      Presence: "InversionTime exists"

    Returns dict with keys: tag_spec, operator, value (None for presence),
    operator_type ('text', 'symbolic', 'presence').
    Returns None if parsing fails.
    """
    filter_str = filter_str.strip()
    if not filter_str:
        return None

    # --- Try presence operators first (no value part) ---
    for op in _PRESENCE_OPERATORS:
        if filter_str.lower().endswith(f" {op}"):
            tag_spec = filter_str[: -(len(op) + 1)].strip()
            if tag_spec:
                return {
                    "tag_spec": tag_spec,
                    "operator": op,
                    "value": None,
                    "operator_type": "presence",
                }

    # --- Try text operators (multi-word, so check longest first) ---
    for op in sorted(_TEXT_OPERATORS.keys(), key=len, reverse=True):
        # Look for " op " in the string (space-delimited)
        marker = f" {op} "
        idx = filter_str.lower().find(marker)
        if idx >= 0:
            tag_spec = filter_str[:idx].strip()
            value = filter_str[idx + len(marker) :].strip()
            if tag_spec and value:
                return {
                    "tag_spec": tag_spec,
                    "operator": op,
                    "value": value,
                    "operator_type": "text",
                }

    # --- Try symbolic operators (check >= <= != before > < =) ---
    for op in sorted(_SYMBOLIC_OPERATORS.keys(), key=len, reverse=True):
        marker = f" {op} "
        idx = filter_str.find(marker)
        if idx >= 0:
            tag_spec = filter_str[:idx].strip()
            value = filter_str[idx + len(marker) :].strip()
            if tag_spec and value:
                return {
                    "tag_spec": tag_spec,
                    "operator": op,
                    "value": value,
                    "operator_type": "symbolic",
                }

    return None


def _apply_filter(filter_def: Dict[str, Any], actual_value: str) -> bool:
    """Apply a parsed filter to an actual DICOM tag value.

    Returns True if the value passes the filter.
    """
    op = filter_def["operator"]
    op_type = filter_def["operator_type"]
    expected = filter_def.get("value")

    if op_type == "presence":
        return _PRESENCE_OPERATORS[op](actual_value)

    if op_type == "text":
        return _TEXT_OPERATORS[op](actual_value, expected)

    if op_type == "symbolic":
        comparator = _SYMBOLIC_OPERATORS[op]
        # Try numeric comparison first
        try:
            num_actual = float(actual_value)
            num_expected = float(expected)
            return comparator(num_actual, num_expected)
        except (ValueError, TypeError):
            pass
        # Fall back to string comparison
        return comparator(actual_value, expected)

    return False
