// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package generator

import (
	"errors"
	"fmt"
	"log"
	"path"
	"path/filepath"
	"sort"
	"strconv"
	"strings"

	"github.com/go-openapi/analysis"
	"github.com/go-openapi/loads"
	"github.com/go-openapi/spec"
	"github.com/go-openapi/swag"
)

const asMethod = "()"

/*
Rewrite specification document first:

* anonymous objects
* tuples
* extensible objects (properties + additionalProperties)
* AllOfs when they match the rewrite criteria (not a nullable allOf)

Find string enums and generate specialized idiomatic enum with them

Every action that happens tracks the path which is a linked list of refs


*/

// GenerateModels generates all model files for some schema definitions
func GenerateModels(modelNames []string, opts *GenOpts) error {
	// overide any default or incompatible options setting
	opts.IncludeModel = true
	opts.IgnoreOperations = true
	opts.ExistingModels = ""
	opts.IncludeHandler = false
	opts.IncludeMain = false
	opts.IncludeSupport = false
	generator, err := newAppGenerator("", modelNames, nil, opts)
	if err != nil {
		return err
	}
	return generator.Generate()
}

// GenerateDefinition generates a single model file for some schema definitions
func GenerateDefinition(modelNames []string, opts *GenOpts) error {
	if err := opts.CheckOpts(); err != nil {
		return err
	}

	if err := opts.setTemplates(); err != nil {
		return err
	}

	specDoc, _, err := opts.analyzeSpec()
	if err != nil {
		return err
	}

	modelNames = pruneEmpty(modelNames)
	if len(modelNames) == 0 {
		for k := range specDoc.Spec().Definitions {
			modelNames = append(modelNames, k)
		}
	}

	for _, modelName := range modelNames {
		// lookup schema
		model, ok := specDoc.Spec().Definitions[modelName]
		if !ok {
			return fmt.Errorf("model %q not found in definitions given by %q", modelName, opts.Spec)
		}

		// generate files
		generator := definitionGenerator{
			Name:    modelName,
			Model:   model,
			SpecDoc: specDoc,
			Target: filepath.Join(
				opts.Target,
				filepath.FromSlash(opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, ""))),
			opts: opts,
		}

		if err := generator.Generate(); err != nil {
			return err
		}
	}

	return nil
}

type definitionGenerator struct {
	Name    string
	Model   spec.Schema
	SpecDoc *loads.Document
	Target  string
	opts    *GenOpts
}

func (m *definitionGenerator) Generate() error {

	mod, err := makeGenDefinition(m.Name, m.Target, m.Model, m.SpecDoc, m.opts)
	if err != nil {
		return fmt.Errorf("could not generate definitions for model %s on target %s: %v", m.Name, m.Target, err)
	}

	if m.opts.DumpData {
		return dumpData(swag.ToDynamicJSON(mod))
	}

	if m.opts.IncludeModel {
		log.Println("including additional model")
		if err := m.generateModel(mod); err != nil {
			return fmt.Errorf("could not generate model: %v", err)
		}
	}
	log.Println("generated model", m.Name)

	return nil
}

func (m *definitionGenerator) generateModel(g *GenDefinition) error {
	debugLog("rendering definitions for %+v", *g)
	return m.opts.renderDefinition(g)
}

func makeGenDefinition(name, pkg string, schema spec.Schema, specDoc *loads.Document, opts *GenOpts) (*GenDefinition, error) {
	gd, err := makeGenDefinitionHierarchy(name, pkg, "", schema, specDoc, opts)

	if err == nil && gd != nil {
		// before yielding the schema to the renderer, we check if the top-level Validate method gets some content
		// this means that the immediate content of the top level definitions has at least one validation.
		//
		// If none is found at this level and that no special case where no Validate() method is exposed at all
		// (e.g. io.ReadCloser and interface{} types and their aliases), then there is an empty Validate() method which
		// just return nil (the object abides by the runtime.Validatable interface, but knows it has nothing to validate).
		//
		// We do this at the top level because of the possibility of aliased types which always bubble up validation to types which
		// are referring to them. This results in correct but inelegant code with empty validations.
		gd.GenSchema.HasValidations = shallowValidationLookup(gd.GenSchema)
	}
	return gd, err
}

func shallowValidationLookup(sch GenSchema) bool {
	// scan top level need for validations
	//
	// NOTE: this supersedes the previous NeedsValidation flag
	// With the introduction of this shallow lookup, it is no more necessary
	// to establish a distinction between HasValidations (e.g. carries on validations)
	// and NeedsValidation (e.g. should have a Validate method with something in it).
	// The latter was almost not used anyhow.

	if sch.HasAdditionalProperties && sch.AdditionalProperties == nil {
		log.Printf("warning: schema for additional properties in schema %q is empty. skipped", sch.Name)
	}

	if sch.IsArray && sch.HasValidations {
		return true
	}
	if sch.IsStream || sch.IsInterface { // these types have no validation - aliased types on those do not implement the Validatable interface
		return false
	}
	if sch.Required || hasFormatValidation(sch.resolvedType) {
		return true
	}
	if sch.HasStringValidations() || sch.HasNumberValidations() || sch.HasEnum() || len(sch.ItemsEnum) > 0 || sch.HasObjectValidations() {
		return true
	}
	for _, a := range sch.AllOf {
		if a.HasValidations {
			return true
		}
	}
	for _, p := range sch.Properties {
		// Using a base type within another structure triggers validation of the base type.
		// The discriminator property in the base type definition itself does not.
		if (p.HasValidations || p.Required) && !(sch.IsBaseType && p.Name == sch.DiscriminatorField) || (p.IsAliased || p.IsComplexObject) && !(p.IsInterface || p.IsStream) {
			return true
		}
	}
	if sch.IsTuple && (sch.AdditionalItems != nil && (sch.AdditionalItems.HasValidations || sch.AdditionalItems.Required)) {
		return true
	}
	if sch.HasAdditionalProperties && sch.AdditionalProperties != nil && (sch.AdditionalProperties.IsInterface || sch.AdditionalProperties.IsStream) {
		return false
	}

	if sch.HasAdditionalProperties && sch.AdditionalProperties != nil && (sch.AdditionalProperties.HasValidations || sch.AdditionalProperties.Required || sch.AdditionalProperties.IsAliased && !(sch.AdditionalProperties.IsInterface || sch.AdditionalProperties.IsStream)) {
		return true
	}

	if sch.IsAliased && (sch.IsPrimitive && sch.HasValidations) { // non primitive aliased have either other attributes with validation (above) or shall not validate
		return true
	}
	if sch.HasBaseType || sch.IsSubType {
		return true
	}
	return false
}

func isExternal(schema spec.Schema) bool {
	extType, ok := hasExternalType(schema.Extensions)
	return ok && !extType.Embedded
}

func makeGenDefinitionHierarchy(name, pkg, container string, schema spec.Schema, specDoc *loads.Document, opts *GenOpts) (*GenDefinition, error) {
	// Check if model is imported from external package using x-go-type
	receiver := "m"
	// models are resolved in the current package
	modelPkg := opts.LanguageOpts.ManglePackageName(path.Base(filepath.ToSlash(pkg)), "definitions")
	resolver := newTypeResolver("", "", specDoc).withDefinitionPackage(modelPkg)
	resolver.ModelName = name
	analyzed := analysis.New(specDoc.Spec())

	di := discriminatorInfo(analyzed)

	pg := schemaGenContext{
		Path:                       "",
		Name:                       name,
		Receiver:                   receiver,
		IndexVar:                   "i",
		ValueExpr:                  receiver,
		Schema:                     schema,
		Required:                   false,
		TypeResolver:               resolver,
		Named:                      true,
		ExtraSchemas:               make(map[string]GenSchema),
		Discrimination:             di,
		Container:                  container,
		IncludeValidator:           opts.IncludeValidator,
		IncludeModel:               opts.IncludeModel,
		StrictAdditionalProperties: opts.StrictAdditionalProperties,
		WithXML:                    opts.WithXML,
		StructTags:                 opts.StructTags,
	}
	if err := pg.makeGenSchema(); err != nil {
		return nil, fmt.Errorf("could not generate schema for %s: %v", name, err)
	}
	dsi, ok := di.Discriminators["#/definitions/"+name]
	if ok {
		// when these 2 are true then the schema will render as an interface
		pg.GenSchema.IsBaseType = true
		pg.GenSchema.IsExported = true
		pg.GenSchema.DiscriminatorField = dsi.FieldName

		if pg.GenSchema.Discriminates == nil {
			pg.GenSchema.Discriminates = make(map[string]string)
		}
		pg.GenSchema.Discriminates[name] = dsi.GoType
		pg.GenSchema.DiscriminatorValue = name

		for _, v := range dsi.Children {
			pg.GenSchema.Discriminates[v.FieldValue] = v.GoType
		}

		for j := range pg.GenSchema.Properties {
			if !strings.HasSuffix(pg.GenSchema.Properties[j].ValueExpression, asMethod) {
				pg.GenSchema.Properties[j].ValueExpression += asMethod
			}
		}
	}

	dse, ok := di.Discriminated["#/definitions/"+name]
	if ok {
		pg.GenSchema.DiscriminatorField = dse.FieldName
		pg.GenSchema.DiscriminatorValue = dse.FieldValue
		pg.GenSchema.IsSubType = true
		knownProperties := make(map[string]struct{})

		// find the referenced definitions
		// check if it has a discriminator defined
		// when it has a discriminator get the schema and run makeGenSchema for it.
		// replace the ref with this new genschema
		swsp := specDoc.Spec()
		for i, ss := range schema.AllOf {
			if pg.GenSchema.AllOf == nil {
				log.Printf("warning: resolved schema for subtype %q.AllOf[%d] is empty. skipped", name, i)
				continue
			}
			ref := ss.Ref
			for ref.String() != "" {
				var rsch *spec.Schema
				var err error
				rsch, err = spec.ResolveRef(swsp, &ref)
				if err != nil {
					return nil, err
				}
				if rsch != nil && rsch.Ref.String() != "" {
					ref = rsch.Ref
					continue
				}
				ref = spec.Ref{}
				if rsch != nil && rsch.Discriminator != "" {
					gs, err := makeGenDefinitionHierarchy(strings.TrimPrefix(ss.Ref.String(), "#/definitions/"), pkg, pg.GenSchema.Name, *rsch, specDoc, opts)
					if err != nil {
						return nil, err
					}
					gs.GenSchema.IsBaseType = true
					gs.GenSchema.IsExported = true
					pg.GenSchema.AllOf[i] = gs.GenSchema
					schPtr := &(pg.GenSchema.AllOf[i])
					if schPtr.AdditionalItems != nil {
						schPtr.AdditionalItems.IsBaseType = true
					}
					if schPtr.AdditionalProperties != nil {
						schPtr.AdditionalProperties.IsBaseType = true
					}
					for j := range schPtr.Properties {
						schPtr.Properties[j].IsBaseType = true
						knownProperties[schPtr.Properties[j].Name] = struct{}{}
					}
				}
			}
		}

		// dedupe the fields
		alreadySeen := make(map[string]struct{})
		for i, ss := range pg.GenSchema.AllOf {
			var remainingProperties GenSchemaList
			for _, p := range ss.Properties {
				if _, ok := knownProperties[p.Name]; !ok || ss.IsBaseType {
					if _, seen := alreadySeen[p.Name]; !seen {
						remainingProperties = append(remainingProperties, p)
						alreadySeen[p.Name] = struct{}{}
					}
				}
			}
			pg.GenSchema.AllOf[i].Properties = remainingProperties
		}

	}

	defaultImports := map[string]string{
		"errors":   "github.com/go-openapi/errors",
		"runtime":  "github.com/go-openapi/runtime",
		"swag":     "github.com/go-openapi/swag",
		"validate": "github.com/go-openapi/validate",
	}

	return &GenDefinition{
		GenCommon: GenCommon{
			Copyright:        opts.Copyright,
			TargetImportPath: opts.LanguageOpts.baseImport(opts.Target),
		},
		Package:        modelPkg,
		GenSchema:      pg.GenSchema,
		DependsOn:      pg.Dependencies,
		DefaultImports: defaultImports,
		ExtraSchemas:   gatherExtraSchemas(pg.ExtraSchemas),
		Imports:        findImports(&pg.GenSchema),
		External:       isExternal(schema),
	}, nil
}

