// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build goexperiment.jsonv2

package json

import (
	
	
	
	
	
	
	
	
	
	

	
	
)

type isZeroer interface {
	IsZero() bool
}

var isZeroerType = reflect.TypeFor[isZeroer]()

type structFields struct {
	flattened       []structField // listed in depth-first ordering
	byActualName    map[string]*structField
	byFoldedName    map[string][]*structField
	inlinedFallback *structField
}

// reindex recomputes index to avoid bounds check during runtime.
//
// During the construction of each [structField] in [makeStructFields],
// the index field is 0-indexed. However, before it returns,
// the 0th field is stored in index0 and index stores the remainder.
func ( *structFields) () {
	 := func( *structField) {
		.index0 = .index[0]
		.index = .index[1:]
		if len(.index) == 0 {
			.index = nil // avoid pinning the backing slice
		}
	}
	for  := range .flattened {
		(&.flattened[])
	}
	if .inlinedFallback != nil {
		(.inlinedFallback)
	}
}

// lookupByFoldedName looks up name by a case-insensitive match
// that also ignores the presence of dashes and underscores.
func ( *structFields) ( []byte) []*structField {
	return .byFoldedName[string(foldName())]
}

type structField struct {
	id      int   // unique numeric ID in breadth-first ordering
	index0  int   // 0th index into a struct according to [reflect.Type.FieldByIndex]
	index   []int // 1st index and remainder according to [reflect.Type.FieldByIndex]
	typ     reflect.Type
	fncs    *arshaler
	isZero  func(addressableValue) bool
	isEmpty func(addressableValue) bool
	fieldOptions
}

var errNoExportedFields = errors.New("Go struct has no exported fields")

