// Code generated by smithy-go-codegen DO NOT EDIT.

package route53

import (
	"context"
	"errors"
	"fmt"
	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/aws/defaults"
	awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
	"github.com/aws/aws-sdk-go-v2/aws/retry"
	"github.com/aws/aws-sdk-go-v2/aws/signer/v4"
	awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
	internalauth "github.com/aws/aws-sdk-go-v2/internal/auth"
	internalauthsmithy "github.com/aws/aws-sdk-go-v2/internal/auth/smithy"
	internalConfig "github.com/aws/aws-sdk-go-v2/internal/configsources"
	route53cust "github.com/aws/aws-sdk-go-v2/service/route53/internal/customizations"
	smithy "github.com/aws/smithy-go"
	smithydocument "github.com/aws/smithy-go/document"
	"github.com/aws/smithy-go/logging"
	"github.com/aws/smithy-go/metrics"
	"github.com/aws/smithy-go/middleware"
	"github.com/aws/smithy-go/tracing"
	smithyhttp "github.com/aws/smithy-go/transport/http"
	"net"
	"net/http"
	"strings"
	"sync/atomic"
	"time"
)

const ServiceID = "Route 53"
const ServiceAPIVersion = "2013-04-01"

type operationMetrics struct {
	Duration                metrics.Float64Histogram
	SerializeDuration       metrics.Float64Histogram
	ResolveIdentityDuration metrics.Float64Histogram
	ResolveEndpointDuration metrics.Float64Histogram
	SignRequestDuration     metrics.Float64Histogram
	DeserializeDuration     metrics.Float64Histogram
}

func (m *operationMetrics) histogramFor(name string) metrics.Float64Histogram {
	switch name {
	case "client.call.duration":
		return m.Duration
	case "client.call.serialization_duration":
		return m.SerializeDuration
	case "client.call.resolve_identity_duration":
		return m.ResolveIdentityDuration
	case "client.call.resolve_endpoint_duration":
		return m.ResolveEndpointDuration
	case "client.call.signing_duration":
		return m.SignRequestDuration
	case "client.call.deserialization_duration":
		return m.DeserializeDuration
	default:
		panic("unrecognized operation metric")
	}
}

func timeOperationMetric[T any](
	ctx context.Context, metric string, fn func() (T, error),
	opts ...metrics.RecordMetricOption,
) (T, error) {
	mm := getOperationMetrics(ctx)
	if mm == nil { // not using the metrics system
		return fn()
	}

	instr := mm.histogramFor(metric)
	opts = append([]metrics.RecordMetricOption{withOperationMetadata(ctx)}, opts...)

	start := time.Now()
	v, err := fn()
	end := time.Now()

	elapsed := end.Sub(start)
	instr.Record(ctx, float64(elapsed)/1e9, opts...)
	return v, err
}

func startMetricTimer(ctx context.Context, metric string, opts ...metrics.RecordMetricOption) func() {
	mm := getOperationMetrics(ctx)
	if mm == nil { // not using the metrics system
		return func() {}
	}

	instr := mm.histogramFor(metric)
	opts = append([]metrics.RecordMetricOption{withOperationMetadata(ctx)}, opts...)

	var ended bool
	start := time.Now()
	return func() {
		if ended {
			return
		}
		ended = true

		end := time.Now()

		elapsed := end.Sub(start)
		instr.Record(ctx, float64(elapsed)/1e9, opts...)
	}
}

func withOperationMetadata(ctx context.Context) metrics.RecordMetricOption {
	return func(o *metrics.RecordMetricOptions) {
		o.Properties.Set("rpc.service", middleware.GetServiceID(ctx))
		o.Properties.Set("rpc.method", middleware.GetOperationName(ctx))
	}
}

type operationMetricsKey struct{}

func withOperationMetrics(parent context.Context, mp metrics.MeterProvider) (context.Context, error) {
	if _, ok := mp.(metrics.NopMeterProvider); ok {
		// not using the metrics system - setting up the metrics context is a memory-intensive operation
		// so we should skip it in this case
		return parent, nil
	}

	meter := mp.Meter("github.com/aws/aws-sdk-go-v2/service/route53")
	om := &operationMetrics{}

	var err error

	om.Duration, err = operationMetricTimer(meter, "client.call.duration",
		"Overall call duration (including retries and time to send or receive request and response body)")
	if err != nil {
		return nil, err
	}
	om.SerializeDuration, err = operationMetricTimer(meter, "client.call.serialization_duration",
		"The time it takes to serialize a message body")
	if err != nil {
		return nil, err
	}
	om.ResolveIdentityDuration, err = operationMetricTimer(meter, "client.call.auth.resolve_identity_duration",
		"The time taken to acquire an identity (AWS credentials, bearer token, etc) from an Identity Provider")
	if err != nil {
		return nil, err
	}
	om.ResolveEndpointDuration, err = operationMetricTimer(meter, "client.call.resolve_endpoint_duration",
		"The time it takes to resolve an endpoint (endpoint resolver, not DNS) for the request")
	if err != nil {
		return nil, err
	}
	om.SignRequestDuration, err = operationMetricTimer(meter, "client.call.auth.signing_duration",
		"The time it takes to sign a request")
	if err != nil {
		return nil, err
	}
	om.DeserializeDuration, err = operationMetricTimer(meter, "client.call.deserialization_duration",
		"The time it takes to deserialize a message body")
	if err != nil {
		return nil, err
	}

	return context.WithValue(parent, operationMetricsKey{}, om), nil
}