func findImports(sch *GenSchema) map[string]string {
	imp := make(map[string]string, 20)
	t := sch.resolvedType
	if t.Pkg != "" && t.PkgAlias != "" {
		imp[t.PkgAlias] = t.Pkg
	}
	if t.IsEmbedded && t.ElemType != nil {
		if t.ElemType.Pkg != "" && t.ElemType.PkgAlias != "" {
			imp[t.ElemType.PkgAlias] = t.ElemType.Pkg
		}
	}
	if sch.Items != nil {
		sub := findImports(sch.Items)
		for k, v := range sub {
			imp[k] = v
		}
	}
	if sch.AdditionalItems != nil {
		sub := findImports(sch.AdditionalItems)
		for k, v := range sub {
			imp[k] = v
		}
	}
	if sch.Object != nil {
		sub := findImports(sch.Object)
		for k, v := range sub {
			imp[k] = v
		}
	}
	if sch.Properties != nil {
		for _, props := range sch.Properties {
			p := props
			sub := findImports(&p)
			for k, v := range sub {
				imp[k] = v
			}
		}
	}
	if sch.AdditionalProperties != nil {
		sub := findImports(sch.AdditionalProperties)
		for k, v := range sub {
			imp[k] = v
		}
	}
	if sch.AllOf != nil {
		for _, props := range sch.AllOf {
			p := props
			sub := findImports(&p)
			for k, v := range sub {
				imp[k] = v
			}
		}
	}
	for k, v := range sch.ExtraImports {
		if k != "" && v != "" {
			imp[k] = v
		}
	}

	return imp
}

type schemaGenContext struct {
	Required                   bool
	AdditionalProperty         bool
	Untyped                    bool
	Named                      bool
	RefHandled                 bool
	IsVirtual                  bool
	IsTuple                    bool
	IncludeValidator           bool
	IncludeModel               bool
	StrictAdditionalProperties bool
	WithXML                    bool
	Index                      int

	Path         string
	Name         string
	ParamName    string
	Accessor     string
	Receiver     string
	IndexVar     string
	KeyVar       string
	ValueExpr    string
	Container    string
	Schema       spec.Schema
	TypeResolver *typeResolver
	StructTags   []string

	GenSchema      GenSchema
	Dependencies   []string // NOTE: Dependencies is actually set nowhere
	ExtraSchemas   map[string]GenSchema
	Discriminator  *discor
	Discriminated  *discee
	Discrimination *discInfo

	// force to use container in inlined definitions (for deconflicting)
	UseContainerInName bool
}

func (sg *schemaGenContext) NewSliceBranch(schema *spec.Schema) *schemaGenContext {
	debugLog("new slice branch %s (model: %s)", sg.Name, sg.TypeResolver.ModelName)
	pg := sg.shallowClone()
	indexVar := pg.IndexVar
	if pg.Path == "" {
		pg.Path = "strconv.Itoa(" + indexVar + ")"
	} else {
		pg.Path = pg.Path + "+ \".\" + strconv.Itoa(" + indexVar + ")"
	}
	// check who is parent, if it's a base type then rewrite the value expression
	if sg.Discrimination != nil && sg.Discrimination.Discriminators != nil {
		_, rewriteValueExpr := sg.Discrimination.Discriminators["#/definitions/"+sg.TypeResolver.ModelName]
		if (pg.IndexVar == "i" && rewriteValueExpr) || sg.GenSchema.ElemType.IsBaseType {
			if !sg.GenSchema.IsAliased {
				pg.ValueExpr = sg.Receiver + "." + swag.ToJSONName(sg.GenSchema.Name) + "Field"
			} else {
				pg.ValueExpr = sg.Receiver
			}
		}
	}
	sg.GenSchema.IsBaseType = sg.GenSchema.ElemType.HasDiscriminator
	pg.IndexVar = indexVar + "i"
	pg.ValueExpr = pg.ValueExpr + "[" + indexVar + "]"
	pg.Schema = *schema
	pg.Required = false
	if sg.IsVirtual {
		pg.TypeResolver = sg.TypeResolver.NewWithModelName(sg.TypeResolver.ModelName)
	}

	// when this is an anonymous complex object, this needs to become a ref
	return pg
}

func (sg *schemaGenContext) NewAdditionalItems(schema *spec.Schema) *schemaGenContext {
	debugLog("new additional items\n")

	pg := sg.shallowClone()
	indexVar := pg.IndexVar
	pg.Name = sg.Name + " items"
	itemsLen := 0
	if sg.Schema.Items != nil {
		itemsLen = sg.Schema.Items.Len()
	}
	var mod string
	if itemsLen > 0 {
		mod = "+" + strconv.Itoa(itemsLen)
	}
	if pg.Path == "" {
		pg.Path = "strconv.Itoa(" + indexVar + mod + ")"
	} else {
		pg.Path = pg.Path + "+ \".\" + strconv.Itoa(" + indexVar + mod + ")"
	}
	pg.IndexVar = indexVar
	pg.ValueExpr = sg.ValueExpr + "." + pascalize(sg.GoName()) + "Items[" + indexVar + "]"
	pg.Schema = spec.Schema{}
	if schema != nil {
		pg.Schema = *schema
	}
	pg.Required = false
	return pg
}

func (sg *schemaGenContext) NewTupleElement(schema *spec.Schema, index int) *schemaGenContext {
	debugLog("New tuple element\n")

	pg := sg.shallowClone()
	if pg.Path == "" {
		pg.Path = "\"" + strconv.Itoa(index) + "\""
	} else {
		pg.Path = pg.Path + "+ \".\"+\"" + strconv.Itoa(index) + "\""
	}
	pg.ValueExpr = pg.ValueExpr + ".P" + strconv.Itoa(index)

	pg.Required = true
	pg.IsTuple = true
	pg.Schema = *schema

	return pg
}

func (sg *schemaGenContext) NewStructBranch(name string, schema spec.Schema) *schemaGenContext {
	debugLog("new struct branch %s (parent %s)", sg.Name, sg.Container)
	pg := sg.shallowClone()
	if sg.Path == "" {
		pg.Path = fmt.Sprintf("%q", name)
	} else {
		pg.Path = pg.Path + "+\".\"+" + fmt.Sprintf("%q", name)
	}
	pg.Name = name
	pg.ValueExpr = pg.ValueExpr + "." + pascalize(goName(&schema, name))
	pg.Schema = schema
	for _, fn := range sg.Schema.Required {
		if name == fn {
			pg.Required = true
			break
		}
	}
	debugLog("made new struct branch %s (parent %s)", pg.Name, pg.Container)
	return pg
}

func (sg *schemaGenContext) shallowClone() *schemaGenContext {
	debugLog("cloning context %s\n", sg.Name)
	pg := new(schemaGenContext)
	*pg = *sg
	if pg.Container == "" {
		pg.Container = sg.Name
	}
	pg.GenSchema = GenSchema{StructTags: sg.StructTags}
	pg.Dependencies = nil
	pg.Named = false
	pg.Index = 0
	pg.IsTuple = false
	pg.IncludeValidator = sg.IncludeValidator
	pg.IncludeModel = sg.IncludeModel
	pg.StrictAdditionalProperties = sg.StrictAdditionalProperties
	return pg
}

func (sg *schemaGenContext) NewCompositionBranch(schema spec.Schema, index int) *schemaGenContext {
	debugLog("new composition branch %s (parent: %s, index: %d)", sg.Name, sg.Container, index)
	pg := sg.shallowClone()
	pg.Schema = schema
	pg.Name = "AO" + strconv.Itoa(index)
	if sg.Name != sg.TypeResolver.ModelName {
		pg.Name = sg.Name + pg.Name
	}
	pg.Index = index
	debugLog("made new composition branch %s (parent: %s)", pg.Name, pg.Container)
	return pg
}