func makeStructFields( reflect.Type) ( structFields,  *SemanticError) {
	 := func( *SemanticError,  reflect.Type,  string,  ...any) *SemanticError {
		return cmp.Or(, &SemanticError{GoType: , Err: fmt.Errorf(, ...)})
	}

	// Setup a queue for a breath-first search.
	var  int
	type  struct {
		           reflect.Type
		         []int
		 bool // whether to recursively visit inlined field in this struct
	}
	 := []{{, nil, true}}
	 := map[reflect.Type]bool{: true}

	// Perform a breadth-first search over all reachable fields.
	// This ensures that len(f.index) will be monotonically increasing.
	var ,  []structField
	for  < len() {
		 := []
		++

		 := .
		 := -1         // index of last inlined fallback field in current struct
		 := make(map[string]int) // index of each field with a given JSON object name in current struct
		var  bool             // whether any Go struct field has a `json` tag
		var  bool           // whether any JSON serializable fields exist in current struct
		for  := range .NumField() {
			 := .Field()
			,  := .Tag.Lookup("json")
			 =  || 
			, ,  := parseFieldOptions()
			if  != nil {
				 = cmp.Or(, &SemanticError{GoType: , Err: })
			}
			if  {
				continue
			}
			 = true
			 := structField{
				// Allocate a new slice (len=N+1) to hold both
				// the parent index (len=N) and the current index (len=1).
				// Do this to avoid clobbering the memory of the parent index.
				index:        append(append(make([]int, 0, len(.)+1), ....), ),
				typ:          .Type,
				fieldOptions: ,
			}
			if .Anonymous && !.hasName {
				if indirectType(.typ).Kind() != reflect.Struct {
					 = (, , "embedded Go struct field %s of non-struct type must be explicitly given a JSON name", .Name)
				} else {
					.inline = true // implied by use of Go embedding without an explicit name
				}
			}
			if .inline || .unknown {
				// Handle an inlined field that serializes to/from
				// zero or more JSON object members.

				switch .fieldOptions {
				case fieldOptions{name: .name, quotedName: .quotedName, inline: true}:
				case fieldOptions{name: .name, quotedName: .quotedName, unknown: true}:
				case fieldOptions{name: .name, quotedName: .quotedName, inline: true, unknown: true}:
					 = (, , "Go struct field %s cannot have both `inline` and `unknown` specified", .Name)
					.inline = false // let `unknown` take precedence
				default:
					 = (, , "Go struct field %s cannot have any options other than `inline` or `unknown` specified", .Name)
					if .hasName {
						continue // invalid inlined field; treat as ignored
					}
					.fieldOptions = fieldOptions{name: .name, quotedName: .quotedName, inline: .inline, unknown: .unknown}
					if .inline && .unknown {
						.inline = false // let `unknown` take precedence
					}
				}

				// Reject any types with custom serialization otherwise
				// it becomes impossible to know what sub-fields to inline.
				 := indirectType(.typ)
				if implementsAny(, allMethodTypes...) &&  != jsontextValueType {
					 = (, , "inlined Go struct field %s of type %s must not implement marshal or unmarshal methods", .Name, )
				}

				// Handle an inlined field that serializes to/from
				// a finite number of JSON object members backed by a Go struct.
				if .Kind() == reflect.Struct {
					if .unknown {
						 = (, , "inlined Go struct field %s of type %s with `unknown` tag must be a Go map of string key or a jsontext.Value", .Name, )
						continue // invalid inlined field; treat as ignored
					}
					if . {
						 = append(, {, .index, ![]})
					}
					[] = true
					continue
				} else if !.IsExported() {
					 = (, , "inlined Go struct field %s is not exported", .Name)
					continue // invalid inlined field; treat as ignored
				}

				// Handle an inlined field that serializes to/from any number of
				// JSON object members back by a Go map or jsontext.Value.
				switch {
				case  == jsontextValueType:
					.fncs = nil // specially handled in arshal_inlined.go
				case .Kind() == reflect.Map && .Key().Kind() == reflect.String:
					if implementsAny(.Key(), allMethodTypes...) {
						 = (, , "inlined map field %s of type %s must have a string key that does not implement marshal or unmarshal methods", .Name, )
						continue // invalid inlined field; treat as ignored
					}
					.fncs = lookupArshaler(.Elem())
				default:
					 = (, , "inlined Go struct field %s of type %s must be a Go struct, Go map of string key, or jsontext.Value", .Name, )
					continue // invalid inlined field; treat as ignored
				}

				// Reject multiple inlined fallback fields within the same struct.
				if  >= 0 {
					 = (, , "inlined Go struct fields %s and %s cannot both be a Go map or jsontext.Value", .Field().Name, .Name)
					// Still append f to inlinedFallbacks as there is still a
					// check for a dominant inlined fallback before returning.
				}
				 = 

				 = append(, )
			} else {
				// Handle normal Go struct field that serializes to/from
				// a single JSON object member.

				// Unexported fields cannot be serialized except for
				// embedded fields of a struct type,
				// which might promote exported fields of their own.
				if !.IsExported() {
					 := indirectType(.typ)
					if !(.Anonymous && .Kind() == reflect.Struct) {
						 = (, , "Go struct field %s is not exported", .Name)
						continue
					}
					// Unfortunately, methods on the unexported field
					// still cannot be called.
					if implementsAny(, allMethodTypes...) ||
						(.omitzero && implementsAny(, isZeroerType)) {
						 = (, , "Go struct field %s is not exported for method calls", .Name)
						continue
					}
				}

				// Provide a function that uses a type's IsZero method.
				switch {
				case .Type.Kind() == reflect.Interface && .Type.Implements(isZeroerType):
					.isZero = func( addressableValue) bool {
						// Avoid panics calling IsZero on a nil interface or
						// non-nil interface with nil pointer.
						return .IsNil() || (.Elem().Kind() == reflect.Pointer && .Elem().IsNil()) || .Interface().(isZeroer).IsZero()
					}
				case .Type.Kind() == reflect.Pointer && .Type.Implements(isZeroerType):
					.isZero = func( addressableValue) bool {
						// Avoid panics calling IsZero on nil pointer.
						return .IsNil() || .Interface().(isZeroer).IsZero()
					}
				case .Type.Implements(isZeroerType):
					.isZero = func( addressableValue) bool { return .Interface().(isZeroer).IsZero() }
				case reflect.PointerTo(.Type).Implements(isZeroerType):
					.isZero = func( addressableValue) bool { return .Addr().Interface().(isZeroer).IsZero() }
				}

				// Provide a function that can determine whether the value would
				// serialize as an empty JSON value.
				switch .Type.Kind() {
				case reflect.String, reflect.Map, reflect.Array, reflect.Slice:
					.isEmpty = func( addressableValue) bool { return .Len() == 0 }
				case reflect.Pointer, reflect.Interface:
					.isEmpty = func( addressableValue) bool { return .IsNil() }
				}

				// Reject multiple fields with same name within the same struct.
				if ,  := [.name];  {
					 = (, , "Go struct fields %s and %s conflict over JSON object name %q", .Field().Name, .Name, .name)
					// Still append f to allFields as there is still a
					// check for a dominant field before returning.
				}
				[.name] = 

				.id = len()
				.fncs = lookupArshaler(.Type)
				 = append(, )
			}
		}

		// NOTE: New users to the json package are occasionally surprised that
		// unexported fields are ignored. This occurs by necessity due to our
		// inability to directly introspect such fields with Go reflection
		// without the use of unsafe.
		//
		// To reduce friction here, refuse to serialize any Go struct that
		// has no JSON serializable fields, has at least one Go struct field,
		// and does not have any `json` tags present. For example,
		// errors returned by errors.New would fail to serialize.
		 := .NumField() == 0
		if ! && ! && ! {
			 = cmp.Or(, &SemanticError{GoType: , Err: errNoExportedFields})
		}
	}

	// Sort the fields by exact name (breaking ties by depth and
	// then by presence of an explicitly provided JSON name).
	// Select the dominant field from each set of fields with the same name.
	// If multiple fields have the same name, then the dominant field
	// is the one that exists alone at the shallowest depth,
	// or the one that is uniquely tagged with a JSON name.
	// Otherwise, no dominant field exists for the set.
	 := [:0]
	slices.SortStableFunc(, func(,  structField) int {
		return cmp.Or(
			strings.Compare(.name, .name),
			cmp.Compare(len(.index), len(.index)),
			boolsCompare(!.hasName, !.hasName))
	})
	for len() > 0 {
		 := 1 // number of fields with the same exact name
		for  < len() && [-1].name == [].name {
			++
		}
		if  == 1 || len([0].index) != len([1].index) || [0].hasName != [1].hasName {
			 = append(, [0]) // only keep field if there is a dominant field
		}
		 = [:]
	}

	// Sort the fields according to a breadth-first ordering
	// so that we can re-number IDs with the smallest possible values.
	// This optimizes use of uintSet such that it fits in the 64-entry bit set.
	slices.SortFunc(, func(,  structField) int {
		return cmp.Compare(.id, .id)
	})
	for  := range  {
		[].id = 
	}

	// Sort the fields according to a depth-first ordering
	// as the typical order that fields are marshaled.
	slices.SortFunc(, func(,  structField) int {
		return slices.Compare(.index, .index)
	})

	// Compute the mapping of fields in the byActualName map.
	// Pre-fold all names so that we can lookup folded names quickly.
	 = structFields{
		flattened:    ,
		byActualName: make(map[string]*structField, len()),
		byFoldedName: make(map[string][]*structField, len()),
	}
	for ,  := range .flattened {
		 := string(foldName([]byte(.name)))
		.byActualName[.name] = &.flattened[]
		.byFoldedName[] = append(.byFoldedName[], &.flattened[])
	}
	for ,  := range .byFoldedName {
		if len() > 1 {
			// The precedence order for conflicting ignoreCase names
			// is by breadth-first order, rather than depth-first order.
			slices.SortFunc(, func(,  *structField) int {
				return cmp.Compare(.id, .id)
			})
			.byFoldedName[] = 
		}
	}
	if  := len();  == 1 || ( > 1 && len([0].index) != len([1].index)) {
		.inlinedFallback = &[0] // dominant inlined fallback field
	}
	.reindex()
	return , 
}

