package avro

import (
	
	
	
	
	
	
	
	
	
	

	
	jsoniter 
)

var jsoniterAPI = jsoniter.Config{
	EscapeHTML:  true,
	SortMapKeys: true,
}.Froze()

type nullDefaultType struct{}

func (nullDefaultType) () ([]byte, error) {
	return []byte("null"), nil
}

var nullDefault nullDefaultType = struct{}{}

var (
	// Note: order matches the order of properties as they are named in the spec.
	// 	https://avro.apache.org/docs/1.12.0/specification
	recordReserved               = []string{"type", "name", "namespace", "doc", "aliases", "fields"}
	fieldReserved                = []string{"name", "doc", "type", "order", "aliases", "default"}
	enumReserved                 = []string{"type", "name", "namespace", "aliases", "doc", "symbols", "default"}
	arrayReserved                = []string{"type", "items"}
	mapReserved                  = []string{"type", "values"}
	fixedReserved                = []string{"type", "name", "namespace", "aliases", "size"}
	fixedWithLogicalTypeReserved = []string{"type", "name", "namespace", "aliases", "size", "logicalType"}
	fixedWithDecimalTypeReserved = []string{
		"type", "name", "namespace", "aliases", "size", "logicalType", "precision", "scale",
	}
	primitiveReserved                = []string{"type"}
	primitiveWithLogicalTypeReserved = []string{"type", "logicalType"}
	primitiveWithDecimalTypeReserved = []string{"type", "logicalType", "precision", "scale"}
)

// Type is a schema type.
type Type string

// Schema type constants.
const (
	Record  Type = "record"
	Error   Type = "error"
	Ref     Type = "<ref>"
	Enum    Type = "enum"
	Array   Type = "array"
	Map     Type = "map"
	Union   Type = "union"
	Fixed   Type = "fixed"
	String  Type = "string"
	Bytes   Type = "bytes"
	Int     Type = "int"
	Long    Type = "long"
	Float   Type = "float"
	Double  Type = "double"
	Boolean Type = "boolean"
	Null    Type = "null"
)

// Order is a field order.
type Order string

// Field orders.
const (
	Asc    Order = "ascending"
	Desc   Order = "descending"
	Ignore Order = "ignore"
)

// LogicalType is a schema logical type.
type LogicalType string

// Schema logical type constants.
const (
	Decimal              LogicalType = "decimal"
	UUID                 LogicalType = "uuid"
	Date                 LogicalType = "date"
	TimeMillis           LogicalType = "time-millis"
	TimeMicros           LogicalType = "time-micros"
	TimestampMillis      LogicalType = "timestamp-millis"
	TimestampMicros      LogicalType = "timestamp-micros"
	LocalTimestampMillis LogicalType = "local-timestamp-millis"
	LocalTimestampMicros LogicalType = "local-timestamp-micros"
	Duration             LogicalType = "duration"
)

// Action is a field action used during decoding process.
type Action string

// Action type constants.
const (
	FieldIgnore     Action = "ignore"
	FieldSetDefault Action = "set_default"
)

// FingerprintType is a fingerprinting algorithm.
type FingerprintType string

// Fingerprint type constants.
const (
	CRC64Avro   FingerprintType = "CRC64-AVRO"
	CRC64AvroLE FingerprintType = "CRC64-AVRO-LE"
	MD5         FingerprintType = "MD5"
	SHA256      FingerprintType = "SHA256"
)

// SchemaCache is a cache of schemas.
type SchemaCache struct {
	cache sync.Map // map[string]Schema
}

// Add adds a schema to the cache with the given name.
func ( *SchemaCache) ( string,  Schema) {
	.cache.Store(, )
}

// Get returns the Schema if it exists.
func ( *SchemaCache) ( string) Schema {
	if ,  := .cache.Load();  {
		return .(Schema)
	}

	return nil
}

// AddAll adds all schemas from the given cache to the current cache.
func ( *SchemaCache) ( *SchemaCache) {
	if  == nil {
		return
	}
	.cache.Range(func(,  interface{}) bool {
		.cache.Store(, )
		return true
	})
}

// Schemas is a slice of Schemas.
type Schemas []Schema

// Get gets a schema and position by type or name if it is a named schema.
func ( Schemas) ( string) (Schema, int) {
	for ,  := range  {
		if schemaTypeName() ==  {
			return , 
		}
	}

	return nil, -1
}

// Schema represents an Avro schema.
type Schema interface {
	// Type returns the type of the schema.
	Type() Type

	// String returns the canonical form of the schema.
	String() string

	// Fingerprint returns the SHA256 fingerprint of the schema.
	Fingerprint() [32]byte

	// FingerprintUsing returns the fingerprint of the schema using the given algorithm or an error.
	FingerprintUsing(FingerprintType) ([]byte, error)

	// CacheFingerprint returns the unique identity of the schema.
	// This returns a unique identity for schemas resolved from a writer schema, otherwise it returns
	// the schemas Fingerprint.
	CacheFingerprint() [32]byte
}

// LogicalSchema represents an Avro schema with a logical type.
type LogicalSchema interface {
	// Type returns the type of the logical schema.
	Type() LogicalType

	// String returns the canonical form of the logical schema.
	String() string
}

// PropertySchema represents a schema with properties.
type PropertySchema interface {
	// Prop gets a property from the schema.
	Prop(string) any
}

// NamedSchema represents a schema with a name.
type NamedSchema interface {
	Schema
	PropertySchema

	// Name returns the name of the schema.
	Name() string

	// Namespace returns the namespace of a schema.
	Namespace() string

	// FullName returns the full qualified name of a schema.
	FullName() string

	// Aliases returns the full qualified aliases of a schema.
	Aliases() []string
}