func operationMetricTimer(m metrics.Meter, name, desc string) (metrics.Float64Histogram, error) {
	return m.Float64Histogram(name, func(o *metrics.InstrumentOptions) {
		o.UnitLabel = "s"
		o.Description = desc
	})
}

func getOperationMetrics(ctx context.Context) *operationMetrics {
	if v := ctx.Value(operationMetricsKey{}); v != nil {
		return v.(*operationMetrics)
	}
	return nil
}

func operationTracer(p tracing.TracerProvider) tracing.Tracer {
	return p.Tracer("github.com/aws/aws-sdk-go-v2/service/route53")
}

// Client provides the API client to make operations call for Amazon Route 53.
type Client struct {
	options Options

	// Difference between the time reported by the server and the client
	timeOffset *atomic.Int64
}

// New returns an initialized Client based on the functional options. Provide
// additional functional options to further configure the behavior of the client,
// such as changing the client's endpoint or adding custom middleware behavior.
func New(options Options, optFns ...func(*Options)) *Client {
	options = options.Copy()

	resolveDefaultLogger(&options)

	setResolvedDefaultsMode(&options)

	resolveRetryer(&options)

	resolveHTTPClient(&options)

	resolveHTTPSignerV4(&options)

	resolveEndpointResolverV2(&options)

	resolveTracerProvider(&options)

	resolveMeterProvider(&options)

	resolveAuthSchemeResolver(&options)

	for _, fn := range optFns {
		fn(&options)
	}

	finalizeRetryMaxAttempts(&options)

	ignoreAnonymousAuth(&options)

	wrapWithAnonymousAuth(&options)

	resolveAuthSchemes(&options)

	client := &Client{
		options: options,
	}

	initializeTimeOffsetResolver(client)

	return client
}

// Options returns a copy of the client configuration.
//
// Callers SHOULD NOT perform mutations on any inner structures within client
// config. Config overrides should instead be made on a per-operation basis through
// functional options.
func (c *Client) Options() Options {
	return c.options.Copy()
}

func (c *Client) invokeOperation(
	ctx context.Context, opID string, params interface{}, optFns []func(*Options), stackFns ...func(*middleware.Stack, Options) error,
) (
	result interface{}, metadata middleware.Metadata, err error,
) {
	ctx = middleware.ClearStackValues(ctx)
	ctx = middleware.WithServiceID(ctx, ServiceID)
	ctx = middleware.WithOperationName(ctx, opID)

	stack := middleware.NewStack(opID, smithyhttp.NewStackRequest)
	options := c.options.Copy()

	for _, fn := range optFns {
		fn(&options)
	}

	finalizeOperationRetryMaxAttempts(&options, *c)

	finalizeClientEndpointResolverOptions(&options)

	if err := c.addCommonMiddlewares(stack, options, opID); err != nil {
		return nil, metadata, err
	}

	for _, fn := range stackFns {
		if err := fn(stack, options); err != nil {
			return nil, metadata, err
		}
	}

	for _, fn := range options.APIOptions {
		if err := fn(stack); err != nil {
			return nil, metadata, err
		}
	}

	ctx, err = withOperationMetrics(ctx, options.MeterProvider)
	if err != nil {
		return nil, metadata, err
	}

	tracer := operationTracer(options.TracerProvider)
	spanName := fmt.Sprintf("%s.%s", ServiceID, opID)

	ctx = tracing.WithOperationTracer(ctx, tracer)

	ctx, span := tracer.StartSpan(ctx, spanName, func(o *tracing.SpanOptions) {
		o.Kind = tracing.SpanKindClient
		o.Properties.Set("rpc.system", "aws-api")
		o.Properties.Set("rpc.method", opID)
		o.Properties.Set("rpc.service", ServiceID)
	})
	endTimer := startMetricTimer(ctx, "client.call.duration")
	defer endTimer()
	defer span.End()

	handler := smithyhttp.NewClientHandlerWithOptions(options.HTTPClient, func(o *smithyhttp.ClientHandler) {
		o.Meter = options.MeterProvider.Meter("github.com/aws/aws-sdk-go-v2/service/route53")
	})
	decorated := middleware.DecorateHandler(handler, stack)
	result, metadata, err = decorated.Handle(ctx, params)
	if err != nil {
		span.SetProperty("exception.type", fmt.Sprintf("%T", err))
		span.SetProperty("exception.message", err.Error())

		var aerr smithy.APIError
		if errors.As(err, &aerr) {
			span.SetProperty("api.error_code", aerr.ErrorCode())
			span.SetProperty("api.error_message", aerr.ErrorMessage())
			span.SetProperty("api.error_fault", aerr.ErrorFault().String())
		}

		err = &smithy.OperationError{
			ServiceID:     ServiceID,
			OperationName: opID,
			Err:           err,
		}
	}

	span.SetProperty("error", err != nil)
	if err == nil {
		span.SetStatus(tracing.SpanStatusOK)
	} else {
		span.SetStatus(tracing.SpanStatusError)
	}

	return result, metadata, err
}

