// 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 (
	
	
	
	
	
	
	
	
	
	
)

// A Schema is a JSON schema object.
// It supports both draft-07 and the 2020-12 draft specifications:
//   - Draft-07: https://json-schema.org/draft-07/draft-handrews-json-schema-01
//     and https://json-schema.org/draft-07/draft-handrews-json-schema-validation-01
//   - Draft 2020-12: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01
//     and https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01
//
// A Schema value may have non-zero values for more than one field:
// all relevant non-zero fields are used for validation.
// There is one exception to provide more Go type-safety: the Type and Types fields
// are mutually exclusive.
//
// Since this struct is a Go representation of a JSON value, it inherits JSON's
// distinction between nil and empty. Nil slices and maps are considered absent,
// but empty ones are present and affect validation. For example,
//
//	Schema{Enum: nil}
//
// is equivalent to an empty schema, so it validates every instance. But
//
//	Schema{Enum: []any{}}
//
// requires equality to some slice element, so it vacuously rejects every instance.
type Schema struct {
	// core
	ID          string             `json:"$id,omitempty"`
	Schema      string             `json:"$schema,omitempty"`
	Ref         string             `json:"$ref,omitempty"`
	Comment     string             `json:"$comment,omitempty"`
	Defs        map[string]*Schema `json:"$defs,omitempty"`
	Definitions map[string]*Schema `json:"definitions,omitempty"`

	// split draft 7 Dependencies into DependencySchemas and DependencyStrings
	DependencySchemas map[string]*Schema  `json:"-"`
	DependencyStrings map[string][]string `json:"-"`

	Anchor        string          `json:"$anchor,omitempty"`
	DynamicAnchor string          `json:"$dynamicAnchor,omitempty"`
	DynamicRef    string          `json:"$dynamicRef,omitempty"`
	Vocabulary    map[string]bool `json:"$vocabulary,omitempty"`

	// metadata
	Title       string          `json:"title,omitempty"`
	Description string          `json:"description,omitempty"`
	Default     json.RawMessage `json:"default,omitempty"`
	Deprecated  bool            `json:"deprecated,omitempty"`
	ReadOnly    bool            `json:"readOnly,omitempty"`
	WriteOnly   bool            `json:"writeOnly,omitempty"`
	Examples    []any           `json:"examples,omitempty"`

	// validation
	// Use Type for a single type, or Types for multiple types; never both.
	Type  string   `json:"-"`
	Types []string `json:"-"`
	Enum  []any    `json:"enum,omitempty"`
	// Const is *any because a JSON null (Go nil) is a valid value.
	Const            *any     `json:"const,omitempty"`
	MultipleOf       *float64 `json:"multipleOf,omitempty"`
	Minimum          *float64 `json:"minimum,omitempty"`
	Maximum          *float64 `json:"maximum,omitempty"`
	ExclusiveMinimum *float64 `json:"exclusiveMinimum,omitempty"`
	ExclusiveMaximum *float64 `json:"exclusiveMaximum,omitempty"`
	MinLength        *int     `json:"minLength,omitempty"`
	MaxLength        *int     `json:"maxLength,omitempty"`
	Pattern          string   `json:"pattern,omitempty"`

	// arrays
	PrefixItems      []*Schema `json:"prefixItems,omitempty"`
	Items            *Schema   `json:"-"`
	ItemsArray       []*Schema `json:"-"`
	MinItems         *int      `json:"minItems,omitempty"`
	MaxItems         *int      `json:"maxItems,omitempty"`
	AdditionalItems  *Schema   `json:"additionalItems,omitempty"`
	UniqueItems      bool      `json:"uniqueItems,omitempty"`
	Contains         *Schema   `json:"contains,omitempty"`
	MinContains      *int      `json:"minContains,omitempty"` // *int, not int: default is 1, not 0
	MaxContains      *int      `json:"maxContains,omitempty"`
	UnevaluatedItems *Schema   `json:"unevaluatedItems,omitempty"`

	// objects
	MinProperties         *int                `json:"minProperties,omitempty"`
	MaxProperties         *int                `json:"maxProperties,omitempty"`
	Required              []string            `json:"required,omitempty"`
	DependentRequired     map[string][]string `json:"dependentRequired,omitempty"`
	Properties            map[string]*Schema  `json:"properties,omitempty"`
	PatternProperties     map[string]*Schema  `json:"patternProperties,omitempty"`
	AdditionalProperties  *Schema             `json:"additionalProperties,omitempty"`
	PropertyNames         *Schema             `json:"propertyNames,omitempty"`
	UnevaluatedProperties *Schema             `json:"unevaluatedProperties,omitempty"`

	// logic
	AllOf []*Schema `json:"allOf,omitempty"`
	AnyOf []*Schema `json:"anyOf,omitempty"`
	OneOf []*Schema `json:"oneOf,omitempty"`
	Not   *Schema   `json:"not,omitempty"`

	// conditional
	If               *Schema            `json:"if,omitempty"`
	Then             *Schema            `json:"then,omitempty"`
	Else             *Schema            `json:"else,omitempty"`
	DependentSchemas map[string]*Schema `json:"dependentSchemas,omitempty"`

	// other
	// https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.8
	ContentEncoding  string  `json:"contentEncoding,omitempty"`
	ContentMediaType string  `json:"contentMediaType,omitempty"`
	ContentSchema    *Schema `json:"contentSchema,omitempty"`

	// https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7
	Format string `json:"format,omitempty"`

	// Extra allows for additional keywords beyond those specified.
	Extra map[string]any `json:"-"`

	// PropertyOrder records the ordering of properties for JSON rendering.
	//
	// During [For], PropertyOrder is set to the field order,
	// if the type used for inference is a struct.
	//
	// If PropertyOrder is set, it controls the relative ordering of properties in [Schema.MarshalJSON].
	// The rendered JSON first lists any properties that appear in the PropertyOrder slice in the order
	// they appear, followed by all other properties that do not appear in the PropertyOrder slice in an
	// undefined but deterministic order.
	PropertyOrder []string `json:"-"`
}