func (sg *schemaGenContext) NewAdditionalProperty(schema spec.Schema) *schemaGenContext {
	debugLog("new additional property %s (expr: %s)", sg.Name, sg.ValueExpr)
	pg := sg.shallowClone()
	pg.Schema = schema
	if pg.KeyVar == "" {
		pg.ValueExpr = sg.ValueExpr
	}
	pg.KeyVar += "k"
	pg.ValueExpr += "[" + pg.KeyVar + "]"
	pg.Path = pg.KeyVar
	pg.GenSchema.Suffix = "Value"
	if sg.Path != "" {
		pg.Path = sg.Path + "+\".\"+" + pg.KeyVar
	}
	// propagates the special IsNullable override for maps of slices and
	// maps of aliased types.
	pg.GenSchema.IsMapNullOverride = sg.GenSchema.IsMapNullOverride
	return pg
}

func hasContextValidations(model *spec.Schema) bool {
	// always assume ref needs context validate
	// TODO: find away to determine ref needs context validate or not
	if model.ReadOnly || model.Ref.String() != "" {
		return true
	}
	return false
}

func hasValidations(model *spec.Schema, isRequired bool) bool {
	if isRequired {
		return true
	}

	v := model.Validations()
	if v.HasNumberValidations() || v.HasStringValidations() || v.HasArrayValidations() || v.HasEnum() || v.HasObjectValidations() {
		return true
	}

	// since this was added to deal with discriminator, we'll fix this when testing discriminated types
	if len(model.Properties) > 0 && model.Discriminator == "" {
		return true
	}

	// lift validations from allOf branches
	for _, s := range model.AllOf {
		schema := s
		if s.Ref.String() != "" || hasValidations(&schema, false) {
			return true
		}
	}

	return false
}

func hasFormatValidation(tpe resolvedType) bool {
	if tpe.IsCustomFormatter && !tpe.IsStream && !tpe.IsBase64 {
		return true
	}
	if tpe.IsArray && tpe.ElemType != nil {
		return hasFormatValidation(*tpe.ElemType)
	}
	return false
}

func (sg *schemaGenContext) schemaValidations() sharedValidations {
	model := sg.Schema

	isRequired := sg.Required
	if model.Default != nil || model.ReadOnly {
		// when readOnly or default is specified, this disables Required validation (Swagger-specific)
		isRequired = false
		if sg.Required {
			log.Printf("warn: properties with a default value or readOnly should not be required [%s]", sg.Name)
		}
	}

	v := model.Validations()
	return sharedValidations{
		Required:            sg.Required, /* TODO(fred): guard for cases with discriminator field, default and readOnly*/
		SchemaValidations:   v,
		HasSliceValidations: v.HasArrayValidations() || v.HasEnum(),
		HasValidations:      hasValidations(&model, isRequired),
	}
}

func mergeValidation(other *schemaGenContext) bool {
	// NOTE: NeesRequired and NeedsValidation are deprecated
	if other.GenSchema.AdditionalProperties != nil && other.GenSchema.AdditionalProperties.HasValidations {
		return true
	}
	if other.GenSchema.AdditionalItems != nil && other.GenSchema.AdditionalItems.HasValidations {
		return true
	}
	for _, sch := range other.GenSchema.AllOf {
		if sch.HasValidations {
			return true
		}
	}
	return other.GenSchema.HasValidations
}

func (sg *schemaGenContext) MergeResult(other *schemaGenContext, liftsRequired bool) {
	sg.GenSchema.HasValidations = sg.GenSchema.HasValidations || mergeValidation(other)
	sg.GenSchema.HasContextValidations = sg.GenSchema.HasContextValidations || other.GenSchema.HasContextValidations

	if liftsRequired && other.GenSchema.AdditionalProperties != nil && other.GenSchema.AdditionalProperties.Required {
		sg.GenSchema.Required = true
	}
	if liftsRequired && other.GenSchema.Required {
		sg.GenSchema.Required = other.GenSchema.Required
	}

	if other.GenSchema.HasBaseType {
		sg.GenSchema.HasBaseType = other.GenSchema.HasBaseType
	}

	sg.Dependencies = append(sg.Dependencies, other.Dependencies...)

	// lift extra schemas
	for k, v := range other.ExtraSchemas {
		sg.ExtraSchemas[k] = v
	}
	if other.GenSchema.IsMapNullOverride {
		sg.GenSchema.IsMapNullOverride = true
	}

	// lift extra imports
	if other.GenSchema.Pkg != "" && other.GenSchema.PkgAlias != "" {
		sg.GenSchema.ExtraImports[other.GenSchema.PkgAlias] = other.GenSchema.Pkg
	}
	for k, v := range other.GenSchema.ExtraImports {
		sg.GenSchema.ExtraImports[k] = v
	}
}

func (sg *schemaGenContext) buildProperties() error {
	debugLog("building properties %s (parent: %s)", sg.Name, sg.Container)

	for k, v := range sg.Schema.Properties {
		debugLogAsJSON("building property %s[%q] (IsTuple: %t) (IsBaseType: %t) (HasValidations: %t)",
			sg.Name, k, sg.IsTuple, sg.GenSchema.IsBaseType, sg.GenSchema.HasValidations, v)

		vv := v

		// check if this requires de-anonymizing, if so lift this as a new struct and extra schema
		tpe, err := sg.TypeResolver.ResolveSchema(&vv, true, sg.IsTuple || swag.ContainsStrings(sg.Schema.Required, k))
		if err != nil {
			return err
		}
		if sg.Schema.Discriminator == k {
			tpe.IsNullable = false
		}

		var hasValidation bool
		if tpe.IsComplexObject && tpe.IsAnonymous && len(v.Properties) > 0 {
			// this is an anonymous complex construct: build a new new type for it
			pg := sg.makeNewStruct(sg.makeRefName()+swag.ToGoName(k), v)
			pg.IsTuple = sg.IsTuple
			if sg.Path != "" {
				pg.Path = sg.Path + "+ \".\"+" + fmt.Sprintf("%q", k)
			} else {
				pg.Path = fmt.Sprintf("%q", k)
			}
			if err := pg.makeGenSchema(); err != nil {
				return err
			}
			if v.Discriminator != "" {
				pg.GenSchema.IsBaseType = true
				pg.GenSchema.IsExported = true
				pg.GenSchema.HasBaseType = true
			}

			vv = *spec.RefProperty("#/definitions/" + pg.Name)
			hasValidation = pg.GenSchema.HasValidations
			sg.ExtraSchemas[pg.Name] = pg.GenSchema
			// NOTE: MergeResult lifts validation status and extra schemas
			sg.MergeResult(pg, false)
		}

		emprop := sg.NewStructBranch(k, vv)
		emprop.IsTuple = sg.IsTuple

		if err := emprop.makeGenSchema(); err != nil {
			return err
		}

		// whatever the validations says, if we have an interface{}, do not validate
		// NOTE: this may be the case when the type is left empty and we get a Enum validation.
		if emprop.GenSchema.IsInterface || emprop.GenSchema.IsStream {
			emprop.GenSchema.HasValidations = false
		} else if hasValidation || emprop.GenSchema.HasValidations || emprop.GenSchema.Required || emprop.GenSchema.IsAliased || len(emprop.GenSchema.AllOf) > 0 {
			emprop.GenSchema.HasValidations = true
			sg.GenSchema.HasValidations = true
		}

		// generates format validation on property
		emprop.GenSchema.HasValidations = emprop.GenSchema.HasValidations || hasFormatValidation(tpe)

		if emprop.Schema.Ref.String() != "" {
			// expand the schema of this property, so we take informed decisions about its type
			ref := emprop.Schema.Ref
			var sch *spec.Schema
			for ref.String() != "" {
				var rsch *spec.Schema
				var err error
				specDoc := sg.TypeResolver.Doc
				rsch, err = spec.ResolveRef(specDoc.Spec(), &ref)
				if err != nil {
					return err
				}
				if rsch == nil {
					return errors.New("spec.ResolveRef returned nil schema")
				}
				if rsch != nil && rsch.Ref.String() != "" {
					ref = rsch.Ref
					continue
				}
				ref = spec.Ref{}
				sch = rsch
			}

			if emprop.Discrimination != nil {
				if _, ok := emprop.Discrimination.Discriminators[emprop.Schema.Ref.String()]; ok {
					emprop.GenSchema.IsBaseType = true
					emprop.GenSchema.IsNullable = false
					emprop.GenSchema.HasBaseType = true
				}
				if _, ok := emprop.Discrimination.Discriminated[emprop.Schema.Ref.String()]; ok {
					emprop.GenSchema.IsSubType = true
				}
			}

			// set property name
			var nm = filepath.Base(emprop.Schema.Ref.GetURL().Fragment)

			tr := sg.TypeResolver.NewWithModelName(goName(&emprop.Schema, swag.ToGoName(nm)))
			ttpe, err := tr.ResolveSchema(sch, false, true)
			if err != nil {
				return err
			}
			if ttpe.IsAliased {
				emprop.GenSchema.IsAliased = true
			}

			// lift validations
			hv := hasValidations(sch, false)

			// include format validation, excluding binary
			hv = hv || hasFormatValidation(ttpe)

			// a base type property is always validated against the base type
			// exception: for the base type definition itself (see shallowValidationLookup())
			if (hv || emprop.GenSchema.IsBaseType) && !(emprop.GenSchema.IsInterface || emprop.GenSchema.IsStream) {
				emprop.GenSchema.HasValidations = true
			}
			if ttpe.HasAdditionalItems && sch.AdditionalItems.Schema != nil {
				// when AdditionalItems specifies a Schema, there is a validation
				// check if we stepped upon an exception
				child, err := tr.ResolveSchema(sch.AdditionalItems.Schema, false, true)
				if err != nil {
					return err
				}
				if !child.IsInterface && !child.IsStream {
					emprop.GenSchema.HasValidations = true
				}
			}
			if ttpe.IsMap && sch.AdditionalProperties != nil && sch.AdditionalProperties.Schema != nil {
				// when AdditionalProperties specifies a Schema, there is a validation
				// check if we stepped upon an exception
				child, err := tr.ResolveSchema(sch.AdditionalProperties.Schema, false, true)
				if err != nil {
					return err
				}
				if !child.IsInterface && !child.IsStream {
					emprop.GenSchema.HasValidations = true
				}
			}
		}

		if sg.Schema.Discriminator == k {
			// this is the discriminator property:
			// it is required, but forced as non-nullable,
			// since we never fill it with a zero-value
			// TODO: when no other property than discriminator, there is no validation
			emprop.GenSchema.IsNullable = false
		}
		if emprop.GenSchema.IsBaseType {
			sg.GenSchema.HasBaseType = true
		}
		sg.MergeResult(emprop, false)

		// when discriminated, data is accessed via a getter func
		if emprop.GenSchema.HasDiscriminator {
			emprop.GenSchema.ValueExpression += asMethod
		}

		emprop.GenSchema.Extensions = emprop.Schema.Extensions

		// set custom serializer tag
		if customTag, found := tpe.Extensions[xGoCustomTag]; found {
			tagAsStr, ok := customTag.(string)
			if ok {
				emprop.GenSchema.CustomTag = tagAsStr
			} else {
				log.Printf("warning: expect %s extension to be a string, got: %v. Skipped", xGoCustomTag, customTag)
			}
		}
		sg.GenSchema.Properties = append(sg.GenSchema.Properties, emprop.GenSchema)
	}
	sort.Sort(sg.GenSchema.Properties)

	return nil
}