type operationInputKey struct{}

func setOperationInput(ctx context.Context, input interface{}) context.Context {
	return middleware.WithStackValue(ctx, operationInputKey{}, input)
}

func getOperationInput(ctx context.Context) interface{} {
	return middleware.GetStackValue(ctx, operationInputKey{})
}

type setOperationInputMiddleware struct {
}

func (*setOperationInputMiddleware) ID() string {
	return "setOperationInput"
}

func (m *setOperationInputMiddleware) HandleSerialize(ctx context.Context, in middleware.SerializeInput, next middleware.SerializeHandler) (
	out middleware.SerializeOutput, metadata middleware.Metadata, err error,
) {
	ctx = setOperationInput(ctx, in.Parameters)
	return next.HandleSerialize(ctx, in)
}

func addProtocolFinalizerMiddlewares(stack *middleware.Stack, options Options, operation string) error {
	if err := stack.Finalize.Add(&resolveAuthSchemeMiddleware{operation: operation, options: options}, middleware.Before); err != nil {
		return fmt.Errorf("add ResolveAuthScheme: %w", err)
	}
	if err := stack.Finalize.Insert(&getIdentityMiddleware{options: options}, "ResolveAuthScheme", middleware.After); err != nil {
		return fmt.Errorf("add GetIdentity: %v", err)
	}
	if err := stack.Finalize.Insert(&resolveEndpointV2Middleware{options: options}, "GetIdentity", middleware.After); err != nil {
		return fmt.Errorf("add ResolveEndpointV2: %v", err)
	}
	if err := stack.Finalize.Insert(&signRequestMiddleware{options: options}, "ResolveEndpointV2", middleware.After); err != nil {
		return fmt.Errorf("add Signing: %w", err)
	}
	return nil
}

func (c *Client) addCommonMiddlewares(stack *middleware.Stack, options Options, operation string) error {
	if err := stack.Serialize.Add(&setOperationInputMiddleware{}, middleware.After); err != nil {
		return err
	}
	if err := addProtocolFinalizerMiddlewares(stack, options, operation); err != nil {
		return fmt.Errorf("add protocol finalizers: %v", err)
	}
	if err := addSetLoggerMiddleware(stack, options); err != nil {
		return err
	}
	if err := addClientRequestID(stack); err != nil {
		return err
	}
	if err := addRetry(stack, options, c); err != nil {
		return err
	}
	if err := addRawResponseToMetadata(stack); err != nil {
		return err
	}
	if err := addSpanRetryLoop(stack, options); err != nil {
		return err
	}
	if err := addClientUserAgent(stack, options); err != nil {
		return err
	}
	if err := addSetLegacyContextSigningOptionsMiddleware(stack); err != nil {
		return err
	}
	if err := addUserAgentRetryMode(stack, options); err != nil {
		return err
	}
	if err := addRecursionDetection(stack); err != nil {
		return err
	}
	if err := addInterceptBeforeRetryLoop(stack, options); err != nil {
		return err
	}
	if err := addInterceptAttempt(stack, options); err != nil {
		return err
	}
	return nil
}
func resolveAuthSchemeResolver(options *Options) {
	if options.AuthSchemeResolver == nil {
		options.AuthSchemeResolver = &defaultAuthSchemeResolver{}
	}
}

func resolveAuthSchemes(options *Options) {
	if options.AuthSchemes == nil {
		options.AuthSchemes = []smithyhttp.AuthScheme{
			internalauth.NewHTTPAuthScheme("aws.auth#sigv4", &internalauthsmithy.V4SignerAdapter{
				Signer:     options.HTTPSignerV4,
				Logger:     options.Logger,
				LogSigning: options.ClientLogMode.IsSigning(),
			}),
		}
	}
}

type noSmithyDocumentSerde = smithydocument.NoSerde

type legacyEndpointContextSetter struct {
	LegacyResolver EndpointResolver
}

func (*legacyEndpointContextSetter) ID() string {
	return "legacyEndpointContextSetter"
}

func (m *legacyEndpointContextSetter) HandleInitialize(ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler) (
	out middleware.InitializeOutput, metadata middleware.Metadata, err error,
) {
	if m.LegacyResolver != nil {
		ctx = awsmiddleware.SetRequiresLegacyEndpoints(ctx, true)
	}

	return next.HandleInitialize(ctx, in)

}
func addlegacyEndpointContextSetter(stack *middleware.Stack, o Options) error {
	return stack.Initialize.Add(&legacyEndpointContextSetter{
		LegacyResolver: o.EndpointResolver,
	}, middleware.Before)
}

