"""
Comprehensive tests for dotpath - Advanced path engine with custom segments.
"""

import pytest
import sys
import os
import re
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../src'))

from depth.dotpath.core import PathSegment, PathEngine, create_default_engine, find_all, find_first
from depth.dotpath.segments import (
    Key, Index, Slice, Wildcard, Descendant, Filter, RegexKey
)


class TestKeySegment:
    """Test Key segment functionality."""

    def test_key_basic_parse(self):
        """Test parsing basic dot notation keys."""
        segment, length = Key.parse("name")
        assert isinstance(segment, Key)
        assert segment.name == "name"
        assert length == 4

        segment, length = Key.parse("user_123")
        assert segment.name == "user_123"
        assert length == 8

    def test_key_bracket_notation(self):
        """Test parsing bracket notation with quotes."""
        segment, length = Key.parse("['special-key']")
        assert isinstance(segment, Key)
        assert segment.name == "special-key"
        assert length == 15

        segment, length = Key.parse('["double-quoted"]')
        assert segment.name == "double-quoted"
        assert length == 17

        # With spaces
        segment, length = Key.parse("[ 'with-spaces' ]")
        assert segment.name == "with-spaces"
        assert length == 17

    def test_key_invalid_parse(self):
        """Test Key parse returns None for invalid input."""
        assert Key.parse("123invalid") is None
        assert Key.parse("[]") is None
        assert Key.parse("[unquoted]") is None
        assert Key.parse("") is None

    def test_key_evaluate(self):
        """Test Key evaluation on documents."""
        key = Key(name="user")

        # Valid dict
        results = list(key.evaluate({"user": "Alice", "age": 30}))
        assert results == ["Alice"]

        # Missing key
        results = list(key.evaluate({"name": "Bob"}))
        assert results == []

        # Non-dict input
        results = list(key.evaluate(["item1", "item2"]))
        assert results == []

        # None input
        results = list(key.evaluate(None))
        assert results == []

    def test_key_to_dict(self):
        """Test Key serialization to dict."""
        key = Key(name="test_key")
        data = key.to_dict()
        assert data == {"$type": "key", "name": "test_key"}

    def test_key_from_dict(self):
        """Test Key deserialization from dict."""
        data = {"$type": "key", "name": "restored"}
        key = Key.from_dict(data)
        assert isinstance(key, Key)
        assert key.name == "restored"


class TestIndexSegment:
    """Test Index segment functionality."""

    def test_index_parse_positive(self):
        """Test parsing positive indices."""
        segment, length = Index.parse("[0]")
        assert isinstance(segment, Index)
        assert segment.value == 0
        assert length == 3

        segment, length = Index.parse("[42]")
        assert segment.value == 42
        assert length == 4

        segment, length = Index.parse("[ 5 ]")
        assert segment.value == 5
        assert length == 5

    def test_index_parse_negative(self):
        """Test parsing negative indices."""
        segment, length = Index.parse("[-1]")
        assert isinstance(segment, Index)
        assert segment.value == -1
        assert length == 4

        segment, length = Index.parse("[-10]")
        assert segment.value == -10
        assert length == 5

    def test_index_invalid_parse(self):
        """Test Index parse returns None for non-indices."""
        assert Index.parse("[abc]") is None
        assert Index.parse("123") is None
        assert Index.parse("[]") is None
        assert Index.parse("[1.5]") is None

    def test_index_evaluate_list(self):
        """Test Index evaluation on lists."""
        index = Index(value=1)

        # Valid list access
        results = list(index.evaluate(["a", "b", "c"]))
        assert results == ["b"]

        # Negative index
        neg_index = Index(value=-1)
        results = list(neg_index.evaluate(["a", "b", "c"]))
        assert results == ["c"]

        # Out of bounds
        big_index = Index(value=10)
        results = list(big_index.evaluate(["a", "b"]))
        assert results == []

        # Out of bounds negative
        neg_big = Index(value=-10)
        results = list(neg_big.evaluate(["a", "b"]))
        assert results == []

    def test_index_evaluate_non_list(self):
        """Test Index evaluation on non-lists."""
        index = Index(value=0)

        # Dict input
        results = list(index.evaluate({"key": "value"}))
        assert results == []

        # String input (should not work like list)
        results = list(index.evaluate("string"))
        assert results == []

    def test_index_serialization(self):
        """Test Index serialization/deserialization."""
        index = Index(value=-3)
        data = index.to_dict()
        assert data == {"$type": "index", "value": -3}

        restored = Index.from_dict(data)
        assert restored.value == -3