func (sg *schemaGenContext) buildAllOf() error {
	if len(sg.Schema.AllOf) == 0 {
		return nil
	}

	var hasArray, hasNonArray int

	sort.Sort(sg.GenSchema.AllOf)
	if sg.Container == "" {
		sg.Container = sg.Name
	}
	debugLogAsJSON("building all of for %d entries", len(sg.Schema.AllOf), sg.Schema)
	for i, schema := range sg.Schema.AllOf {
		sch := schema
		tpe, ert := sg.TypeResolver.ResolveSchema(&sch, sch.Ref.String() == "", false)
		if ert != nil {
			return ert
		}

		// check for multiple arrays in allOf branches.
		// Although a valid JSON-Schema construct, it is not suited for serialization.
		// This is the same if we attempt to serialize an array with another object.
		// We issue a generation warning on this.
		if tpe.IsArray {
			hasArray++
		} else {
			hasNonArray++
		}
		debugLogAsJSON("trying", sch)
		if (tpe.IsAnonymous && len(sch.AllOf) > 0) || (sch.Ref.String() == "" && !tpe.IsComplexObject && (tpe.IsArray || tpe.IsInterface || tpe.IsPrimitive)) {
			// cases where anonymous structures cause the creation of a new type:
			// - nested allOf: this one is itself a AllOf: build a new type for it
			// - anonymous simple types for edge cases: array, primitive, interface{}
			// NOTE: when branches are aliased or anonymous, the nullable property in the branch type is lost.
			name := swag.ToVarName(goName(&sch, sg.makeRefName()+"AllOf"+strconv.Itoa(i)))
			debugLog("building anonymous nested allOf in %s: %s", sg.Name, name)
			ng := sg.makeNewStruct(name, sch)
			if err := ng.makeGenSchema(); err != nil {
				return err
			}

			newsch := spec.RefProperty("#/definitions/" + ng.Name)
			sg.Schema.AllOf[i] = *newsch

			pg := sg.NewCompositionBranch(*newsch, i)
			if err := pg.makeGenSchema(); err != nil {
				return err
			}

			// lift extra schemas & validations from new type
			pg.MergeResult(ng, true)

			// lift validations when complex or ref'ed:
			// - parent always calls its Validatable child
			// - child may or may not have validations
			//
			// Exception: child is not Validatable when interface or stream
			if !pg.GenSchema.IsInterface && !pg.GenSchema.IsStream {
				sg.GenSchema.HasValidations = true
			}

			// add the newly created type to the list of schemas to be rendered inline
			pg.ExtraSchemas[ng.Name] = ng.GenSchema

			sg.MergeResult(pg, true)

			sg.GenSchema.AllOf = append(sg.GenSchema.AllOf, pg.GenSchema)

			continue
		}

		comprop := sg.NewCompositionBranch(sch, i)
		if err := comprop.makeGenSchema(); err != nil {
			return err
		}
		if comprop.GenSchema.IsMap && comprop.GenSchema.HasAdditionalProperties && comprop.GenSchema.AdditionalProperties != nil && !comprop.GenSchema.IsInterface {
			// the anonymous branch is a map for AdditionalProperties: rewrite value expression
			comprop.GenSchema.ValueExpression = comprop.GenSchema.ValueExpression + "." + comprop.Name
			comprop.GenSchema.AdditionalProperties.ValueExpression = comprop.GenSchema.ValueExpression + "[" + comprop.GenSchema.AdditionalProperties.KeyVar + "]"
		}

		// lift validations when complex or ref'ed
		if (comprop.GenSchema.IsComplexObject || comprop.Schema.Ref.String() != "") && !(comprop.GenSchema.IsInterface || comprop.GenSchema.IsStream) {
			comprop.GenSchema.HasValidations = true
		}
		sg.MergeResult(comprop, true)
		sg.GenSchema.AllOf = append(sg.GenSchema.AllOf, comprop.GenSchema)
	}

	if hasArray > 1 || (hasArray > 0 && hasNonArray > 0) {
		log.Printf("warning: cannot generate serializable allOf with conflicting array definitions in %s", sg.Container)
	}

	// AllOf types are always considered nullable, except when an extension says otherwise
	if override, ok := sg.TypeResolver.isNullableOverride(&sg.Schema); ok {
		sg.GenSchema.IsNullable = override
	} else {
		sg.GenSchema.IsNullable = true
	}

	// prevent IsAliased to bubble up (e.g. when a single branch is itself aliased)
	sg.GenSchema.IsAliased = sg.GenSchema.IsAliased && len(sg.GenSchema.AllOf) < 2

	return nil
}

type mapStack struct {
	Type     *spec.Schema
	Next     *mapStack
	Previous *mapStack
	ValueRef *schemaGenContext
	Context  *schemaGenContext
	NewObj   *schemaGenContext
}

func newMapStack(context *schemaGenContext) (first, last *mapStack, err error) {
	ms := &mapStack{
		Type:    &context.Schema,
		Context: context,
	}

	l := ms
	for l.HasMore() {
		tpe, err := l.Context.TypeResolver.ResolveSchema(l.Type.AdditionalProperties.Schema, true, true)
		if err != nil {
			return nil, nil, err
		}

		if !tpe.IsMap {
			// reached the end of the rabbit hole
			if tpe.IsComplexObject && tpe.IsAnonymous {
				// found an anonymous object: create the struct from a newly created definition
				nw := l.Context.makeNewStruct(l.Context.makeRefName()+" Anon", *l.Type.AdditionalProperties.Schema)
				sch := spec.RefProperty("#/definitions/" + nw.Name)
				l.NewObj = nw

				l.Type.AdditionalProperties.Schema = sch
				l.ValueRef = l.Context.NewAdditionalProperty(*sch)
			}

			// other cases where to stop are: a $ref or a simple object
			break
		}

		// continue digging for maps
		l.Next = &mapStack{
			Previous: l,
			Type:     l.Type.AdditionalProperties.Schema,
			Context:  l.Context.NewAdditionalProperty(*l.Type.AdditionalProperties.Schema),
		}
		l = l.Next
	}

	// return top and bottom entries of this stack of AdditionalProperties
	return ms, l, nil
}

