// Copyright 2025 The JSON Schema Go Project Authors. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package jsonschema

import (
	
	
	
	
	
	
	
	
	
	
	
	
)

// The values of the "$schema" keyword for the versions that we can validate.
const (
	draft7SchemaVersion      = "http://json-schema.org/draft-07/schema#"
	draft7SecSchemaVersion   = "https://json-schema.org/draft-07/schema#"
	draft202012SchemaVersion = "https://json-schema.org/draft/2020-12/schema"
)

// isValidSchemaVersion checks if the given schema version is supported
func isValidSchemaVersion( string) bool {
	return  == "" ||  == draft7SchemaVersion ||  == draft7SecSchemaVersion ||  == draft202012SchemaVersion
}

// Validate validates the instance, which must be a JSON value, against the schema.
// It returns nil if validation is successful or an error if it is not.
// If the schema type is "object", instance should be a map[string]any.
func ( *Resolved) ( any) error {
	if  := .root.Schema; !isValidSchemaVersion() {
		return fmt.Errorf("cannot validate version %s, supported versions: draft-07 and draft 2020-12", )
	}
	 := &state{rs: }
	return .validate(reflect.ValueOf(), .rs.root, nil)
}

// validateDefaults walks the schema tree. If it finds a default, it validates it
// against the schema containing it.
//
// TODO(jba): account for dynamic refs. This algorithm simple-mindedly
// treats each schema with a default as its own root.
func ( *Resolved) () error {
	if  := .root.Schema; !isValidSchemaVersion() {
		return fmt.Errorf("cannot validate version %s, supported versions: draft-07 and draft 2020-12", )
	}
	 := &state{rs: }
	for  := range .root.all() {
		// We checked for nil schemas in [Schema.Resolve].
		assert( != nil, "nil schema")
		if .DynamicRef != "" {
			return fmt.Errorf("jsonschema: %s: validateDefaults does not support dynamic refs", .schemaString())
		}
		if .Default != nil {
			var  any
			if  := json.Unmarshal(.Default, &);  != nil {
				return fmt.Errorf("unmarshaling default value of schema %s: %w", .schemaString(), )
			}
			if  := .validate(reflect.ValueOf(), , nil);  != nil {
				return 
			}
		}
	}
	return nil
}

// state is the state of single call to ResolvedSchema.Validate.
type state struct {
	rs *Resolved
	// stack holds the schemas from recursive calls to validate.
	// These are the "dynamic scopes" used to resolve dynamic references.
	// https://json-schema.org/draft/2020-12/json-schema-core#scopes
	stack []*Schema
}

