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

package cfggen // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/cfggen"

import (
	"cmp"
	"errors"
	"fmt"
	"maps"
	"slices"
	"strings"
	"time"

	"go.opentelemetry.io/collector/cmd/mdatagen/internal/helpers"
)

// NewCfgFns returns template functions for config generation with rootPackage and componentPackage
// baked into closures. This way the template itself never needs to pass these context values around.
func NewCfgFns(rootPackage, componentPackage string) map[string]any {
	return map[string]any{
		"extractImports": func(md *ConfigsMetadata) []string {
			if md == nil {
				return nil
			}
			imports, err := ExtractImports(md, rootPackage, componentPackage)
			if err != nil {
				panic(err)
			}
			return imports
		},
		"extractDefs": func(md *ConfigsMetadata) map[string]*ConfigMetadata {
			if md == nil {
				return nil
			}
			return ExtractDefs(md)
		},
		"extractValidators": func(cfg *ConfigMetadata) []Validator {
			if cfg == nil {
				return nil
			}
			return ExtractValidators(cfg)
		},
		"mapGoType": func(cfg *ConfigMetadata, propName string) string {
			if cfg == nil {
				return "any"
			}
			goType, err := MapGoType(cfg, propName, rootPackage, componentPackage)
			if err != nil {
				panic(err)
			}
			return goType
		},
		"isPrimitiveSchema": IsPrimitiveSchema,
		"primitiveGoType": func(cfg *ConfigMetadata) string {
			goType, err := PrimitiveGoType(cfg, rootPackage, componentPackage)
			if err != nil {
				panic(err)
			}
			return goType
		},
		"publicType": func(ref string) string {
			typeName, err := FormatTypeName(ref, rootPackage, componentPackage)
			if err != nil {
				panic(err)
			}
			return typeName
		},
		"camelVar": CamelVar,
		"formatDefaultValue": func(md *ConfigMetadata, name string, defaultValue any) string {
			return FormatDefaultValue(md, name, defaultValue, rootPackage, componentPackage)
		},
		"formatBaseValue": func(md *ConfigMetadata, name string, defaultValue any) string {
			return FormatBaseValue(md, name, defaultValue, rootPackage, componentPackage)
		},
		"wrapDefaultValue": WrapDefaultValue,
		"mapCustomDefaults": func(schema *ConfigMetadata, defaultValue any) []string {
			return MapCustomDefaults(schema, defaultValue, rootPackage, componentPackage)
		},
		"hasDefaultValue":  hasDefaultValue,
		"formatEnumSlice":  formatEnumSlice,
		"formatEnumValues": formatEnumValues,
		"invalidTestValue": invalidTestValue,
		"isExternalRef": func(ref string) bool {
			if ref == "" {
				return false
			}
			tr, err := ResolveGoTypeRef(ref, rootPackage, componentPackage)
			return err == nil && tr.ImportPath != ""
		},
		"externalDefaultCall": func(ref string) string {
			call, err := ExternalDefaultCall(ref, rootPackage, componentPackage)
			if err != nil {
				panic(err)
			}
			return call
		},
		"embeddedProps": func(props map[string]*ConfigMetadata) map[string]*ConfigMetadata {
			return filterProps(props, func(prop *ConfigMetadata) bool {
				return prop.Embed
			})
		},
		"namedProps": func(props map[string]*ConfigMetadata) map[string]*ConfigMetadata {
			return filterProps(props, func(prop *ConfigMetadata) bool {
				return !prop.Embed
			})
		},
		"isRequired": isRequired,
		"shouldOmitEmpty": func(md *ConfigMetadata, propName string, prop *ConfigMetadata) bool {
			return !isRequired(md, propName) && !hasNonZeroDefault(prop)
		},
	}
}

func isRequired(md *ConfigMetadata, propName string) bool {
	return slices.Contains(md.Required, propName)
}