class TestSliceSegment:
    """Test Slice segment functionality."""

    def test_slice_parse_basic(self):
        """Test parsing basic slices."""
        # Start only
        segment, length = Slice.parse("[1:]")
        assert isinstance(segment, Slice)
        assert segment.start == 1
        assert segment.stop is None
        assert segment.step is None
        assert length == 4

        # Stop only
        segment, length = Slice.parse("[:5]")
        assert segment.start is None
        assert segment.stop == 5
        assert segment.step is None

        # Both start and stop
        segment, length = Slice.parse("[2:8]")
        assert segment.start == 2
        assert segment.stop == 8
        assert segment.step is None

    def test_slice_parse_with_step(self):
        """Test parsing slices with step."""
        segment, length = Slice.parse("[1:10:2]")
        assert isinstance(segment, Slice)
        assert segment.start == 1
        assert segment.stop == 10
        assert segment.step == 2

        # All empty except step
        segment, length = Slice.parse("[::2]")
        assert segment.start is None
        assert segment.stop is None
        assert segment.step == 2

    def test_slice_parse_negative(self):
        """Test parsing slices with negative values."""
        segment, length = Slice.parse("[-5:-1]")
        assert segment.start == -5
        assert segment.stop == -1

        segment, length = Slice.parse("[1:-1]")
        assert segment.start == 1
        assert segment.stop == -1

    def test_slice_invalid_parse(self):
        """Test Slice parse returns None for invalid input."""
        assert Slice.parse("[5]") is None  # No colon, this is an index
        assert Slice.parse("1:5") is None  # No brackets
        assert Slice.parse("[a:b]") is None  # Non-numeric
        assert Slice.parse("[:::]") is None  # Too many colons
        assert Slice.parse("[1:2:3:4]") is None  # Too many parts

    def test_slice_evaluate(self):
        """Test Slice evaluation on lists."""
        # Basic slice
        segment = Slice(start=1, stop=3, step=None)
        results = list(segment.evaluate([0, 1, 2, 3, 4]))
        assert results == [1, 2]

        # With step
        segment = Slice(start=0, stop=6, step=2)
        results = list(segment.evaluate([0, 1, 2, 3, 4, 5, 6]))
        assert results == [0, 2, 4]

        # Negative indices
        segment = Slice(start=-3, stop=-1, step=None)
        results = list(segment.evaluate([0, 1, 2, 3, 4]))
        assert results == [2, 3]

        # On non-list
        segment = Slice(start=0, stop=2, step=None)
        results = list(segment.evaluate({"key": "value"}))
        assert results == []

    def test_slice_serialization(self):
        """Test Slice serialization/deserialization."""
        segment = Slice(start=1, stop=5, step=2)
        data = segment.to_dict()
        assert "$type" in data
        assert data["$type"] == "slice"
        assert data["start"] == 1
        assert data["stop"] == 5
        assert data["step"] == 2

        restored = Slice.from_dict(data)
        assert restored.start == 1
        assert restored.stop == 5
        assert restored.step == 2


class TestWildcardSegment:
    """Test Wildcard segment functionality."""

    def test_wildcard_parse(self):
        """Test parsing wildcard notation."""
        segment, length = Wildcard.parse("*")
        assert isinstance(segment, Wildcard)
        assert length == 1

        segment, length = Wildcard.parse("[*]")
        assert isinstance(segment, Wildcard)
        assert length == 3

        # Wildcard only matches single * at beginning
        result = Wildcard.parse("**")
        assert result == (Wildcard(), 1)  # Only consumes first *

        assert Wildcard.parse("abc*") is None  # Not at start

    def test_wildcard_evaluate_dict(self):
        """Test Wildcard evaluation on dicts."""
        wildcard = Wildcard()

        data = {"a": 1, "b": 2, "c": 3}
        results = list(wildcard.evaluate(data))
        assert sorted(results) == [1, 2, 3]

        # Empty dict
        results = list(wildcard.evaluate({}))
        assert results == []

    def test_wildcard_evaluate_list(self):
        """Test Wildcard evaluation on lists."""
        wildcard = Wildcard()

        data = ["x", "y", "z"]
        results = list(wildcard.evaluate(data))
        assert results == ["x", "y", "z"]

        # Empty list
        results = list(wildcard.evaluate([]))
        assert results == []

    def test_wildcard_evaluate_other(self):
        """Test Wildcard evaluation on non-containers."""
        wildcard = Wildcard()

        results = list(wildcard.evaluate("string"))
        assert results == []

        results = list(wildcard.evaluate(None))
        assert results == []

        results = list(wildcard.evaluate(42))
        assert results == []

    def test_wildcard_serialization(self):
        """Test Wildcard serialization/deserialization."""
        wildcard = Wildcard()
        data = wildcard.to_dict()
        assert data == {"$type": "wildcard"}

        restored = Wildcard.from_dict(data)
        assert isinstance(restored, Wildcard)