// Build rewinds the stack of additional properties, building schemas from bottom to top
func (mt *mapStack) Build() error {
	if mt.NewObj == nil && mt.ValueRef == nil && mt.Next == nil && mt.Previous == nil {
		csch := mt.Type.AdditionalProperties.Schema
		cp := mt.Context.NewAdditionalProperty(*csch)
		d := mt.Context.TypeResolver.Doc

		asch, err := analysis.Schema(analysis.SchemaOpts{
			Root:     d.Spec(),
			BasePath: d.SpecFilePath(),
			Schema:   csch,
		})
		if err != nil {
			return err
		}
		cp.Required = !asch.IsSimpleSchema && !asch.IsMap

		// when the schema is an array or an alias, this may result in inconsistent
		// nullable status between the map element and the array element (resp. the aliased type).
		//
		// Example: when an object has no property and only additionalProperties,
		// which turn out to be arrays of some other object.

		// save the initial override
		hadOverride := cp.GenSchema.IsMapNullOverride
		if err := cp.makeGenSchema(); err != nil {
			return err
		}

		// if we have an override at the top of stack, propagates it down nested arrays
		if hadOverride && cp.GenSchema.IsArray {
			// do it for nested arrays: override is also about map[string][][]... constructs
			it := &cp.GenSchema
			for it.Items != nil && it.IsArray {
				it.Items.IsMapNullOverride = hadOverride
				it = it.Items
			}
		}
		// cover other cases than arrays (aliased types)
		cp.GenSchema.IsMapNullOverride = hadOverride

		mt.Context.MergeResult(cp, false)
		mt.Context.GenSchema.AdditionalProperties = &cp.GenSchema

		// lift validations
		if (csch.Ref.String() != "" || cp.GenSchema.IsAliased) && !(cp.GenSchema.IsInterface || cp.GenSchema.IsStream) {
			// - we stopped on a ref, or anything else that require we call its Validate() method
			// - if the alias / ref is on an interface (or stream) type: no validation
			mt.Context.GenSchema.HasValidations = true
			mt.Context.GenSchema.AdditionalProperties.HasValidations = true
		}

		debugLog("early mapstack exit, nullable: %t for %s", cp.GenSchema.IsNullable, cp.GenSchema.Name)
		return nil
	}
	cur := mt
	for cur != nil {
		if cur.NewObj != nil {
			// a new model has been created during the stack construction (new ref on anonymous object)
			if err := cur.NewObj.makeGenSchema(); err != nil {
				return err
			}
		}

		if cur.ValueRef != nil {
			if err := cur.ValueRef.makeGenSchema(); err != nil {
				return nil
			}
		}

		if cur.NewObj != nil {
			// newly created model from anonymous object is declared as extra schema
			cur.Context.MergeResult(cur.NewObj, false)

			// propagates extra schemas
			cur.Context.ExtraSchemas[cur.NewObj.Name] = cur.NewObj.GenSchema
		}

		if cur.ValueRef != nil {
			// this is the genSchema for this new anonymous AdditionalProperty
			if err := cur.Context.makeGenSchema(); err != nil {
				return err
			}

			// if there is a ValueRef, we must have a NewObj (from newMapStack() construction)
			cur.ValueRef.GenSchema.HasValidations = cur.NewObj.GenSchema.HasValidations
			cur.Context.MergeResult(cur.ValueRef, false)
			cur.Context.GenSchema.AdditionalProperties = &cur.ValueRef.GenSchema
		}

		if cur.Previous != nil {
			// we have a parent schema: build a schema for current AdditionalProperties
			if err := cur.Context.makeGenSchema(); err != nil {
				return err
			}
		}
		if cur.Next != nil {
			// we previously made a child schema: lifts things from that one
			// - Required is not lifted (in a cascade of maps, only the last element is actually checked for Required)
			cur.Context.MergeResult(cur.Next.Context, false)
			cur.Context.GenSchema.AdditionalProperties = &cur.Next.Context.GenSchema

			// lift validations
			c := &cur.Next.Context.GenSchema
			if (cur.Next.Context.Schema.Ref.String() != "" || c.IsAliased) && !(c.IsInterface || c.IsStream) {
				// - we stopped on a ref, or anything else that require we call its Validate()
				// - if the alias / ref is on an interface (or stream) type: no validation
				cur.Context.GenSchema.HasValidations = true
				cur.Context.GenSchema.AdditionalProperties.HasValidations = true
			}
		}
		if cur.ValueRef != nil {
			cur.Context.MergeResult(cur.ValueRef, false)
			cur.Context.GenSchema.AdditionalProperties = &cur.ValueRef.GenSchema
		}

		if cur.Context.GenSchema.AdditionalProperties != nil {
			// propagate overrides up the resolved schemas, but leaves any ExtraSchema untouched
			cur.Context.GenSchema.AdditionalProperties.IsMapNullOverride = cur.Context.GenSchema.IsMapNullOverride
		}
		cur = cur.Previous
	}

	return nil
}

func (mt *mapStack) HasMore() bool {
	return mt.Type.AdditionalProperties != nil && (mt.Type.AdditionalProperties.Schema != nil || mt.Type.AdditionalProperties.Allows)
}

/* currently unused:
func (mt *mapStack) Dict() map[string]interface{} {
	res := make(map[string]interface{})
	res["context"] = mt.Context.Schema
	if mt.Next != nil {
		res["next"] = mt.Next.Dict()
	}
	if mt.NewObj != nil {
		res["obj"] = mt.NewObj.Schema
	}
	if mt.ValueRef != nil {
		res["value"] = mt.ValueRef.Schema
	}
	return res
}
*/

func (sg *schemaGenContext) buildAdditionalProperties() error {
	if sg.Schema.AdditionalProperties == nil {
		return nil
	}
	addp := *sg.Schema.AdditionalProperties

	wantsAdditional := addp.Schema != nil || addp.Allows
	sg.GenSchema.HasAdditionalProperties = wantsAdditional
	if !wantsAdditional {
		return nil
	}

	// flag swap
	if sg.GenSchema.IsComplexObject {
		sg.GenSchema.IsAdditionalProperties = true
		sg.GenSchema.IsComplexObject = false
		sg.GenSchema.IsMap = false
	}

	if addp.Schema == nil {
		// this is for AdditionalProperties:true|false
		if addp.Allows {
			// additionalProperties: true is rendered as: map[string]interface{}
			addp.Schema = &spec.Schema{}

			addp.Schema.Typed("object", "")
			sg.GenSchema.HasAdditionalProperties = true
			sg.GenSchema.IsComplexObject = false
			sg.GenSchema.IsMap = true

			sg.GenSchema.ValueExpression += "." + swag.ToGoName(sg.Name+" additionalProperties")
			cp := sg.NewAdditionalProperty(*addp.Schema)
			cp.Name += "AdditionalProperties"
			cp.Required = false
			if err := cp.makeGenSchema(); err != nil {
				return err
			}
			sg.MergeResult(cp, false)
			sg.GenSchema.AdditionalProperties = &cp.GenSchema
			debugLog("added interface{} schema for additionalProperties[allows == true], IsInterface=%t", cp.GenSchema.IsInterface)
		}
		return nil
	}

	if !sg.GenSchema.IsMap && (sg.GenSchema.IsAdditionalProperties && sg.Named) {
		// we have a complex object with an AdditionalProperties schema

		tpe, ert := sg.TypeResolver.ResolveSchema(addp.Schema, addp.Schema.Ref.String() == "", false)
		if ert != nil {
			return ert
		}

		if tpe.IsComplexObject && tpe.IsAnonymous {
			// if the AdditionalProperties is an anonymous complex object, generate a new type for it
			pg := sg.makeNewStruct(sg.makeRefName()+" Anon", *addp.Schema)
			if err := pg.makeGenSchema(); err != nil {
				return err
			}
			sg.MergeResult(pg, false)
			sg.ExtraSchemas[pg.Name] = pg.GenSchema

			sg.Schema.AdditionalProperties.Schema = spec.RefProperty("#/definitions/" + pg.Name)
			sg.IsVirtual = true

			comprop := sg.NewAdditionalProperty(*sg.Schema.AdditionalProperties.Schema)
			if err := comprop.makeGenSchema(); err != nil {
				return err
			}

			comprop.GenSchema.Required = true
			comprop.GenSchema.HasValidations = true

			comprop.GenSchema.ValueExpression = sg.GenSchema.ValueExpression + "." + swag.ToGoName(sg.GenSchema.Name) + "[" + comprop.KeyVar + "]"

			sg.GenSchema.AdditionalProperties = &comprop.GenSchema
			sg.GenSchema.HasAdditionalProperties = true
			sg.GenSchema.ValueExpression += "." + swag.ToGoName(sg.GenSchema.Name)

			sg.MergeResult(comprop, false)

			return nil
		}

		// this is a regular named schema for AdditionalProperties
		sg.GenSchema.ValueExpression += "." + swag.ToGoName(sg.GenSchema.Name)
		comprop := sg.NewAdditionalProperty(*addp.Schema)
		d := sg.TypeResolver.Doc
		asch, err := analysis.Schema(analysis.SchemaOpts{
			Root:     d.Spec(),
			BasePath: d.SpecFilePath(),
			Schema:   addp.Schema,
		})
		if err != nil {
			return err
		}
		comprop.Required = !asch.IsSimpleSchema && !asch.IsMap
		if err := comprop.makeGenSchema(); err != nil {
			return err
		}

		sg.MergeResult(comprop, false)
		sg.GenSchema.AdditionalProperties = &comprop.GenSchema
		sg.GenSchema.AdditionalProperties.ValueExpression = sg.GenSchema.ValueExpression + "[" + comprop.KeyVar + "]"

		// rewrite value expression for arrays and arrays of arrays in maps (rendered as map[string][][]...)
		if sg.GenSchema.AdditionalProperties.IsArray {
			// maps of slices are where an override may take effect
			sg.GenSchema.AdditionalProperties.Items.IsMapNullOverride = sg.GenSchema.AdditionalProperties.IsMapNullOverride
			sg.GenSchema.AdditionalProperties.Items.ValueExpression = sg.GenSchema.ValueExpression + "[" + comprop.KeyVar + "]" + "[" + sg.GenSchema.AdditionalProperties.IndexVar + "]"
			ap := sg.GenSchema.AdditionalProperties.Items
			for ap != nil && ap.IsArray {
				ap.Items.IsMapNullOverride = ap.IsMapNullOverride
				ap.Items.ValueExpression = ap.ValueExpression + "[" + ap.IndexVar + "]"
				ap = ap.Items
			}
		}

		// lift validation
		if (sg.GenSchema.AdditionalProperties.IsComplexObject || sg.GenSchema.AdditionalProperties.IsAliased || sg.GenSchema.AdditionalProperties.Required) && !(sg.GenSchema.AdditionalProperties.IsInterface || sg.GenSchema.IsStream) {
			sg.GenSchema.HasValidations = true
		}
		return nil
	}

	if sg.GenSchema.IsMap && wantsAdditional {
		// this is itself an AdditionalProperties schema with some AdditionalProperties.
		// this also runs for aliased map types (with zero properties save additionalProperties)
		//
		// find out how deep this rabbit hole goes
		// descend, unwind and rewrite
		// This needs to be depth first, so it first goes as deep as it can and then
		// builds the result in reverse order.
		_, ls, err := newMapStack(sg)
		if err != nil {
			return err
		}
		return ls.Build()
	}

	if sg.GenSchema.IsAdditionalProperties && !sg.Named {
		// for an anonymous object, first build the new object
		// and then replace the current one with a $ref to the
		// new object
		newObj := sg.makeNewStruct(sg.GenSchema.Name+" P"+strconv.Itoa(sg.Index), sg.Schema)
		if err := newObj.makeGenSchema(); err != nil {
			return err
		}

		hasMapNullOverride := sg.GenSchema.IsMapNullOverride
		sg.GenSchema = GenSchema{StructTags: sg.StructTags}
		sg.Schema = *spec.RefProperty("#/definitions/" + newObj.Name)
		if err := sg.makeGenSchema(); err != nil {
			return err
		}
		sg.MergeResult(newObj, false)

		sg.GenSchema.IsMapNullOverride = hasMapNullOverride
		if sg.GenSchema.IsArray {
			sg.GenSchema.Items.IsMapNullOverride = hasMapNullOverride
		}

		sg.GenSchema.HasValidations = newObj.GenSchema.HasValidations
		sg.ExtraSchemas[newObj.Name] = newObj.GenSchema
		return nil
	}
	return nil
}