// indirectType unwraps one level of pointer indirection
// similar to how Go only allows embedding either T or *T,
// but not **T or P (which is a named pointer).
func indirectType( reflect.Type) reflect.Type {
	if .Kind() == reflect.Pointer && .Name() == "" {
		 = .Elem()
	}
	return 
}

// matchFoldedName matches a case-insensitive name depending on the options.
// It assumes that foldName(f.name) == foldName(name).
//
// Case-insensitive matching is used if the `case:ignore` tag option is specified
// or the MatchCaseInsensitiveNames call option is specified
// (and the `case:strict` tag option is not specified).
// Functionally, the `case:ignore` and `case:strict` tag options take precedence.
//
// The v1 definition of case-insensitivity operated under strings.EqualFold
// and would strictly compare dashes and underscores,
// while the v2 definition would ignore the presence of dashes and underscores.
// Thus, if the MatchCaseSensitiveDelimiter call option is specified,
// the match is further restricted to using strings.EqualFold.
func ( *structField) ( []byte,  *jsonflags.Flags) bool {
	if .casing == caseIgnore || (.Get(jsonflags.MatchCaseInsensitiveNames) && .casing != caseStrict) {
		if !.Get(jsonflags.MatchCaseSensitiveDelimiter) || strings.EqualFold(string(), .name) {
			return true
		}
	}
	return false
}

const (
	caseIgnore = 1
	caseStrict = 2
)