// validate validates the reflected value of the instance.
func ( *state) ( reflect.Value,  *Schema,  *annotations) ( error) {
	defer wrapf(&, "validating %s", .rs.schemaString())

	// Maintain a stack for dynamic schema resolution.
	.stack = append(.stack, ) // push
	defer func() {
		.stack = .stack[:len(.stack)-1] // pop
	}()

	// We checked for nil schemas in [Schema.Resolve].
	assert( != nil, "nil schema")

	// Step through interfaces and pointers.
	for .Kind() == reflect.Pointer || .Kind() == reflect.Interface {
		 = .Elem()
	}

	 := .rs.resolvedInfos[]

	var  annotations // all the annotations for this call and child calls
	// $ref: https://json-schema.org/draft/2020-12/json-schema-core#section-8.2.3.1
	if .Ref != "" {
		if  := .(, .resolvedRef, &);  != nil {
			return 
		}
		// https://json-schema.org/draft-07/draft-handrews-json-schema-01#rfc.section.8.3
		// "All other properties in a "$ref" object MUST be ignored."
		if .rs.draft == draft7 {
			return nil
		}
	}

	// type: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.1.1
	if .Type != "" || .Types != nil {
		,  := jsonType()
		if ! {
			return fmt.Errorf("type: %v of type %[1]T is not a valid JSON value", )
		}
		if .Type != "" {
			// "number" subsumes integers
			if !( == .Type ||
				 == "integer" && .Type == "number") {
				return fmt.Errorf("type: %v has type %q, want %q", , , .Type)
			}
		} else {
			if !(slices.Contains(.Types, ) || ( == "integer" && slices.Contains(.Types, "number"))) {
				return fmt.Errorf("type: %v has type %q, want one of %q",
					, , strings.Join(.Types, ", "))
			}
		}
	}
	// enum: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.1.2
	if .Enum != nil {
		 := false
		for ,  := range .Enum {
			if equalValue(reflect.ValueOf(), ) {
				 = true
				break
			}
		}
		if ! {
			return fmt.Errorf("enum: %v does not equal any of: %v", , .Enum)
		}
	}

	// const: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.1.3
	if .Const != nil {
		if !equalValue(reflect.ValueOf(*.Const), ) {
			return fmt.Errorf("const: %v does not equal %v", , *.Const)
		}
	}

	// numbers: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.2
	if .MultipleOf != nil || .Minimum != nil || .Maximum != nil || .ExclusiveMinimum != nil || .ExclusiveMaximum != nil {
		,  := jsonNumber()
		if  { // these keywords don't apply to non-numbers
			if .MultipleOf != nil {
				// TODO: validate MultipleOf as non-zero.
				// The test suite assumes floats.
				,  := .Float64() // don't care if it's exact or not
				if ,  := math.Modf( / *.MultipleOf);  != 0 {
					return fmt.Errorf("multipleOf: %s is not a multiple of %f", , *.MultipleOf)
				}
			}

			 := new(big.Rat) // reuse for all of the following
			 := func( float64) int { return .Cmp(.SetFloat64()) }

			if .Minimum != nil && (*.Minimum) < 0 {
				return fmt.Errorf("minimum: %s is less than %f", , *.Minimum)
			}
			if .Maximum != nil && (*.Maximum) > 0 {
				return fmt.Errorf("maximum: %s is greater than %f", , *.Maximum)
			}
			if .ExclusiveMinimum != nil && (*.ExclusiveMinimum) <= 0 {
				return fmt.Errorf("exclusiveMinimum: %s is less than or equal to %f", , *.ExclusiveMinimum)
			}
			if .ExclusiveMaximum != nil && (*.ExclusiveMaximum) >= 0 {
				return fmt.Errorf("exclusiveMaximum: %s is greater than or equal to %f", , *.ExclusiveMaximum)
			}
		}
	}

	// strings: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.3
	if .Kind() == reflect.String && (.MinLength != nil || .MaxLength != nil || .Pattern != "") {
		 := .String()
		 := utf8.RuneCountInString()
		if .MinLength != nil {
			if  := *.MinLength;  <  {
				return fmt.Errorf("minLength: %q contains %d Unicode code points, fewer than %d", , , )
			}
		}
		if .MaxLength != nil {
			if  := *.MaxLength;  >  {
				return fmt.Errorf("maxLength: %q contains %d Unicode code points, more than %d", , , )
			}
		}

		if .Pattern != "" && !.pattern.MatchString() {
			return fmt.Errorf("pattern: %q does not match regular expression %q", , .Pattern)
		}
	}

	// $dynamicRef: https://json-schema.org/draft/2020-12/json-schema-core#section-8.2.3.2
	if .DynamicRef != "" {
		// The ref behaves lexically or dynamically, but not both.
		assert((.resolvedDynamicRef == nil) != (.dynamicRefAnchor == ""),
			"DynamicRef not resolved properly")
		if .resolvedDynamicRef != nil {
			// Same as $ref.
			if  := .(, .resolvedDynamicRef, &);  != nil {
				return 
			}
		} else {
			// Dynamic behavior.
			// Look for the base of the outermost schema on the stack with this dynamic
			// anchor. (Yes, outermost: the one farthest from here. This the opposite
			// of how ordinary dynamic variables behave.)
			// Why the base of the schema being validated and not the schema itself?
			// Because the base is the scope for anchors. In fact it's possible to
			// refer to a schema that is not on the stack, but a child of some base
			// on the stack.
			// For an example, search for "detached" in testdata/draft2020-12/dynamicRef.json.
			var  *Schema
			for ,  := range .stack {
				 := .rs.resolvedInfos[].base
				,  := .rs.resolvedInfos[].anchors[.dynamicRefAnchor]
				if  && .dynamic {
					 = .schema
					break
				}
			}
			if  == nil {
				return fmt.Errorf("missing dynamic anchor %q", .dynamicRefAnchor)
			}
			if  := .(, , &);  != nil {
				return 
			}
		}
	}

	// logic
	// https://json-schema.org/draft/2020-12/json-schema-core#section-10.2
	// These must happen before arrays and objects because if they evaluate an item or property,
	// then the unevaluatedItems/Properties schemas don't apply to it.
	// See https://json-schema.org/draft/2020-12/json-schema-core#section-11.2, paragraph 4.
	//
	// If any of these fail, then validation fails, even if there is an unevaluatedXXX
	// keyword in the schema. The spec is unclear about this, but that is the intention.

	 := func( *Schema,  *annotations) bool { return .(, , ) == nil }

	if .AllOf != nil {
		for ,  := range .AllOf {
			if  := .(, , &);  != nil {
				return 
			}
		}
	}
	if .AnyOf != nil {
		// We must visit them all, to collect annotations.
		 := false
		for ,  := range .AnyOf {
			if (, &) {
				 = true
			}
		}
		if ! {
			return fmt.Errorf("anyOf: did not validate against any of %v", .AnyOf)
		}
	}
	if .OneOf != nil {
		// Exactly one.
		var  *Schema
		for ,  := range .OneOf {
			if (, &) {
				if  != nil {
					return fmt.Errorf("oneOf: validated against both %v and %v", , )
				}
				 = 
			}
		}
		if  == nil {
			return fmt.Errorf("oneOf: did not validate against any of %v", .OneOf)
		}
	}
	if .Not != nil {
		// Ignore annotations from "not".
		if (.Not, nil) {
			return fmt.Errorf("not: validated against %v", .Not)
		}
	}
	if .If != nil {
		var  *Schema
		if (.If, &) {
			 = .Then
		} else {
			 = .Else
		}
		if  != nil {
			if  := .(, , &);  != nil {
				return 
			}
		}
	}

	// arrays
	if .Kind() == reflect.Array || .Kind() == reflect.Slice {
		// Handle both draft-07 and draft 2020-12
		// https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.1
		// This validate call doesn't collect annotations for the items of the instance; they are separate
		// instances in their own right.
		// TODO(jba): if the test suite doesn't cover this case, add a test. For example, nested arrays.
		if .rs.draft == draft7 {
			// For draft-07: additionalItems applies to remaining items after items array.
			// If items is a Schema or if items is not set, additionalItems should be ignored
			if .ItemsArray != nil {
				for ,  := range .ItemsArray {
					if  >= .Len() {
						break // shorter is OK
					}
					if  := .(.Index(), , nil);  != nil {
						return 
					}
				}
				.noteEndIndex(min(len(.ItemsArray), .Len()))
				if .AdditionalItems != nil {
					for  := len(.ItemsArray);  < .Len(); ++ {
						if  := .(.Index(), .AdditionalItems, nil);  != nil {
							return 
						}
					}
					.allItems = true
				}
			} else if .Items != nil {
				for  := 0;  < .Len(); ++ {
					if  := .(.Index(), .Items, nil);  != nil {
						return 
					}
				}
				// Note that all the items in this array have been validated.
				.allItems = true
			}
		} else if .rs.draft == draft2020 {
			// For draft 2020-12: items applies to remaining items after prefixItems
			for ,  := range .PrefixItems {
				if  >= .Len() {
					break // shorter is OK
				}
				if  := .(.Index(), , nil);  != nil {
					return 
				}
			}
			.noteEndIndex(min(len(.PrefixItems), .Len()))
			if .Items != nil {
				for  := len(.PrefixItems);  < .Len(); ++ {
					if  := .(.Index(), .Items, nil);  != nil {
						return 
					}
				}
				// Note that all the items in this array have been validated.
				.allItems = true
			}
		}
		 := 0
		if .Contains != nil {
			for  := range .Len() {
				if  := .(.Index(), .Contains, nil);  == nil {
					++
					.noteIndex()
				}
			}
			if  == 0 && (.MinContains == nil || *.MinContains > 0) {
				return fmt.Errorf("contains: %s does not have an item matching %s", , .Contains)
			}
		}

		// https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.4
		// TODO(jba): check that these next four keywords' values are integers.
		if .MinContains != nil && .Contains != nil {
			if  := *.MinContains;  <  {
				return fmt.Errorf("minContains: contains validated %d items, less than %d", , )
			}
		}
		if .MaxContains != nil && .Contains != nil {
			if  := *.MaxContains;  >  {
				return fmt.Errorf("maxContains: contains validated %d items, greater than %d", , )
			}
		}
		if .MinItems != nil {
			if  := *.MinItems; .Len() <  {
				return fmt.Errorf("minItems: array length %d is less than %d", .Len(), )
			}
		}
		if .MaxItems != nil {
			if  := *.MaxItems; .Len() >  {
				return fmt.Errorf("maxItems: array length %d is greater than %d", .Len(), )
			}
		}
		if .UniqueItems {
			if .Len() > 1 {
				// Hash each item and compare the hashes.
				// If two hashes differ, the items differ.
				// If two hashes are the same, compare the collisions for equality.
				// (The same logic as hash table lookup.)
				// TODO(jba): Use container/hash.Map when it becomes available (https://go.dev/issue/69559),
				 := map[uint64][]int{} // from hash to indices
				 := maphash.MakeSeed()
				for  := range .Len() {
					 := .Index()
					var  maphash.Hash
					.SetSeed()
					hashValue(&, )
					 := .Sum64()
					if  := []; len() > 0 {
						for ,  := range  {
							if equalValue(, .Index()) {
								return fmt.Errorf("uniqueItems: array items %d and %d are equal", , )
							}
						}
					}
					[] = append([], )
				}
			}
		}

		// https://json-schema.org/draft/2020-12/json-schema-core#section-11.2
		if .UnevaluatedItems != nil && !.allItems {
			// Apply this subschema to all items in the array that haven't been successfully validated.
			// That includes validations by subschemas on the same instance, like allOf.
			for  := .endIndex;  < .Len(); ++ {
				if !.evaluatedIndexes[] {
					if  := .(.Index(), .UnevaluatedItems, nil);  != nil {
						return 
					}
				}
			}
			.allItems = true
		}
	}

	// objects
	// https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.2
	// Validating structs is problematic. See https://github.com/google/jsonschema-go/issues/23.
	if .Kind() == reflect.Struct {
		return errors.New("cannot validate against a struct; see https://github.com/google/jsonschema-go/issues/23 for details")
	}
	if .Kind() == reflect.Map {
		if  := .Type().Key(); .Kind() != reflect.String {
			return fmt.Errorf("map key type %s is not a string", )
		}
		// Track the evaluated properties for just this schema, to support additionalProperties.
		// If we used anns here, then we'd be including properties evaluated in subschemas
		// from allOf, etc., which additionalProperties shouldn't observe.
		 := map[string]bool{}
		for ,  := range .Properties {
			 := property(, )
			if !.IsValid() {
				// It's OK if the instance doesn't have the property.
				continue
			}
			// If the instance is a struct and an optional property has the zero
			// value, then we could interpret it as present or missing. Be generous:
			// assume it's missing, and thus always validates successfully.
			if .Kind() == reflect.Struct && .IsZero() && !.isRequired[] {
				continue
			}
			if  := .(, , nil);  != nil {
				return 
			}
			[] = true
		}
		if len(.PatternProperties) > 0 {
			for ,  := range properties() {
				// Check every matching pattern.
				for ,  := range .patternProperties {
					if .MatchString() {
						if  := .(, , nil);  != nil {
							return 
						}
						[] = true
					}
				}
			}
		}
		if .AdditionalProperties != nil {
			// Special case for a better error message when additional properties is
			// 'falsy'
			//
			// If additionalProperties is {"not":{}} (which is how we
			// unmarshal "false"), we can produce a better error message that
			// summarizes all the extra properties. Otherwise, we fall back to the
			// default validation.
			//
			// Note: this is much faster than comparing with falseSchema using Equal.
			 := .AdditionalProperties.Not != nil && reflect.ValueOf(*.AdditionalProperties.Not).IsZero()
			if  {
				var  []string
				for  := range properties() {
					if ![] {
						 = append(, )
					}
				}
				if len() > 0 {
					return fmt.Errorf("unexpected additional properties %q", )
				}
			} else {
				// Apply to all properties not handled above.
				for ,  := range properties() {
					if ![] {
						if  := .(, .AdditionalProperties, nil);  != nil {
							return 
						}
						[] = true
					}
				}
			}
		}
		.noteProperties()
		if .PropertyNames != nil {
			// Note: properties unnecessarily fetches each value. We could define a propertyNames function
			// if performance ever matters.
			for  := range properties() {
				if  := .(reflect.ValueOf(), .PropertyNames, nil);  != nil {
					return 
				}
			}
		}

		// https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.5
		var ,  int
		if .MinProperties != nil || .MaxProperties != nil {
			,  = numPropertiesBounds(, .isRequired)
		}
		if .MinProperties != nil {
			if ,  := , *.MinProperties;  <  {
				return fmt.Errorf("minProperties: object has %d properties, less than %d", , )
			}
		}
		if .MaxProperties != nil {
			if ,  := , *.MaxProperties;  >  {
				return fmt.Errorf("maxProperties: object has %d properties, greater than %d", , )
			}
		}

		 := func( string) bool {
			return property(, ).IsValid()
		}

		 := func( []string) []string {
			var  []string
			for ,  := range  {
				if !() {
					 = append(, )
				}
			}
			return 
		}

		if .Required != nil {
			if  := (.Required); len() > 0 {
				return fmt.Errorf("required: missing properties: %q", )
			}
		}

		if .rs.draft == draft7 {
			if .DependencyStrings != nil {
				for ,  := range .DependencyStrings {
					if () {
						if  := (); len() > 0 {
							return fmt.Errorf("dependentRequired[%q]: missing properties %q", , )
						}
					}
				}
			}
			if .DependencySchemas != nil {
				for ,  := range .DependencySchemas {
					if () {
						 := .(, , &)
						if  != nil {
							return 
						}
					}
				}
			}
		} else if .rs.draft == draft2020 {
			if .DependentRequired != nil {
				// "Validation succeeds if, for each name that appears in both the instance
				// and as a name within this keyword's value, every item in the corresponding
				// array is also the name of a property in the instance." ยง6.5.4
				for ,  := range .DependentRequired {
					if () {
						if  := (); len() > 0 {
							return fmt.Errorf("dependentRequired[%q]: missing properties %q", , )
						}
					}
				}
			}

			// https://json-schema.org/draft/2020-12/json-schema-core#section-10.2.2.4
			if .DependentSchemas != nil {
				// This does not collect annotations, although it seems like it should.
				for ,  := range .DependentSchemas {
					if () {
						// TODO: include dependentSchemas[dprop] in the errors.
						 := .(, , &)
						if  != nil {
							return 
						}
					}
				}
			}
		}

		if .UnevaluatedProperties != nil && !.allProperties {
			// This looks a lot like AdditionalProperties, but depends on in-place keywords like allOf
			// in addition to sibling keywords.
			for ,  := range properties() {
				if !.evaluatedProperties[] {
					if  := .(, .UnevaluatedProperties, nil);  != nil {
						return 
					}
				}
			}
			// The spec says the annotation should be the set of evaluated properties, but we can optimize
			// by setting a single boolean, since after this succeeds all properties will be validated.
			// See https://json-schema.slack.com/archives/CT7FF623C/p1745592564381459.
			.allProperties = true
		}
	}

	if  != nil {
		// Our caller wants to know what we've validated.
		.merge(&)
	}
	return nil
}