func (sg *schemaGenContext) makeNewStruct(name string, schema spec.Schema) *schemaGenContext {
	debugLog("making new struct: name: %s, container: %s", name, sg.Container)
	sp := sg.TypeResolver.Doc.Spec()
	name = swag.ToGoName(name)
	if sg.TypeResolver.ModelName != sg.Name {
		name = swag.ToGoName(sg.TypeResolver.ModelName + " " + name)
	}
	if sp.Definitions == nil {
		sp.Definitions = make(spec.Definitions)
	}
	sp.Definitions[name] = schema
	pg := schemaGenContext{
		Path:                       "",
		Name:                       name,
		Receiver:                   sg.Receiver,
		IndexVar:                   "i",
		ValueExpr:                  sg.Receiver,
		Schema:                     schema,
		Required:                   false,
		Named:                      true,
		ExtraSchemas:               make(map[string]GenSchema),
		Discrimination:             sg.Discrimination,
		Container:                  sg.Container,
		IncludeValidator:           sg.IncludeValidator,
		IncludeModel:               sg.IncludeModel,
		StrictAdditionalProperties: sg.StrictAdditionalProperties,
		StructTags:                 sg.StructTags,
	}
	if schema.Ref.String() == "" {
		pg.TypeResolver = sg.TypeResolver.NewWithModelName(name)
	}
	pg.GenSchema.IsVirtual = true

	sg.ExtraSchemas[name] = pg.GenSchema
	return &pg
}

func (sg *schemaGenContext) buildArray() error {
	tpe, err := sg.TypeResolver.ResolveSchema(sg.Schema.Items.Schema, true, false)
	if err != nil {
		return err
	}

	// check if the element is a complex object, if so generate a new type for it
	if tpe.IsComplexObject && tpe.IsAnonymous {
		pg := sg.makeNewStruct(sg.makeRefName()+" items"+strconv.Itoa(sg.Index), *sg.Schema.Items.Schema)
		if err := pg.makeGenSchema(); err != nil {
			return err
		}
		sg.MergeResult(pg, false)
		sg.ExtraSchemas[pg.Name] = pg.GenSchema
		sg.Schema.Items.Schema = spec.RefProperty("#/definitions/" + pg.Name)
		sg.IsVirtual = true
		return sg.makeGenSchema()
	}

	// create the generation schema for items
	elProp := sg.NewSliceBranch(sg.Schema.Items.Schema)

	// when building a slice of maps, the map item is not required
	// items from maps of aliased or nullable type remain required

	// NOTE(fredbi): since this is reset below, this Required = true serves the obscure purpose
	// of indirectly lifting validations from the slice. This is carried out differently now.
	// elProp.Required = true

	if err := elProp.makeGenSchema(); err != nil {
		return err
	}

	sg.MergeResult(elProp, false)

	sg.GenSchema.IsBaseType = elProp.GenSchema.IsBaseType
	sg.GenSchema.ItemsEnum = elProp.GenSchema.Enum
	elProp.GenSchema.Suffix = "Items"

	elProp.GenSchema.IsNullable = tpe.IsNullable && !tpe.HasDiscriminator
	if elProp.GenSchema.IsNullable {
		sg.GenSchema.GoType = "[]*" + elProp.GenSchema.GoType
	} else {
		sg.GenSchema.GoType = "[]" + elProp.GenSchema.GoType
	}

	sg.GenSchema.IsArray = true

	schemaCopy := elProp.GenSchema

	schemaCopy.Required = false

	// validations of items
	// include format validation, excluding binary and base64 format validation
	hv := hasValidations(sg.Schema.Items.Schema, false) || hasFormatValidation(schemaCopy.resolvedType)

	// base types of polymorphic types must be validated
	// NOTE: IsNullable is not useful to figure out a validation: we use Refed and IsAliased below instead
	if hv || elProp.GenSchema.IsBaseType {
		schemaCopy.HasValidations = true
	}

	if (elProp.Schema.Ref.String() != "" || elProp.GenSchema.IsAliased) && !(elProp.GenSchema.IsInterface || elProp.GenSchema.IsStream) {
		schemaCopy.HasValidations = true
	}

	// lift validations
	sg.GenSchema.HasValidations = sg.GenSchema.HasValidations || schemaCopy.HasValidations
	sg.GenSchema.HasSliceValidations = sg.Schema.Validations().HasArrayValidations() || sg.Schema.Validations().HasEnum()

	// prevents bubbling custom formatter flag
	sg.GenSchema.IsCustomFormatter = false

	sg.GenSchema.Items = &schemaCopy
	if sg.Named {
		sg.GenSchema.AliasedType = sg.GenSchema.GoType
	}

	return nil
}

func (sg *schemaGenContext) buildItems() error {
	if sg.Schema.Items == nil {
		// in swagger, arrays MUST have an items schema
		return nil
	}

	// in Items spec, we have either Schema (array) or Schemas (tuple)
	presentsAsSingle := sg.Schema.Items.Schema != nil
	if presentsAsSingle && sg.Schema.AdditionalItems != nil { // unsure if this a valid of invalid schema
		return fmt.Errorf("single schema (%s) can't have additional items", sg.Name)
	}
	if presentsAsSingle {
		return sg.buildArray()
	}

	// This is a tuple, build a new model that represents this
	if sg.Named {
		sg.GenSchema.Name = sg.Name
		sg.GenSchema.GoType = sg.TypeResolver.goTypeName(sg.Name)
		for i, sch := range sg.Schema.Items.Schemas {
			s := sch
			elProp := sg.NewTupleElement(&s, i)

			if s.Ref.String() == "" {
				tpe, err := sg.TypeResolver.ResolveSchema(&s, s.Ref.String() == "", true)
				if err != nil {
					return err
				}
				if tpe.IsComplexObject && tpe.IsAnonymous {
					// if the tuple element is an anonymous complex object, build a new type for it
					pg := sg.makeNewStruct(sg.makeRefName()+" Items"+strconv.Itoa(i), s)
					if err := pg.makeGenSchema(); err != nil {
						return err
					}
					elProp.Schema = *spec.RefProperty("#/definitions/" + pg.Name)
					elProp.MergeResult(pg, false)
					elProp.ExtraSchemas[pg.Name] = pg.GenSchema
				}
			}

			if err := elProp.makeGenSchema(); err != nil {
				return err
			}
			if elProp.GenSchema.IsInterface || elProp.GenSchema.IsStream {
				elProp.GenSchema.HasValidations = false
			}
			sg.MergeResult(elProp, false)

			elProp.GenSchema.Name = "p" + strconv.Itoa(i)
			sg.GenSchema.Properties = append(sg.GenSchema.Properties, elProp.GenSchema)
			sg.GenSchema.IsTuple = true
		}
		return nil
	}

	// for an anonymous object, first build the new object
	// and then replace the current one with a $ref to the
	// new tuple object
	var sch spec.Schema
	sch.Typed("object", "")
	sch.Properties = make(map[string]spec.Schema, len(sg.Schema.Items.Schemas))
	for i, v := range sg.Schema.Items.Schemas {
		sch.Required = append(sch.Required, "P"+strconv.Itoa(i))
		sch.Properties["P"+strconv.Itoa(i)] = v
	}
	sch.AdditionalItems = sg.Schema.AdditionalItems
	tup := sg.makeNewStruct(sg.GenSchema.Name+"Tuple"+strconv.Itoa(sg.Index), sch)
	tup.IsTuple = true
	if err := tup.makeGenSchema(); err != nil {
		return err
	}
	tup.GenSchema.IsTuple = true
	tup.GenSchema.IsComplexObject = false
	tup.GenSchema.Title = tup.GenSchema.Name + " a representation of an anonymous Tuple type"
	tup.GenSchema.Description = ""
	sg.ExtraSchemas[tup.Name] = tup.GenSchema

	sg.Schema = *spec.RefProperty("#/definitions/" + tup.Name)
	if err := sg.makeGenSchema(); err != nil {
		return err
	}
	sg.MergeResult(tup, false)
	return nil
}

func (sg *schemaGenContext) buildAdditionalItems() error {
	wantsAdditionalItems :=
		sg.Schema.AdditionalItems != nil &&
			(sg.Schema.AdditionalItems.Allows || sg.Schema.AdditionalItems.Schema != nil)

	sg.GenSchema.HasAdditionalItems = wantsAdditionalItems
	if wantsAdditionalItems {
		// check if the element is a complex object, if so generate a new type for it
		tpe, err := sg.TypeResolver.ResolveSchema(sg.Schema.AdditionalItems.Schema, true, true)
		if err != nil {
			return err
		}
		if tpe.IsComplexObject && tpe.IsAnonymous {
			pg := sg.makeNewStruct(sg.makeRefName()+" Items", *sg.Schema.AdditionalItems.Schema)
			if err := pg.makeGenSchema(); err != nil {
				return err
			}
			sg.Schema.AdditionalItems.Schema = spec.RefProperty("#/definitions/" + pg.Name)
			pg.GenSchema.HasValidations = true
			sg.MergeResult(pg, false)
			sg.ExtraSchemas[pg.Name] = pg.GenSchema
		}

		it := sg.NewAdditionalItems(sg.Schema.AdditionalItems.Schema)
		// if AdditionalItems are themselves arrays, bump the index var
		if tpe.IsArray {
			it.IndexVar += "i"
		}

		if tpe.IsInterface {
			it.Untyped = true
		}

		if err := it.makeGenSchema(); err != nil {
			return err
		}

		// lift validations when complex is not anonymous or ref'ed
		if (tpe.IsComplexObject || it.Schema.Ref.String() != "") && !(tpe.IsInterface || tpe.IsStream) {
			it.GenSchema.HasValidations = true
		}

		sg.MergeResult(it, true)
		sg.GenSchema.AdditionalItems = &it.GenSchema
	}
	return nil
}

