package codegen

import (
	"errors"
	"fmt"
	"reflect"
	"regexp"
	"strings"
)

// defaultStreamingContentTypes are the regex patterns matched against
// response Content-Type to decide when the strict-server should emit a
// flush-per-chunk streaming path. User-supplied patterns are merged on top.
var defaultStreamingContentTypes = []string{
	"text/event-stream",
	"application/jsonl",
	"application/x-ndjson",
}

type AdditionalImport struct {
	Alias   string `yaml:"alias,omitempty"`
	Package string `yaml:"package"`
}

// Configuration defines code generation customizations
type Configuration struct {
	// PackageName to generate the code under
	PackageName string `yaml:"package"`
	// Generate specifies which supported output formats to generate
	Generate GenerateOptions `yaml:"generate,omitempty"`
	// CompatibilityOptions specifies backward compatibility settings for the code generator
	Compatibility CompatibilityOptions `yaml:"compatibility,omitempty"`
	// OutputOptions are used to modify the output code in some way.
	OutputOptions OutputOptions `yaml:"output-options,omitempty"`
	// ImportMapping specifies the golang package path for each external reference
	ImportMapping map[string]string `yaml:"import-mapping,omitempty"`
	// AdditionalImports defines any additional Go imports to add to the generated code
	AdditionalImports []AdditionalImport `yaml:"additional-imports,omitempty"`
	// NoVCSVersionOverride allows overriding the version of the application for cases where no Version Control System (VCS) is available when building, for instance when using a Nix derivation.
	// See documentation for how to use it in examples/no-vcs-version-override/README.md
	NoVCSVersionOverride *string `yaml:"-"`
}

// Validate checks whether Configuration represent a valid configuration
func (o Configuration) Validate() error {
	if o.PackageName == "" {
		return errors.New("package name must be specified")
	}

	// Only one server type should be specified at a time.
	nServers := 0
	if o.Generate.IrisServer {
		nServers++
	}
	if o.Generate.ChiServer {
		nServers++
	}
	if o.Generate.FiberServer {
		nServers++
	}
	if o.Generate.FiberV3Server {
		nServers++
	}
	if o.Generate.EchoServer {
		nServers++
	}
	if o.Generate.Echo5Server {
		nServers++
	}
	if o.Generate.GorillaServer {
		nServers++
	}
	if o.Generate.StdHTTPServer {
		nServers++
	}
	if o.Generate.GinServer {
		nServers++
	}
	if nServers > 1 {
		return errors.New("only one server type is supported at a time")
	}

	var errs []error
	if problems := o.Generate.Validate(); problems != nil {
		for k, v := range problems {
			errs = append(errs, fmt.Errorf("`generate` configuration for %v was incorrect: %v", k, v))
		}
	}

	if problems := o.Compatibility.Validate(); problems != nil {
		for k, v := range problems {
			errs = append(errs, fmt.Errorf("`compatibility-options` configuration for %v was incorrect: %v", k, v))
		}
	}

	if problems := o.OutputOptions.Validate(); problems != nil {
		for k, v := range problems {
			errs = append(errs, fmt.Errorf("`output-options` configuration for %v was incorrect: %v", k, v))
		}
	}

	// import-mapping keys are the paths of $ref'd documents (a relative
	// file path or URL). A JSON pointer key can never match anything —
	// references within the same document always resolve to the package
	// being generated — so it is a configuration mistake, not a mapping.
	for _, specPath := range SortedMapKeys(o.ImportMapping) {
		if strings.HasPrefix(specPath, "#") {
			errs = append(errs, fmt.Errorf("`import-mapping` key %q is a JSON pointer, but keys must be the path or URL of a $ref'd document; references within the same document cannot be remapped to another package — see https://github.com/oapi-codegen/oapi-codegen/tree/main/examples/import-mapping", specPath))
		}
	}

	err := errors.Join(errs...)
	if err != nil {
		return fmt.Errorf("failed to validate configuration: %w", err)
	}

	return nil
}

