Optimize mappings
diff --git a/ast/ast.go b/ast/ast.go
index dc7d7f3..e47a78e 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -29,6 +29,7 @@
 // Node represents a node in the AST
 type Node interface {
 	Type() NodeType
+	Clone() Node
 }
 
 // Rewrite represents a koral:rewrite
@@ -88,6 +89,18 @@
 	return RewriteNode
 }
 
+// Clone creates a deep copy of the Rewrite node
+func (r *Rewrite) Clone() Node {
+	return &Rewrite{
+		Editor:    r.Editor,
+		Operation: r.Operation,
+		Scope:     r.Scope,
+		Src:       r.Src,
+		Comment:   r.Comment,
+		Original:  r.Original, // Note: this is a shallow copy of the Original field
+	}
+}
+
 // MarshalJSON implements custom JSON marshaling to ensure clean output
 func (r *Rewrite) MarshalJSON() ([]byte, error) {
 	// Create a map with only the modern field names
@@ -128,6 +141,27 @@
 	return TokenNode
 }
 
+// Clone creates a deep copy of the Token node
+func (t *Token) Clone() Node {
+	var clonedWrap Node
+	if t.Wrap != nil {
+		clonedWrap = t.Wrap.Clone()
+	}
+	tc := &Token{
+		Wrap: clonedWrap,
+	}
+
+	if t.Rewrites != nil {
+		clonedRewrites := make([]Rewrite, len(t.Rewrites))
+		for i, rewrite := range t.Rewrites {
+			clonedRewrites[i] = *rewrite.Clone().(*Rewrite)
+		}
+		tc.Rewrites = clonedRewrites
+	}
+
+	return tc
+}
+
 // TermGroup represents a koral:termGroup
 type TermGroup struct {
 	Operands []Node       `json:"operands"`
@@ -139,6 +173,27 @@
 	return TermGroupNode
 }
 
+// Clone creates a deep copy of the TermGroup node
+func (tg *TermGroup) Clone() Node {
+	clonedOperands := make([]Node, len(tg.Operands))
+	for i, operand := range tg.Operands {
+		clonedOperands[i] = operand.Clone()
+	}
+	tgc := &TermGroup{
+		Operands: clonedOperands,
+		Relation: tg.Relation,
+	}
+	if tg.Rewrites != nil {
+		clonedRewrites := make([]Rewrite, len(tg.Rewrites))
+		for i, rewrite := range tg.Rewrites {
+			clonedRewrites[i] = *rewrite.Clone().(*Rewrite)
+		}
+		tgc.Rewrites = clonedRewrites
+	}
+
+	return tgc
+}
+
 // Term represents a koral:term
 type Term struct {
 	Foundry  string    `json:"foundry"`
@@ -153,6 +208,27 @@
 	return TermNode
 }
 