func (sg *schemaGenContext) buildXMLNameWithTags() error {
	// render some "xml" struct tag under one the following conditions:
	// - consumes/produces in spec contains xml
	// - struct tags CLI option contains xml
	// - XML object present in spec for this schema
	if sg.WithXML || swag.ContainsStrings(sg.StructTags, "xml") || sg.Schema.XML != nil {
		sg.GenSchema.XMLName = sg.Name

		if sg.Schema.XML != nil {
			if sg.Schema.XML.Name != "" {
				sg.GenSchema.XMLName = sg.Schema.XML.Name
			}
			if sg.Schema.XML.Attribute {
				sg.GenSchema.XMLName += ",attr"
			}
		}
	}
	return nil
}

func (sg *schemaGenContext) shortCircuitNamedRef() (bool, error) {
	// This if block ensures that a struct gets
	// rendered with the ref as embedded ref.
	//
	// NOTE: this assumes that all $ref point to a definition,
	// i.e. the spec is canonical, as guaranteed by minimal flattening.
	//
	// TODO: RefHandled is actually set nowhere
	if sg.RefHandled || !sg.Named || sg.Schema.Ref.String() == "" {
		return false, nil
	}
	debugLogAsJSON("short circuit named ref: %q", sg.Schema.Ref.String(), sg.Schema)

	// Simple aliased types (arrays, maps and primitives)
	//
	// Before deciding to make a struct with a composition branch (below),
	// check if the $ref points to a simple type or polymorphic (base) type.
	//
	// If this is the case, just realias this simple type, without creating a struct.
	asch, era := analysis.Schema(analysis.SchemaOpts{
		Root:     sg.TypeResolver.Doc.Spec(),
		BasePath: sg.TypeResolver.Doc.SpecFilePath(),
		Schema:   &sg.Schema,
	})
	if era != nil {
		return false, era
	}

	if asch.IsArray || asch.IsMap || asch.IsKnownType || asch.IsBaseType {
		tpx, ers := sg.TypeResolver.ResolveSchema(&sg.Schema, false, true)
		if ers != nil {
			return false, ers
		}
		tpe := resolvedType{}
		tpe.IsMap = asch.IsMap
		tpe.IsArray = asch.IsArray
		tpe.IsPrimitive = asch.IsKnownType

		tpe.IsAliased = true
		tpe.AliasedType = ""
		tpe.IsComplexObject = false
		tpe.IsAnonymous = false
		tpe.IsCustomFormatter = false
		tpe.IsBaseType = tpx.IsBaseType

		tpe.GoType = sg.TypeResolver.goTypeName(path.Base(sg.Schema.Ref.String()))
		tpe.Pkg = sg.TypeResolver.definitionPkg

		tpe.IsNullable = tpx.IsNullable // TODO
		tpe.IsInterface = tpx.IsInterface
		tpe.IsStream = tpx.IsStream
		tpe.IsEmbedded = tpx.IsEmbedded

		tpe.SwaggerType = tpx.SwaggerType
		sch := spec.Schema{}
		pg := sg.makeNewStruct(sg.Name, sch)
		if err := pg.makeGenSchema(); err != nil {
			return true, err
		}
		sg.MergeResult(pg, true)
		sg.GenSchema = pg.GenSchema
		sg.GenSchema.resolvedType = tpe
		sg.GenSchema.resolvedType.IsSuperAlias = true
		sg.GenSchema.IsBaseType = tpe.IsBaseType

		return true, nil
	}

	// Aliased object: use golang struct composition.
	// This is rendered as a struct with type field, i.e. :
	// Alias struct {
	//		AliasedType
	// }
	nullableOverride := sg.GenSchema.IsNullable

	tpe := resolvedType{}
	tpe.GoType = sg.TypeResolver.goTypeName(sg.Name)
	tpe.Pkg = sg.TypeResolver.definitionPkg
	tpe.SwaggerType = "object"
	tpe.IsComplexObject = true
	tpe.IsMap = false
	tpe.IsArray = false
	tpe.IsAnonymous = false
	tpe.IsNullable = sg.TypeResolver.isNullable(&sg.Schema)

	item := sg.NewCompositionBranch(sg.Schema, 0)
	if err := item.makeGenSchema(); err != nil {
		return true, err
	}
	sg.GenSchema.resolvedType = tpe
	sg.GenSchema.IsNullable = sg.GenSchema.IsNullable || nullableOverride
	// prevent format from bubbling up in composed type
	item.GenSchema.IsCustomFormatter = false

	sg.MergeResult(item, true)
	sg.GenSchema.AllOf = append(sg.GenSchema.AllOf, item.GenSchema)
	return true, nil
}

// liftSpecialAllOf attempts to simplify the rendering of allOf constructs by lifting simple things into the current schema.
func (sg *schemaGenContext) liftSpecialAllOf() error {
	// if there is only a $ref or a primitive and an x-isnullable schema then this is a nullable pointer
	// so this should not compose several objects, just 1
	// if there is a ref with a discriminator then we look for x-class on the current definition to know
	// the value of the discriminator to instantiate the class
	if len(sg.Schema.AllOf) < 2 {
		return nil
	}
	var seenSchema int
	var seenNullable bool
	var schemaToLift spec.Schema

	for _, schema := range sg.Schema.AllOf {
		sch := schema
		tpe, err := sg.TypeResolver.ResolveSchema(&sch, true, true)
		if err != nil {
			return err
		}
		if sg.TypeResolver.isNullable(&sch) {
			seenNullable = true
		}
		if len(sch.Type) > 0 || len(sch.Properties) > 0 || sch.Ref.GetURL() != nil || len(sch.AllOf) > 0 {
			seenSchema++
			if seenSchema > 1 {
				// won't do anything if several candidates for a lift
				break
			}
			if (!tpe.IsAnonymous && tpe.IsComplexObject) || tpe.IsPrimitive {
				// lifting complex objects here results in inlined structs in the model
				schemaToLift = sch
			}
		}
	}

	if seenSchema == 1 {
		// when there only a single schema to lift in allOf, replace the schema by its allOf definition
		debugLog("lifted schema in allOf for %s", sg.Name)
		sg.Schema = schemaToLift
		sg.GenSchema.IsNullable = seenNullable
	}
	return nil
}

func (sg *schemaGenContext) buildAliased() error {
	if !sg.GenSchema.IsPrimitive && !sg.GenSchema.IsMap && !sg.GenSchema.IsArray && !sg.GenSchema.IsInterface {
		return nil
	}

	if sg.GenSchema.IsPrimitive {
		if sg.GenSchema.SwaggerType == "string" && sg.GenSchema.SwaggerFormat == "" {
			sg.GenSchema.IsAliased = sg.GenSchema.GoType != sg.GenSchema.SwaggerType
		}
		if sg.GenSchema.IsNullable && sg.Named {
			sg.GenSchema.IsNullable = false
		}
	}

	if sg.GenSchema.IsInterface {
		sg.GenSchema.IsAliased = sg.GenSchema.GoType != iface
	}

	if sg.GenSchema.IsMap {
		sg.GenSchema.IsAliased = !strings.HasPrefix(sg.GenSchema.GoType, "map[")
	}

	if sg.GenSchema.IsArray {
		sg.GenSchema.IsAliased = !strings.HasPrefix(sg.GenSchema.GoType, "[]")
	}
	return nil
}

func (sg schemaGenContext) makeRefName() string {
	// figure out a longer name for deconflicting anonymous models.
	// This is used when makeNewStruct() is followed by the creation of a new ref to definitions
	if sg.UseContainerInName && sg.Container != sg.Name {
		return sg.Container + swag.ToGoName(sg.Name)
	}
	return sg.Name
}

func (sg *schemaGenContext) GoName() string {
	return goName(&sg.Schema, sg.Name)
}

func goName(sch *spec.Schema, orig string) string {
	name, _ := sch.Extensions.GetString(xGoName)
	if name != "" {
		return name
	}
	return orig
}

func (sg *schemaGenContext) derefMapElement(outer *GenSchema, _ *GenSchema, elem *GenSchema) {
	derefType := strings.TrimPrefix(elem.GoType, "*")

	if outer.IsAliased {
		nesting := strings.TrimSuffix(strings.TrimSuffix(outer.AliasedType, elem.GoType), "*")
		outer.AliasedType = nesting + derefType
		outer.GoType = derefType
	} else {
		nesting := strings.TrimSuffix(strings.TrimSuffix(outer.GoType, elem.GoType), "*")
		outer.GoType = nesting + derefType
	}

	elem.GoType = derefType
}

func (sg *schemaGenContext) checkNeedsPointer(outer *GenSchema, sch *GenSchema, elem *GenSchema) {
	derefType := strings.TrimPrefix(elem.GoType, "*")
	switch {
	case outer.IsAliased && !strings.HasSuffix(outer.AliasedType, "*"+derefType):
		// override nullability of map of primitive elements: render element of aliased or anonymous map as a pointer
		outer.AliasedType = strings.TrimSuffix(outer.AliasedType, derefType) + "*" + derefType
	case sch != nil:
		// nullable primitive
		if sch.IsAnonymous && !strings.HasSuffix(outer.GoType, "*"+derefType) {
			sch.GoType = strings.TrimSuffix(sch.GoType, derefType) + "*" + derefType
		}
	case outer.IsAnonymous && !strings.HasSuffix(outer.GoType, "*"+derefType):
		outer.GoType = strings.TrimSuffix(outer.GoType, derefType) + "*" + derefType
	}
}