// Warnings returns informational diagnostics about cross-field configuration
// combinations that aren't outright errors but are likely to produce
// generated code that doesn't compile, or that requires the user to know a
// specific multi-config convention. The warnings are surfaced on stderr by
// the CLI so the user notices before running `go build`.
func (o Configuration) Warnings() map[string]string {
	warnings := make(map[string]string)

	// `generate-types-for-anonymous-schemas` hoists inline schemas into
	// named Go types. The declarations of those named types are emitted
	// only when `generate.models: true`. If the flag is set with
	// `models: false` alongside a client or server generator, the
	// generated code will reference type names that this config does
	// not declare.
	//
	// This is fine when a sibling config (e.g. a `models: true` config
	// emitting into the same Go package) declares the types, but is a
	// hard compile failure if no sibling is present. We can't tell the
	// two cases apart from a single config, so we warn rather than
	// error — split-config users can ignore the warning; single-config
	// users get a heads-up before `go build` fails.
	if o.OutputOptions.GenerateTypesForAnonymousSchemas && !o.Generate.Models && o.Generate.AnyOperationGenerator() {
		warnings["generate-types-for-anonymous-schemas"] = "the flag is set with `generate.models: false` and a client/server generator. The hoisted named types this config emits references to will not be declared by this config. If a sibling config emits `generate.models: true` into the same Go package with the same flag setting, you can ignore this warning. Otherwise, set `generate.models: true` in this config or remove the flag to fall back to anonymous structs."
	}

	return warnings
}

// UpdateDefaults sets reasonable default values for unset fields in Configuration
func (o Configuration) UpdateDefaults() Configuration {
	if reflect.ValueOf(o.Generate).IsZero() {
		o.Generate = GenerateOptions{
			EchoServer:   true,
			Models:       true,
			EmbeddedSpec: true,
		}
	}
	return o
}

// GenerateOptions specifies which supported output formats to generate.
type GenerateOptions struct {
	// IrisServer specifies whether to generate iris server boilerplate
	IrisServer bool `yaml:"iris-server,omitempty"`
	// ChiServer specifies whether to generate chi server boilerplate
	ChiServer bool `yaml:"chi-server,omitempty"`
	// FiberServer specifies whether to generate fiber server boilerplate
	FiberServer bool `yaml:"fiber-server,omitempty"`
	// FiberV3Server specifies whether to generate fiber v3 server boilerplate
	FiberV3Server bool `yaml:"fiber-v3-server,omitempty"`
	// EchoServer specifies whether to generate echo server boilerplate
	EchoServer bool `yaml:"echo-server,omitempty"`
	// Echo5Server specifies whether to generate echo v5 server boilerplate
	Echo5Server bool `yaml:"echo5-server,omitempty"`
	// GinServer specifies whether to generate gin server boilerplate
	GinServer bool `yaml:"gin-server,omitempty"`
	// GorillaServer specifies whether to generate Gorilla server boilerplate
	GorillaServer bool `yaml:"gorilla-server,omitempty"`
	// StdHTTPServer specifies whether to generate stdlib http server boilerplate
	StdHTTPServer bool `yaml:"std-http-server,omitempty"`
	// Strict specifies whether to generate strict server wrapper
	Strict bool `yaml:"strict-server,omitempty"`
	// Client specifies whether to generate client boilerplate
	Client bool `yaml:"client,omitempty"`
	// Models specifies whether to generate type definitions
	Models bool `yaml:"models,omitempty"`
	// EmbeddedSpec indicates whether to embed the swagger spec in the generated code
	EmbeddedSpec bool `yaml:"embedded-spec,omitempty"`
	// ServerURLs generates types for the `Server` definitions' URLs, instead of needing to provide your own values
	ServerURLs bool `yaml:"server-urls,omitempty"`
}

// RouterImports returns the framework-specific and strict middleware imports
// needed based on which server type is selected.
func (g GenerateOptions) RouterImports() []AdditionalImport {
	var imports []AdditionalImport

	switch {
	case g.EchoServer:
		imports = append(imports, AdditionalImport{Package: "github.com/labstack/echo/v4"})
	case g.Echo5Server:
		imports = append(imports, AdditionalImport{Package: "github.com/labstack/echo/v5"})
	case g.ChiServer:
		imports = append(imports, AdditionalImport{Package: "github.com/go-chi/chi/v5"})
	case g.GinServer:
		imports = append(imports, AdditionalImport{Package: "github.com/gin-gonic/gin"})
	case g.GorillaServer:
		imports = append(imports, AdditionalImport{Package: "github.com/gorilla/mux"})
	case g.FiberServer:
		imports = append(imports, AdditionalImport{Package: "github.com/gofiber/fiber/v2"})
	case g.FiberV3Server:
		imports = append(imports, AdditionalImport{Package: "github.com/gofiber/fiber/v3"})
	case g.IrisServer:
		imports = append(imports, AdditionalImport{Package: "github.com/kataras/iris/v12"})
		imports = append(imports, AdditionalImport{Package: "github.com/kataras/iris/v12/core/router"})
	case g.StdHTTPServer:
	}

	return imports
}