class TestDescendantSegment:
    """Test Descendant segment functionality."""

    def test_descendant_parse(self):
        """Test parsing descendant notation."""
        segment, length = Descendant.parse("**")
        assert isinstance(segment, Descendant)
        assert length == 2

        assert Descendant.parse("*") is None  # Single star is wildcard

        # *** should parse as descendant for first two stars
        result = Descendant.parse("***")
        assert result == (Descendant(), 2)  # Consumes only **

    def test_descendant_evaluate_nested(self):
        """Test Descendant evaluation on nested structures."""
        descendant = Descendant()

        data = {
            "a": 1,
            "b": {
                "c": 2,
                "d": {
                    "e": 3
                }
            },
            "f": [4, 5, {"g": 6}]
        }

        results = list(descendant.evaluate(data))

        # Should include the root
        assert data in results

        # Should include nested dicts
        assert {"c": 2, "d": {"e": 3}} in results
        assert {"e": 3} in results

        # Should include list items
        assert 4 in results
        assert 5 in results
        assert {"g": 6} in results

        # Should include deeply nested values
        assert 1 in results
        assert 2 in results
        assert 3 in results
        assert 6 in results

        # Should include the list itself
        assert [4, 5, {"g": 6}] in results

    def test_descendant_evaluate_simple(self):
        """Test Descendant on simple types."""
        descendant = Descendant()

        # Scalar value - should yield itself
        results = list(descendant.evaluate(42))
        assert results == [42]

        # String - should yield itself
        results = list(descendant.evaluate("hello"))
        assert results == ["hello"]

        # None - should yield itself
        results = list(descendant.evaluate(None))
        assert results == [None]

    def test_descendant_serialization(self):
        """Test Descendant serialization/deserialization."""
        descendant = Descendant()
        data = descendant.to_dict()
        assert data == {"$type": "descendant"}

        restored = Descendant.from_dict(data)
        assert isinstance(restored, Descendant)