// falseSchema returns a new Schema tree that fails to validate any value.
func falseSchema() *Schema {
	return &Schema{Not: &Schema{}}
}

// anchorInfo records the subschema to which an anchor refers, and whether
// the anchor keyword is $anchor or $dynamicAnchor.
type anchorInfo struct {
	schema  *Schema
	dynamic bool
}

// String returns a short description of the schema.
func ( *Schema) () string {
	if .ID != "" {
		return .ID
	}
	if  := cmp.Or(.Anchor, .DynamicAnchor);  != "" {
		return fmt.Sprintf("anchor %s", )
	}
	return "<anonymous schema>"
}

// CloneSchemas returns a copy of s.
// The copy is shallow except for sub-schemas, which are themelves copied with CloneSchemas.
// This allows both s and s.CloneSchemas() to appear as sub-schemas of the same parent.
func ( *Schema) () *Schema {
	if  == nil {
		return nil
	}
	 := *
	 := reflect.ValueOf(&)
	for ,  := range schemaFieldInfos {
		 := .Elem().FieldByIndex(.sf.Index)
		switch .sf.Type {
		case schemaType:
			 := .Interface().(*Schema)
			.Set(reflect.ValueOf(.()))

		case schemaSliceType:
			 := .Interface().([]*Schema)
			 = slices.Clone()
			for ,  := range  {
				[] = .()
			}
			.Set(reflect.ValueOf())

		case schemaMapType:
			 := .Interface().(map[string]*Schema)
			 = maps.Clone()
			for ,  := range  {
				[] = .()
			}
			.Set(reflect.ValueOf())

		}
	}
	return &
}