// AnyOperationGenerator returns true if any code generator that emits
// per-operation Go code (clients, server frameworks, strict-server) is
// enabled. Used to detect configurations where operation-derived type
// references would be emitted without a corresponding declaration.
func (g GenerateOptions) AnyOperationGenerator() bool {
	return g.Client || g.IrisServer || g.EchoServer || g.Echo5Server ||
		g.ChiServer || g.FiberServer || g.FiberV3Server || g.GinServer || g.GorillaServer ||
		g.StdHTTPServer || g.Strict
}

func (oo GenerateOptions) Validate() map[string]string {
	return nil
}

func (oo GenerateOptions) Warnings() map[string]string {
	warnings := make(map[string]string)

	if oo.StdHTTPServer {
		if warning := oo.warningForStdHTTP(); warning != "" {
			warnings["std-http-server"] = warning
		}
	}

	return warnings
}

func (oo GenerateOptions) warningForStdHTTP() string {
	pathToGoMod, mod, err := findAndParseGoModuleForDepth(".", maximumDepthToSearchForGoMod)
	if err != nil {
		return fmt.Sprintf("Encountered an error while trying to find a `go.mod` or a `tools.mod` in this directory, or %d levels above it: %v", maximumDepthToSearchForGoMod, err)
	}

	if mod == nil {
		return fmt.Sprintf("Failed to find a `go.mod` or a `tools.mod` in this directory, or %d levels above it, so unable to validate that you're using Go 1.22+. If you start seeing API interactions resulting in a `404 page not found`, the Go directive (implying source compatibility for this module) needs to be bumped. See also: https://www.jvt.me/posts/2024/03/04/go-net-http-why-404/", maximumDepthToSearchForGoMod)
	}

	if !hasMinimalMinorGoDirective(minimumGoVersionForGenerateStdHTTPServer, mod) {
		return fmt.Sprintf("Found a `go.mod` or a `tools.mod` at path %v, but it only had a version of %v, whereas the minimum required is 1.%d. It's very likely API interactions will result in a `404 page not found`. The Go directive (implying source compatibility for this module) needs to be bumped. See also: https://www.jvt.me/posts/2024/03/04/go-net-http-why-404/", pathToGoMod, mod.Go.Version, minimumGoVersionForGenerateStdHTTPServer)
	}

	return ""
}