// resolveDynamicRef returns the schema referred to by the argument schema's
// $dynamicRef value.
// It returns an error if the dynamic reference has no referent.
// If there is no $dynamicRef, resolveDynamicRef returns nil, nil.
// See https://json-schema.org/draft/2020-12/json-schema-core#section-8.2.3.2.
func ( *state) ( *Schema) (*Schema, error) {
	if .DynamicRef == "" {
		return nil, nil
	}
	 := .rs.resolvedInfos[]
	// The ref behaves lexically or dynamically, but not both.
	assert((.resolvedDynamicRef == nil) != (.dynamicRefAnchor == ""),
		"DynamicRef not statically resolved properly")
	if  := .resolvedDynamicRef;  != nil {
		// Same as $ref.
		return , nil
	}
	// Dynamic behavior.
	// Look for the base of the outermost schema on the stack with this dynamic
	// anchor. (Yes, outermost: the one farthest from here. This the opposite
	// of how ordinary dynamic variables behave.)
	// Why the base of the schema being validated and not the schema itself?
	// Because the base is the scope for anchors. In fact it's possible to
	// refer to a schema that is not on the stack, but a child of some base
	// on the stack.
	// For an example, search for "detached" in testdata/draft2020-12/dynamicRef.json.
	for ,  := range .stack {
		 := .rs.resolvedInfos[].base
		,  := .rs.resolvedInfos[].anchors[.dynamicRefAnchor]
		if  && .dynamic {
			return .schema, nil
		}
	}
	return nil, fmt.Errorf("missing dynamic anchor %q", .dynamicRefAnchor)
}

