Disallow non-supported nodes in pattern and replacement
diff --git a/pkg/mapper/mapper.go b/pkg/mapper/mapper.go
index f540525..8350823 100644
--- a/pkg/mapper/mapper.go
+++ b/pkg/mapper/mapper.go
@@ -70,7 +70,7 @@
 }
 
 // ApplyMappings applies the specified mapping rules to a JSON object
-func (m *Mapper) ApplyMappings(mappingID string, opts MappingOptions, jsonData interface{}) (interface{}, error) {
+func (m *Mapper) ApplyMappings(mappingID string, opts MappingOptions, jsonData any) (any, error) {
 	// Validate mapping ID
 	if _, exists := m.mappingLists[mappingID]; !exists {
 		return nil, fmt.Errorf("mapping list with ID %s not found", mappingID)
@@ -95,9 +95,13 @@
 		return nil, fmt.Errorf("failed to parse JSON into AST: %w", err)
 	}
 
-	// Extract the inner node if it's a token
+	// Store whether the input was a Token
+	isToken := false
+	var tokenWrap ast.Node
 	if token, ok := node.(*ast.Token); ok {
-		node = token.Wrap
+		isToken = true
+		tokenWrap = token.Wrap
+		node = tokenWrap
 	}
 
 	// Apply each rule to the AST
@@ -130,12 +134,20 @@
 		}
 
 		// Create matcher and apply replacement
-		m := matcher.NewMatcher(ast.Pattern{Root: pattern}, ast.Replacement{Root: replacement})
+		m, err := matcher.NewMatcher(ast.Pattern{Root: pattern}, ast.Replacement{Root: replacement})
+		if err != nil {
+			return nil, fmt.Errorf("failed to create matcher: %w", err)
+		}
 		node = m.Replace(node)
 	}
 
-	// Wrap the result in a token
-	result := &ast.Token{Wrap: node}
+	// Wrap the result in a token if the input was a token
+	var result ast.Node
+	if isToken {
+		result = &ast.Token{Wrap: node}
+	} else {
+		result = node
+	}
 
 	// Convert AST back to JSON
 	resultBytes, err := parser.SerializeToJSON(result)
diff --git a/pkg/mapper/mapper_test.go b/pkg/mapper/mapper_test.go
index b5a8950..40ec88e 100644
--- a/pkg/mapper/mapper_test.go
+++ b/pkg/mapper/mapper_test.go
@@ -6,6 +6,8 @@
 	"path/filepath"
 	"testing"
 
+	"github.com/KorAP/KoralPipe-TermMapper2/pkg/ast"
+	"github.com/KorAP/KoralPipe-TermMapper2/pkg/matcher"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -226,3 +228,151 @@
 		})
 	}
 }
+
+func TestMatchComplexPatterns(t *testing.T) {
+	tests := []struct {
+		name        string
+		pattern     ast.Pattern
+		replacement ast.Replacement
+		input       ast.Node
+		expected    ast.Node
+	}{
+		{
+			name: "Deep nested pattern with mixed operators",
+			pattern: ast.Pattern{
+				Root: &ast.TermGroup{
+					Operands: []ast.Node{
+						&ast.Term{
+							Key:   "A",
+							Match: ast.MatchEqual,
+						},
+						&ast.TermGroup{
+							Operands: []ast.Node{
+								&ast.Term{
+									Key:   "B",
+									Match: ast.MatchEqual,
+								},
+								&ast.TermGroup{
+									Operands: []ast.Node{
+										&ast.Term{
+											Key:   "C",
+											Match: ast.MatchEqual,
+										},
+										&ast.Term{
+											Key:   "D",
+											Match: ast.MatchEqual,
+										},
+									},
+									Relation: ast.AndRelation,
+								},
+							},
+							Relation: ast.OrRelation,
+						},
+					},
+					Relation: ast.AndRelation,
+				},
+			},
+			replacement: ast.Replacement{
+				Root: &ast.Term{
+					Key:   "RESULT",
+					Match: ast.MatchEqual,
+				},
+			},
+			input: &ast.TermGroup{
+				Operands: []ast.Node{
+					&ast.Term{
+						Key:   "A",
+						Match: ast.MatchEqual,
+					},
+					&ast.TermGroup{
+						Operands: []ast.Node{
+							&ast.Term{
+								Key:   "C",
+								Match: ast.MatchEqual,
+							},
+							&ast.Term{
+								Key:   "D",
+								Match: ast.MatchEqual,
+							},
+						},
+						Relation: ast.AndRelation,
+					},
+				},
+				Relation: ast.AndRelation,
+			},
+			expected: &ast.Term{
+				Key:   "RESULT",
+				Match: ast.MatchEqual,
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			m, err := matcher.NewMatcher(tt.pattern, tt.replacement)
+			require.NoError(t, err)
+			result := m.Replace(tt.input)
+			assert.Equal(t, tt.expected, result)
+		})
+	}
+}
+
+func TestInvalidPatternReplacement(t *testing.T) {
+	// Create a temporary config file
+	tmpDir := t.TempDir()
+	configFile := filepath.Join(tmpDir, "test-config.yaml")
+
+	configContent := `- id: test-mapper
+  foundryA: opennlp
+  layerA: p
+  foundryB: upos
+  layerB: p
+  mappings:
+    - "[PIDAT] <> [opennlp/p=PIDAT & opennlp/p=AdjType:Pdt]"`
+
+	err := os.WriteFile(configFile, []byte(configContent), 0644)
+	require.NoError(t, err)
+
+	// Create a new mapper
+	m, err := NewMapper(configFile)
+	require.NoError(t, err)
+
+	tests := []struct {
+		name        string
+		input       string
+		expectError bool
+		errorMsg    string
+	}{
+		{
+			name: "Invalid input - empty term group",
+			input: `{
+				"@type": "koral:token",
+				"wrap": {
+					"@type": "koral:termGroup",
+					"operands": [],
+					"relation": "relation:and"
+				}
+			}`,
+			expectError: true,
+			errorMsg:    "failed to parse JSON into AST: error parsing wrapped node: term group must have at least one operand",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var inputData any
+			err := json.Unmarshal([]byte(tt.input), &inputData)
+			require.NoError(t, err)
+
+			result, err := m.ApplyMappings("test-mapper", MappingOptions{Direction: AtoB}, inputData)
+			if tt.expectError {
+				assert.Error(t, err)
+				assert.Equal(t, tt.errorMsg, err.Error())
+				assert.Nil(t, result)
+			} else {
+				assert.NoError(t, err)
+				assert.NotNil(t, result)
+			}
+		})
+	}
+}