// CompatibilityOptions specifies backward compatibility settings for the
// code generator.
type CompatibilityOptions struct {
	// In the past, we merged schemas for `allOf` by inlining each schema
	// within the schema list. This approach, though, is incorrect because
	// `allOf` merges at the schema definition level, not at the resulting model
	// level. So, new behavior merges OpenAPI specs but generates different code
	// than we have in the past. Set OldMergeSchemas to true for the old behavior.
	// Please see https://github.com/oapi-codegen/oapi-codegen/issues/531
	OldMergeSchemas bool `yaml:"old-merge-schemas,omitempty"`
	// In the past, when a schema combined `allOf` with sibling fields at the
	// same level (`properties`, `required`, `additionalProperties`,
	// `description`), those siblings were silently discarded and the schema
	// was emitted as a Go type alias to its sole `allOf` target. New behavior
	// merges the parent's siblings with the `allOf` members so the generated
	// type carries every field declared in the spec. This is a more accurate
	// translation of OpenAPI semantics, but it changes the shape of generated
	// types: a schema that previously produced `type X = Y` may now produce
	// a distinct struct embedding Y with extra fields, which is not
	// interchangeable with Y in downstream Go code. Set OldAllOfSiblingMerging
	// to true to restore the prior behavior.
	// Please see https://github.com/oapi-codegen/oapi-codegen/issues/697
	OldAllOfSiblingMerging bool `yaml:"old-allof-sibling-merging,omitempty"`
	// Enum values can generate conflicting typenames, so we've updated the
	// code for enum generation to avoid these conflicts, but it will result
	// in some enum types being renamed in existing code. Set OldEnumConflicts to true
	// to revert to old behavior. Please see:
	// Please see https://github.com/oapi-codegen/oapi-codegen/issues/549
	OldEnumConflicts bool `yaml:"old-enum-conflicts,omitempty"`
	// It was a mistake to generate a go type definition for every $ref in
	// the OpenAPI schema. New behavior uses type aliases where possible, but
	// this can generate code which breaks existing builds. Set OldAliasing to true
	// for old behavior.
	// Please see https://github.com/oapi-codegen/oapi-codegen/issues/549
	OldAliasing bool `yaml:"old-aliasing,omitempty"`
	// When an object contains no members, and only an additionalProperties specification,
	// it is flattened to a map
	DisableFlattenAdditionalProperties bool `yaml:"disable-flatten-additional-properties,omitempty"`
	// When an object property is both required and readOnly the go model is generated
	// as a pointer. Set DisableRequiredReadOnlyAsPointer to true to mark them as non pointer.
	// Please see https://github.com/oapi-codegen/oapi-codegen/issues/604
	DisableRequiredReadOnlyAsPointer bool `yaml:"disable-required-readonly-as-pointer,omitempty"`
	// When set to true, always prefix enum values with their type name instead of only
	// when typenames would be conflicting.
	AlwaysPrefixEnumValues bool `yaml:"always-prefix-enum-values,omitempty"`
	// DisableEnumValueConflictResolution turns off cross-enum value conflict
	// resolution. By default, when two enums declare the same value, the
	// generated constants are prefixed with their type name so they don't
	// collide. Set this to true to disable that resolution and restore the
	// prior behavior, in which conflict detection compared already-prefixed
	// constant names and could miss overlaps depending on declaration order.
	// Please see https://github.com/oapi-codegen/oapi-codegen/issues/2391
	DisableEnumValueConflictResolution bool `yaml:"disable-enum-value-conflict-resolution,omitempty"`
	// Our generated code for Chi has historically inverted the order in which Chi middleware is
	// applied such that the last invoked middleware ends up executing first in the Chi chain
	// This resolves the behavior such that middlewares are chained in the order they are invoked.
	// Please see https://github.com/oapi-codegen/oapi-codegen/issues/786
	ApplyChiMiddlewareFirstToLast bool `yaml:"apply-chi-middleware-first-to-last,omitempty"`
	// Our generated code for gorilla/mux has historically inverted the order in which gorilla/mux middleware is
	// applied such that the last invoked middleware ends up executing first in the middlewares chain
	// This resolves the behavior such that middlewares are chained in the order they are invoked.
	// Please see https://github.com/oapi-codegen/oapi-codegen/issues/841
	ApplyGorillaMiddlewareFirstToLast bool `yaml:"apply-gorilla-middleware-first-to-last,omitempty"`
	// CircularReferenceLimit allows controlling the limit for circular reference checking.
	// In some OpenAPI specifications, we have a higher number of circular
	// references than is allowed out-of-the-box, but can be tuned to allow
	// traversing them.
	// Deprecated: In kin-openapi v0.126.0 (https://github.com/getkin/kin-openapi/tree/v0.126.0?tab=readme-ov-file#v01260) the Circular Reference Counter functionality was removed, instead resolving all references with backtracking, to avoid needing to provide a limit to reference counts.
	CircularReferenceLimit int `yaml:"circular-reference-limit"`
	// AllowUnexportedStructFieldNames makes it possible to output structs that have fields that are unexported.
	//
	// This is expected to be used in conjunction with `x-go-name` and `x-oapi-codegen-only-honour-go-name` to override the resulting output field name, and `x-oapi-codegen-extra-tags` to not produce JSON tags for `encoding/json`, such as:
	//
	//  ```yaml
	//   id:
	//     type: string
	//     x-go-name: accountIdentifier
	//     x-oapi-codegen-extra-tags:
	//       json: "-"
	//     x-oapi-codegen-only-honour-go-name: true
	//   ```
	//
	// NOTE that this can be confusing to users of your OpenAPI specification, who may see a field present and therefore be expecting to see/use it in the request/response, without understanding the nuance of how `oapi-codegen` generates the code.
	AllowUnexportedStructFieldNames bool `yaml:"allow-unexported-struct-field-names"`

	// PreserveOriginalOperationIdCasingInEmbeddedSpec ensures that the `operationId` from the source spec is kept intact in case when embedding it into the Embedded Spec output.
	// When `oapi-codegen` parses the original OpenAPI specification, it will apply the configured `output-options.name-normalizer` to each operation's `operationId` before that is used to generate code from.
	// However, this is also applied to the copy of the `operationId`s in the `embedded-spec` generation, which means that the embedded OpenAPI specification is then out-of-sync with the input specificiation.
	// To ensure that the `operationId` in the embedded spec is preserved as-is from the input specification, set this.
	// NOTE that this will not impact generated code.
	// NOTE that if you're using `include-operation-ids` or `exclude-operation-ids` you may want to ensure that the `operationId`s used are correct.
	PreserveOriginalOperationIdCasingInEmbeddedSpec bool `yaml:"preserve-original-operation-id-casing-in-embedded-spec"`

	// HeadersImplicitlyRequired treats all response headers as required, ignoring
	// the `required` property from the header definition. Prior to v2.6.0,
	// oapi-codegen generated all response headers as direct values (implicitly
	// required). The OpenAPI specification defaults headers to optional
	// (required: false), so the corrected behavior generates optional headers as
	// pointers. Set this to true to restore the old behavior where all headers
	// are treated as required.
	// Please see https://github.com/oapi-codegen/oapi-codegen/issues/2267
	HeadersImplicitlyRequired bool `yaml:"headers-implicitly-required,omitempty"`

	// EnableAuthScopesOnContext re-enables the legacy emission of security
	// scheme scopes by generated server code: the per-scheme context key
	// types (e.g. `bearerAuthContextKey`), the scope constants (e.g.
	// `BearerAuthScopes`), and the per-operation calls that store the
	// operation's scopes into the request context.
	//
	// A security scheme that is a $ref into a spec covered by import-mapping
	// does not declare its own context key type; its scopes constant is an
	// alias of the one in the mapped package, so `context.Value` lookups use
	// the same key across the generated packages. This requires the mapped
	// spec's config to also set this flag, so that the referenced constant
	// exists. Please see
	// https://github.com/oapi-codegen/oapi-codegen/issues/2383
	//
	// Deprecated: this mechanism is off by default because it flattens the
	// OpenAPI `security` requirements into a per-scheme list of scopes, and
	// cannot represent alternative schemes (OR), combined schemes (AND), or
	// anonymous (`{}`) alternatives. Authentication and authorization should
	// instead be performed at runtime using the request validation
	// middleware, which evaluates the spec's security requirements directly.
	// Please see https://github.com/oapi-codegen/oapi-codegen/issues/1524
	EnableAuthScopesOnContext bool `yaml:"enable-auth-scopes-on-context,omitempty"`

	// SortHandlerRegistrations restores the historical behavior of registering
	// generated route handlers in sorted (lexicographic, by path then method)
	// order. By default handlers are registered in the order their paths are
	// declared in the spec, so that on routers which match in registration
	// order (e.g. Fiber, Gorilla/mux) overlapping paths can be disambiguated by
	// ordering them in the spec. Set this to true to opt out and go back to the
	// old sorted registration order.
	// Please see https://github.com/oapi-codegen/oapi-codegen/issues/1887
	SortHandlerRegistrations bool `yaml:"sort-handler-registrations,omitempty"`
}