// ExternalDefaultCall returns the Go expression that delegates to the upstream package's
// NewDefault constructor for an external type reference (e.g. "controller.NewDefaultControllerConfig()").
func ExternalDefaultCall(ref, rootPackage, componentPackage string) (string, error) {
	tr, err := ResolveGoTypeRef(ref, rootPackage, componentPackage)
	if err != nil {
		return "", err
	}
	fnCall := fmt.Sprintf("NewDefault%s()", tr.TypeName)
	if tr.Qualifier() != "" {
		fnCall = tr.Qualifier() + "." + fnCall
	}
	return fnCall, nil
}

// WithCfgFns merges config generation template functions into the given function map.
// The rootPackage and componentPackage are captured in closures so the template doesn't need to thread them through.
func WithCfgFns(fns map[string]any, rootPackage, componentPackage string) map[string]any {
	cfgFns := NewCfgFns(rootPackage, componentPackage)
	maps.Copy(fns, cfgFns)
	maps.Copy(fns, NewCfgDocFns())
	return fns
}

var primitiveSchemaGoTypes = map[SchemaType]string{
	StringType:  "string",
	BoolType:    "bool",
	ByteType:    "byte",
	RuneType:    "rune",
	UintType:    "uint",
	IntType:     "int",
	Int8Type:    "int8",
	Uint8Type:   "uint8",
	Int16Type:   "int16",
	Uint16Type:  "uint16",
	Int32Type:   "int32",
	Uint32Type:  "uint32",
	Int64Type:   "int64",
	Uint64Type:  "uint64",
	Float32Type: "float32",
	Float64Type: "float64",
	AnyType:     "any",
}

func IsPrimitiveSchema(md *ConfigMetadata) bool {
	if md == nil {
		return false
	}
	_, ok := primitiveSchemaGoTypes[md.Type]
	return ok
}

func PrimitiveGoType(md *ConfigMetadata, rootPackage, componentPackage string) (string, error) {
	if md == nil {
		return "", errors.New("nil ConfigMetadata")
	}
	goType, ok := primitiveSchemaGoTypes[md.Type]
	if !ok {
		return "", fmt.Errorf("unsupported primitive type: %q", md.Type)
	}
	if md.GoType != "" {
		typeName, err := FormatTypeName(md.GoType, rootPackage, componentPackage)
		if err != nil {
			return "", fmt.Errorf("failed to format custom type %q: %w", md.GoType, err)
		}
		return typeName, nil
	}
	return goType, nil
}

// MapGoType maps a ConfigMetadata to its corresponding Go type as a string.
func MapGoType(md *ConfigMetadata, propName, rootPackage, componentPackage string) (string, error) {
	if md == nil {
		return "", errors.New("nil ConfigMetadata")
	}
	goType, err := resolveGoType(md, propName, rootPackage, componentPackage)
	if err != nil {
		return "", fmt.Errorf("failed to resolve Go type for property %q: %w", propName, err)
	}
	if md.IsPointer {
		goType = "*" + goType
	}
	if md.IsOptional {
		return "configoptional.Optional[" + goType + "]", nil
	}
	return goType, nil
}

func resolveGoType(md *ConfigMetadata, propName, rootPackage, componentPackage string) (string, error) {
	if md.Ref != "" {
		typeName, err := FormatTypeName(md.Ref, rootPackage, componentPackage)
		if err != nil {
			return "", fmt.Errorf("failed to format reference type %q: %w", md.Ref, err)
		}
		return typeName, nil
	}
	if md.GoType != "" {
		typeName, err := FormatTypeName(md.GoType, rootPackage, componentPackage)
		if err != nil {
			return "", fmt.Errorf("failed to format custom type %q: %w", md.GoType, err)
		}
		return typeName, nil
	}

	switch md.Type {
	case StringType:
		if strings.HasPrefix(md.GoType, "time.") {
			return md.GoType, nil
		}
		return "string", nil
	case SliceType:
		if md.Values == nil {
			return "[]any", nil
		}
		itemType, err := MapGoType(md.Values, propName+"_item", rootPackage, componentPackage)
		if err != nil {
			return "", fmt.Errorf("failed to resolve slice value type: %w", err)
		}
		return "[]" + itemType, nil
	case MapType:
		if md.Values != nil {
			valueType, err := MapGoType(md.Values, propName, rootPackage, componentPackage)
			if err != nil {
				return "", fmt.Errorf("failed to resolve map value type: %w", err)
			}
			return "map[string]" + valueType, nil
		}
		return "map[string]any", nil
	case ObjectType:
		if md.Properties != nil {
			formatted, err := helpers.FormatIdentifier(propName, true)
			if err != nil {
				return "", fmt.Errorf("failed to format embedded object type name %q: %w", propName, err)
			}
			return formatted, nil
		}
		return "", fmt.Errorf("empty object properties in %q field", propName)
	case "":
		return "any", nil
	default:
		return string(md.Type), nil
	}
}