func ( *Schema) () error {
	if .Type != "" && .Types != nil {
		return errors.New("both Type and Types are set; at most one should be")
	}
	if .Defs != nil && .Definitions != nil {
		return errors.New("both Defs and Definitions are set; at most one should be")
	}
	if .Items != nil && .ItemsArray != nil {
		return errors.New("both Items and ItemsArray are set; at most one should be")
	}
	 := make(map[string]bool)
	for ,  := range .PropertyOrder {
		if ,  := [];  {
			// Duplicate found
			return fmt.Errorf("property order slice cannot contain duplicate entries, found duplicate %q", )
		}
		[] = true
	}

	for  := range .DependencySchemas {
		// Check if the key exists in the dependency strings map
		if ,  := .DependencyStrings[];  {
			return fmt.Errorf("dependency key %q cannot be defined as both a schema and a string array", )
		}
	}
	return nil
}

type schemaWithoutMethods Schema // doesn't implement json.{Unm,M}arshaler

func ( Schema) () ([]byte, error) {
	// NOTE: Use a value receiver here to avoid the encoding/json bugs
	// described in golang/go#22967, golang/go#33993, and golang/go#55890.
	// With a pointer receiver, MarshalJSON is only called for Schema in
	// some cases (for example when the field value is addressable, or not
	// stored as a map value), which leads to inconsistent JSON encoding.
	// A value receiver makes Schema itself implement json.Marshaler and
	// ensures that encoding/json always calls this method.
	if  := .basicChecks();  != nil {
		return nil, 
	}
	// Marshal either Type or Types as "type".
	var  any
	switch {
	case .Type != "":
		 = .Type
	case .Types != nil:
		 = .Types
	}

	var  any
	switch {
	case .Items != nil:
		 = .Items
	case .ItemsArray != nil:
		 = .ItemsArray
	}

	var  map[string]any
	 := len(.DependencySchemas) + len(.DependencyStrings)
	if  > 0 {
		 = make(map[string]any, )
		for ,  := range .DependencySchemas {
			[] = 
		}
		for ,  := range .DependencyStrings {
			[] = 
		}
	}

	 := struct {
		         any            `json:"type,omitempty"`
		   json.Marshaler `json:"properties,omitempty"`
		 map[string]any `json:"dependencies,omitempty"`
		        any            `json:"items,omitempty"`
		*schemaWithoutMethods
	}{
		:                 ,
		:         ,
		:                ,
		: (*schemaWithoutMethods)(&),
	}
	// Marshal properties, even if the empty map (but not nil).
	if .Properties != nil {
		. = orderedProperties{
			props: .Properties,
			order: .PropertyOrder,
		}
	}

	,  := marshalStructWithMap(&, "Extra")
	if  != nil {
		return nil, 
	}
	// Marshal {} as true and {"not": {}} as false.
	// It is wasteful to do this here instead of earlier, but much easier.
	switch {
	case bytes.Equal(, []byte(`{}`)):
		 = []byte("true")
	case bytes.Equal(, []byte(`{"not":true}`)):
		 = []byte("false")
	}
	return , nil
}

// orderedProperties is a helper to marshal the properties map in a specific order.
type orderedProperties struct {
	props map[string]*Schema
	order []string
}

func ( orderedProperties) () ([]byte, error) {
	var  bytes.Buffer
	.WriteByte('{')

	 := true
	 := make(map[string]bool, len(.props))

	// Helper closure to write "key": value
	 := func( string,  *Schema) error {
		if ! {
			.WriteByte(',')
		}
		 = false

		// Marshal the Key
		,  := json.Marshal()
		if  != nil {
			return 
		}
		.Write()

		.WriteByte(':')

		// Marshal the Value
		,  := json.Marshal()
		if  != nil {
			return 
		}
		.Write()
		return nil
	}

	// Write keys explicitly listed in PropertyOrder
	for ,  := range .order {
		if ,  := .props[];  {
			if  := (, );  != nil {
				return nil, 
			}
			[] = true
		}
	}

	// Write any remaining keys
	var  []string
	for  := range .props {
		if ![] {
			 = append(, )
		}
	}

	// Sort the slice alphabetically
	slices.Sort()

	for ,  := range  {
		if  := (, .props[]);  != nil {
			return nil, 
		}
	}

	.WriteByte('}')
	return .Bytes(), nil
}