+// Clone creates a deep copy of the Term node
+func (t *Term) Clone() Node {
+
+	tc := &Term{
+		Foundry: t.Foundry,
+		Key:     t.Key,
+		Layer:   t.Layer,
+		Match:   t.Match,
+		Value:   t.Value,
+	}
+
+	if t.Rewrites != nil {
+		clonedRewrites := make([]Rewrite, len(t.Rewrites))
+		for i, rewrite := range t.Rewrites {
+			clonedRewrites[i] = *rewrite.Clone().(*Rewrite)
+		}
+		tc.Rewrites = clonedRewrites
+	}
+	return tc
+}
+
 // Pattern represents a pattern to match in the AST
 type Pattern struct {
 	Root Node
@@ -174,3 +250,61 @@
 func (c *CatchallNode) Type() NodeType {
 	return NodeType(c.NodeType)
 }
+
+// Clone creates a deep copy of the CatchallNode
+func (c *CatchallNode) Clone() Node {
+	newNode := &CatchallNode{
+		NodeType: c.NodeType,
+	}
+
+	// Handle RawContent properly - preserve nil if it's nil
+	if c.RawContent != nil {
+		newNode.RawContent = make(json.RawMessage, len(c.RawContent))
+		copy(newNode.RawContent, c.RawContent)
+	}
+
+	if c.Wrap != nil {
+		newNode.Wrap = c.Wrap.Clone()
+	}
+
+	if len(c.Operands) > 0 {
+		newNode.Operands = make([]Node, len(c.Operands))
+		for i, operand := range c.Operands {
+			newNode.Operands[i] = operand.Clone()
+		}
+	}
+
+	return newNode
+}
+
+// ApplyFoundryAndLayerOverrides recursively applies foundry and layer overrides to terms
+func ApplyFoundryAndLayerOverrides(node Node, foundry, layer string) {
+	if node == nil {
+		return
+	}
+
+	switch n := node.(type) {
+	case *Term:
+		if foundry != "" {
+			n.Foundry = foundry
+		}
+		if layer != "" {
+			n.Layer = layer
+		}
+	case *TermGroup:
+		for _, op := range n.Operands {
+			ApplyFoundryAndLayerOverrides(op, foundry, layer)
+		}
+	case *Token:
+		if n.Wrap != nil {
+			ApplyFoundryAndLayerOverrides(n.Wrap, foundry, layer)
+		}
+	case *CatchallNode:
+		if n.Wrap != nil {
+			ApplyFoundryAndLayerOverrides(n.Wrap, foundry, layer)
+		}
+		for _, op := range n.Operands {
+			ApplyFoundryAndLayerOverrides(op, foundry, layer)
+		}
+	}
+}
diff --git a/ast/ast_test.go b/ast/ast_test.go
index 0daaf7c..49fdbe3 100644
--- a/ast/ast_test.go
+++ b/ast/ast_test.go
@@ -343,49 +343,39 @@
 }
 
 func TestComplexNestedStructures(t *testing.T) {
-	// Create a complex nested structure
-	innerGroup1 := &TermGroup{
+	// Test nested tokens and term groups
+	termGroup := &TermGroup{
 		Operands: []Node{
-			&Term{Foundry: "f1", Key: "k1", Layer: "l1", Match: MatchEqual},
-			&Term{Foundry: "f2", Key: "k2", Layer: "l2", Match: MatchNotEqual},
+			&Term{
+				Foundry: "opennlp",
+				Key:     "DET",
+				Layer:   "p",
+				Match:   MatchEqual,
+			},
+			&Term{
+				Foundry: "opennlp",
+				Key:     "AdjType",
+				Layer:   "m",
+				Match:   MatchEqual,
+				Value:   "Pdt",
+			},
 		},
 		Relation: AndRelation,
 	}
 
-	innerGroup2 := &TermGroup{
-		Operands: []Node{
-			&Term{Foundry: "f3", Key: "k3", Layer: "l3", Match: MatchEqual},
-			&Term{Foundry: "f4", Key: "k4", Layer: "l4", Match: MatchEqual, Value: "test"},
-		},
-		Relation: OrRelation,
+	token := &Token{
+		Wrap: termGroup,
 	}
 
-	topGroup := &TermGroup{
-		Operands: []Node{
-			innerGroup1,
-			innerGroup2,
-			&Token{Wrap: &Term{Foundry: "f5", Key: "k5", Layer: "l5", Match: MatchEqual}},
-		},
-		Relation: AndRelation,
-	}
-
-	assert.Equal(t, TermGroupNode, topGroup.Type())
-	assert.Len(t, topGroup.Operands, 3)
-	assert.Equal(t, AndRelation, topGroup.Relation)
-
-	// Test inner groups
-	group1 := topGroup.Operands[0].(*TermGroup)
-	assert.Len(t, group1.Operands, 2)
-	assert.Equal(t, AndRelation, group1.Relation)
-
-	group2 := topGroup.Operands[1].(*TermGroup)
-	assert.Len(t, group2.Operands, 2)
-	assert.Equal(t, OrRelation, group2.Relation)
-
-	// Test token wrapping
-	token := topGroup.Operands[2].(*Token)
+	assert.Equal(t, TokenNode, token.Type())
 	assert.NotNil(t, token.Wrap)
-	assert.Equal(t, TermNode, token.Wrap.Type())
+	assert.Equal(t, TermGroupNode, token.Wrap.Type())
+
+	// Test that the nested structure is correct
+	if tg, ok := token.Wrap.(*TermGroup); ok {
+		assert.Equal(t, 2, len(tg.Operands))
+		assert.Equal(t, AndRelation, tg.Relation)
+	}
 }
 
 func TestEdgeCases(t *testing.T) {
@@ -462,3 +452,402 @@
 		t.Run(tt.name, tt.test)
 	}
 }
+
+func TestCloneMethod(t *testing.T) {
+	tests := []struct {
+		name string
+		node Node
+	}{
+		{
+			name: "Clone Term",
+			node: &Term{
+				Foundry: "opennlp",
+				Key:     "DET",
+				Layer:   "p",
+				Match:   MatchEqual,
+				Value:   "test",
+				Rewrites: []Rewrite{
+					{
+						Editor: "test",
+						Scope:  "foundry",
+					},
+				},
+			},
+		},
+		{
+			name: "Clone Token",
+			node: &Token{
+				Wrap: &Term{
+					Foundry: "opennlp",
+					Key:     "DET",
+					Layer:   "p",
+					Match:   MatchEqual,
+				},
+				Rewrites: []Rewrite{
+					{
+						Editor: "test",
+						Scope:  "layer",
+					},
+				},
+			},
+		},
+		{
+			name: "Clone TermGroup",
+			node: &TermGroup{
+				Operands: []Node{
+					&Term{
+						Foundry: "opennlp",
+						Key:     "DET",
+						Layer:   "p",
+						Match:   MatchEqual,
+					},
+					&Term{
+						Foundry: "opennlp",
+						Key:     "AdjType",
+						Layer:   "m",
+						Match:   MatchEqual,
+						Value:   "Pdt",
+					},
+				},
+				Relation: AndRelation,
+				Rewrites: []Rewrite{
+					{
+						Editor: "test",
+						Scope:  "foundry",
+					},
+				},
+			},
+		},
+		{
+			name: "Clone CatchallNode",
+			node: &CatchallNode{
+				NodeType:   "koral:unknown",
+				RawContent: []byte(`{"@type":"koral:unknown","test":"value"}`),
+				Wrap: &Term{
+					Foundry: "opennlp",
+					Key:     "DET",
+					Layer:   "p",
+					Match:   MatchEqual,
+				},
+				Operands: []Node{
+					&Term{
+						Foundry: "opennlp",
+						Key:     "AdjType",
+						Layer:   "m",
+						Match:   MatchEqual,
+						Value:   "Pdt",
+					},
+				},
+			},
+		},
+		{
+			name: "Clone Rewrite",
+			node: &Rewrite{
+				Editor:    "termMapper",
+				Operation: "injection",
+				Scope:     "foundry",
+				Src:       "test",
+				Comment:   "test comment",
+				Original:  "original_value",
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			cloned := tt.node.Clone()
+
+			// Check that the clone is not the same instance
+			assert.NotSame(t, tt.node, cloned)
+
+			// Check that the clone has the same type
+			assert.Equal(t, tt.node.Type(), cloned.Type())
+
+			// Check that nodes are equal (deep comparison)
+			assert.True(t, NodesEqual(tt.node, cloned))
+
+			// Test that modifying the clone doesn't affect the original
+			switch original := tt.node.(type) {
+			case *Term:
+				clonedTerm := cloned.(*Term)
+				clonedTerm.Foundry = "modified"
+				assert.NotEqual(t, original.Foundry, clonedTerm.Foundry)
+
+			case *Token:
+				clonedToken := cloned.(*Token)
+				if clonedToken.Wrap != nil {
+					if termWrap, ok := clonedToken.Wrap.(*Term); ok {
+						termWrap.Foundry = "modified"
+						if originalWrap, ok := original.Wrap.(*Term); ok {
+							assert.NotEqual(t, originalWrap.Foundry, termWrap.Foundry)
+						}
+					}
+				}
+
+			case *TermGroup:
+				clonedGroup := cloned.(*TermGroup)
+				clonedGroup.Relation = OrRelation
+				assert.NotEqual(t, original.Relation, clonedGroup.Relation)
+
+			case *CatchallNode:
+				clonedCatchall := cloned.(*CatchallNode)
+				clonedCatchall.NodeType = "modified"
+				assert.NotEqual(t, original.NodeType, clonedCatchall.NodeType)
+
+			case *Rewrite:
+				clonedRewrite := cloned.(*Rewrite)
+				clonedRewrite.Editor = "modified"
+				assert.NotEqual(t, original.Editor, clonedRewrite.Editor)
+			}
+		})
+	}
+}
+
+func TestCloneNilNodes(t *testing.T) {
+	// Test cloning nodes with nil fields
+	tests := []struct {
+		name string
+		node Node
+	}{
+		{
+			name: "Token with nil wrap",
+			node: &Token{Wrap: nil},
+		},
+		{
+			name: "TermGroup with empty operands",
+			node: &TermGroup{
+				Operands: []Node{},
+				Relation: AndRelation,
+			},
+		},
+		{
+			name: "CatchallNode with nil wrap and operands",
+			node: &CatchallNode{
+				NodeType:   "koral:unknown",
+				RawContent: nil,
+				Wrap:       nil,
+				Operands:   nil,
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			cloned := tt.node.Clone()
+			assert.NotSame(t, tt.node, cloned)
+			assert.Equal(t, tt.node.Type(), cloned.Type())
+			assert.True(t, NodesEqual(tt.node, cloned))
+		})
+	}
+}
+
+func TestApplyFoundryAndLayerOverrides(t *testing.T) {
+	tests := []struct {
+		name            string
+		node            Node
+		foundry         string
+		layer           string
+		expectedChanges func(t *testing.T, node Node)
+	}{
+		{
+			name: "Apply foundry and layer to Term",
+			node: &Term{
+				Foundry: "original",
+				Key:     "DET",
+				Layer:   "original",
+				Match:   MatchEqual,
+			},
+			foundry: "new_foundry",
+			layer:   "new_layer",
+			expectedChanges: func(t *testing.T, node Node) {
+				term := node.(*Term)
+				assert.Equal(t, "new_foundry", term.Foundry)
+				assert.Equal(t, "new_layer", term.Layer)
+			},
+		},
+		{
+			name: "Apply only foundry to Term",
+			node: &Term{
+				Foundry: "original",
+				Key:     "DET",
+				Layer:   "original",
+				Match:   MatchEqual,
+			},
+			foundry: "new_foundry",
+			layer:   "",
+			expectedChanges: func(t *testing.T, node Node) {
+				term := node.(*Term)
+				assert.Equal(t, "new_foundry", term.Foundry)
+				assert.Equal(t, "original", term.Layer) // Should remain unchanged
+			},
+		},
+		{
+			name: "Apply to TermGroup",
+			node: &TermGroup{
+				Operands: []Node{
+					&Term{
+						Foundry: "original1",
+						Key:     "DET",
+						Layer:   "original1",
+						Match:   MatchEqual,
+					},
+					&Term{
+						Foundry: "original2",
+						Key:     "AdjType",
+						Layer:   "original2",
+						Match:   MatchEqual,
+						Value:   "Pdt",
+					},
+				},
+				Relation: AndRelation,
+			},
+			foundry: "new_foundry",
+			layer:   "new_layer",
+			expectedChanges: func(t *testing.T, node Node) {
+				termGroup := node.(*TermGroup)
+				for _, operand := range termGroup.Operands {
+					if term, ok := operand.(*Term); ok {
+						assert.Equal(t, "new_foundry", term.Foundry)
+						assert.Equal(t, "new_layer", term.Layer)
+					}
+				}
+			},
+		},
+		{
+			name: "Apply to Token with wrapped Term",
+			node: &Token{
+				Wrap: &Term{
+					Foundry: "original",
+					Key:     "DET",
+					Layer:   "original",
+					Match:   MatchEqual,
+				},
+			},
+			foundry: "new_foundry",
+			layer:   "new_layer",
+			expectedChanges: func(t *testing.T, node Node) {
+				token := node.(*Token)
+				if term, ok := token.Wrap.(*Term); ok {
+					assert.Equal(t, "new_foundry", term.Foundry)
+					assert.Equal(t, "new_layer", term.Layer)
+				}
+			},
+		},
+		{
+			name: "Apply to CatchallNode",
+			node: &CatchallNode{
+				NodeType: "koral:unknown",
+				Wrap: &Term{
+					Foundry: "original",
+					Key:     "DET",
+					Layer:   "original",
+					Match:   MatchEqual,
+				},
+				Operands: []Node{
+					&Term{
+						Foundry: "original2",
+						Key:     "AdjType",
+						Layer:   "original2",
+						Match:   MatchEqual,
+						Value:   "Pdt",
+					},
+				},
+			},
+			foundry: "new_foundry",
+			layer:   "new_layer",
+			expectedChanges: func(t *testing.T, node Node) {
+				catchall := node.(*CatchallNode)
+				if term, ok := catchall.Wrap.(*Term); ok {
+					assert.Equal(t, "new_foundry", term.Foundry)
+					assert.Equal(t, "new_layer", term.Layer)
+				}
+				for _, operand := range catchall.Operands {
+					if term, ok := operand.(*Term); ok {
+						assert.Equal(t, "new_foundry", term.Foundry)
+						assert.Equal(t, "new_layer", term.Layer)
+					}
+				}
+			},
+		},
+		{
+			name: "Apply to nested structure",
+			node: &Token{
+				Wrap: &TermGroup{
+					Operands: []Node{
+						&Term{
+							Foundry: "original1",
+							Key:     "DET",
+							Layer:   "original1",
+							Match:   MatchEqual,
+						},
+						&Token{
+							Wrap: &Term{
+								Foundry: "original2",
+								Key:     "AdjType",
+								Layer:   "original2",
+								Match:   MatchEqual,
+								Value:   "Pdt",
+							},
+						},
+					},
+					Relation: AndRelation,
+				},
+			},
+			foundry: "new_foundry",
+			layer:   "new_layer",
+			expectedChanges: func(t *testing.T, node Node) {
+				token := node.(*Token)
+				if termGroup, ok := token.Wrap.(*TermGroup); ok {
+					for _, operand := range termGroup.Operands {
+						switch op := operand.(type) {
+						case *Term:
+							assert.Equal(t, "new_foundry", op.Foundry)
+							assert.Equal(t, "new_layer", op.Layer)
+						case *Token:
+							if innerTerm, ok := op.Wrap.(*Term); ok {
+								assert.Equal(t, "new_foundry", innerTerm.Foundry)
+								assert.Equal(t, "new_layer", innerTerm.Layer)
+							}
+						}
+					}
+				}
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// Clone the node to avoid modifying the original test data
+			cloned := tt.node.Clone()
+
+			// Apply the overrides
+			ApplyFoundryAndLayerOverrides(cloned, tt.foundry, tt.layer)
+
+			// Check the expected changes
+			tt.expectedChanges(t, cloned)
+		})
+	}
+}
+
+func TestApplyFoundryAndLayerOverridesNilNode(t *testing.T) {
+	// Test that applying overrides to a nil node doesn't panic
+	assert.NotPanics(t, func() {
+		ApplyFoundryAndLayerOverrides(nil, "foundry", "layer")
+	})
+}
+
+func TestApplyFoundryAndLayerOverridesEmptyValues(t *testing.T) {
+	// Test applying empty foundry and layer values
+	term := &Term{
+		Foundry: "original_foundry",
+		Key:     "DET",
+		Layer:   "original_layer",
+		Match:   MatchEqual,
+	}
+
+	ApplyFoundryAndLayerOverrides(term, "", "")
+
+	// Values should remain unchanged
+	assert.Equal(t, "original_foundry", term.Foundry)
+	assert.Equal(t, "original_layer", term.Layer)
+}
diff --git a/ast/compare.go b/ast/compare.go
index fec22b1..617e8f0 100644
--- a/ast/compare.go
+++ b/ast/compare.go
@@ -21,7 +21,8 @@
 				n1.Key == n2.Key &&
 				n1.Layer == n2.Layer &&
 				n1.Match == n2.Match &&
-				n1.Value == n2.Value
+				n1.Value == n2.Value &&
+				rewritesEqual(n1.Rewrites, n2.Rewrites)
 		}
 	case *TermGroup:
 		if n2, ok := b.(*TermGroup); ok {
@@ -33,22 +34,62 @@
 					return false
 				}
 			}