func ExtractImports(md *ConfigsMetadata, rootPackage, componentPackage string) ([]string, error) {
	imports := make(map[string]bool)
	if md.Config != nil {
		collected, err := ExtractImportsFromConfig(md.Config, rootPackage, componentPackage)
		if err != nil {
			return nil, err
		}
		for _, imp := range collected {
			imports[imp] = true
		}
	}
	if md.ExportedConfigs != nil {
		for _, expCfg := range md.ExportedConfigs {
			if expCfg.InternalOnly {
				continue
			}
			collected, err := ExtractImportsFromConfig(expCfg, rootPackage, componentPackage)
			if err != nil {
				return nil, err
			}
			for _, imp := range collected {
				imports[imp] = true
			}
		}
	}
	return slices.Collect(maps.Keys(imports)), nil
}

// ExtractImportsFromConfig recursively scans the ConfigMetadata and collects all unique import paths needed for the generated Go code.
func ExtractImportsFromConfig(md *ConfigMetadata, rootPackage, componentPackage string) ([]string, error) {
	if md == nil {
		return nil, nil
	}

	imports := make(map[string]bool)
	if err := collectImports(md, imports, rootPackage, componentPackage); err != nil {
		return nil, err
	}

	return slices.Collect(maps.Keys(imports)), nil
}

func collectImports(md *ConfigMetadata, imports map[string]bool, rootPackage, componentPackage string) error {
	if md == nil {
		return nil
	}

	if md.GoType != "" {
		ref, err := ResolveGoTypeRef(md.GoType, rootPackage, componentPackage)
		if err != nil {
			return fmt.Errorf("failed to resolve import for custom type %q: %w", md.GoType, err)
		}
		if ref.ImportPath != "" {
			imports[ref.ImportPath] = true
		}
	}

	if md.Type == StringType && strings.HasPrefix(md.GoType, "time.") {
		imports["time"] = true
	}

	if md.IsOptional {
		imports["go.opentelemetry.io/collector/config/configoptional"] = true
	}

	if md.Ref != "" {
		ref, err := ResolveGoTypeRef(md.Ref, rootPackage, componentPackage)
		if err != nil {
			return fmt.Errorf("failed to resolve import for reference %q: %w", md.Ref, err)
		}
		if ref.ImportPath != "" {
			imports[ref.ImportPath] = true
		}
		refDesc := NewRef(md.Ref)
		if !refDesc.IsInternal() {
			if err := collectCustomDefaultImports(md, md.Default, imports, rootPackage, componentPackage); err != nil {
				return err
			}
			return nil
		}
	}

	if hasValidators(md) {
		imports["errors"] = true
	}

	if md.Pattern != "" && !strings.HasPrefix(md.GoType, "time.") {
		imports["regexp"] = true
	}

	if len(md.Enum) > 0 {
		imports["slices"] = true
	}

	for _, prop := range md.Properties {
		if err := collectImports(prop, imports, rootPackage, componentPackage); err != nil {
			return err
		}
	}

	if md.Values != nil {
		if err := collectImports(md.Values, imports, rootPackage, componentPackage); err != nil {
			return err
		}
	}

	return nil
}