func resolveDefaultLogger(o *Options) {
	if o.Logger != nil {
		return
	}
	o.Logger = logging.Nop{}
}

func addSetLoggerMiddleware(stack *middleware.Stack, o Options) error {
	return middleware.AddSetLoggerMiddleware(stack, o.Logger)
}

func setResolvedDefaultsMode(o *Options) {
	if len(o.resolvedDefaultsMode) > 0 {
		return
	}

	var mode aws.DefaultsMode
	mode.SetFromString(string(o.DefaultsMode))

	if mode == aws.DefaultsModeAuto {
		mode = defaults.ResolveDefaultsModeAuto(o.Region, o.RuntimeEnvironment)
	}

	o.resolvedDefaultsMode = mode
}

// NewFromConfig returns a new client from the provided config.
func NewFromConfig(cfg aws.Config, optFns ...func(*Options)) *Client {
	opts := Options{
		Region:               cfg.Region,
		DefaultsMode:         cfg.DefaultsMode,
		RuntimeEnvironment:   cfg.RuntimeEnvironment,
		HTTPClient:           cfg.HTTPClient,
		Credentials:          cfg.Credentials,
		APIOptions:           cfg.APIOptions,
		Logger:               cfg.Logger,
		ClientLogMode:        cfg.ClientLogMode,
		AppID:                cfg.AppID,
		AuthSchemePreference: cfg.AuthSchemePreference,
	}
	resolveAWSRetryerProvider(cfg, &opts)
	resolveAWSRetryMaxAttempts(cfg, &opts)
	resolveAWSRetryMode(cfg, &opts)
	resolveAWSEndpointResolver(cfg, &opts)
	resolveInterceptors(cfg, &opts)
	resolveUseDualStackEndpoint(cfg, &opts)
	resolveUseFIPSEndpoint(cfg, &opts)
	resolveBaseEndpoint(cfg, &opts)
	return New(opts, func(o *Options) {
		for _, opt := range cfg.ServiceOptions {
			opt(ServiceID, o)
		}
		for _, opt := range optFns {
			opt(o)
		}
	})
}

func resolveHTTPClient(o *Options) {
	var buildable *awshttp.BuildableClient

	if o.HTTPClient != nil {
		var ok bool
		buildable, ok = o.HTTPClient.(*awshttp.BuildableClient)
		if !ok {
			return
		}
	} else {
		buildable = awshttp.NewBuildableClient()
	}

	modeConfig, err := defaults.GetModeConfiguration(o.resolvedDefaultsMode)
	if err == nil {
		buildable = buildable.WithDialerOptions(func(dialer *net.Dialer) {
			if dialerTimeout, ok := modeConfig.GetConnectTimeout(); ok {
				dialer.Timeout = dialerTimeout
			}
		})

		buildable = buildable.WithTransportOptions(func(transport *http.Transport) {
			if tlsHandshakeTimeout, ok := modeConfig.GetTLSNegotiationTimeout(); ok {
				transport.TLSHandshakeTimeout = tlsHandshakeTimeout
			}
		})
	}

	o.HTTPClient = buildable
}

func resolveRetryer(o *Options) {
	if o.Retryer != nil {
		return
	}

	if len(o.RetryMode) == 0 {
		modeConfig, err := defaults.GetModeConfiguration(o.resolvedDefaultsMode)
		if err == nil {
			o.RetryMode = modeConfig.RetryMode
		}
	}
	if len(o.RetryMode) == 0 {
		o.RetryMode = aws.RetryModeStandard
	}

	var standardOptions []func(*retry.StandardOptions)
	if v := o.RetryMaxAttempts; v != 0 {
		standardOptions = append(standardOptions, func(so *retry.StandardOptions) {
			so.MaxAttempts = v
		})
	}

	switch o.RetryMode {
	case aws.RetryModeAdaptive:
		var adaptiveOptions []func(*retry.AdaptiveModeOptions)
		if len(standardOptions) != 0 {
			adaptiveOptions = append(adaptiveOptions, func(ao *retry.AdaptiveModeOptions) {
				ao.StandardOptions = append(ao.StandardOptions, standardOptions...)
			})
		}
		o.Retryer = retry.NewAdaptiveMode(adaptiveOptions...)

	default:
		o.Retryer = retry.NewStandard(standardOptions...)
	}
}

func resolveAWSRetryerProvider(cfg aws.Config, o *Options) {
	if cfg.Retryer == nil {
		return
	}
	o.Retryer = cfg.Retryer()
}