// ApplyDefaults modifies an instance by applying the schema's defaults to it. If
// a schema or sub-schema has a default, then a corresponding missing instance value
// is set to the default.
//
// The JSON Schema specification does not describe how defaults should be interpreted.
// This method honors defaults only on properties, and only those that are not required.
// If the instance is a map and the property is missing, the property is added to
// the map with the default.
// ApplyDefaults does not support structs, because it cannot know whether a field
// is missing in the JSON, or was explicitly set to its zero value.
//
// ApplyDefaults can panic if a default cannot be assigned to a field.
//
// The argument must be a pointer to the instance.
// (In case we decide that top-level defaults are meaningful.)
//
// It is recommended to first call Resolve with a ValidateDefaults option of true,
// then call this method, and lastly call Validate.
func ( *Resolved) ( any) error {
	// TODO(jba): consider what defaults on top-level or array instances might mean.
	// TODO(jba): follow $ref and $dynamicRef
	 := &state{rs: }
	return .applyDefaults(reflect.ValueOf(), .root)
}

// Recursive helper used by ApplyDefaults. Applies defaults on sub-schemas
// of object properties recursively.
func ( *state) ( reflect.Value,  *Schema) ( error) {
	defer wrapf(&, "applyDefaults: schema %s, instance %v", .rs.schemaString(), )

	 := .rs.resolvedInfos[]
	 := .Elem()
	if .Kind() == reflect.Interface && .IsValid() {
		// If we unmarshalled into 'any', the default object unmarshalling will be map[string]any.
		 = .Elem()
	}
	if .Kind() == reflect.Map || .Kind() == reflect.Struct {
		if .Kind() == reflect.Map {
			if  := .Type().Key(); .Kind() != reflect.String {
				return fmt.Errorf("map key type %s is not a string", )
			}
		}
		for ,  := range .Properties {
			// Ignore defaults on required properties. (A required property shouldn't have a default.)
			if .isRequired[] {
				continue
			}
			 := property(, )
			switch .Kind() {
			case reflect.Map:
				// If there is a default for this property, and the map key is missing,
				// set the map value to the default.
				if .Default != nil && !.IsValid() {
					// Create an lvalue, since map values aren't addressable.
					 := reflect.New(.Type().Elem())
					if  := json.Unmarshal(.Default, .Interface());  != nil {
						return 
					}
					// Recurse unconditionally; applyDefaults will only act on object-like values.
					if  := .(, );  != nil {
						return 
					}
					.SetMapIndex(reflect.ValueOf(), .Elem())
				} else if .IsValid() {
					// Recurse into an existing sub-instance.
					// MapIndex returns a non-addressable value; copy into an addressable lvalue, recurse, then set back.
					 := reflect.New(.Type().Elem())
					// Initialize the lvalue with current value.
					.Elem().Set()
					if  := .(, );  != nil {
						return 
					}
					.SetMapIndex(reflect.ValueOf(), .Elem())
				} else if schemaHasDefaultsInProperties() {
					// Property is missing, but descendants still have some defaults
					// Create an empty container and recurse to populate
					 := .Type().Elem()
					var  reflect.Value
					switch .Kind() {
					case reflect.Interface:
						 = reflect.ValueOf(map[string]any{})
					case reflect.Map:
						 = reflect.MakeMap()
					case reflect.Struct:
						 = reflect.New().Elem()
					}
					if .IsValid() {
						 := reflect.New()
						.Elem().Set()
						if  := .(, );  != nil {
							return 
						}
						.SetMapIndex(reflect.ValueOf(), .Elem())
					}
				}
			case reflect.Struct:
				return errors.New("cannot apply defaults to a struct")
			default:
				panic(fmt.Sprintf("applyDefaults: property %s: bad value %s of kind %s",
					, , .Kind()))
			}
		}
	}
	return nil
}