func collectCustomDefaultImports(md *ConfigMetadata, defaultValue any, imports map[string]bool, rootPackage, componentPackage string) error {
	if md == nil || md.GoStruct.IgnoreDefault {
		return nil
	}

	switch typedValue := defaultValue.(type) {
	case map[string]any:
		if md.Values != nil {
			return nil
		}
		for key, value := range typedValue {
			prop := md.Properties[key]
			if prop == nil {
				continue
			}
			if err := collectImports(prop, imports, rootPackage, componentPackage); err != nil {
				return err
			}
			if err := collectCustomDefaultImports(prop, value, imports, rootPackage, componentPackage); err != nil {
				return err
			}
		}
	case []any:
		if md.Values == nil || md.Values.Type != ObjectType {
			return nil
		}
		for _, item := range typedValue {
			if err := collectCustomDefaultImports(md.Values, item, imports, rootPackage, componentPackage); err != nil {
				return err
			}
		}
	}

	return nil
}

func hasValidators(md *ConfigMetadata) bool {
	return md.GoStruct.CustomValidator != nil || // custom validation
		len(md.Required) > 0 || // required validation
		md.MinLength != nil || md.MaxLength != nil || md.Pattern != "" || // string validation
		md.Minimum != nil || md.Maximum != nil || md.ExclusiveMinimum != nil || md.ExclusiveMaximum != nil // numeric validation
}

// FormatTypeName resolves a reference string to a Go type expression using GoTypeRef.
func FormatTypeName(ref, rootPackage, componentPackage string) (string, error) {
	tr, err := ResolveGoTypeRef(ref, rootPackage, componentPackage)
	if err != nil {
		return "", err
	}
	return tr.String(), nil
}

func ExtractDefs(md *ConfigsMetadata) map[string]*ConfigMetadata {
	defs := make(map[string]*ConfigMetadata)
	if md.Config != nil {
		maps.Copy(defs, ExtractDefsFromConfig(md.Config))
	}
	for name, expCfg := range md.ExportedConfigs {
		if !expCfg.InternalOnly {
			defs[name] = expCfg
		}
	}
	return defs
}

// ExtractDefsFromConfig recursively collects all definitions from the ConfigMetadata, including nested ones,
// and returns a flat map of definition names to their corresponding ConfigMetadata.
func ExtractDefsFromConfig(md *ConfigMetadata) map[string]*ConfigMetadata {
	defs := make(map[string]*ConfigMetadata)
	collectDefs(md, defs)
	return defs
}

func collectDefs(md *ConfigMetadata, defs map[string]*ConfigMetadata) {
	if md == nil {
		return
	}
	if md.Ref != "" {
		refDesc := NewRef(md.Ref)
		if !refDesc.IsInternal() {
			return
		}
		if _, exists := defs[md.Ref]; !exists {
			defs[md.Ref] = md
		}
	}

	for _, propName := range slices.Sorted(maps.Keys(md.Properties)) {
		collectDefsForSchema(propName, md.Properties[propName], defs)
	}
}

func collectDefsForSchema(propName string, md *ConfigMetadata, defs map[string]*ConfigMetadata) {
	if md == nil || md.GoType != "" {
		return
	}

	if md.Ref != "" {
		refDesc := NewRef(md.Ref)
		if refDesc.IsInternal() {
			// Only register the ref-site node if no authoritative definition was already collected from md.Defs.
			if _, exists := defs[md.Ref]; !exists {
				defs[md.Ref] = md
			}
			collectDefs(md, defs)
		}
		return
	}

	switch md.Type {
	case ObjectType:
		if len(md.Properties) > 0 {
			defs[propName] = md
			collectDefs(md, defs)
		}
	case SliceType, MapType:
		if md.Values != nil {
			collectDefsForSchema(propName+"_item", md.Values, defs)
		}
	}
}

// ExtractValidators recursively scans the ConfigMetadata and collects validators for required fields and nested schemas.
func ExtractValidators(md *ConfigMetadata) []Validator {
	validators := make([]Validator, 0)

	if md == nil {
		return validators
	}
	collectValidators(md, &validators)
	slices.SortFunc(validators, func(a, b Validator) int {
		return cmp.Compare(a.FieldName, b.FieldName)
	})

	return validators
}

type ValidationRules struct {
	MaxLength        *int
	MinLength        *int
	Pattern          *string
	Required         bool
	Minimum          *float64
	Maximum          *float64
	ExclusiveMinimum *float64
	ExclusiveMaximum *float64
	Enum             []any
}