type fieldOptions struct {
	name           string
	quotedName     string // quoted name per RFC 8785, section 3.2.2.2.
	hasName        bool
	nameNeedEscape bool
	casing         int8 // either 0, caseIgnore, or caseStrict
	inline         bool
	unknown        bool
	omitzero       bool
	omitempty      bool
	string         bool
	format         string
}

// parseFieldOptions parses the `json` tag in a Go struct field as
// a structured set of options configuring parameters such as
// the JSON member name and other features.
func parseFieldOptions( reflect.StructField) ( fieldOptions,  bool,  error) {
	,  := .Tag.Lookup("json")

	// Check whether this field is explicitly ignored.
	if  == "-" {
		return fieldOptions{}, true, nil
	}

	// Check whether this field is unexported and not embedded,
	// which Go reflection cannot mutate for the sake of serialization.
	//
	// An embedded field of an unexported type is still capable of
	// forwarding exported fields, which may be JSON serialized.
	// This technically operates on the edge of what is permissible by
	// the Go language, but the most recent decision is to permit this.
	//
	// See https://go.dev/issue/24153 and https://go.dev/issue/32772.
	if !.IsExported() && !.Anonymous {
		// Tag options specified on an unexported field suggests user error.
		if  {
			 = cmp.Or(, fmt.Errorf("unexported Go struct field %s cannot have non-ignored `json:%q` tag", .Name, ))
		}
		return fieldOptions{}, true, 
	}

	// Determine the JSON member name for this Go field. A user-specified name
	// may be provided as either an identifier or a single-quoted string.
	// The single-quoted string allows arbitrary characters in the name.
	// See https://go.dev/issue/2718 and https://go.dev/issue/3546.
	.name = .Name // always starts with an uppercase character
	if len() > 0 && !strings.HasPrefix(, ",") {
		// For better compatibility with v1, accept almost any unescaped name.
		 := len() - len(strings.TrimLeftFunc(, func( rune) bool {
			return !strings.ContainsRune(",\\'\"`", ) // reserve comma, backslash, and quotes
		}))
		 := [:]

		// If the next character is not a comma, then the name is either
		// malformed (if n > 0) or a single-quoted name.
		// In either case, call consumeTagOption to handle it further.
		var  error
		if !strings.HasPrefix([:], ",") && len() != len() {
			, ,  = consumeTagOption()
			if  != nil {
				 = cmp.Or(, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", .Name, ))
			}
		}
		if !utf8.ValidString() {
			 = cmp.Or(, fmt.Errorf("Go struct field %s has JSON object name %q with invalid UTF-8", .Name, ))
			 = string([]rune()) // replace invalid UTF-8 with utf8.RuneError
		}
		if  == nil {
			.hasName = true
			.name = 
		}
		 = [:]
	}
	,  := jsonwire.AppendQuote(nil, .name, &jsonflags.Flags{})
	.quotedName = string()
	.nameNeedEscape = jsonwire.NeedEscape(.name)

	// Handle any additional tag options (if any).
	var  bool
	 := make(map[string]bool)
	for len() > 0 {
		// Consume comma delimiter.
		if [0] != ',' {
			 = cmp.Or(, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid character %q before next option (expecting ',')", .Name, [0]))
		} else {
			 = [len(","):]
			if len() == 0 {
				 = cmp.Or(, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid trailing ',' character", .Name))
				break
			}
		}

		// Consume and process the tag option.
		, ,  := consumeTagOption()
		if  != nil {
			 = cmp.Or(, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", .Name, ))
		}
		 := [:]
		 = [:]
		switch {
		case :
			 = cmp.Or(, fmt.Errorf("Go struct field %s has `format` tag option that was not specified last", .Name))
		case strings.HasPrefix(, "'") && strings.TrimFunc(, isLetterOrDigit) == "":
			 = cmp.Or(, fmt.Errorf("Go struct field %s has unnecessarily quoted appearance of `%s` tag option; specify `%s` instead", .Name, , ))
		}
		switch  {
		case "case":
			if !strings.HasPrefix(, ":") {
				 = cmp.Or(, fmt.Errorf("Go struct field %s is missing value for `case` tag option; specify `case:ignore` or `case:strict` instead", .Name))
				break
			}
			 = [len(":"):]
			, ,  := consumeTagOption()
			if  != nil {
				 = cmp.Or(, fmt.Errorf("Go struct field %s has malformed value for `case` tag option: %v", .Name, ))
				break
			}
			 := [:]
			 = [:]
			if strings.HasPrefix(, "'") {
				 = cmp.Or(, fmt.Errorf("Go struct field %s has unnecessarily quoted appearance of `case:%s` tag option; specify `case:%s` instead", .Name, , ))
			}
			switch  {
			case "ignore":
				.casing |= caseIgnore
			case "strict":
				.casing |= caseStrict
			default:
				 = cmp.Or(, fmt.Errorf("Go struct field %s has unknown `case:%s` tag value", .Name, ))
			}
		case "inline":
			.inline = true
		case "unknown":
			.unknown = true
		case "omitzero":
			.omitzero = true
		case "omitempty":
			.omitempty = true
		case "string":
			.string = true
		case "format":
			if !strings.HasPrefix(, ":") {
				 = cmp.Or(, fmt.Errorf("Go struct field %s is missing value for `format` tag option", .Name))
				break
			}
			 = [len(":"):]
			, ,  := consumeTagOption()
			if  != nil {
				 = cmp.Or(, fmt.Errorf("Go struct field %s has malformed value for `format` tag option: %v", .Name, ))
				break
			}
			 = [:]
			.format = 
			 = true
		default:
			// Reject keys that resemble one of the supported options.
			// This catches invalid mutants such as "omitEmpty" or "omit_empty".
			 := strings.ReplaceAll(strings.ToLower(), "_", "")
			switch  {
			case "case", "inline", "unknown", "omitzero", "omitempty", "string", "format":
				 = cmp.Or(, fmt.Errorf("Go struct field %s has invalid appearance of `%s` tag option; specify `%s` instead", .Name, , ))
			}

			// NOTE: Everything else is ignored. This does not mean it is
			// forward compatible to insert arbitrary tag options since
			// a future version of this package may understand that tag.
		}

		// Reject duplicates.
		switch {
		case .casing == caseIgnore|caseStrict:
			 = cmp.Or(, fmt.Errorf("Go struct field %s cannot have both `case:ignore` and `case:strict` tag options", .Name))
		case []:
			 = cmp.Or(, fmt.Errorf("Go struct field %s has duplicate appearance of `%s` tag option", .Name, ))
		}
		[] = true
	}
	return , false, 
}

// consumeTagOption consumes the next option,
// which is either a Go identifier or a single-quoted string.
// If the next option is invalid, it returns all of in until the next comma,
// and reports an error.
func consumeTagOption( string) (string, int, error) {
	// For legacy compatibility with v1, assume options are comma-separated.
	 := strings.IndexByte(, ',')
	if  < 0 {
		 = len()
	}

	switch ,  := utf8.DecodeRuneInString(); {
	// Option as a Go identifier.
	case  == '_' || unicode.IsLetter():
		 := len() - len(strings.TrimLeftFunc(, isLetterOrDigit))
		return [:], , nil
	// Option as a single-quoted string.
	case  == '\'':
		// The grammar is nearly identical to a double-quoted Go string literal,
		// but uses single quotes as the terminators. The reason for a custom
		// grammar is because both backtick and double quotes cannot be used
		// verbatim in a struct tag.
		//
		// Convert a single-quoted string to a double-quote string and rely on
		// strconv.Unquote to handle the rest.
		var  bool
		 := []byte{'"'}
		 := len(`'`)
		for len() >  {
			,  := utf8.DecodeRuneInString([:])
			switch {
			case :
				if  == '\'' {
					 = [:len()-1] // remove escape character: `\'` => `'`
				}
				 = false
			case  == '\\':
				 = true
			case  == '"':
				 = append(, '\\') // insert escape character: `"` => `\"`
			case  == '\'':
				 = append(, '"')
				 += len(`'`)
				,  := strconv.Unquote(string())
				if  != nil {
					return [:], , fmt.Errorf("invalid single-quoted string: %s", [:])
				}
				return , , nil
			}
			 = append(, [:][:]...)
			 += 
		}
		if  > 10 {
			 = 10 // limit the amount of context printed in the error
		}
		return [:], , fmt.Errorf("single-quoted string not terminated: %s...", [:])
	case len() == 0:
		return [:], , io.ErrUnexpectedEOF
	default:
		return [:], , fmt.Errorf("invalid character %q at start of option (expecting Unicode letter or single quote)", )
	}
}

func isLetterOrDigit( rune) bool {
	return  == '_' || unicode.IsLetter() || unicode.IsNumber()
}

// boolsCompare compares x and y, ordering false before true.
func boolsCompare(,  bool) int {
	switch {
	case ! && :
		return -1
	default:
		return 0
	case  && !:
		return +1
	}
}