// schemaHasDefaultsInProperties reports whether s or any descendant schema under
// its Properties contains a default. Only walks Properties to match ApplyDefaults semantics.
func schemaHasDefaultsInProperties( *Schema) bool {
	if  == nil {
		return false
	}
	if .Default != nil {
		return true
	}
	if .Properties != nil {
		for ,  := range .Properties {
			if () {
				return true
			}
		}
	}
	return false
}

// property returns the value of the property of v with the given name, or the invalid
// reflect.Value if there is none.
// If v is a map, the property is the value of the map whose key is name.
// If v is a struct, the property is the value of the field with the given name according
// to the encoding/json package (see [jsonName]).
// If v is anything else, property panics.
func property( reflect.Value,  string) reflect.Value {
	switch .Kind() {
	case reflect.Map:
		return .MapIndex(reflect.ValueOf())
	case reflect.Struct:
		 := structPropertiesOf(.Type())
		// Ignore nonexistent properties.
		if ,  := [];  {
			return .FieldByIndex(.Index)
		}
		return reflect.Value{}
	default:
		panic(fmt.Sprintf("property(%q): bad value %s of kind %s", , , .Kind()))
	}
}

// properties returns an iterator over the names and values of all properties
// in v, which must be a map or a struct.
// If a struct, zero-valued properties that are marked omitempty or omitzero
// are excluded.
func properties( reflect.Value) iter.Seq2[string, reflect.Value] {
	return func( func(string, reflect.Value) bool) {
		switch .Kind() {
		case reflect.Map:
			for ,  := range .Seq2() {
				if !(.String(), ) {
					return
				}
			}
		case reflect.Struct:
			for ,  := range structPropertiesOf(.Type()) {
				 := .FieldByIndex(.Index)
				if .IsZero() {
					 := fieldJSONInfo()
					if .settings["omitempty"] || .settings["omitzero"] {
						continue
					}
				}
				if !(, ) {
					return
				}
			}
		default:
			panic(fmt.Sprintf("bad value %s of kind %s", , .Kind()))
		}
	}
}