func (vr *ValidationRules) HasValueRule() bool {
	return vr.MaxLength != nil || vr.MinLength != nil || vr.Pattern != nil ||
		vr.Minimum != nil || vr.Maximum != nil || vr.ExclusiveMinimum != nil || vr.ExclusiveMaximum != nil ||
		len(vr.Enum) > 0
}

func (vr *ValidationRules) Enabled() bool {
	return vr.HasValueRule() || vr.Required
}

type Validator struct {
	FieldName       string
	FieldType       string
	IsPointer       bool
	IsOptional      bool
	Rules           ValidationRules
	CustomValidator string
}

func collectValidators(md *ConfigMetadata, validators *[]Validator) {
	for _, propName := range slices.Sorted(maps.Keys(md.Properties)) {
		prop := md.Properties[propName]
		if prop.Embed {
			continue
		}
		rules := ValidationRules{
			MaxLength:        prop.MaxLength,
			MinLength:        prop.MinLength,
			Minimum:          prop.Minimum,
			Maximum:          prop.Maximum,
			ExclusiveMinimum: prop.ExclusiveMinimum,
			ExclusiveMaximum: prop.ExclusiveMaximum,
			Enum:             prop.Enum,
		}

		rules.Required = slices.Contains(md.Required, propName)
		if prop.Pattern != "" && !strings.HasPrefix(prop.GoType, "time.") {
			rules.Pattern = &prop.Pattern
		}

		fieldName := prop.GoStruct.FieldName
		if fieldName == "" {
			fieldName = propName
		}
		if rules.Enabled() {
			*validators = append(*validators, Validator{
				FieldName:  fieldName,
				FieldType:  resolveType(prop),
				IsPointer:  prop.IsPointer,
				IsOptional: prop.IsOptional,
				Rules:      rules,
			})
		}

		if prop.GoStruct.CustomValidator != nil {
			*validators = append(*validators, Validator{
				FieldName:       propName,
				FieldType:       resolveType(prop),
				IsPointer:       prop.IsPointer,
				IsOptional:      prop.IsOptional,
				CustomValidator: generateValidatorName(propName, prop.GoStruct.CustomValidator),
			})
		}
	}

	// root custom validation
	if md.GoStruct.CustomValidator != nil {
		*validators = append(*validators, Validator{
			FieldName:       ".",
			FieldType:       string(md.Type),
			CustomValidator: generateValidatorName("", md.GoStruct.CustomValidator),
		})
	}
}

func resolveType(md *ConfigMetadata) string {
	switch {
	case md.Ref != "":
		return "ref"
	case md.GoType == "time.Time":
		return "datetime"
	case md.GoType == "time.Duration":
		return "duration"
	default:
		return string(md.Type)
	}
}

func generateValidatorName(propName string, desc *CustomValidatorConfig) string {
	if desc.Name != "" {
		return desc.Name
	}
	id, _ := helpers.FormatIdentifier(propName, true)
	return "validate" + id
}

func MapCustomDefaults(schema *ConfigMetadata, defaultValue any, rootPackage, componentPackage string) []string {
	if schema.GoStruct.IgnoreDefault {
		return nil
	}
	exps := make([]string, 0)
	switch typedValue := defaultValue.(type) {
	case map[string]any:
		// is nested struct
		if schema.Values == nil {
			for key, value := range typedValue {
				propSchema := schema.Properties[key]
				if propSchema == nil {
					panic("schema does not contain required property: " + key)
				}
				varName, _ := helpers.FormatIdentifier(key, true)
				exp := fmt.Sprintf(".%s = %s", varName, FormatDefaultValue(propSchema, key, value, rootPackage, componentPackage))
				exps = append(exps, exp)
			}
		} else if schema.Values.Type == ObjectType { // is a map of object
			panic("map of structs is not supported yet")
		}
	case []any:
		// is an array of objects
		if schema.Values == nil || schema.Values.Type != ObjectType {
			return nil
		}
		for i, item := range typedValue {
			nestedExps := MapCustomDefaults(schema.Values, item, rootPackage, componentPackage)
			for _, exp := range nestedExps {
				exps = append(exps, fmt.Sprintf("[%d]%s", i, exp))
			}
		}
	}

	return exps
}