func resolveAWSRetryMode(cfg aws.Config, o *Options) {
	if len(cfg.RetryMode) == 0 {
		return
	}
	o.RetryMode = cfg.RetryMode
}
func resolveAWSRetryMaxAttempts(cfg aws.Config, o *Options) {
	if cfg.RetryMaxAttempts == 0 {
		return
	}
	o.RetryMaxAttempts = cfg.RetryMaxAttempts
}

func finalizeRetryMaxAttempts(o *Options) {
	if o.RetryMaxAttempts == 0 {
		return
	}

	o.Retryer = retry.AddWithMaxAttempts(o.Retryer, o.RetryMaxAttempts)
}

func finalizeOperationRetryMaxAttempts(o *Options, client Client) {
	if v := o.RetryMaxAttempts; v == 0 || v == client.options.RetryMaxAttempts {
		return
	}

	o.Retryer = retry.AddWithMaxAttempts(o.Retryer, o.RetryMaxAttempts)
}

func resolveAWSEndpointResolver(cfg aws.Config, o *Options) {
	if cfg.EndpointResolver == nil && cfg.EndpointResolverWithOptions == nil {
		return
	}
	o.EndpointResolver = withEndpointResolver(cfg.EndpointResolver, cfg.EndpointResolverWithOptions)
}

func resolveInterceptors(cfg aws.Config, o *Options) {
	o.Interceptors = cfg.Interceptors.Copy()
}

func addClientUserAgent(stack *middleware.Stack, options Options) error {
	ua, err := getOrAddRequestUserAgent(stack)
	if err != nil {
		return err
	}

	ua.AddSDKAgentKeyValue(awsmiddleware.APIMetadata, "route53", goModuleVersion)
	if len(options.AppID) > 0 {
		ua.AddSDKAgentKey(awsmiddleware.ApplicationIdentifier, options.AppID)
	}

	return nil
}

func getOrAddRequestUserAgent(stack *middleware.Stack) (*awsmiddleware.RequestUserAgent, error) {
	id := (*awsmiddleware.RequestUserAgent)(nil).ID()
	mw, ok := stack.Build.Get(id)
	if !ok {
		mw = awsmiddleware.NewRequestUserAgent()
		if err := stack.Build.Add(mw, middleware.After); err != nil {
			return nil, err
		}
	}

	ua, ok := mw.(*awsmiddleware.RequestUserAgent)
	if !ok {
		return nil, fmt.Errorf("%T for %s middleware did not match expected type", mw, id)
	}

	return ua, nil
}

type HTTPSignerV4 interface {
	SignHTTP(ctx context.Context, credentials aws.Credentials, r *http.Request, payloadHash string, service string, region string, signingTime time.Time, optFns ...func(*v4.SignerOptions)) error
}

func resolveHTTPSignerV4(o *Options) {
	if o.HTTPSignerV4 != nil {
		return
	}
	o.HTTPSignerV4 = newDefaultV4Signer(*o)
}

func newDefaultV4Signer(o Options) *v4.Signer {
	return v4.NewSigner(func(so *v4.SignerOptions) {
		so.Logger = o.Logger
		so.LogSigning = o.ClientLogMode.IsSigning()
	})
}

func addClientRequestID(stack *middleware.Stack) error {
	return stack.Build.Add(&awsmiddleware.ClientRequestID{}, middleware.After)
}

func addComputeContentLength(stack *middleware.Stack) error {
	return stack.Build.Insert(&smithyhttp.ComputeContentLength{}, "ClientRequestID", middleware.After)
}

func addRawResponseToMetadata(stack *middleware.Stack) error {
	return stack.Deserialize.Add(&awsmiddleware.AddRawResponse{}, middleware.Before)
}

func addRecordResponseTiming(stack *middleware.Stack) error {
	return stack.Deserialize.Add(&awsmiddleware.RecordResponseTiming{}, middleware.After)
}

func addSpanRetryLoop(stack *middleware.Stack, options Options) error {
	return stack.Finalize.Insert(&spanRetryLoop{options: options}, "Retry", middleware.Before)
}

type spanRetryLoop struct {
	options Options
}

func (*spanRetryLoop) ID() string {
	return "spanRetryLoop"
}

func (m *spanRetryLoop) HandleFinalize(
	ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler,
) (
	middleware.FinalizeOutput, middleware.Metadata, error,
) {
	tracer := operationTracer(m.options.TracerProvider)
	ctx, span := tracer.StartSpan(ctx, "RetryLoop")
	defer span.End()

	return next.HandleFinalize(ctx, in)
}
func addStreamingEventsPayload(stack *middleware.Stack) error {
	return stack.Finalize.Add(&v4.StreamingEventsPayload{}, middleware.Before)
}

func addUnsignedPayload(stack *middleware.Stack) error {
	return stack.Finalize.Insert(&v4.UnsignedPayload{}, "ResolveEndpointV2", middleware.After)
}

func addComputePayloadSHA256(stack *middleware.Stack) error {
	return stack.Finalize.Insert(&v4.ComputePayloadSHA256{}, "ResolveEndpointV2", middleware.After)
}

