package source

import (
	"fmt"
	"math"
	"reflect"
	"sort"
)

// CloneMetadata returns an independently owned copy of JSON-shaped metadata.
// It preserves concrete map, slice, and array types while rejecting values that
// cannot be safely represented as JSON-shaped metadata.
func CloneMetadata(metadata map[string]any) (map[string]any, error) {
	if len(metadata) == 0 {
		return nil, nil
	}
	cloned, err := cloneMetadataValue(reflect.ValueOf(metadata), "metadata", make(map[metadataVisit]struct{}))
	if err != nil {
		return nil, err
	}
	return cloned.Interface().(map[string]any), nil
}

type metadataVisit struct {
	typ reflect.Type
	ptr uintptr
}

func cloneMetadataValue(value reflect.Value, location string, active map[metadataVisit]struct{}) (reflect.Value, error) {
	if !value.IsValid() {
		return value, nil
	}

	switch value.Kind() {
	case reflect.Interface:
		if value.IsNil() {
			return reflect.Zero(value.Type()), nil
		}
		cloned, err := cloneMetadataValue(value.Elem(), location, active)
		if err != nil {
			return reflect.Value{}, err
		}
		result := reflect.New(value.Type()).Elem()
		result.Set(cloned)
		return result, nil
	case reflect.Map:
		if value.IsNil() {
			return reflect.Zero(value.Type()), nil
		}
		if value.Type().Key().Kind() != reflect.String {
			return reflect.Value{}, fmt.Errorf("%s has unsupported map key type %s", location, value.Type().Key())
		}
		leave, err := enterMetadataValue(value, active, location)
		if err != nil {
			return reflect.Value{}, err
		}
		defer leave()

		keys := value.MapKeys()
		sort.Slice(keys, func(i, j int) bool { return keys[i].String() < keys[j].String() })
		result := reflect.MakeMapWithSize(value.Type(), value.Len())
		for _, key := range keys {
			cloned, err := cloneMetadataValue(value.MapIndex(key), location+"."+key.String(), active)
			if err != nil {
				return reflect.Value{}, err
			}
			result.SetMapIndex(key, cloned)
		}
		return result, nil
	case reflect.Slice:
		if value.IsNil() {
			return reflect.Zero(value.Type()), nil
		}
		leave, err := enterMetadataValue(value, active, location)
		if err != nil {
			return reflect.Value{}, err
		}
		defer leave()

		result := reflect.MakeSlice(value.Type(), value.Len(), value.Len())
		for i := 0; i < value.Len(); i++ {
			cloned, err := cloneMetadataValue(value.Index(i), fmt.Sprintf("%s[%d]", location, i), active)
			if err != nil {
				return reflect.Value{}, err
			}
			result.Index(i).Set(cloned)
		}
		return result, nil
	case reflect.Array:
		result := reflect.New(value.Type()).Elem()
		for i := 0; i < value.Len(); i++ {
			cloned, err := cloneMetadataValue(value.Index(i), fmt.Sprintf("%s[%d]", location, i), active)
			if err != nil {
				return reflect.Value{}, err
			}
			result.Index(i).Set(cloned)
		}
		return result, nil
	case reflect.Float32, reflect.Float64:
		if math.IsNaN(value.Float()) || math.IsInf(value.Float(), 0) {
			return reflect.Value{}, fmt.Errorf("%s has a non-finite number", location)
		}
		return value, nil
	case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.String:
		return value, nil
	default:
		return reflect.Value{}, fmt.Errorf("%s has unsupported type %s", location, value.Type())
	}
}

func enterMetadataValue(value reflect.Value, active map[metadataVisit]struct{}, location string) (func(), error) {
	visit := metadataVisit{typ: value.Type(), ptr: value.Pointer()}
	if _, exists := active[visit]; exists {
		return nil, fmt.Errorf("%s contains a cycle", location)
	}
	active[visit] = struct{}{}
	return func() { delete(active, visit) }, nil
}