func FormatDefaultValue(md *ConfigMetadata, name string, defaultValue any, rootPackage, componentPackage string) string {
	if md.GoStruct.IgnoreDefault || (defaultValue == nil && !hasDefaultValue(md)) {
		if md.IsPointer {
			return "nil"
		}
		if md.IsOptional {
			t, _ := resolveGoType(md, name, rootPackage, componentPackage)
			return fmt.Sprintf("configoptional.None[%s]()", t)
		}
		return ""
	}
	exp := formatSimpleValue(md, name, defaultValue, rootPackage, componentPackage)
	if md.IsPointer {
		return "&" + exp
	}
	if md.IsOptional {
		if md.Type == ObjectType && md.Properties != nil {
			return fmt.Sprintf("configoptional.Default(%s)", exp)
		}
		return fmt.Sprintf("configoptional.Some(%s)", exp)
	}
	return exp
}

// FormatBaseValue returns the default value expression without IsPointer/IsOptional wrappers.
// Use this when initializing a local variable that will be mutated before wrapping.
func FormatBaseValue(md *ConfigMetadata, name string, defaultValue any, rootPackage, componentPackage string) string {
	return formatSimpleValue(md, name, defaultValue, rootPackage, componentPackage)
}

// WrapDefaultValue wraps a variable name expression with IsPointer/IsOptional modifiers.
// Use this to produce the final field assignment after mutation of the local variable.
func WrapDefaultValue(md *ConfigMetadata, varName string) string {
	exp := varName
	if md.IsPointer {
		return "&" + exp
	}
	if md.IsOptional {
		if md.Type == ObjectType && md.Properties != nil {
			return fmt.Sprintf("configoptional.Default(%s)", exp)
		}
		return fmt.Sprintf("configoptional.Some(%s)", exp)
	}
	return exp
}

func hasDefaultValue(md *ConfigMetadata) bool {
	if !md.GoStruct.IgnoreDefault && md.Default != nil {
		return true
	}
	for _, prop := range md.Properties {
		if hasDefaultValue(prop) {
			return true
		}
	}
	return false
}

func hasNonZeroDefault(md *ConfigMetadata) bool {
	if !md.GoStruct.IgnoreDefault && md.Default != nil {
		m, isMap := md.Default.(map[string]any)
		// empty map {} is the zero value
		if !isMap || len(m) > 0 {
			return true
		}
	}
	for _, prop := range md.Properties {
		if hasNonZeroDefault(prop) {
			return true
		}
	}
	return false
}

// CamelVar converts a reference string to an unexported Go identifier
func CamelVar(ref string) string {
	if ref == "" {
		panic("attempted to use CamelVar with an empty ref")
	}
	refDesc := NewRef(ref)
	name, _ := helpers.FormatIdentifier(refDesc.ConfigName(), false)
	return name
}