func addContentSHA256Header(stack *middleware.Stack) error {
	return stack.Finalize.Insert(&v4.ContentSHA256Header{}, (*v4.ComputePayloadSHA256)(nil).ID(), middleware.After)
}

func addIsWaiterUserAgent(o *Options) {
	o.APIOptions = append(o.APIOptions, func(stack *middleware.Stack) error {
		ua, err := getOrAddRequestUserAgent(stack)
		if err != nil {
			return err
		}

		ua.AddUserAgentFeature(awsmiddleware.UserAgentFeatureWaiter)
		return nil
	})
}

func addIsPaginatorUserAgent(o *Options) {
	o.APIOptions = append(o.APIOptions, func(stack *middleware.Stack) error {
		ua, err := getOrAddRequestUserAgent(stack)
		if err != nil {
			return err
		}

		ua.AddUserAgentFeature(awsmiddleware.UserAgentFeaturePaginator)
		return nil
	})
}

func addRetry(stack *middleware.Stack, o Options, c *Client) error {
	attempt := retry.NewAttemptMiddleware(o.Retryer, smithyhttp.RequestCloner, func(m *retry.Attempt) {
		m.LogAttempts = o.ClientLogMode.IsRetries()
		m.OperationMeter = o.MeterProvider.Meter("github.com/aws/aws-sdk-go-v2/service/route53")
		m.ClientSkew = c.timeOffset
	})
	if err := stack.Finalize.Insert(attempt, "ResolveAuthScheme", middleware.Before); err != nil {
		return err
	}
	if err := stack.Finalize.Insert(&retry.MetricsHeader{}, attempt.ID(), middleware.After); err != nil {
		return err
	}
	return nil
}

// resolves dual-stack endpoint configuration
func resolveUseDualStackEndpoint(cfg aws.Config, o *Options) error {
	if len(cfg.ConfigSources) == 0 {
		return nil
	}
	value, found, err := internalConfig.ResolveUseDualStackEndpoint(context.Background(), cfg.ConfigSources)
	if err != nil {
		return err
	}
	if found {
		o.EndpointOptions.UseDualStackEndpoint = value
	}
	return nil
}

// resolves FIPS endpoint configuration
func resolveUseFIPSEndpoint(cfg aws.Config, o *Options) error {
	if len(cfg.ConfigSources) == 0 {
		return nil
	}
	value, found, err := internalConfig.ResolveUseFIPSEndpoint(context.Background(), cfg.ConfigSources)
	if err != nil {
		return err
	}
	if found {
		o.EndpointOptions.UseFIPSEndpoint = value
	}
	return nil
}

func initializeTimeOffsetResolver(c *Client) {
	c.timeOffset = new(atomic.Int64)
}

func addUserAgentRetryMode(stack *middleware.Stack, options Options) error {
	ua, err := getOrAddRequestUserAgent(stack)
	if err != nil {
		return err
	}

	switch options.Retryer.(type) {
	case *retry.Standard:
		ua.AddUserAgentFeature(awsmiddleware.UserAgentFeatureRetryModeStandard)
	case *retry.AdaptiveMode:
		ua.AddUserAgentFeature(awsmiddleware.UserAgentFeatureRetryModeAdaptive)
	}
	return nil
}

type setCredentialSourceMiddleware struct {
	ua      *awsmiddleware.RequestUserAgent
	options Options
}

func (m setCredentialSourceMiddleware) ID() string { return "SetCredentialSourceMiddleware" }

func (m setCredentialSourceMiddleware) HandleBuild(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) (
	out middleware.BuildOutput, metadata middleware.Metadata, err error,
) {
	asProviderSource, ok := m.options.Credentials.(aws.CredentialProviderSource)
	if !ok {
		return next.HandleBuild(ctx, in)
	}
	providerSources := asProviderSource.ProviderSources()
	for _, source := range providerSources {
		m.ua.AddCredentialsSource(source)
	}
	return next.HandleBuild(ctx, in)
}

func addCredentialSource(stack *middleware.Stack, options Options) error {
	ua, err := getOrAddRequestUserAgent(stack)
	if err != nil {
		return err
	}

	mw := setCredentialSourceMiddleware{ua: ua, options: options}
	return stack.Build.Insert(&mw, "UserAgent", middleware.Before)
}

func resolveTracerProvider(options *Options) {
	if options.TracerProvider == nil {
		options.TracerProvider = &tracing.NopTracerProvider{}
	}
}

func resolveMeterProvider(options *Options) {
	if options.MeterProvider == nil {
		options.MeterProvider = metrics.NopMeterProvider{}
	}
}

func newServiceMetadataMiddleware(region, operation string) *awsmiddleware.RegisterServiceMetadata {
	return &awsmiddleware.RegisterServiceMetadata{
		Region:        region,
		ServiceID:     ServiceID,
		OperationName: operation,
	}
}

func addRecursionDetection(stack *middleware.Stack) error {
	return stack.Build.Add(&awsmiddleware.RecursionDetection{}, middleware.After)
}