func (co CompatibilityOptions) Validate() map[string]string {
	return nil
}

// OutputOptions are used to modify the output code in some way.
type OutputOptions struct {
	// Whether to skip go imports on the generated code
	SkipFmt bool `yaml:"skip-fmt,omitempty"`
	// Whether to skip pruning unused components on the generated code
	SkipPrune bool `yaml:"skip-prune,omitempty"`
	// SkipEnumValidate disables the generation of the `Valid()` method on
	// enum types. By default each generated enum type includes a `Valid()
	// bool` method which returns true when the value matches one of the
	// defined constants; set this to true to suppress that method when it
	// conflicts with user-defined methods of the same name.
	SkipEnumValidate bool `yaml:"skip-enum-validate,omitempty"`
	// SkipEnumViaOneOf disables detection of the OpenAPI 3.1 enum-via-oneOf
	// idiom: a schema with `type: string|integer` and `oneOf:` members that
	// each carry `const` + `title` will normally be emitted as a Go enum with
	// named constants. Set this to true to fall through to the standard union
	// generator instead.
	SkipEnumViaOneOf bool `yaml:"skip-enum-via-oneof,omitempty"`
	// Only include operations that have one of these tags. Ignored when empty.
	IncludeTags []string `yaml:"include-tags,omitempty"`
	// Exclude operations that have one of these tags. Ignored when empty.
	ExcludeTags []string `yaml:"exclude-tags,omitempty"`
	// Only include operations that have one of these operation-ids. Ignored when empty.
	IncludeOperationIDs []string `yaml:"include-operation-ids,omitempty"`
	// Exclude operations that have one of these operation-ids. Ignored when empty.
	ExcludeOperationIDs []string `yaml:"exclude-operation-ids,omitempty"`
	// Override built-in templates from user-provided files
	UserTemplates map[string]string `yaml:"user-templates,omitempty"`

	// Exclude from generation schemas with given names. Ignored when empty.
	ExcludeSchemas []string `yaml:"exclude-schemas,omitempty"`
	// The suffix used for responses types
	ResponseTypeSuffix string `yaml:"response-type-suffix,omitempty"`
	// Override the default generated client type with the value
	ClientTypeName string `yaml:"client-type-name,omitempty"`
	// AdditionalInitialisms is a list of additional initialisms to use when generating names.
	// NOTE that this has no effect unless the `name-normalizer` is set to `ToCamelCaseWithInitialisms`
	AdditionalInitialisms []string `yaml:"additional-initialisms,omitempty"`
	// StreamingContentTypes are regex patterns matched against response
	// Content-Type to decide when to generate a flush-per-chunk streaming
	// response path. User-provided patterns are merged with the defaults
	// (text/event-stream, application/jsonl, application/x-ndjson); invalid
	// regexes fail Validate().
	StreamingContentTypes []string `yaml:"streaming-content-types,omitempty"`
	// Whether to generate nullable type for nullable fields
	NullableType bool `yaml:"nullable-type,omitempty"`

	// DisableTypeAliasesForType allows defining which OpenAPI `type`s will explicitly not use type aliases
	// Currently supports:
	//   "array"
	DisableTypeAliasesForType []string `yaml:"disable-type-aliases-for-type"`

	// SkipResponseBodyGetters decides whether to generate getter methods for response bodies.
	SkipResponseBodyGetters bool `yaml:"skip-response-body-getters,omitempty"`

	// NameNormalizer is the method used to normalize Go names and types, for instance converting the text `MyApi` to `MyAPI`. Corresponds with the constants defined for `codegen.NameNormalizerFunction`
	NameNormalizer string `yaml:"name-normalizer,omitempty"`

	// Overlay defines configuration for the OpenAPI Overlay (https://github.com/OAI/Overlay-Specification) to manipulate the OpenAPI specification before generation. This allows modifying the specification without needing to apply changes directly to it, making it easier to keep it up-to-date.
	Overlay OutputOptionsOverlay `yaml:"overlay"`

	// EnableYamlTags adds YAML tags to generated structs, in addition to default JSON ones
	EnableYamlTags bool `yaml:"yaml-tags,omitempty"`

	// ClientResponseBytesFunction decides whether to enable the generation of a `Bytes()` method on response objects for `ClientWithResponses`
	ClientResponseBytesFunction bool `yaml:"client-response-bytes-function,omitempty"`

	// SkipClientResponseContentType disables the generation of a `ContentType()` method on response objects for `ClientWithResponses`, which is otherwise generated by default.
	SkipClientResponseContentType bool `yaml:"skip-client-response-content-type,omitempty"`

	// PreferSkipOptionalPointer allows defining at a global level whether to omit the pointer for a type to indicate that the field/type is optional.
	// This is the same as adding `x-go-type-skip-optional-pointer` to each field (manually, or using an OpenAPI Overlay)
	PreferSkipOptionalPointer bool `yaml:"prefer-skip-optional-pointer,omitempty"`

	// PreferSkipOptionalPointerWithOmitzero allows generating the `omitzero` JSON tag types that would have had an optional pointer.
	// This is the same as adding `x-omitzero` to each field (manually, or using an OpenAPI Overlay).
	// A field can set `x-omitzero: false` to disable the `omitzero` JSON tag.
	// NOTE that this must be used alongside `prefer-skip-optional-pointer`, otherwise makes no difference.
	PreferSkipOptionalPointerWithOmitzero bool `yaml:"prefer-skip-optional-pointer-with-omitzero,omitempty"`

	// PreferSkipOptionalPointerOnContainerTypes allows disabling the generation of an "optional pointer" for an optional field that is a container type (such as a slice or a map), which ends up requiring an additional, unnecessary, `... != nil` check
	PreferSkipOptionalPointerOnContainerTypes bool `yaml:"prefer-skip-optional-pointer-on-container-types,omitempty"`

	// ResolveTypeNameCollisions, when set to true, automatically renames
	// types that collide across different OpenAPI component sections
	// (schemas, parameters, requestBodies, responses, headers) by appending
	// a suffix based on the component section (e.g., "Parameter", "Response",
	// "RequestBody"). It also detects collisions between component types and
	// client response wrapper types (e.g., issue #1474). Without this, the
	// codegen will error on duplicate type names, requiring manual resolution
	// via x-go-name.
	ResolveTypeNameCollisions bool `yaml:"resolve-type-name-collisions,omitempty"`

	// GenerateTypesForAnonymousSchemas, when true, causes oapi-codegen to
	// emit a named Go type for every inline schema that would otherwise
	// generate as an anonymous `struct { ... }`. The type's name is derived
	// from the schema path (e.g. `GetRolesIdResponseBody_Data`). Default
	// false. Equivalent to adding `x-go-type-name` to every inline schema;
	// when both are present at the same site, `x-go-type-name` wins.
	//
	// The hoisted named types are declared by the same emission path that
	// `generate.models` controls. In a single-config setup, this flag is
	// only effective when `generate.models: true` is also set in the same
	// config — otherwise the generated client/server code will reference
	// type names that no emission path declares, and `go build` will fail.
	//
	// In a multi-config setup where one config emits `models` and a
	// sibling config emits a client or server framework into the same
	// Go package, the flag must be set consistently across all configs.
	// Mixing the flag (set in one config, omitted in the other) causes
	// references and declarations to disagree on whether to use the
	// hoisted name or the inline struct. The sibling config that does
	// not emit `models` will produce a warning at codegen time noting
	// that it does not declare the hoisted names; you can ignore the
	// warning when the sibling models config will declare them.
	//
	// See https://github.com/oapi-codegen/oapi-codegen/issues/1139
	GenerateTypesForAnonymousSchemas bool `yaml:"generate-types-for-anonymous-schemas,omitempty"`

	// TypeMapping allows customizing OpenAPI type/format to Go type mappings.
	// User-specified mappings are merged on top of the defaults.
	TypeMapping *TypeMapping `yaml:"type-mapping,omitempty"`
}