func ( *Schema) ( []byte) error {
	// A JSON boolean is a valid schema.
	var  bool
	if  := json.Unmarshal(, &);  == nil {
		if  {
			// true is the empty schema, which validates everything.
			* = Schema{}
		} else {
			// false is the schema that validates nothing.
			* = *falseSchema()
		}
		return nil
	}

	 := struct {
		          json.RawMessage            `json:"type,omitempty"`
		  map[string]json.RawMessage `json:"dependencies,omitempty"`
		         json.RawMessage            `json:"items,omitempty"`
		         json.RawMessage            `json:"const,omitempty"`
		     *integer                   `json:"minLength,omitempty"`
		     *integer                   `json:"maxLength,omitempty"`
		      *integer                   `json:"minItems,omitempty"`
		      *integer                   `json:"maxItems,omitempty"`
		 *integer                   `json:"minProperties,omitempty"`
		 *integer                   `json:"maxProperties,omitempty"`
		   *integer                   `json:"minContains,omitempty"`
		   *integer                   `json:"maxContains,omitempty"`

		*schemaWithoutMethods
	}{
		: (*schemaWithoutMethods)(),
	}
	if  := unmarshalStructWithMap(, &, "Extra");  != nil {
		return 
	}
	// Unmarshal "type" as either Type or Types.
	var  error
	if len(.) > 0 {
		switch .[0] {
		case '"':
			 = json.Unmarshal(., &.Type)
		case '[':
			 = json.Unmarshal(., &.Types)
		default:
			 = fmt.Errorf(`invalid value for "type": %q`, .)
		}
	}
	if  != nil {
		return 
	}

	// Unmarshal "items" as either Items or ItemsArray.
	if len(.) > 0 {
		switch .[0] {
		case '[':
			var  []*Schema
			 = json.Unmarshal(., &)
			.ItemsArray = 
		default:
			var  Schema
			 = json.Unmarshal(., &)
			.Items = &
		}
	}
	if  != nil {
		return 
	}

	// Unmarshal "Dependencies" values as either string arrays or schemas
	// and assign them to specific map DependencySchemas or DependencyStrings.
	for ,  := range . {
		if len() > 0 {
			switch [0] {
			case '[':
				var  []string
				 = json.Unmarshal(, &)
				if .DependencyStrings == nil {
					.DependencyStrings = make(map[string][]string)
				}
				.DependencyStrings[] = 
			default:
				var  Schema
				 = json.Unmarshal(, &)
				if .DependencySchemas == nil {
					.DependencySchemas = make(map[string]*Schema)
				}
				.DependencySchemas[] = &
			}
		}
		if  != nil {
			return 
		}
	}

	 := func( **any,  json.RawMessage) error {
		if len() == 0 {
			return nil
		}
		if bytes.Equal(, []byte("null")) {
			* = new(any)
			return nil
		}
		return json.Unmarshal(, )
	}

	// Setting Const to a pointer to null will marshal properly, but won't
	// unmarshal: the *any is set to nil, not a pointer to nil.
	if  := (&.Const, .);  != nil {
		return 
	}

	 := func( **int,  *integer) {
		if  != nil {
			* = Ptr(int(*))
		}
	}

	(&.MinLength, .)
	(&.MaxLength, .)
	(&.MinItems, .)
	(&.MaxItems, .)
	(&.MinProperties, .)
	(&.MaxProperties, .)
	(&.MinContains, .)
	(&.MaxContains, .)

	return nil
}

type integer int32 // for the integer-valued fields of Schema