func addRequestIDRetrieverMiddleware(stack *middleware.Stack) error {
	return stack.Deserialize.Insert(&awsmiddleware.RequestIDRetriever{}, "OperationDeserializer", middleware.Before)

}

func addResponseErrorMiddleware(stack *middleware.Stack) error {
	return stack.Deserialize.Insert(&awshttp.ResponseErrorWrapper{}, "RequestIDRetriever", middleware.Before)

}

func addSanitizeURLMiddleware(stack *middleware.Stack) error {
	return route53cust.AddSanitizeURLMiddleware(stack, route53cust.AddSanitizeURLMiddlewareOptions{SanitizeURLInput: sanitizeURLInput})
}

// Check for and split apart Route53 resource IDs, setting only the last piece.
// This allows the output of one operation e.g. foo/1234 to be used as input in
// another operation (e.g. it expects just '1234')
func sanitizeURLInput(input interface{}) error {
	switch i := input.(type) {
	case *ActivateKeySigningKeyInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *AssociateVPCWithHostedZoneInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *ChangeResourceRecordSetsInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *CreateHostedZoneInput:
		if i.DelegationSetId != nil {
			idx := strings.LastIndex(*i.DelegationSetId, `/`)
			v := (*i.DelegationSetId)[idx+1:]
			i.DelegationSetId = &v
		}

	case *CreateKeySigningKeyInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *CreateQueryLoggingConfigInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *CreateReusableDelegationSetInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *CreateTrafficPolicyInstanceInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *CreateVPCAssociationAuthorizationInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *DeactivateKeySigningKeyInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *DeleteHostedZoneInput:
		if i.Id != nil {
			idx := strings.LastIndex(*i.Id, `/`)
			v := (*i.Id)[idx+1:]
			i.Id = &v
		}

	case *DeleteKeySigningKeyInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *DeleteReusableDelegationSetInput:
		if i.Id != nil {
			idx := strings.LastIndex(*i.Id, `/`)
			v := (*i.Id)[idx+1:]
			i.Id = &v
		}

	case *DeleteVPCAssociationAuthorizationInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *DisableHostedZoneDNSSECInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *DisassociateVPCFromHostedZoneInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *EnableHostedZoneDNSSECInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *GetChangeInput:
		if i.Id != nil {
			idx := strings.LastIndex(*i.Id, `/`)
			v := (*i.Id)[idx+1:]
			i.Id = &v
		}

	case *GetDNSSECInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *GetHostedZoneInput:
		if i.Id != nil {
			idx := strings.LastIndex(*i.Id, `/`)
			v := (*i.Id)[idx+1:]
			i.Id = &v
		}

	case *GetHostedZoneLimitInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *GetReusableDelegationSetInput:
		if i.Id != nil {
			idx := strings.LastIndex(*i.Id, `/`)
			v := (*i.Id)[idx+1:]
			i.Id = &v
		}

	case *GetReusableDelegationSetLimitInput:
		if i.DelegationSetId != nil {
			idx := strings.LastIndex(*i.DelegationSetId, `/`)
			v := (*i.DelegationSetId)[idx+1:]
			i.DelegationSetId = &v
		}

	case *ListHostedZonesInput:
		if i.DelegationSetId != nil {
			idx := strings.LastIndex(*i.DelegationSetId, `/`)
			v := (*i.DelegationSetId)[idx+1:]
			i.DelegationSetId = &v
		}

	case *ListHostedZonesByNameInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *ListQueryLoggingConfigsInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *ListResourceRecordSetsInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *ListTrafficPolicyInstancesInput:
		if i.HostedZoneIdMarker != nil {
			idx := strings.LastIndex(*i.HostedZoneIdMarker, `/`)
			v := (*i.HostedZoneIdMarker)[idx+1:]
			i.HostedZoneIdMarker = &v
		}

	case *ListTrafficPolicyInstancesByHostedZoneInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *ListTrafficPolicyInstancesByPolicyInput:
		if i.HostedZoneIdMarker != nil {
			idx := strings.LastIndex(*i.HostedZoneIdMarker, `/`)
			v := (*i.HostedZoneIdMarker)[idx+1:]
			i.HostedZoneIdMarker = &v
		}

	case *ListVPCAssociationAuthorizationsInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *TestDNSAnswerInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	case *UpdateHostedZoneCommentInput:
		if i.Id != nil {
			idx := strings.LastIndex(*i.Id, `/`)
			v := (*i.Id)[idx+1:]
			i.Id = &v
		}

	case *UpdateHostedZoneFeaturesInput:
		if i.HostedZoneId != nil {
			idx := strings.LastIndex(*i.HostedZoneId, `/`)
			v := (*i.HostedZoneId)[idx+1:]
			i.HostedZoneId = &v
		}

	default:
		break
	}
	return nil
}