// buildMapOfNullable equalizes the nullablity status for aliased and anonymous maps of simple things,
// with the nullability of its innermost element.
//
// NOTE: at the moment, we decide to align the type of the outer element (map) to the type of the inner element
// The opposite could be done and result in non nullable primitive elements. If we do so, the validation
// code needs to be adapted by removing IsZero() and Required() calls in codegen.
func (sg *schemaGenContext) buildMapOfNullable(sch *GenSchema) {
	outer := &sg.GenSchema
	if sch == nil {
		sch = outer
	}
	if sch.IsMap && (outer.IsAliased || outer.IsAnonymous) {
		elem := sch.AdditionalProperties
		for elem != nil {
			if elem.IsPrimitive && elem.IsNullable {
				sg.checkNeedsPointer(outer, nil, elem)
			} else if elem.IsArray {
				// override nullability of array of primitive elements:
				// render element of aliased or anonyous map as a pointer
				it := elem.Items
				for it != nil {
					switch {
					case it.IsPrimitive && it.IsNullable:
						sg.checkNeedsPointer(outer, sch, it)
					case it.IsMap:
						sg.buildMapOfNullable(it)
					case !it.IsPrimitive && !it.IsArray && it.IsComplexObject && it.IsNullable:
						// structs in map are not rendered as pointer by default
						// unless some x-nullable overrides says so
						_, forced := it.Extensions[xNullable]
						if !forced {
							_, forced = it.Extensions[xIsNullable]
						}
						if !forced {
							sg.derefMapElement(outer, sch, it)
						}
					}
					it = it.Items
				}
			}
			elem = elem.AdditionalProperties
		}
	}
}

func (sg *schemaGenContext) makeGenSchema() error {
	debugLogAsJSON("making gen schema (anon: %t, req: %t, tuple: %t) %s\n",
		!sg.Named, sg.Required, sg.IsTuple, sg.Name, sg.Schema)

	sg.GenSchema.Example = ""
	if sg.Schema.Example != nil {
		data, err := asJSON(sg.Schema.Example)
		if err != nil {
			return err
		}
		// Deleting the unnecessary double quotes for string types
		// otherwise the generate spec will generate as "\"foo\""
		sg.GenSchema.Example = strings.Trim(data, "\"")
	}
	sg.GenSchema.ExternalDocs = trimExternalDoc(sg.Schema.ExternalDocs)
	sg.GenSchema.IsExported = true
	sg.GenSchema.Path = sg.Path
	sg.GenSchema.IndexVar = sg.IndexVar
	sg.GenSchema.Location = body
	sg.GenSchema.ValueExpression = sg.ValueExpr
	sg.GenSchema.KeyVar = sg.KeyVar
	sg.GenSchema.OriginalName = sg.Name
	sg.GenSchema.Name = sg.GoName()
	sg.GenSchema.Title = sg.Schema.Title
	sg.GenSchema.Description = trimBOM(sg.Schema.Description)
	sg.GenSchema.ReceiverName = sg.Receiver
	sg.GenSchema.sharedValidations = sg.schemaValidations()
	sg.GenSchema.ReadOnly = sg.Schema.ReadOnly
	sg.GenSchema.IncludeValidator = sg.IncludeValidator
	sg.GenSchema.IncludeModel = sg.IncludeModel
	sg.GenSchema.StrictAdditionalProperties = sg.StrictAdditionalProperties
	sg.GenSchema.Default = sg.Schema.Default
	sg.GenSchema.StructTags = sg.StructTags
	sg.GenSchema.ExtraImports = make(map[string]string)

	var err error
	returns, err := sg.shortCircuitNamedRef()
	if err != nil {
		return err
	}
	if returns {
		return nil
	}
	debugLogAsJSON("after short circuit named ref", sg.Schema)

	if e := sg.liftSpecialAllOf(); e != nil {
		return e
	}
	nullableOverride := sg.GenSchema.IsNullable
	debugLogAsJSON("after lifting special all of", sg.Schema)

	if sg.Container == "" {
		sg.Container = sg.GenSchema.Name
	}
	if e := sg.buildAllOf(); e != nil {
		return e
	}

	var tpe resolvedType
	if sg.Untyped {
		tpe, err = sg.TypeResolver.ResolveSchema(nil, !sg.Named, sg.IsTuple || sg.Required || sg.GenSchema.Required)
	} else {
		tpe, err = sg.TypeResolver.ResolveSchema(&sg.Schema, !sg.Named, sg.IsTuple || sg.Required || sg.GenSchema.Required)
	}
	if err != nil {
		return err
	}

	debugLog("gschema rrequired: %t, nullable: %t", sg.GenSchema.Required, sg.GenSchema.IsNullable)
	tpe.IsNullable = tpe.IsNullable || nullableOverride
	sg.GenSchema.resolvedType = tpe
	sg.GenSchema.IsBaseType = tpe.IsBaseType
	sg.GenSchema.HasDiscriminator = tpe.HasDiscriminator

	// include format validations, excluding binary
	sg.GenSchema.HasValidations = sg.GenSchema.HasValidations || hasFormatValidation(tpe)

	// include context validations
	sg.GenSchema.HasContextValidations = sg.GenSchema.HasContextValidations || hasContextValidations(&sg.Schema) && !tpe.IsInterface && !tpe.IsStream && !tpe.SkipExternalValidation

	// usage of a polymorphic base type is rendered with getter funcs on private properties.
	// In the case of aliased types, the value expression remains unchanged to the receiver.
	if tpe.IsArray && tpe.ElemType != nil && tpe.ElemType.IsBaseType && sg.GenSchema.ValueExpression != sg.GenSchema.ReceiverName {
		sg.GenSchema.ValueExpression += asMethod
	}

	if tpe.IsExternal { // anonymous external types
		extType, pkg, alias := sg.TypeResolver.knownDefGoType(sg.GenSchema.Name, sg.Schema, sg.TypeResolver.goTypeName)
		if pkg != "" && alias != "" {
			sg.GenSchema.ExtraImports[alias] = pkg
		}

		if !tpe.IsEmbedded {
			sg.GenSchema.resolvedType = tpe
			sg.GenSchema.Required = sg.Required
			// assume we validate everything but interface and io.Reader - validation may be disabled by using the noValidation hint
			sg.GenSchema.HasValidations = !(tpe.IsInterface || tpe.IsStream || tpe.SkipExternalValidation)
			sg.GenSchema.IsAliased = sg.GenSchema.HasValidations

			log.Printf("INFO: type %s is external, with inferred spec type %s, referred to as %s", sg.GenSchema.Name, sg.GenSchema.GoType, extType)
			sg.GenSchema.GoType = extType
			sg.GenSchema.AliasedType = extType
			return nil
		}
		// TODO: case for embedded types as anonymous definitions
		return fmt.Errorf("ERROR: inline definitions embedded types are not supported")
	}

	debugLog("gschema nullable: %t", sg.GenSchema.IsNullable)
	if e := sg.buildAdditionalProperties(); e != nil {
		return e
	}

	// rewrite value expression from top-down
	cur := &sg.GenSchema
	for cur.AdditionalProperties != nil {
		cur.AdditionalProperties.ValueExpression = cur.ValueExpression + "[" + cur.AdditionalProperties.KeyVar + "]"
		cur = cur.AdditionalProperties
	}

	prev := sg.GenSchema
	if sg.Untyped {
		debugLogAsJSON("untyped resolve:%t", sg.Named || sg.IsTuple || sg.Required || sg.GenSchema.Required, sg.Schema)
		tpe, err = sg.TypeResolver.ResolveSchema(nil, !sg.Named, sg.Named || sg.IsTuple || sg.Required || sg.GenSchema.Required)
	} else {
		debugLogAsJSON("typed resolve, isAnonymous(%t), n: %t, t: %t, sgr: %t, sr: %t, isRequired(%t), BaseType(%t)",
			!sg.Named, sg.Named, sg.IsTuple, sg.Required, sg.GenSchema.Required,
			sg.Named || sg.IsTuple || sg.Required || sg.GenSchema.Required, sg.GenSchema.IsBaseType, sg.Schema)
		tpe, err = sg.TypeResolver.ResolveSchema(&sg.Schema, !sg.Named, sg.Named || sg.IsTuple || sg.Required || sg.GenSchema.Required)
	}
	if err != nil {
		return err
	}
	otn := tpe.IsNullable // for debug only
	tpe.IsNullable = tpe.IsNullable || nullableOverride
	sg.GenSchema.resolvedType = tpe
	sg.GenSchema.IsComplexObject = prev.IsComplexObject
	sg.GenSchema.IsMap = prev.IsMap
	sg.GenSchema.IsAdditionalProperties = prev.IsAdditionalProperties
	sg.GenSchema.IsBaseType = sg.GenSchema.HasDiscriminator

	debugLogAsJSON("gschema nnullable:IsNullable:%t,resolver.IsNullable:%t,nullableOverride:%t",
		sg.GenSchema.IsNullable, otn, nullableOverride, sg.Schema)
	if err := sg.buildProperties(); err != nil {
		return err
	}

	if err := sg.buildXMLNameWithTags(); err != nil {
		return err
	}

	if err := sg.buildAdditionalItems(); err != nil {
		return err
	}

	if err := sg.buildItems(); err != nil {
		return err
	}

	if err := sg.buildAliased(); err != nil {
		return err
	}

	sg.buildMapOfNullable(nil)

	// extra serializers & interfaces

	// generate MarshalBinary for:
	// - tuple
	// - struct
	// - map
	// - aliased primitive of a formatter type which is not a stringer
	//
	// but not for:
	// - interface{}
	// - io.Reader
	gs := sg.GenSchema
	sg.GenSchema.WantsMarshalBinary = !(gs.IsInterface || gs.IsStream || gs.IsBaseType) &&
		(gs.IsTuple || gs.IsComplexObject || gs.IsAdditionalProperties || (gs.IsPrimitive && gs.IsAliased && gs.IsCustomFormatter && !strings.Contains(gs.Zero(), `("`)))

	debugLog("finished gen schema for %q", sg.Name)
	return nil
}