class TestFilterSegment:
    """Test Filter segment functionality."""

    def test_filter_parse(self):
        """Test parsing filter notation."""
        segment, length = Filter.parse("[?(@ > 5)]")
        assert isinstance(segment, Filter)
        assert segment.predicate_str == "@ > 5"
        assert length == 10

        segment, length = Filter.parse("[?(@['name'] == 'Alice')]")
        assert segment.predicate_str == "@['name'] == 'Alice'"

        assert Filter.parse("[5]") is None  # Not a filter
        assert Filter.parse("[@]") is None  # Missing ?()

    def test_filter_evaluate_basic(self):
        """Test Filter evaluation with basic predicates."""
        # Numeric comparison
        filter_gt = Filter(predicate_str="@ > 3")
        results = list(filter_gt.evaluate([1, 2, 3, 4, 5]))
        assert results == [4, 5]

        # Equality
        filter_eq = Filter(predicate_str="@ == 'test'")
        results = list(filter_eq.evaluate(['test', 'other', 'test']))
        assert results == ['test', 'test']

    def test_filter_evaluate_dict_items(self):
        """Test Filter evaluation on list of dicts."""
        data = [
            {"name": "Alice", "age": 30},
            {"name": "Bob", "age": 25},
            {"name": "Charlie", "age": 35}
        ]

        # Filter by age
        filter_age = Filter(predicate_str="@['age'] > 28")
        results = list(filter_age.evaluate(data))
        assert len(results) == 2
        assert results[0]["name"] == "Alice"
        assert results[1]["name"] == "Charlie"

        # Filter by name
        filter_name = Filter(predicate_str="@['name'] == 'Bob'")
        results = list(filter_name.evaluate(data))
        assert len(results) == 1
        assert results[0]["age"] == 25

    def test_filter_evaluate_errors(self):
        """Test Filter handles evaluation errors gracefully."""
        filter_key = Filter(predicate_str="@['missing'] > 0")

        # Should skip items that cause errors
        data = [
            {"value": 5},
            {"missing": 10},
            {"value": 3}
        ]
        results = list(filter_key.evaluate(data))
        assert len(results) == 1
        assert results[0]["missing"] == 10

    def test_filter_non_list(self):
        """Test Filter on non-list input."""
        filter_gt = Filter(predicate_str="@ > 5")

        # Dict input - no results
        results = list(filter_gt.evaluate({"key": 10}))
        assert results == []

        # Scalar input - no results
        results = list(filter_gt.evaluate(10))
        assert results == []

    def test_filter_invalid_predicate_runtime(self):
        """Test Filter with predicate that fails at runtime."""
        # The Filter uses eval in a lambda, so syntax errors only occur when evaluated
        # Create a filter with syntax that will fail when executed
        filter_bad = Filter(predicate_str="@[")  # Missing closing bracket

        # The filter is created successfully but fails when evaluating
        data = [1, 2, 3]
        results = list(filter_bad.evaluate(data))
        # Errors are silently caught in evaluate(), so no results
        assert results == []

    def test_filter_serialization(self):
        """Test Filter serialization/deserialization."""
        filter_seg = Filter(predicate_str="@ > 10")
        data = filter_seg.to_dict()
        assert data == {"$type": "filter", "predicate": "@ > 10"}

        restored = Filter.from_dict(data)
        assert restored.predicate_str == "@ > 10"


class TestRegexKeySegment:
    """Test RegexKey segment functionality."""

    def test_regex_parse_basic(self):
        """Test parsing regex key notation."""
        segment, length = RegexKey.parse("~r/user_\\d+/")
        assert isinstance(segment, RegexKey)
        assert segment.pattern.pattern == "user_\\d+"
        assert length == 12

        # With flags
        segment, length = RegexKey.parse("~r/USER_\\d+/i")
        assert segment.pattern.pattern == "USER_\\d+"
        assert segment.pattern.flags & re.IGNORECASE
        assert length == 13

    def test_regex_parse_escaped_slash(self):
        """Test parsing regex with escaped forward slash."""
        segment, length = RegexKey.parse("~r/path\\/to\\/file/")
        assert isinstance(segment, RegexKey)
        assert segment.pattern.pattern == "path/to/file"
        assert length == 18

    def test_regex_parse_flags(self):
        """Test parsing regex with multiple flags."""
        segment, length = RegexKey.parse("~r/pattern/im")
        assert segment.pattern.flags & re.IGNORECASE
        assert segment.pattern.flags & re.MULTILINE

        # Unknown flags are ignored
        segment, length = RegexKey.parse("~r/pattern/ixyz")
        assert segment.pattern.flags & re.IGNORECASE
        assert not (segment.pattern.flags & re.VERBOSE)  # x not implemented

    def test_regex_invalid_parse(self):
        """Test RegexKey parse returns None for invalid input."""
        assert RegexKey.parse("r/pattern/") is None  # Missing ~
        assert RegexKey.parse("~r/[invalid/") is None  # Invalid regex
        assert RegexKey.parse("~r/") is None  # Incomplete
        assert RegexKey.parse("~/pattern/") is None  # Missing r

    def test_regex_evaluate(self):
        """Test RegexKey evaluation on dicts."""
        pattern = re.compile(r"user_\d+")
        regex_key = RegexKey(pattern=pattern)

        data = {
            "user_1": "Alice",
            "user_2": "Bob",
            "admin": "Charlie",
            "user_abc": "Dave"
        }

        results = list(regex_key.evaluate(data))
        assert sorted(results) == ["Alice", "Bob"]

        # Case insensitive
        pattern_i = re.compile(r"USER_\d+", re.IGNORECASE)
        regex_key_i = RegexKey(pattern=pattern_i)
        results = list(regex_key_i.evaluate(data))
        assert sorted(results) == ["Alice", "Bob"]

    def test_regex_evaluate_non_dict(self):
        """Test RegexKey evaluation on non-dicts."""
        pattern = re.compile(r"test")
        regex_key = RegexKey(pattern=pattern)

        # List input
        results = list(regex_key.evaluate(["item1", "item2"]))
        assert results == []

        # None input
        results = list(regex_key.evaluate(None))
        assert results == []

    def test_regex_serialization(self):
        """Test RegexKey serialization/deserialization."""
        pattern = re.compile(r"test_\d+", re.IGNORECASE | re.MULTILINE)
        regex_key = RegexKey(pattern=pattern)

        data = regex_key.to_dict()
        assert data["$type"] == "regex_key"
        assert data["pattern"] == "test_\\d+"
        assert "i" in data["flags"]
        assert "m" in data["flags"]

        restored = RegexKey.from_dict(data)
        assert restored.pattern.pattern == "test_\\d+"
        assert restored.pattern.flags & re.IGNORECASE
        assert restored.pattern.flags & re.MULTILINE