// LogicalTypeSchema represents a schema that can contain a logical type.
type LogicalTypeSchema interface {
	// Logical returns the logical schema or nil.
	Logical() LogicalSchema
}

type name struct {
	name      string
	namespace string
	full      string
	aliases   []string
}

func newName(,  string,  []string) (name, error) {
	if  := strings.LastIndexByte(, '.');  > -1 {
		 = [:]
		 = [+1:]
	}

	 := 
	if  != "" {
		 =  + "." + 
	}

	for ,  := range strings.Split(, ".") {
		if  := validateName();  != nil {
			return name{}, fmt.Errorf("avro: invalid name part %q in name %q: %w", , , )
		}
	}

	 := make([]string, 0, len())
	for ,  := range  {
		if !strings.Contains(, ".") {
			if  := validateName();  != nil {
				return name{}, fmt.Errorf("avro: invalid name %q: %w", , )
			}
			if  == "" {
				 = append(, )
				continue
			}
			 = append(, +"."+)
			continue
		}

		for ,  := range strings.Split(, ".") {
			if  := validateName();  != nil {
				return name{}, fmt.Errorf("avro: invalid name part %q in name %q: %w", , , )
			}
		}
		 = append(, )
	}

	return name{
		name:      ,
		namespace: ,
		full:      ,
		aliases:   ,
	}, nil
}

// Name returns the name of a schema.
func ( name) () string {
	return .name
}

// Namespace returns the namespace of a schema.
func ( name) () string {
	return .namespace
}

// FullName returns the fully qualified name of a schema.
func ( name) () string {
	return .full
}

// Aliases returns the fully qualified aliases of a schema.
func ( name) () []string {
	return .aliases
}