func formatSimpleValue(md *ConfigMetadata, name string, defaultValue any, rootPackage, componentPackage string) string {
	// handle references
	isReference := md.Ref != ""
	isSubStruct := md.Type == ObjectType
	if (isReference && !IsPrimitiveSchema(md)) || isSubStruct {
		if hasDefaultValue(md) {
			if isReference {
				refType, err := ResolveGoTypeRef(md.Ref, rootPackage, componentPackage)
				if err != nil {
					panic(err)
				}

				fnCall := fmt.Sprintf("NewDefault%s()", refType.TypeName)
				if refType.Qualifier() != "" {
					fnCall = refType.Qualifier() + "." + fnCall
				}
				return fnCall
			}
			typeName, _ := helpers.FormatIdentifier(name, true)
			return fmt.Sprintf("NewDefault%s()", typeName)
		}
		// no defaults, skip it
		return ""
	}

	// do not process further if "ignore_default" attribute set
	if md.GoStruct.IgnoreDefault {
		return ""
	}

	switch md.Type {
	case SliceType:
		typeExpr, err := resolveGoType(md.Values, name+"_item", "", "")
		if err == nil {
			if defaultValues, ok := defaultValue.([]any); ok {
				exps := make([]string, 0, len(defaultValues))
				for _, defaultValue := range defaultValues {
					exps = append(exps, FormatDefaultValue(md.Values, name+"_item", defaultValue, rootPackage, componentPackage))
				}
				return fmt.Sprintf("[]%s{%s}", typeExpr, strings.Join(exps, ", "))
			}
			panic("invalid default value, array expected")
		}
		panic(fmt.Sprintf("Could not resolve type, due to %e", err))
	case MapType:
		typeExpr, err := resolveGoType(md.Values, name, "", "")
		if err == nil {
			if defaultValues, ok := defaultValue.(map[string]any); ok {
				exps := make([]string, 0, len(defaultValues))
				for _, keyName := range slices.Sorted(maps.Keys(defaultValues)) {
					value := defaultValues[keyName]
					exps = append(
						exps,
						fmt.Sprintf("%q: %v", keyName, FormatDefaultValue(md.Values, name, value, rootPackage, componentPackage)),
					)
				}
				return fmt.Sprintf("map[string]%s{%s}", typeExpr, strings.Join(exps, ", "))
			}
			panic("invalid default value, map expected")
		}
		panic(fmt.Sprintf("Could not resolve type, due to %e", err))
	case StringType:
		switch md.GoType {
		case "time.Duration":
			if durationExpr, ok := renderDurationExpr(defaultValue); ok {
				return durationExpr
			}
		default:
			return fmt.Sprintf("%q", defaultValue)
		}
	default:
		return fmt.Sprintf("%v", defaultValue)
	}

	panic("unreachable")
}

func renderDurationExpr(value any) (string, bool) {
	switch v := value.(type) {
	case int:
		return formatDurationAsGoExpr(time.Duration(v)), true
	case float64:
		return formatDurationAsGoExpr(time.Duration(v)), true
	case string:
		d, err := time.ParseDuration(v)
		if err != nil {
			return "", false
		}
		return formatDurationAsGoExpr(d), true
	default:
		return "", false
	}
}

func formatDurationAsGoExpr(d time.Duration) string {
	if d == 0 {
		return "0"
	}
	units := []struct {
		name  string
		value time.Duration
	}{
		{"time.Hour", time.Hour},
		{"time.Minute", time.Minute},
		{"time.Second", time.Second},
		{"time.Millisecond", time.Millisecond},
		{"time.Microsecond", time.Microsecond},
		{"time.Nanosecond", time.Nanosecond},
	}
	var parts []string
	rem := d
	for _, u := range units {
		if rem >= u.value {
			n := rem / u.value
			rem -= n * u.value
			parts = append(parts, fmt.Sprintf("%d*%s", n, u.name))
		}
	}
	return strings.Join(parts, " + ")
}

func formatEnumSlice(values []any, fieldType string) string {
	var goType string
	switch fieldType {
	case "string":
		goType = "string"
	case "integer":
		goType = "int"
	case "number":
		goType = "float64"
	case "boolean":
		goType = "bool"
	default:
		goType = "any"
	}

	formatted := make([]string, 0, len(values))
	for _, v := range values {
		switch tv := v.(type) {
		case string:
			formatted = append(formatted, fmt.Sprintf("%q", tv))
		default:
			formatted = append(formatted, fmt.Sprintf("%v", tv))
		}
	}
	return fmt.Sprintf("[]%s{%s}", goType, strings.Join(formatted, ", "))
}

func formatEnumValues(values []any) string {
	formatted := make([]string, 0, len(values))
	for _, v := range values {
		formatted = append(formatted, fmt.Sprintf("%v", v))
	}
	return "[" + strings.Join(formatted, ", ") + "]"
}

func invalidTestValue(fieldType string) string {
	switch fieldType {
	case "string":
		return `"__invalid__"`
	case "integer":
		return "-1"
	case "number":
		return "-1.0"
	default:
		return `"__invalid__"`
	}
}

func filterProps(props map[string]*ConfigMetadata, pred func(value *ConfigMetadata) bool) map[string]*ConfigMetadata {
	result := make(map[string]*ConfigMetadata)
	for k, v := range props {
		if pred(v) {
			result[k] = v
		}
	}
	return result
}