class TestPathEngineIntegration:
    """Test PathEngine and segment integration."""

    def test_engine_parse_simple(self):
        """Test parsing simple paths with PathEngine."""
        engine = create_default_engine()

        ast = engine.parse("user.name")
        assert len(ast) == 2
        assert isinstance(ast[0], Key)
        assert ast[0].name == "user"
        assert isinstance(ast[1], Key)
        assert ast[1].name == "name"

    def test_engine_parse_mixed(self):
        """Test parsing paths with mixed segment types."""
        engine = create_default_engine()

        ast = engine.parse("users[0].name")
        assert len(ast) == 3
        assert isinstance(ast[0], Key)
        assert isinstance(ast[1], Index)
        assert isinstance(ast[2], Key)

    def test_engine_parse_complex(self):
        """Test parsing complex paths."""
        engine = create_default_engine()

        # With wildcard
        ast = engine.parse("users.*.name")
        assert len(ast) == 3
        assert isinstance(ast[1], Wildcard)

        # With slice
        ast = engine.parse("items[1:3]")
        assert len(ast) == 2
        assert isinstance(ast[1], Slice)

        # With descendant
        # Note: ** is parsed as two wildcards by the current implementation
        # since Wildcard.parse returns (Wildcard(), 1) for each *
        ast = engine.parse("root.**.leaf")
        # This might parse as root, *, *, leaf = 4 segments
        # Let's check what actually gets parsed
        if len(ast) == 4:
            assert isinstance(ast[0], Key)
            assert isinstance(ast[1], Wildcard)
            assert isinstance(ast[2], Wildcard)
            assert isinstance(ast[3], Key)
        else:
            assert len(ast) == 3
            assert isinstance(ast[1], Descendant)

    def test_engine_evaluate(self):
        """Test PathEngine evaluation on documents."""
        engine = create_default_engine()

        data = {
            "users": [
                {"name": "Alice", "age": 30},
                {"name": "Bob", "age": 25}
            ]
        }

        # Simple path
        ast = engine.parse("users[0].name")
        results = list(engine.evaluate(ast, data))
        assert results == ["Alice"]

        # With wildcard
        ast = engine.parse("users.*.name")
        results = list(engine.evaluate(ast, data))
        assert sorted(results) == ["Alice", "Bob"]

    def test_engine_serialization(self):
        """Test PathEngine serialization/deserialization."""
        engine = create_default_engine()

        original_ast = engine.parse("users[0].name")
        json_str = engine.ast_to_json(original_ast)

        restored_ast = engine.json_to_ast(json_str)
        assert len(restored_ast) == 3
        assert isinstance(restored_ast[0], Key)
        assert isinstance(restored_ast[1], Index)
        assert isinstance(restored_ast[2], Key)

        # Test evaluation works the same
        test_data = {"users": [{"name": "Test"}]}
        assert list(engine.evaluate(original_ast, test_data)) == list(engine.evaluate(restored_ast, test_data))

    def test_find_all_api(self):
        """Test the find_all convenience function."""
        data = {
            "users": [
                {"name": "Alice", "role": "admin"},
                {"name": "Bob", "role": "user"},
                {"name": "Charlie", "role": "admin"}
            ]
        }

        # Find all names
        names = find_all("users.*.name", data)
        assert sorted(names) == ["Alice", "Bob", "Charlie"]

        # Find with filter
        admins = find_all("users[?(@['role'] == 'admin')].name", data)
        assert sorted(admins) == ["Alice", "Charlie"]

    def test_find_first_api(self):
        """Test the find_first convenience function."""
        data = {
            "config": {
                "database": {
                    "host": "localhost",
                    "port": 5432
                }
            }
        }

        # Find first match
        host = find_first("config.database.host", data)
        assert host == "localhost"

        # Non-existent path
        missing = find_first("config.redis.host", data)
        assert missing is None

    def test_custom_engine(self):
        """Test creating and using a custom engine."""
        # Create engine with limited capabilities
        custom_engine = PathEngine()
        custom_engine.register(Key)
        custom_engine.register(Index)

        # Can parse simple paths
        ast = custom_engine.parse("users[0]")
        assert len(ast) == 2

        # Cannot parse wildcards (not registered)
        with pytest.raises(ValueError) as exc_info:
            custom_engine.parse("users.*")
        assert "Could not parse" in str(exc_info.value)