-			return true
+			return rewritesEqual(n1.Rewrites, n2.Rewrites)
 		}
 	case *Token:
 		if n2, ok := b.(*Token); ok {
-			return NodesEqual(n1.Wrap, n2.Wrap)
+			return NodesEqual(n1.Wrap, n2.Wrap) &&
+				rewritesEqual(n1.Rewrites, n2.Rewrites)
 		}
 	case *CatchallNode:
 		if n2, ok := b.(*CatchallNode); ok {
-			return n1.NodeType == n2.NodeType &&
-				reflect.DeepEqual(n1.RawContent, n2.RawContent) &&
-				NodesEqual(n1.Wrap, n2.Wrap)
+			if n1.NodeType != n2.NodeType ||
+				!reflect.DeepEqual(n1.RawContent, n2.RawContent) ||
+				!NodesEqual(n1.Wrap, n2.Wrap) {
+				return false
+			}
+			// Compare operands
+			if len(n1.Operands) != len(n2.Operands) {
+				return false
+			}
+			for i := range n1.Operands {
+				if !NodesEqual(n1.Operands[i], n2.Operands[i]) {
+					return false
+				}
+			}
+			return true
+		}
+	case *Rewrite:
+		if n2, ok := b.(*Rewrite); ok {
+			return n1.Editor == n2.Editor &&
+				n1.Operation == n2.Operation &&
+				n1.Scope == n2.Scope &&
+				n1.Src == n2.Src &&
+				n1.Comment == n2.Comment &&
+				reflect.DeepEqual(n1.Original, n2.Original)
 		}
 	}
 	return false
 }
 