func addRequestResponseLogging(stack *middleware.Stack, o Options) error {
	return stack.Deserialize.Add(&smithyhttp.RequestResponseLogger{
		LogRequest:          o.ClientLogMode.IsRequest(),
		LogRequestWithBody:  o.ClientLogMode.IsRequestWithBody(),
		LogResponse:         o.ClientLogMode.IsResponse(),
		LogResponseWithBody: o.ClientLogMode.IsResponseWithBody(),
	}, middleware.After)
}

type disableHTTPSMiddleware struct {
	DisableHTTPS bool
}

func (*disableHTTPSMiddleware) ID() string {
	return "disableHTTPS"
}

func (m *disableHTTPSMiddleware) HandleFinalize(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (
	out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
) {
	req, ok := in.Request.(*smithyhttp.Request)
	if !ok {
		return out, metadata, fmt.Errorf("unknown transport type %T", in.Request)
	}

	if m.DisableHTTPS && !smithyhttp.GetHostnameImmutable(ctx) {
		req.URL.Scheme = "http"
	}

	return next.HandleFinalize(ctx, in)
}

func addDisableHTTPSMiddleware(stack *middleware.Stack, o Options) error {
	return stack.Finalize.Insert(&disableHTTPSMiddleware{
		DisableHTTPS: o.EndpointOptions.DisableHTTPS,
	}, "ResolveEndpointV2", middleware.After)
}

func addInterceptBeforeRetryLoop(stack *middleware.Stack, opts Options) error {
	return stack.Finalize.Insert(&smithyhttp.InterceptBeforeRetryLoop{
		Interceptors: opts.Interceptors.BeforeRetryLoop,
	}, "Retry", middleware.Before)
}

func addInterceptAttempt(stack *middleware.Stack, opts Options) error {
	return stack.Finalize.Insert(&smithyhttp.InterceptAttempt{
		BeforeAttempt: opts.Interceptors.BeforeAttempt,
		AfterAttempt:  opts.Interceptors.AfterAttempt,
	}, "Retry", middleware.After)
}

func addInterceptors(stack *middleware.Stack, opts Options) error {
	// middlewares are expensive, don't add all of these interceptor ones unless the caller
	// actually has at least one interceptor configured
	//
	// at the moment it's all-or-nothing because some of the middlewares here are responsible for
	// setting fields in the interceptor context for future ones
	if len(opts.Interceptors.BeforeExecution) == 0 &&
		len(opts.Interceptors.BeforeSerialization) == 0 && len(opts.Interceptors.AfterSerialization) == 0 &&
		len(opts.Interceptors.BeforeRetryLoop) == 0 &&
		len(opts.Interceptors.BeforeAttempt) == 0 &&
		len(opts.Interceptors.BeforeSigning) == 0 && len(opts.Interceptors.AfterSigning) == 0 &&
		len(opts.Interceptors.BeforeTransmit) == 0 && len(opts.Interceptors.AfterTransmit) == 0 &&
		len(opts.Interceptors.BeforeDeserialization) == 0 && len(opts.Interceptors.AfterDeserialization) == 0 &&
		len(opts.Interceptors.AfterAttempt) == 0 && len(opts.Interceptors.AfterExecution) == 0 {
		return nil
	}

	return errors.Join(
		stack.Initialize.Add(&smithyhttp.InterceptExecution{
			BeforeExecution: opts.Interceptors.BeforeExecution,
			AfterExecution:  opts.Interceptors.AfterExecution,
		}, middleware.Before),
		stack.Serialize.Insert(&smithyhttp.InterceptBeforeSerialization{
			Interceptors: opts.Interceptors.BeforeSerialization,
		}, "OperationSerializer", middleware.Before),
		stack.Serialize.Insert(&smithyhttp.InterceptAfterSerialization{
			Interceptors: opts.Interceptors.AfterSerialization,
		}, "OperationSerializer", middleware.After),
		stack.Finalize.Insert(&smithyhttp.InterceptBeforeSigning{
			Interceptors: opts.Interceptors.BeforeSigning,
		}, "Signing", middleware.Before),
		stack.Finalize.Insert(&smithyhttp.InterceptAfterSigning{
			Interceptors: opts.Interceptors.AfterSigning,
		}, "Signing", middleware.After),
		stack.Deserialize.Add(&smithyhttp.InterceptTransmit{
			BeforeTransmit: opts.Interceptors.BeforeTransmit,
			AfterTransmit:  opts.Interceptors.AfterTransmit,
		}, middleware.After),
		stack.Deserialize.Insert(&smithyhttp.InterceptBeforeDeserialization{
			Interceptors: opts.Interceptors.BeforeDeserialization,
		}, "OperationDeserializer", middleware.After), // (deserialize stack is called in reverse)
		stack.Deserialize.Insert(&smithyhttp.InterceptAfterDeserialization{
			Interceptors: opts.Interceptors.AfterDeserialization,
		}, "OperationDeserializer", middleware.Before),
	)
}