class TestEdgeCases:
    """Test edge cases and error conditions."""

    def test_empty_inputs(self):
        """Test segments with empty inputs."""
        key = Key(name="test")
        assert list(key.evaluate({})) == []
        assert list(key.evaluate([])) == []
        assert list(key.evaluate(None)) == []
        assert list(key.evaluate("")) == []

    def test_special_characters_in_keys(self):
        """Test keys with special characters."""
        # Bracket notation supports special chars
        segment, _ = Key.parse("['key-with-dash']")
        assert segment.name == "key-with-dash"

        segment, _ = Key.parse("['key.with.dots']")
        assert segment.name == "key.with.dots"

        segment, _ = Key.parse("['key with spaces']")
        assert segment.name == "key with spaces"

    def test_boundary_indices(self):
        """Test boundary conditions for indices."""
        data = [1, 2, 3]

        # Exact boundaries
        assert list(Index(value=0).evaluate(data)) == [1]
        assert list(Index(value=2).evaluate(data)) == [3]
        assert list(Index(value=-1).evaluate(data)) == [3]
        assert list(Index(value=-3).evaluate(data)) == [1]

        # Out of bounds
        assert list(Index(value=3).evaluate(data)) == []
        assert list(Index(value=-4).evaluate(data)) == []

    def test_empty_slice(self):
        """Test slices that produce empty results."""
        data = [1, 2, 3, 4, 5]

        # Start > stop
        assert list(Slice(start=3, stop=1, step=None).evaluate(data)) == []

        # Start beyond end
        assert list(Slice(start=10, stop=20, step=None).evaluate(data)) == []

        # Negative step (not typically supported in our implementation)
        # This should still work with Python's slice
        assert list(Slice(start=None, stop=None, step=-1).evaluate(data)) == [5, 4, 3, 2, 1]

    def test_nested_empty_structures(self):
        """Test paths through empty nested structures."""
        # Empty nested dicts
        data = {"a": {"b": {}}}
        results = find_all("a.b.c", data)
        assert results == []

        # Empty nested lists
        data = {"items": []}
        results = find_all("items[0]", data)
        assert results == []

        # Mixed empty
        data = {"users": [{"posts": []}]}
        results = find_all("users[0].posts[0].title", data)
        assert results == []

    def test_engine_invalid_input(self):
        """Test PathEngine with invalid inputs."""
        engine = create_default_engine()

        # Non-string path
        with pytest.raises(TypeError) as exc_info:
            engine.parse(123)
        assert "Path must be a string" in str(exc_info.value)

        # Invalid path syntax
        with pytest.raises(ValueError) as exc_info:
            engine.parse("user.[invalid]")
        assert "Could not parse" in str(exc_info.value)

    def test_engine_empty_path(self):
        """Test PathEngine with empty path."""
        engine = create_default_engine()

        # Empty string
        ast = engine.parse("")
        assert ast == []

        # Whitespace only
        ast = engine.parse("   ")
        assert ast == []

    def test_serialization_errors(self):
        """Test serialization error handling."""
        engine = create_default_engine()

        # Missing $type field
        with pytest.raises(ValueError) as exc_info:
            engine.json_to_ast('[{"name": "test"}]')
        assert "missing '$type' field" in str(exc_info.value)

        # Unknown type
        with pytest.raises(TypeError) as exc_info:
            engine.json_to_ast('[{"$type": "unknown", "value": 1}]')
        assert "Unknown path segment type" in str(exc_info.value)