+// rewritesEqual compares two slices of Rewrite structs for equality
+func rewritesEqual(a, b []Rewrite) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	for i := range a {
+		if a[i].Editor != b[i].Editor ||
+			a[i].Operation != b[i].Operation ||
+			a[i].Scope != b[i].Scope ||
+			a[i].Src != b[i].Src ||
+			a[i].Comment != b[i].Comment ||
+			!reflect.DeepEqual(a[i].Original, b[i].Original) {
+			return false
+		}
+	}
+	return true
+}
+
 // IsTermNode checks if a node is a Term node
 func IsTermNode(node Node) bool {
 	_, ok := node.(*Term)
diff --git a/mapper/mapper.go b/mapper/mapper.go
index 424896e..0bbe39f 100644
--- a/mapper/mapper.go
+++ b/mapper/mapper.go
@@ -148,18 +148,30 @@
 	// Store original node for rewrite if needed
 	var originalNode ast.Node
 	if opts.AddRewrites {
-		originalBytes, err := parser.SerializeToJSON(node)
-		if err != nil {
-			return nil, fmt.Errorf("failed to serialize original node for rewrite: %w", err)
-		}
-		originalNode, err = parser.ParseJSON(originalBytes)
-		if err != nil {
-			return nil, fmt.Errorf("failed to parse original node for rewrite: %w", err)
-		}
+		originalNode = node.Clone()
 	}
 
+	// Pre-check foundry/layer overrides to optimize processing
+	var patternFoundry, patternLayer, replacementFoundry, replacementLayer string
+	if opts.Direction { // true means AtoB
+		patternFoundry, patternLayer = opts.FoundryA, opts.LayerA
+		replacementFoundry, replacementLayer = opts.FoundryB, opts.LayerB
+	} else {
+		patternFoundry, patternLayer = opts.FoundryB, opts.LayerB
+		replacementFoundry, replacementLayer = opts.FoundryA, opts.LayerA
+	}
+
+	// Create a pattern cache key for memoization
+	type patternCacheKey struct {
+		ruleIndex     int
+		foundry       string
+		layer         string
+		isReplacement bool
+	}
+	patternCache := make(map[patternCacheKey]ast.Node)
+
 	// Apply each rule to the AST
-	for _, rule := range rules {
+	for i, rule := range rules {
 		// Create pattern and replacement based on direction
 		var pattern, replacement ast.Node
 		if opts.Direction { // true means AtoB
@@ -178,40 +190,55 @@
 			replacement = token.Wrap
 		}
 
-		// Create deep copies of pattern and replacement to avoid modifying the original parsed rules
-		patternBytes, err := parser.SerializeToJSON(pattern)
-		if err != nil {
-			return nil, fmt.Errorf("failed to serialize pattern for copying: %w", err)
-		}
-		patternCopy, err := parser.ParseJSON(patternBytes)
-		if err != nil {
-			return nil, fmt.Errorf("failed to parse pattern copy: %w", err)
+		// First, quickly check if the pattern could match without creating a full matcher
+		// This is a lightweight pre-check to avoid expensive operations
+		if !m.couldPatternMatch(node, pattern) {
+			continue
 		}
 
-		replacementBytes, err := parser.SerializeToJSON(replacement)
-		if err != nil {
-			return nil, fmt.Errorf("failed to serialize replacement for copying: %w", err)
-		}
-		replacementCopy, err := parser.ParseJSON(replacementBytes)
-		if err != nil {
-			return nil, fmt.Errorf("failed to parse replacement copy: %w", err)
+		// Get or create pattern with overrides
+		patternKey := patternCacheKey{ruleIndex: i, foundry: patternFoundry, layer: patternLayer, isReplacement: false}
+		processedPattern, exists := patternCache[patternKey]
+		if !exists {
+			// Clone pattern only when needed
+			processedPattern = pattern.Clone()
+			// Apply foundry and layer overrides only if they're non-empty
+			if patternFoundry != "" || patternLayer != "" {
+				ast.ApplyFoundryAndLayerOverrides(processedPattern, patternFoundry, patternLayer)
+			}
+			patternCache[patternKey] = processedPattern
 		}
 
-		// Apply foundry and layer overrides to the copies
-		if opts.Direction { // true means AtoB
-			applyFoundryAndLayerOverrides(patternCopy, opts.FoundryA, opts.LayerA)
-			applyFoundryAndLayerOverrides(replacementCopy, opts.FoundryB, opts.LayerB)
-		} else {
-			applyFoundryAndLayerOverrides(patternCopy, opts.FoundryB, opts.LayerB)
-			applyFoundryAndLayerOverrides(replacementCopy, opts.FoundryA, opts.LayerA)
+		// Create a temporary matcher to check for actual matches
+		tempMatcher, err := matcher.NewMatcher(ast.Pattern{Root: processedPattern}, ast.Replacement{Root: &ast.Term{}})
+		if err != nil {
+			return nil, fmt.Errorf("failed to create temporary matcher: %w", err)
 		}
 
-		// Create matcher and apply replacement using the copies
-		m, err := matcher.NewMatcher(ast.Pattern{Root: patternCopy}, ast.Replacement{Root: replacementCopy})
+		// Only proceed if there's an actual match
+		if !tempMatcher.Match(node) {
+			continue
+		}
+
+		// Get or create replacement with overrides (lazy evaluation)
+		replacementKey := patternCacheKey{ruleIndex: i, foundry: replacementFoundry, layer: replacementLayer, isReplacement: true}
+		processedReplacement, exists := patternCache[replacementKey]
+		if !exists {
+			// Clone replacement only when we have a match
+			processedReplacement = replacement.Clone()
+			// Apply foundry and layer overrides only if they're non-empty
+			if replacementFoundry != "" || replacementLayer != "" {
+				ast.ApplyFoundryAndLayerOverrides(processedReplacement, replacementFoundry, replacementLayer)
+			}
+			patternCache[replacementKey] = processedReplacement
+		}
+
+		// Create the actual matcher and apply replacement
+		actualMatcher, err := matcher.NewMatcher(ast.Pattern{Root: processedPattern}, ast.Replacement{Root: processedReplacement})
 		if err != nil {
 			return nil, fmt.Errorf("failed to create matcher: %w", err)
 		}
-		node = m.Replace(node)
+		node = actualMatcher.Replace(node)
 	}
 
 	// Wrap the result in a token if the input was a token
@@ -374,34 +401,87 @@
 	return true
 }
 
-// applyFoundryAndLayerOverrides recursively applies foundry and layer overrides to terms
-func applyFoundryAndLayerOverrides(node ast.Node, foundry, layer string) {
+// couldPatternMatch performs a lightweight check to see if a pattern could potentially match a node
+// This is an optimization to avoid expensive operations when there's clearly no match possible
+func (m *Mapper) couldPatternMatch(node, pattern ast.Node) bool {
+	if pattern == nil {
+		return true
+	}
 	if node == nil {
-		return
+		return false
+	}
+
+	// Handle Token wrappers
+	if token, ok := pattern.(*ast.Token); ok {
+		pattern = token.Wrap
+	}
+	if token, ok := node.(*ast.Token); ok {
+		node = token.Wrap
+	}
+
+	// For simple terms, check basic compatibility
+	if patternTerm, ok := pattern.(*ast.Term); ok {
+		// Check if there's any term in the node structure that could match
+		return m.hasMatchingTerm(node, patternTerm)
+	}
+
+	// For TermGroups, we need to check all possible matches
+	if patternGroup, ok := pattern.(*ast.TermGroup); ok {
+		if patternGroup.Relation == ast.OrRelation {
+			// For OR relations, any operand could match
+			for _, op := range patternGroup.Operands {
+				if m.couldPatternMatch(node, op) {
+					return true
+				}
+			}
+			return false
+		} else {
+			// For AND relations, all operands must have potential matches
+			for _, op := range patternGroup.Operands {
+				if !m.couldPatternMatch(node, op) {
+					return false
+				}
+			}
+			return true
+		}
+	}
+
+	// For other cases, assume they could match (conservative approach)
+	return true
+}
+
+// hasMatchingTerm checks if there's any term in the node structure that could match the pattern term
+func (m *Mapper) hasMatchingTerm(node ast.Node, patternTerm *ast.Term) bool {
+	if node == nil {
+		return false
 	}
 
 	switch n := node.(type) {
 	case *ast.Term:
-		if foundry != "" {
-			n.Foundry = foundry
-		}
-		if layer != "" {
-			n.Layer = layer
-		}
+		// Check if this term could match the pattern
+		// We only check key as that's the most distinctive attribute
+		return n.Key == patternTerm.Key
 	case *ast.TermGroup:
+		// Check all operands
 		for _, op := range n.Operands {
-			applyFoundryAndLayerOverrides(op, foundry, layer)
+			if m.hasMatchingTerm(op, patternTerm) {
+				return true
+			}
 		}
+		return false
 	case *ast.Token:
-		if n.Wrap != nil {
-			applyFoundryAndLayerOverrides(n.Wrap, foundry, layer)
-		}
+		return m.hasMatchingTerm(n.Wrap, patternTerm)
 	case *ast.CatchallNode:
-		if n.Wrap != nil {
-			applyFoundryAndLayerOverrides(n.Wrap, foundry, layer)
+		if n.Wrap != nil && m.hasMatchingTerm(n.Wrap, patternTerm) {
+			return true
 		}
 		for _, op := range n.Operands {
-			applyFoundryAndLayerOverrides(op, foundry, layer)
+			if m.hasMatchingTerm(op, patternTerm) {
+				return true
+			}
 		}
+		return false
+	default:
+		return false
 	}
 }