func (oo OutputOptions) Validate() map[string]string {
	if NameNormalizerFunction(oo.NameNormalizer) != NameNormalizerFunctionToCamelCaseWithInitialisms && len(oo.AdditionalInitialisms) > 0 {
		return map[string]string{
			"additional-initialisms": "You have specified `additional-initialisms`, but the `name-normalizer` is not set to `ToCamelCaseWithInitialisms`. Please specify `name-normalizer: ToCamelCaseWithInitialisms` or remove the `additional-initialisms` configuration",
		}
	}

	if _, err := compileStreamingContentTypes(oo.StreamingContentTypes); err != nil {
		return map[string]string{
			"streaming-content-types": err.Error(),
		}
	}

	return nil
}

// compileStreamingContentTypes returns the merged default + user-provided
// patterns compiled into regexes. The first compile error is returned
// including the offending pattern.
func compileStreamingContentTypes(user []string) ([]*regexp.Regexp, error) {
	all := make([]string, 0, len(defaultStreamingContentTypes)+len(user))
	all = append(all, defaultStreamingContentTypes...)
	all = append(all, user...)
	out := make([]*regexp.Regexp, 0, len(all))
	for _, p := range all {
		re, err := regexp.Compile(p)
		if err != nil {
			return nil, fmt.Errorf("invalid streaming-content-type pattern %q: %w", p, err)
		}
		out = append(out, re)
	}
	return out, nil
}

type OutputOptionsOverlay struct {
	Path string `yaml:"path"`

	// Strict defines whether the Overlay should be applied in a strict way, highlighting any actions that will not take any effect. This can, however, lead to more work when testing new actions in an Overlay, so can be turned off with this setting.
	// Defaults to true.
	Strict *bool `yaml:"strict,omitempty"`
}
