// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package confmap // import "go.opentelemetry.io/collector/confmap"

import (
	"reflect"

	"github.com/gobwas/glob"
	"github.com/knadh/koanf/maps"
)

func mergeAppend(src, dest map[string]any) error {
	// mergeAppend recursively merges the src map into the dest map (left to right),
	// modifying and expanding the dest map in the process.
	// This function does not overwrite component lists, and ensures that the
	// final value is a name-aware copy of lists from src and dest.

	// Compile the globs once
	patterns := []string{
		"service::extensions",
		"service::**::receivers",
		"service::**::exporters",
	}
	var globs []glob.Glob
	for _, p := range patterns {
		if g, err := glob.Compile(p); err == nil {
			globs = append(globs, g)
		}
	}

	// Flatten both source and destination maps
	srcFlat, _ := maps.Flatten(src, []string{}, KeyDelimiter)
	destFlat, _ := maps.Flatten(dest, []string{}, KeyDelimiter)

	for sKey, sVal := range srcFlat {
		if !isMatch(sKey, globs) {
			continue
		}

		dVal, dOk := destFlat[sKey]
		if !dOk {
			continue // Let maps.Merge handle missing keys
		}

		srcVal := reflect.ValueOf(sVal)
		destVal := reflect.ValueOf(dVal)

		// Only merge if the value is a slice or array; let maps.Merge handle other types
		if srcVal.Kind() == reflect.Slice || srcVal.Kind() == reflect.Array {
			srcFlat[sKey] = mergeSlice(srcVal, destVal)
		}
	}

	// Unflatten and merge
	mergedSrc := maps.Unflatten(srcFlat, KeyDelimiter)
	maps.Merge(mergedSrc, dest)

	return nil
}

// isMatch checks if a key matches any glob in the list
func isMatch(key string, globs []glob.Glob) bool {
	for _, g := range globs {
		if g.Match(key) {
			return true
		}
	}
	return false
}

func mergeSlice(src, dest reflect.Value) any {
	slice := reflect.MakeSlice(src.Type(), 0, src.Cap()+dest.Cap())
	for i := 0; i < dest.Len(); i++ {
		slice = reflect.Append(slice, dest.Index(i))
	}

	for i := 0; i < src.Len(); i++ {
		if isPresent(slice, src.Index(i)) {
			continue
		}
		slice = reflect.Append(slice, src.Index(i))
	}
	return slice.Interface()
}

func isPresent(slice reflect.Value, val reflect.Value) bool {
	for i := 0; i < slice.Len(); i++ {
		if reflect.DeepEqual(val.Interface(), slice.Index(i).Interface()) {
			return true
		}
	}
	return false
}