// numPropertiesBounds returns bounds on the number of v's properties.
// v must be a map or a struct.
// If v is a map, both bounds are the map's size.
// If v is a struct, the max is the number of struct properties.
// But since we don't know whether a zero value indicates a missing optional property
// or not, be generous and use the number of non-zero properties as the min.
func numPropertiesBounds( reflect.Value,  map[string]bool) (int, int) {
	switch .Kind() {
	case reflect.Map:
		return .Len(), .Len()
	case reflect.Struct:
		 := structPropertiesOf(.Type())
		 := 0
		for ,  := range  {
			if !.FieldByIndex(.Index).IsZero() || [] {
				++
			}
		}
		return , len()
	default:
		panic(fmt.Sprintf("properties: bad value: %s of kind %s", , .Kind()))
	}
}

// A propertyMap is a map from property name to struct field index.
type propertyMap = map[string]reflect.StructField

var structProperties sync.Map // from reflect.Type to propertyMap

// structPropertiesOf returns the JSON Schema properties for the struct type t.
// The caller must not mutate the result.
func structPropertiesOf( reflect.Type) propertyMap {
	// Mutex not necessary: at worst we'll recompute the same value.
	if ,  := structProperties.Load();  {
		return .(propertyMap)
	}
	 := map[string]reflect.StructField{}
	for ,  := range reflect.VisibleFields() {
		if .Anonymous {
			continue
		}
		 := fieldJSONInfo()
		if !.omit {
			[.name] = 
		}
	}
	structProperties.Store(, )
	return 
}