func ( *integer) ( []byte) error {
	if len() == 0 {
		// nothing to do
		return nil
	}
	// If there is a decimal point, src is a floating-point number.
	var  int64
	if bytes.ContainsRune(, '.') {
		var  float64
		if  := json.Unmarshal(, &);  != nil {
			return errors.New("not a number")
		}
		 = int64()
		if float64() !=  {
			return errors.New("not an integer value")
		}
	} else {
		if  := json.Unmarshal(, &);  != nil {
			return errors.New("cannot be unmarshaled into an int")
		}
	}
	// Ensure behavior is the same on both 32-bit and 64-bit systems.
	if  < math.MinInt32 ||  > math.MaxInt32 {
		return errors.New("integer is out of range")
	}
	* = integer()
	return nil
}

// Ptr returns a pointer to a new variable whose value is x.
func [ any]( ) * { return & }

// every applies f preorder to every schema under s including s.
// The second argument to f is the path to the schema appended to the argument path.
// It stops when f returns false.
func ( *Schema) ( func(*Schema) bool) bool {
	return () && .everyChild(func( *Schema) bool { return .() })
}

// everyChild reports whether f is true for every immediate child schema of s.
func ( *Schema) ( func(*Schema) bool) bool {
	 := reflect.ValueOf()
	for ,  := range schemaFieldInfos {
		 := .Elem().FieldByIndex(.sf.Index)
		switch .sf.Type {
		case schemaType:
			// A field that contains an individual schema. A nil is valid: it just means the field isn't present.
			 := .Interface().(*Schema)
			if  != nil && !() {
				return false
			}

		case schemaSliceType:
			 := .Interface().([]*Schema)
			for ,  := range  {
				if !() {
					return false
				}
			}

		case schemaMapType:
			// Sort keys for determinism.
			 := .Interface().(map[string]*Schema)
			for ,  := range slices.Sorted(maps.Keys()) {
				if !([]) {
					return false
				}
			}
		}
	}

	return true
}

// all wraps every in an iterator.
func ( *Schema) () iter.Seq[*Schema] {
	return func( func(*Schema) bool) { .every() }
}

// children wraps everyChild in an iterator.
func ( *Schema) () iter.Seq[*Schema] {
	return func( func(*Schema) bool) { .everyChild() }
}

var (
	schemaType      = reflect.TypeFor[*Schema]()
	schemaSliceType = reflect.TypeFor[[]*Schema]()
	schemaMapType   = reflect.TypeFor[map[string]*Schema]()
)

type structFieldInfo struct {
	sf       reflect.StructField
	jsonName string
}

var (
	// the visible fields of Schema that have a JSON name, sorted by that name
	schemaFieldInfos []structFieldInfo
	// map from JSON name to field
	schemaFieldMap = map[string]reflect.StructField{}
)

func init() {
	 := reflect.VisibleFields(reflect.TypeFor[Schema]())
	for ,  := range  {
		 := fieldJSONInfo()
		if !.omit {
			schemaFieldInfos = append(schemaFieldInfos, structFieldInfo{, .name})
		} else {
			// jsoninfo.name is used to build the info paths. The items and dependencies are ommited,
			// since the original fields are separated to handle the union types supported in json and
			// these fields have custom marshalling and unmarshalling logic.
			// we still need these fields in schemaFieldInfos for creating schema trees and calculating paths and refs.
			// so we manually create them and assign the jsonName to the original field json name.
			switch .Name {
			case "Items", "ItemsArray":
				schemaFieldInfos = append(schemaFieldInfos, structFieldInfo{, "items"})
			case "DependencySchemas", "DependencyStrings":
				schemaFieldInfos = append(schemaFieldInfos, structFieldInfo{, "dependencies"})
			}
		}
	}
	// The value of "dependencies" this sort of schemaFieldInfos.
	// This sort is unstable and is comparing the json.names of DependencyStrings and DependencySchemas which are both "dependencies".
	// Since the sort is unstable it cannot be guarantied that "dependencies" has the DependencySchemas value.
	slices.SortFunc(schemaFieldInfos, func(,  structFieldInfo) int {
		return cmp.Compare(.jsonName, .jsonName)
	})
	for ,  := range schemaFieldInfos {
		schemaFieldMap[.jsonName] = .sf
	}
}