Fix layer precedence and document precedences

Change-Id: I7fdc9f9122d7e723f98674a9f9e3010c38118ba9
diff --git a/mapper/response.go b/mapper/response.go
index 6ed425c..e89d12a 100644
--- a/mapper/response.go
+++ b/mapper/response.go
@@ -6,6 +6,7 @@
 
 	"github.com/KorAP/KoralPipe-TermMapper/ast"
 	"github.com/KorAP/KoralPipe-TermMapper/matcher"
+	"github.com/KorAP/KoralPipe-TermMapper/parser"
 	"github.com/rs/zerolog/log"
 )
 
@@ -37,7 +38,7 @@
 
 	// Process the snippet with each rule
 	processedSnippet := snippet
-	for _, rule := range rules {
+	for ruleIndex, rule := range rules {
 		// Create pattern and replacement based on direction
 		var pattern, replacement ast.Node
 		if opts.Direction { // true means AtoB
@@ -56,34 +57,34 @@
 			replacement = token.Wrap
 		}
 
-		// Apply foundry and layer overrides to pattern and replacement
+		// Apply foundry and layer overrides with proper precedence
+		mappingList := m.mappingLists[mappingID]
+
+		// Determine foundry and layer values based on direction
 		var patternFoundry, patternLayer, replacementFoundry, replacementLayer string
-		if opts.Direction { // true means AtoB
+		if opts.Direction { // AtoB
 			patternFoundry, patternLayer = opts.FoundryA, opts.LayerA
 			replacementFoundry, replacementLayer = opts.FoundryB, opts.LayerB
-		} else {
+			// Apply mapping list defaults if not specified
+			if replacementFoundry == "" {
+				replacementFoundry = mappingList.FoundryB
+			}
+			if replacementLayer == "" {
+				replacementLayer = mappingList.LayerB
+			}
+		} else { // BtoA
 			patternFoundry, patternLayer = opts.FoundryB, opts.LayerB
 			replacementFoundry, replacementLayer = opts.FoundryA, opts.LayerA
-		}
-
-		// If foundry/layer are empty in options, get them from the mapping list
-		mappingList := m.mappingLists[mappingID]
-		if replacementFoundry == "" {
-			if opts.Direction { // AtoB
-				replacementFoundry = mappingList.FoundryB
-			} else {
+			// Apply mapping list defaults if not specified
+			if replacementFoundry == "" {
 				replacementFoundry = mappingList.FoundryA
 			}
-		}
-		if replacementLayer == "" {
-			if opts.Direction { // AtoB
-				replacementLayer = mappingList.LayerB
-			} else {
+			if replacementLayer == "" {
 				replacementLayer = mappingList.LayerA
 			}
 		}
 
-		// Clone pattern and apply overrides
+		// Clone pattern and apply foundry and layer overrides
 		processedPattern := pattern.Clone()
 		if patternFoundry != "" || patternLayer != "" {
 			ast.ApplyFoundryAndLayerOverrides(processedPattern, patternFoundry, patternLayer)
@@ -108,9 +109,10 @@
 			continue // No matches, try next rule
 		}
 
-		// Apply RestrictToObligatory to the replacement to get the annotations to add
-		// Note: Only pass foundry override, not layer, since replacement terms have correct layers
-		restrictedReplacement := ast.RestrictToObligatory(replacement, replacementFoundry, "")
+		// Apply RestrictToObligatory with layer precedence logic
+		restrictedReplacement := m.applyReplacementWithLayerPrecedence(
+			replacement, replacementFoundry, replacementLayer,
+			mappingID, ruleIndex, bool(opts.Direction))
 		if restrictedReplacement == nil {
 			continue // Nothing obligatory to add
 		}
@@ -239,3 +241,124 @@
 
 	return result, nil
 }
+
+// applyReplacementWithLayerPrecedence applies RestrictToObligatory with proper layer precedence
+func (m *Mapper) applyReplacementWithLayerPrecedence(
+	replacement ast.Node, foundry, layerOverride string,
+	mappingID string, ruleIndex int, direction bool) ast.Node {
+
+	// First, apply RestrictToObligatory without layer override to preserve explicit layers
+	restricted := ast.RestrictToObligatory(replacement, foundry, "")
+	if restricted == nil {
+		return nil
+	}
+
+	// If no layer override is specified, we're done
+	if layerOverride == "" {
+		return restricted
+	}
+
+	// Apply layer override only to terms that didn't have explicit layers in the original rule
+	mappingList := m.mappingLists[mappingID]
+	if ruleIndex < len(mappingList.Mappings) {
+		originalRule := string(mappingList.Mappings[ruleIndex])
+		m.applySelectiveLayerOverrides(restricted, layerOverride, originalRule, direction)
+	}
+
+	return restricted
+}
+
+// applySelectiveLayerOverrides applies layer overrides only to terms without explicit layers
+func (m *Mapper) applySelectiveLayerOverrides(node ast.Node, layerOverride, originalRule string, direction bool) {
+	if node == nil {
+		return
+	}
+
+	// Parse the original rule without defaults to detect explicit layers
+	explicitTerms := m.getExplicitTerms(originalRule, direction)
+
+	// Apply overrides only to terms that weren't explicit in the original rule
+	termIndex := 0
+	m.applyLayerOverrideToImplicitTerms(node, layerOverride, explicitTerms, &termIndex)
+}
+
+// getExplicitTerms parses the original rule without defaults to identify terms with explicit layers
+func (m *Mapper) getExplicitTerms(originalRule string, direction bool) map[int]bool {
+	explicitTerms := make(map[int]bool)
+
+	// Parse without defaults to see what was explicitly specified
+	parser, err := parser.NewGrammarParser("", "")
+	if err != nil {
+		return explicitTerms
+	}
+
+	result, err := parser.ParseMapping(originalRule)
+	if err != nil {
+		return explicitTerms
+	}
+
+	// Get the replacement side based on direction
+	var replacement ast.Node
+	if direction { // AtoB
+		replacement = result.Lower.Wrap
+	} else { // BtoA
+		replacement = result.Upper.Wrap
+	}
+
+	// Extract terms and check which ones have explicit layers
+	termIndex := 0
+	m.markExplicitTerms(replacement, explicitTerms, &termIndex)
+	return explicitTerms
+}
+
+// markExplicitTerms recursively marks terms that have explicit layers
+func (m *Mapper) markExplicitTerms(node ast.Node, explicitTerms map[int]bool, termIndex *int) {
+	if node == nil {
+		return
+	}
+
+	switch n := node.(type) {
+	case *ast.Term:
+		// A term has an explicit layer if it was specified in the original rule
+		if n.Layer != "" {
+			explicitTerms[*termIndex] = true
+		}
+		*termIndex++
+
+	case *ast.TermGroup:
+		for _, operand := range n.Operands {
+			m.markExplicitTerms(operand, explicitTerms, termIndex)
+		}
+
+	case *ast.Token:
+		if n.Wrap != nil {
+			m.markExplicitTerms(n.Wrap, explicitTerms, termIndex)
+		}
+	}
+}
+
+// applyLayerOverrideToImplicitTerms applies layer override only to terms not marked as explicit
+func (m *Mapper) applyLayerOverrideToImplicitTerms(node ast.Node, layerOverride string, explicitTerms map[int]bool, termIndex *int) {
+	if node == nil {
+		return
+	}
+
+	switch n := node.(type) {
+	case *ast.Term:
+		// Apply override only if this term wasn't explicit in the original rule
+		if !explicitTerms[*termIndex] && n.Layer != "" {
+			n.Layer = layerOverride
+		}
+		*termIndex++
+
+	case *ast.TermGroup:
+		for _, operand := range n.Operands {
+			m.applyLayerOverrideToImplicitTerms(operand, layerOverride, explicitTerms, termIndex)
+		}
+
+	case *ast.Token:
+		if n.Wrap != nil {
+			m.applyLayerOverrideToImplicitTerms(n.Wrap, layerOverride, explicitTerms, termIndex)
+		}
+	}
+}