type fingerprinter struct {
	fingerprint atomic.Value // [32]byte
	cache       sync.Map     // map[FingerprintType][]byte
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func ( *fingerprinter) ( fmt.Stringer) [32]byte {
	if  := .fingerprint.Load();  != nil {
		return .([32]byte)
	}

	 := sha256.Sum256([]byte(.String()))
	.fingerprint.Store()
	return 
}

// FingerprintUsing returns the fingerprint of the schema using the given algorithm or an error.
func ( *fingerprinter) ( FingerprintType,  fmt.Stringer) ([]byte, error) {
	if ,  := .cache.Load();  {
		return .([]byte), nil
	}

	 := []byte(.String())

	var  []byte
	switch  {
	case CRC64Avro:
		 := crc64.Sum()
		 = [:]
	case CRC64AvroLE:
		 := crc64.SumWithByteOrder(, crc64.LittleEndian)
		 = [:]
	case MD5:
		 := md5.Sum()
		 = [:]
	case SHA256:
		 := sha256.Sum256()
		 = [:]
	default:
		return nil, fmt.Errorf("avro: unknown fingerprint algorithm %s", )
	}

	.cache.Store(, )
	return , nil
}

type cacheFingerprinter struct {
	writerFingerprint *[32]byte

	cache atomic.Value // [32]byte
}

// CacheFingerprint returns the SHA256 identity of the schema.
func ( *cacheFingerprinter) ( Schema,  func() []byte) [32]byte {
	if  := .cache.Load();  != nil {
		return .([32]byte)
	}

	if .writerFingerprint == nil {
		 := .Fingerprint()
		.cache.Store()
		return 
	}

	 := .Fingerprint()
	 := append([]byte{}, [:]...)
	 = append(, (*.writerFingerprint)[:]...)
	if  != nil {
		 = append(, ()...)
	}
	 := sha256.Sum256()
	.cache.Store()
	return 
}

type properties struct {
	props map[string]any
}

func newProperties( map[string]any,  []string) properties {
	 := properties{props: map[string]any{}}
	for ,  := range  {
		if isReserved(, ) {
			continue
		}
		.props[] = 
	}
	return 
}

func isReserved( []string,  string) bool {
	for ,  := range  {
		if  ==  {
			return true
		}
	}
	return false
}

// Prop gets a property from the schema.
func ( properties) ( string) any {
	if .props == nil {
		return nil
	}

	return .props[]
}

// Props returns a map that contains all schema custom properties.
// Any accidental change to the returned map will directly modify the schema custom properties.
func ( properties) () map[string]any {
	return .props
}

func ( properties) ( *bytes.Buffer) error {
	 := make([]string, 0, len(.props))
	for  := range .props {
		 = append(, )
	}
	sort.Strings()
	for ,  := range  {
		,  := jsoniterAPI.Marshal(.props[])
		if  != nil {
			return 
		}
		,  := jsoniterAPI.Marshal()
		if  != nil {
			return 
		}
		.WriteString(`,`)
		.Write()
		.WriteString(`:`)
		.Write()
	}
	return nil
}

type schemaConfig struct {
	aliases []string
	doc     string
	def     any
	order   Order
	props   map[string]any
	wfp     *[32]byte
}

// SchemaOption is a function that sets a schema option.
type SchemaOption func(*schemaConfig)

// WithAliases sets the aliases on a schema.
func ( []string) SchemaOption {
	return func( *schemaConfig) {
		.aliases = 
	}
}

// WithDoc sets the doc on a schema.
func ( string) SchemaOption {
	return func( *schemaConfig) {
		.doc = 
	}
}

// WithDefault sets the default on a schema.
func ( any) SchemaOption {
	return func( *schemaConfig) {
		.def = 
	}
}

// WithOrder sets the order on a schema.
func ( Order) SchemaOption {
	return func( *schemaConfig) {
		.order = 
	}
}

// WithProps sets the properties on a schema.
func ( map[string]any) SchemaOption {
	return func( *schemaConfig) {
		.props = 
	}
}

func withWriterFingerprint( [32]byte) SchemaOption {
	return func( *schemaConfig) {
		.wfp = &
	}
}

func withWriterFingerprintIfResolved( [32]byte,  bool) SchemaOption {
	return func( *schemaConfig) {
		if  {
			.wfp = &
		}
	}
}

// PrimitiveSchema is an Avro primitive type schema.
type PrimitiveSchema struct {
	properties
	fingerprinter
	cacheFingerprinter

	typ     Type
	logical LogicalSchema

	// encodedType is the type of the encoded value, if it is different from the typ.
	// It's only used in the context of write-read schema resolution.
	encodedType Type
}

// NewPrimitiveSchema creates a new PrimitiveSchema.
func ( Type,  LogicalSchema,  ...SchemaOption) *PrimitiveSchema {
	var  schemaConfig
	for ,  := range  {
		(&)
	}
	 := primitiveReserved
	if  != nil {
		if .Type() == Decimal {
			 = primitiveWithDecimalTypeReserved
		} else {
			 = primitiveWithLogicalTypeReserved
		}
	}
	return &PrimitiveSchema{
		properties:         newProperties(.props, ),
		cacheFingerprinter: cacheFingerprinter{writerFingerprint: .wfp},
		typ:                ,
		logical:            ,
	}
}

// Type returns the type of the schema.
func ( *PrimitiveSchema) () Type {
	return .typ
}

// Logical returns the logical schema or nil.
func ( *PrimitiveSchema) () LogicalSchema {
	return .logical
}

// String returns the canonical form of the schema.
func ( *PrimitiveSchema) () string {
	if .logical == nil {
		return `"` + string(.typ) + `"`
	}

	return `{"type":"` + string(.typ) + `",` + .logical.String() + `}`
}

// MarshalJSON marshals the schema to json.
func ( *PrimitiveSchema) () ([]byte, error) {
	if .logical == nil && len(.props) == 0 {
		return jsoniterAPI.Marshal(.typ)
	}

	 := new(bytes.Buffer)
	.WriteString(`{"type":"` + string(.typ) + `"`)
	if .logical != nil {
		.WriteString(`,"logicalType":"` + string(.logical.Type()) + `"`)
		if ,  := .logical.(*DecimalLogicalSchema);  {
			.WriteString(`,"precision":` + strconv.Itoa(.prec))
			if .scale > 0 {
				.WriteString(`,"scale":` + strconv.Itoa(.scale))
			}
		}
	}
	if  := .marshalPropertiesToJSON();  != nil {
		return nil, 
	}
	.WriteString("}")
	return .Bytes(), nil
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func ( *PrimitiveSchema) () [32]byte {
	return .fingerprinter.Fingerprint()
}

// FingerprintUsing returns the fingerprint of the schema using the given algorithm or an error.
func ( *PrimitiveSchema) ( FingerprintType) ([]byte, error) {
	return .fingerprinter.FingerprintUsing(, )
}

// CacheFingerprint returns unique identity of the schema.
func ( *PrimitiveSchema) () [32]byte {
	return .cacheFingerprinter.CacheFingerprint(, nil)
}

// RecordSchema is an Avro record type schema.
type RecordSchema struct {
	name
	properties
	fingerprinter
	cacheFingerprinter
	isError bool
	fields  []*Field
	doc     string
}

// NewRecordSchema creates a new record schema instance.
func (,  string,  []*Field,  ...SchemaOption) (*RecordSchema, error) {
	var  schemaConfig
	for ,  := range  {
		(&)
	}

	,  := newName(, , .aliases)
	if  != nil {
		return nil, 
	}

	return &RecordSchema{
		name:               ,
		properties:         newProperties(.props, recordReserved),
		cacheFingerprinter: cacheFingerprinter{writerFingerprint: .wfp},
		fields:             ,
		doc:                .doc,
	}, nil
}

// NewErrorRecordSchema creates a new error record schema instance.
func (,  string,  []*Field,  ...SchemaOption) (*RecordSchema, error) {
	,  := NewRecordSchema(, , , ...)
	if  != nil {
		return nil, 
	}

	.isError = true

	return , nil
}

// Type returns the type of the schema.
func ( *RecordSchema) () Type {
	return Record
}

// Doc returns the documentation of a record.
func ( *RecordSchema) () string {
	return .doc
}

// IsError determines is this is an error record.
func ( *RecordSchema) () bool {
	return .isError
}

// Fields returns the fields of a record.
func ( *RecordSchema) () []*Field {
	return .fields
}

// String returns the canonical form of the schema.
func ( *RecordSchema) () string {
	 := "record"
	if .isError {
		 = "error"
	}

	 := ""
	for ,  := range .fields {
		 += .String() + ","
	}
	if len() > 0 {
		 = [:len()-1]
	}

	return `{"name":"` + .FullName() + `","type":"` +  + `","fields":[` +  + `]}`
}

// MarshalJSON marshals the schema to json.
func ( *RecordSchema) () ([]byte, error) {
	 := new(bytes.Buffer)
	.WriteString(`{"name":"` + .full + `"`)
	if len(.aliases) > 0 {
		,  := jsoniterAPI.Marshal(.aliases)
		if  != nil {
			return nil, 
		}
		.WriteString(`,"aliases":`)
		.Write()
	}
	if .doc != "" {
		,  := jsoniterAPI.Marshal(.doc)
		if  != nil {
			return nil, 
		}
		.WriteString(`,"doc":`)
		.Write()
	}
	if .isError {
		.WriteString(`,"type":"error"`)
	} else {
		.WriteString(`,"type":"record"`)
	}
	,  := jsoniterAPI.Marshal(.fields)
	if  != nil {
		return nil, 
	}
	.WriteString(`,"fields":`)
	.Write()
	if  := .marshalPropertiesToJSON();  != nil {
		return nil, 
	}
	.WriteString("}")
	return .Bytes(), nil
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func ( *RecordSchema) () [32]byte {
	return .fingerprinter.Fingerprint()
}

// FingerprintUsing returns the fingerprint of the schema using the given algorithm or an error.
func ( *RecordSchema) ( FingerprintType) ([]byte, error) {
	return .fingerprinter.FingerprintUsing(, )
}

// CacheFingerprint returns unique identity of the schema.
func ( *RecordSchema) () [32]byte {
	return .cacheFingerprinter.CacheFingerprint(, func() []byte {
		var  []any
		for ,  := range .fields {
			if !.HasDefault() {
				continue
			}
			 = append(, .Default())
		}
		,  := jsoniterAPI.Marshal()
		return 
	})
}

// Field is an Avro record type field.
type Field struct {
	properties

	name    string
	aliases []string
	doc     string
	typ     Schema
	hasDef  bool
	def     any
	order   Order

	// action mainly used when decoding data that lack the field for schema evolution purposes.
	action Action
	// encodedDef mainly used when decoding data that lack the field for schema evolution purposes.
	// Its value remains empty unless the field's encodeDefault function is called.
	encodedDef atomic.Value
}

type noDef struct{}

// NoDefault is used when no default exists for a field.
var NoDefault = noDef{}

// NewField creates a new field instance.
func ( string,  Schema,  ...SchemaOption) (*Field, error) {
	 := schemaConfig{def: NoDefault}
	for ,  := range  {
		(&)
	}

	if  := validateName();  != nil {
		return nil, 
	}
	for ,  := range .aliases {
		if  := validateName();  != nil {
			return nil, 
		}
	}

	switch .order {
	case "":
		.order = Asc
	case Asc, Desc, Ignore:
	default:
		return nil, fmt.Errorf("avro: field %q order %q is invalid", , .order)
	}

	 := &Field{
		properties: newProperties(.props, fieldReserved),
		name:       ,
		aliases:    .aliases,
		doc:        .doc,
		typ:        ,
		order:      .order,
	}

	if .def != NoDefault {
		,  := validateDefault(, , .def)
		if  != nil {
			return nil, 
		}
		.def = 
		.hasDef = true
	}

	return , nil
}

// Name returns the name of a field.
func ( *Field) () string {
	return .name
}

// Aliases return the field aliases.
func ( *Field) () []string {
	return .aliases
}

// Type returns the schema of a field.
func ( *Field) () Schema {
	return .typ
}

// HasDefault determines if the field has a default value.
func ( *Field) () bool {
	return .hasDef
}

// Default returns the default of a field or nil.
//
// The only time a nil default is valid is for a Null Type.
func ( *Field) () any {
	if .def == nullDefault {
		return nil
	}

	return .def
}

func ( *Field) ( func(any) ([]byte, error)) ([]byte, error) {
	if  := .encodedDef.Load();  != nil {
		return .([]byte), nil
	}
	if !.hasDef {
		return nil, fmt.Errorf("avro: '%s' field must have a non-empty default value", .name)
	}
	if  == nil {
		return nil, fmt.Errorf("avro: failed to encode '%s' default value", .name)
	}
	,  := (.Default())
	if  != nil {
		return nil, 
	}
	.encodedDef.Store()

	return , nil
}

// Doc returns the documentation of a field.
func ( *Field) () string {
	return .doc
}

// Order returns the field order.
func ( *Field) () Order {
	return .order
}

// String returns the canonical form of a field.
func ( *Field) () string {
	return `{"name":"` + .name + `","type":` + .typ.String() + `}`
}

// MarshalJSON marshals the schema to json.
func ( *Field) () ([]byte, error) {
	 := new(bytes.Buffer)
	.WriteString(`{"name":"` + .name + `"`)
	if len(.aliases) > 0 {
		,  := jsoniterAPI.Marshal(.aliases)
		if  != nil {
			return nil, 
		}
		.WriteString(`,"aliases":`)
		.Write()
	}
	if .doc != "" {
		,  := jsoniterAPI.Marshal(.doc)
		if  != nil {
			return nil, 
		}
		.WriteString(`,"doc":`)
		.Write()
	}
	,  := jsoniterAPI.Marshal(.typ)
	if  != nil {
		return nil, 
	}
	.WriteString(`,"type":`)
	.Write()
	if .hasDef {
		,  := jsoniterAPI.Marshal(.Default())
		if  != nil {
			return nil, 
		}
		.WriteString(`,"default":`)
		.Write()
	}
	if .order != "" && .order != Asc {
		.WriteString(`,"order":"` + string(.order) + `"`)
	}
	if  := .marshalPropertiesToJSON();  != nil {
		return nil, 
	}
	.WriteString("}")
	return .Bytes(), nil
}

// EnumSchema is an Avro enum type schema.
type EnumSchema struct {
	name
	properties
	fingerprinter
	cacheFingerprinter

	symbols []string
	def     string
	doc     string

	// encodedSymbols is the symbols of the encoded value.
	// It's only used in the context of write-read schema resolution.
	encodedSymbols []string
}

// NewEnumSchema creates a new enum schema instance.
func (,  string,  []string,  ...SchemaOption) (*EnumSchema, error) {
	var  schemaConfig
	for ,  := range  {
		(&)
	}

	,  := newName(, , .aliases)
	if  != nil {
		return nil, 
	}

	if len() == 0 {
		return nil, errors.New("avro: enum must have a non-empty array of symbols")
	}

	 := make(map[string]struct{})
	for ,  := range  {
		if  = validateName();  != nil {
			return nil, fmt.Errorf("avro: invalid symbol %q", )
		}

		if ,  := [];  {
			return nil, fmt.Errorf("avro: duplicate symbol %q", )
		}
		[] = struct{}{}
	}

	var  string
	if ,  := .def.(string);  &&  != "" {
		if !hasSymbol(, ) {
			return nil, fmt.Errorf("avro: symbol default %q must be a symbol", )
		}
		 = 
	}

	return &EnumSchema{
		name:               ,
		properties:         newProperties(.props, enumReserved),
		cacheFingerprinter: cacheFingerprinter{writerFingerprint: .wfp},
		symbols:            ,
		def:                ,
		doc:                .doc,
	}, nil
}

func hasSymbol( []string,  string) bool {
	for ,  := range  {
		if  ==  {
			return true
		}
	}
	return false
}

// Type returns the type of the schema.
func ( *EnumSchema) () Type {
	return Enum
}

// Doc returns the schema doc.
func ( *EnumSchema) () string {
	return .doc
}

// Symbols returns the symbols of an enum.
func ( *EnumSchema) () []string {
	return .symbols
}

// Symbol returns the symbol for the given index.
// It might return the default value in the context of write-read schema resolution.
func ( *EnumSchema) ( int) (string, bool) {
	 := len(.encodedSymbols) > 0
	 := .symbols
	if  {
		// A different set of symbols is encoded.
		 = .encodedSymbols
	}

	if  < 0 ||  >= len() {
		return "", false
	}

	 := []
	if  && !hasSymbol(.symbols, ) {
		if !.HasDefault() {
			return "", false
		}
		return .Default(), true
	}
	return , true
}

// Default returns the default of an enum or an empty string.
func ( *EnumSchema) () string {
	return .def
}

// HasDefault determines if the schema has a default value.
func ( *EnumSchema) () bool {
	return .def != ""
}

// String returns the canonical form of the schema.
func ( *EnumSchema) () string {
	 := ""
	for ,  := range .symbols {
		 += `"` +  + `",`
	}
	if len() > 0 {
		 = [:len()-1]
	}

	return `{"name":"` + .FullName() + `","type":"enum","symbols":[` +  + `]}`
}

// MarshalJSON marshals the schema to json.
func ( *EnumSchema) () ([]byte, error) {
	 := new(bytes.Buffer)
	.WriteString(`{"name":"` + .full + `"`)
	if len(.aliases) > 0 {
		,  := jsoniterAPI.Marshal(.aliases)
		if  != nil {
			return nil, 
		}
		.WriteString(`,"aliases":`)
		.Write()
	}
	if .doc != "" {
		,  := jsoniterAPI.Marshal(.doc)
		if  != nil {
			return nil, 
		}
		.WriteString(`,"doc":`)
		.Write()
	}
	.WriteString(`,"type":"enum"`)
	,  := jsoniterAPI.Marshal(.symbols)
	if  != nil {
		return nil, 
	}
	.WriteString(`,"symbols":`)
	.Write()
	if .def != "" {
		.WriteString(`,"default":"` + .def + `"`)
	}
	if  := .marshalPropertiesToJSON();  != nil {
		return nil, 
	}
	.WriteString("}")
	return .Bytes(), nil
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func ( *EnumSchema) () [32]byte {
	return .fingerprinter.Fingerprint()
}

// FingerprintUsing returns the fingerprint of the schema using the given algorithm or an error.
func ( *EnumSchema) ( FingerprintType) ([]byte, error) {
	return .fingerprinter.FingerprintUsing(, )
}

// CacheFingerprint returns unique identity of the schema.
func ( *EnumSchema) () [32]byte {
	return .cacheFingerprinter.CacheFingerprint(, func() []byte {
		if !.HasDefault() {
			return []byte{}
		}
		return []byte(.Default())
	})
}

// ArraySchema is an Avro array type schema.
type ArraySchema struct {
	properties
	fingerprinter
	cacheFingerprinter

	items Schema
}

// NewArraySchema creates an array schema instance.
func ( Schema,  ...SchemaOption) *ArraySchema {
	var  schemaConfig
	for ,  := range  {
		(&)
	}

	return &ArraySchema{
		properties:         newProperties(.props, arrayReserved),
		cacheFingerprinter: cacheFingerprinter{writerFingerprint: .wfp},
		items:              ,
	}
}

// Type returns the type of the schema.
func ( *ArraySchema) () Type {
	return Array
}

// Items returns the items schema of an array.
func ( *ArraySchema) () Schema {
	return .items
}

// String returns the canonical form of the schema.
func ( *ArraySchema) () string {
	return `{"type":"array","items":` + .items.String() + `}`
}

// MarshalJSON marshals the schema to json.
func ( *ArraySchema) () ([]byte, error) {
	 := new(bytes.Buffer)
	.WriteString(`{"type":"array"`)
	,  := jsoniterAPI.Marshal(.items)
	if  != nil {
		return nil, 
	}
	.WriteString(`,"items":`)
	.Write()
	if  = .marshalPropertiesToJSON();  != nil {
		return nil, 
	}
	.WriteString("}")
	return .Bytes(), nil
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func ( *ArraySchema) () [32]byte {
	return .fingerprinter.Fingerprint()
}

// FingerprintUsing returns the fingerprint of the schema using the given algorithm or an error.
func ( *ArraySchema) ( FingerprintType) ([]byte, error) {
	return .fingerprinter.FingerprintUsing(, )
}

// CacheFingerprint returns unique identity of the schema.
func ( *ArraySchema) () [32]byte {
	return .cacheFingerprinter.CacheFingerprint(, nil)
}

// MapSchema is an Avro map type schema.
type MapSchema struct {
	properties
	fingerprinter
	cacheFingerprinter

	values Schema
}

// NewMapSchema creates a map schema instance.
func ( Schema,  ...SchemaOption) *MapSchema {
	var  schemaConfig
	for ,  := range  {
		(&)
	}

	return &MapSchema{
		properties:         newProperties(.props, mapReserved),
		cacheFingerprinter: cacheFingerprinter{writerFingerprint: .wfp},
		values:             ,
	}
}

// Type returns the type of the schema.
func ( *MapSchema) () Type {
	return Map
}

// Values returns the values schema of a map.
func ( *MapSchema) () Schema {
	return .values
}

// String returns the canonical form of the schema.
func ( *MapSchema) () string {
	return `{"type":"map","values":` + .values.String() + `}`
}

// MarshalJSON marshals the schema to json.
func ( *MapSchema) () ([]byte, error) {
	 := new(bytes.Buffer)
	.WriteString(`{"type":"map"`)
	,  := jsoniterAPI.Marshal(.values)
	if  != nil {
		return nil, 
	}
	.WriteString(`,"values":`)
	.Write()
	if  := .marshalPropertiesToJSON();  != nil {
		return nil, 
	}
	.WriteString("}")
	return .Bytes(), nil
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func ( *MapSchema) () [32]byte {
	return .fingerprinter.Fingerprint()
}

// FingerprintUsing returns the fingerprint of the schema using the given algorithm or an error.
func ( *MapSchema) ( FingerprintType) ([]byte, error) {
	return .fingerprinter.FingerprintUsing(, )
}

// CacheFingerprint returns unique identity of the schema.
func ( *MapSchema) () [32]byte {
	return .cacheFingerprinter.CacheFingerprint(, nil)
}

// UnionSchema is an Avro union type schema.
type UnionSchema struct {
	fingerprinter
	cacheFingerprinter

	types Schemas
}

// NewUnionSchema creates a union schema instance.
func ( []Schema,  ...SchemaOption) (*UnionSchema, error) {
	var  schemaConfig
	for ,  := range  {
		(&)
	}

	 := map[string]bool{}
	for ,  := range  {
		if .Type() == Union {
			return nil, errors.New("avro: union type cannot be a union")
		}

		 := schemaTypeName()

		if [] {
			return nil, errors.New("avro: union type must be unique")
		}
		[] = true
	}

	return &UnionSchema{
		cacheFingerprinter: cacheFingerprinter{writerFingerprint: .wfp},
		types:              ,
	}, nil
}

// Type returns the type of the schema.
func ( *UnionSchema) () Type {
	return Union
}

// Types returns the types of a union.
func ( *UnionSchema) () Schemas {
	return .types
}

// Contains returns true if the union contains the given type.
func ( *UnionSchema) ( Type) bool {
	,  := .types.Get(string())
	return  != -1
}

// Nullable returns true if the union is nullable, otherwise false.
func ( *UnionSchema) () bool {
	if len(.types) != 2 || .types[0].Type() != Null && .types[1].Type() != Null {
		return false
	}

	return true
}

// Indices returns the index of the null and type schemas for a
// nullable schema. For non-nullable schemas 0 is returned for
// both.
func ( *UnionSchema) () (,  int) {
	if !.Nullable() {
		return 0, 0
	}
	if .types[0].Type() == Null {
		return 0, 1
	}
	return 1, 0
}

// String returns the canonical form of the schema.
func ( *UnionSchema) () string {
	 := ""
	for ,  := range .types {
		 += .String() + ","
	}
	if len() > 0 {
		 = [:len()-1]
	}

	return `[` +  + `]`
}

// MarshalJSON marshals the schema to json.
func ( *UnionSchema) () ([]byte, error) {
	return jsoniterAPI.Marshal(.types)
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func ( *UnionSchema) () [32]byte {
	return .fingerprinter.Fingerprint()
}

// FingerprintUsing returns the fingerprint of the schema using the given algorithm or an error.
func ( *UnionSchema) ( FingerprintType) ([]byte, error) {
	return .fingerprinter.FingerprintUsing(, )
}

// CacheFingerprint returns unique identity of the schema.
func ( *UnionSchema) () [32]byte {
	return .cacheFingerprinter.CacheFingerprint(, nil)
}

// FixedSchema is an Avro fixed type schema.
type FixedSchema struct {
	name
	properties
	fingerprinter
	cacheFingerprinter

	size    int
	logical LogicalSchema
}

// NewFixedSchema creates a new fixed schema instance.
func (
	,  string,
	 int,
	 LogicalSchema,
	 ...SchemaOption,
) (*FixedSchema, error) {
	var  schemaConfig
	for ,  := range  {
		(&)
	}

	,  := newName(, , .aliases)
	if  != nil {
		return nil, 
	}

	if  < 0 {
		return nil, errors.New("avro: fixed size cannot be negative")
	}

	 := fixedReserved
	if  != nil {
		if .Type() == Decimal {
			 = fixedWithDecimalTypeReserved
		} else {
			 = fixedWithLogicalTypeReserved
		}
	}
	return &FixedSchema{
		name:               ,
		properties:         newProperties(.props, ),
		cacheFingerprinter: cacheFingerprinter{writerFingerprint: .wfp},
		size:               ,
		logical:            ,
	}, nil
}

// Type returns the type of the schema.
func ( *FixedSchema) () Type {
	return Fixed
}

// Size returns the number of bytes of the fixed schema.
func ( *FixedSchema) () int {
	return .size
}

// Logical returns the logical schema or nil.
func ( *FixedSchema) () LogicalSchema {
	return .logical
}

// String returns the canonical form of the schema.
func ( *FixedSchema) () string {
	 := strconv.Itoa(.size)

	var  string
	if .logical != nil {
		 = "," + .logical.String()
	}

	return `{"name":"` + .FullName() + `","type":"fixed","size":` +  +  + `}`
}

// MarshalJSON marshals the schema to json.
func ( *FixedSchema) () ([]byte, error) {
	 := new(bytes.Buffer)
	.WriteString(`{"name":"` + .full + `"`)
	if len(.aliases) > 0 {
		,  := jsoniterAPI.Marshal(.aliases)
		if  != nil {
			return nil, 
		}
		.WriteString(`,"aliases":`)
		.Write()
	}
	.WriteString(`,"type":"fixed"`)
	.WriteString(`,"size":` + strconv.Itoa(.size))
	if .logical != nil {
		.WriteString(`,"logicalType":"` + string(.logical.Type()) + `"`)
		if ,  := .logical.(*DecimalLogicalSchema);  {
			.WriteString(`,"precision":` + strconv.Itoa(.prec))
			if .scale > 0 {
				.WriteString(`,"scale":` + strconv.Itoa(.scale))
			}
		}
	}
	if  := .marshalPropertiesToJSON();  != nil {
		return nil, 
	}
	.WriteString("}")
	return .Bytes(), nil
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func ( *FixedSchema) () [32]byte {
	return .fingerprinter.Fingerprint()
}

// FingerprintUsing returns the fingerprint of the schema using the given algorithm or an error.
func ( *FixedSchema) ( FingerprintType) ([]byte, error) {
	return .fingerprinter.FingerprintUsing(, )
}

// CacheFingerprint returns unique identity of the schema.
func ( *FixedSchema) () [32]byte {
	return .cacheFingerprinter.CacheFingerprint(, nil)
}

// NullSchema is an Avro null type schema.
type NullSchema struct {
	properties
	fingerprinter
}

// NewNullSchema creates a new NullSchema.
func ( ...SchemaOption) *NullSchema {
	var  schemaConfig
	for ,  := range  {
		(&)
	}

	return &NullSchema{
		properties: newProperties(.props, primitiveReserved),
	}
}

// Type returns the type of the schema.
func ( *NullSchema) () Type {
	return Null
}

// String returns the canonical form of the schema.
func ( *NullSchema) () string {
	return `"null"`
}

// MarshalJSON marshals the schema to json.
func ( *NullSchema) () ([]byte, error) {
	if len(.props) == 0 {
		return []byte(`"null"`), nil
	}
	 := new(bytes.Buffer)
	.WriteString(`{"type":"null"`)
	if  := .marshalPropertiesToJSON();  != nil {
		return nil, 
	}
	.WriteString("}")
	return .Bytes(), nil
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func ( *NullSchema) () [32]byte {
	return .fingerprinter.Fingerprint()
}

// FingerprintUsing returns the fingerprint of the schema using the given algorithm or an error.
func ( *NullSchema) ( FingerprintType) ([]byte, error) {
	return .fingerprinter.FingerprintUsing(, )
}

// CacheFingerprint returns unique identity of the schema.
func ( *NullSchema) () [32]byte {
	return .Fingerprint()
}

// RefSchema is a reference to a named Avro schema.
type RefSchema struct {
	actual NamedSchema
}

// NewRefSchema creates a ref schema instance.
func ( NamedSchema) *RefSchema {
	return &RefSchema{
		actual: ,
	}
}

// Type returns the type of the schema.
func ( *RefSchema) () Type {
	return Ref
}

// Schema returns the schema being referenced.
func ( *RefSchema) () NamedSchema {
	return .actual
}

// String returns the canonical form of the schema.
func ( *RefSchema) () string {
	return `"` + .actual.FullName() + `"`
}

// MarshalJSON marshals the schema to json.
func ( *RefSchema) () ([]byte, error) {
	return []byte(`"` + .actual.FullName() + `"`), nil
}

// Fingerprint returns the SHA256 fingerprint of the schema.
func ( *RefSchema) () [32]byte {
	return .actual.Fingerprint()
}

// FingerprintUsing returns the fingerprint of the schema using the given algorithm or an error.
func ( *RefSchema) ( FingerprintType) ([]byte, error) {
	return .actual.FingerprintUsing()
}

// CacheFingerprint returns unique identity of the schema.
func ( *RefSchema) () [32]byte {
	return .actual.CacheFingerprint()
}

// PrimitiveLogicalSchema is a logical type with no properties.
type PrimitiveLogicalSchema struct {
	typ LogicalType
}

// NewPrimitiveLogicalSchema creates a new primitive logical schema instance.
func ( LogicalType) *PrimitiveLogicalSchema {
	return &PrimitiveLogicalSchema{
		typ: ,
	}
}

// Type returns the type of the logical schema.
func ( *PrimitiveLogicalSchema) () LogicalType {
	return .typ
}

// String returns the canonical form of the logical schema.
func ( *PrimitiveLogicalSchema) () string {
	return `"logicalType":"` + string(.typ) + `"`
}

// DecimalLogicalSchema is a decimal logical type.
type DecimalLogicalSchema struct {
	prec  int
	scale int
}

// NewDecimalLogicalSchema creates a new decimal logical schema instance.
func (,  int) *DecimalLogicalSchema {
	return &DecimalLogicalSchema{
		prec:  ,
		scale: ,
	}
}

// Type returns the type of the logical schema.
func ( *DecimalLogicalSchema) () LogicalType {
	return Decimal
}

// Precision returns the precision of the decimal logical schema.
func ( *DecimalLogicalSchema) () int {
	return .prec
}

// Scale returns the scale of the decimal logical schema.
func ( *DecimalLogicalSchema) () int {
	return .scale
}

// String returns the canonical form of the logical schema.
func ( *DecimalLogicalSchema) () string {
	var  string
	if .scale > 0 {
		 = `,"scale":` + strconv.Itoa(.scale)
	}
	 := strconv.Itoa(.prec)

	return `"logicalType":"` + string(Decimal) + `","precision":` +  + 
}

func invalidNameFirstChar( rune) bool {
	return ( < 'A' ||  > 'Z') && ( < 'a' ||  > 'z') &&  != '_'
}

func invalidNameOtherChar( rune) bool {
	return invalidNameFirstChar() && ( < '0' ||  > '9')
}

func validateName( string) error {
	if  == "" {
		return errors.New("name must be a non-empty")
	}

	if SkipNameValidation {
		return nil
	}

	if strings.IndexFunc([:1], invalidNameFirstChar) > -1 {
		return fmt.Errorf("invalid name %s", )
	}
	if strings.IndexFunc([1:], invalidNameOtherChar) > -1 {
		return fmt.Errorf("invalid name %s", )
	}

	return nil
}

func validateDefault( string,  Schema,  any) (any, error) {
	,  := isValidDefault(, )
	if ! {
		return nil, fmt.Errorf("avro: invalid default for field %s. %+v not a %s", , , .Type())
	}
	return , nil
}

func isValidDefault( Schema,  any) (any, bool) {
	switch .Type() {
	case Ref:
		 := .(*RefSchema)
		return (.Schema(), )
	case Null:
		return nullDefault,  == nil
	case Enum:
		,  := .(string)
		if ! || len() == 0 {
			return , false
		}

		var  bool
		for ,  := range .(*EnumSchema).symbols {
			if  ==  {
				 = true
				break
			}
		}
		return , 
	case String:
		if ,  := .(string);  {
			return , true
		}
	case Bytes, Fixed:
		// Spec: Default values for bytes and fixed fields are JSON strings,
		// where Unicode code points 0-255 are mapped to unsigned 8-bit byte values 0-255.
		if ,  := .(string);  {
			if ,  := isValidDefaultBytes();  {
				if .Type() == Fixed {
					return byteSliceToArray(, .(*FixedSchema).Size()), true
				}
				return , true
			}
		}
	case Boolean:
		if ,  := .(bool);  {
			return , true
		}
	case Int:
		if ,  := .(int8);  {
			return int(), true
		}
		if ,  := .(int16);  {
			return int(), true
		}
		if ,  := .(int32);  {
			return int(), true
		}
		if ,  := .(int);  {
			return , true
		}
		if ,  := .(float64);  {
			return int(), true
		}
	case Long:
		if ,  := .(int64);  {
			return , true
		}
		if ,  := .(float64);  {
			return int64(), true
		}
	case Float:
		if ,  := .(float32);  {
			return , true
		}
		if ,  := .(float64);  {
			return float32(), true
		}
	case Double:
		if ,  := .(float64);  {
			return , true
		}
	case Array:
		,  := .([]any)
		if ! {
			return nil, false
		}

		 := .(*ArraySchema)
		for ,  := range  {
			,  := (.Items(), )
			if ! {
				return nil, false
			}
			[] = 
		}
		return , true
	case Map:
		,  := .(map[string]any)
		if ! {
			return nil, false
		}

		 := .(*MapSchema)
		for ,  := range  {
			,  := (.Values(), )
			if ! {
				return nil, false
			}

			[] = 
		}
		return , true
	case Union:
		 := .(*UnionSchema)
		return (.Types()[0], )
	case Record:
		,  := .(map[string]any)
		if ! {
			return nil, false
		}

		for ,  := range .(*RecordSchema).Fields() {
			 := .Default()
			if ,  := [.Name()];  {
				 = 
			}

			,  := (.Type(), )
			if ! {
				return nil, false
			}

			[.Name()] = 
		}
		return , true
	}
	return nil, false
}

func schemaTypeName( Schema) string {
	if .Type() == Ref {
		 = .(*RefSchema).Schema()
	}

	if ,  := .(NamedSchema);  {
		return .FullName()
	}

	 := string(.Type())
	if  := getLogicalType();  != "" {
		 += "." + string()
	}
	return 
}

func isValidDefaultBytes( string) ([]byte, bool) {
	 := []rune()
	 := len()
	 := make([]byte, )
	for  := range  {
		if [] < 0 || [] > 255 {
			return nil, false
		}
		[] = byte([])
	}
	return , true
}