// Copyright 2019 DeepMap, Inc.
//
// 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 runtime

import (
	"encoding"
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"reflect"
	"strings"
	"time"

	"github.com/oapi-codegen/runtime/types"
)

// RequiredParameterError is returned when a required query parameter is missing.
// Generated server code can use errors.As to detect this and produce a
// framework-specific typed error for the application's error handler.
type RequiredParameterError struct {
	ParamName string
}

func (e *RequiredParameterError) Error() string {
	return fmt.Sprintf("query parameter '%s' is required", e.ParamName)
}

// BindStyledParameter binds a parameter as described in the Path Parameters
// section here to a Go object:
// https://swagger.io/docs/specification/serialization/
// It is a backward compatible function to clients generated with codegen
// up to version v1.5.5. v1.5.6+ calls the function below.
// Deprecated: BindStyledParameter is deprecated.
func BindStyledParameter(style string, explode bool, paramName string,
	value string, dest interface{}) error {
	return BindStyledParameterWithOptions(style, paramName, value, dest, BindStyledParameterOptions{
		ParamLocation: ParamLocationUndefined,
		Explode:       explode,
		Required:      true,
	})
}

// BindStyledParameterWithLocation binds a parameter as described in the Path Parameters
// section here to a Go object:
// https://swagger.io/docs/specification/serialization/
// This is a compatibility function which is used by oapi-codegen v2.0.0 and earlier.
// Deprecated: BindStyledParameterWithLocation is deprecated.
func BindStyledParameterWithLocation(style string, explode bool, paramName string,
	paramLocation ParamLocation, value string, dest interface{}) error {
	return BindStyledParameterWithOptions(style, paramName, value, dest, BindStyledParameterOptions{
		ParamLocation: paramLocation,
		Explode:       explode,
		Required:      true, // This emulates behavior before the required parameter was optional.
	})
}

// BindStyledParameterOptions defines optional arguments for BindStyledParameterWithOptions
type BindStyledParameterOptions struct {
	// ParamLocation tells us where the parameter is located in the request.
	ParamLocation ParamLocation
	// Whether the parameter should use exploded structure
	Explode bool
	// Whether the parameter is required in the query
	Required bool
	// Type is the OpenAPI type of the parameter (e.g. "string", "integer").
	Type string
	// Format is the OpenAPI format of the parameter (e.g. "byte", "date-time").
	// When set to "byte" and the destination is []byte, the value is
	// base64-decoded rather than treated as a generic slice.
	Format string
	// AllowReserved, when true, indicates that the parameter value may
	// contain RFC 3986 reserved characters without percent-encoding.
	AllowReserved bool
}

// BindStyledParameterWithOptions binds a parameter as described in the Path Parameters
// section here to a Go object:
// https://swagger.io/docs/specification/serialization/
func BindStyledParameterWithOptions(style string, paramName string, value string, dest any, opts BindStyledParameterOptions) error {
	if opts.Required {
		if value == "" {
			return fmt.Errorf("parameter '%s' is empty, can't bind its value", paramName)
		}
	}

	// Based on the location of the parameter, we need to unescape it properly.
	var err error
	switch opts.ParamLocation {
	case ParamLocationQuery, ParamLocationUndefined:
		// We unescape undefined parameter locations here for older generated code,
		// since prior to this refactoring, they always query unescaped.
		value, err = url.QueryUnescape(value)
		if err != nil {
			return fmt.Errorf("error unescaping query parameter '%s': %w", paramName, err)
		}
	case ParamLocationPath:
		value, err = url.PathUnescape(value)
		if err != nil {
			return fmt.Errorf("error unescaping path parameter '%s': %w", paramName, err)
		}
	default:
		// Headers and cookies aren't escaped.
	}

	// If the destination implements encoding.TextUnmarshaler we use it for binding
	if tu, ok := dest.(encoding.TextUnmarshaler); ok {
		if err := tu.UnmarshalText([]byte(value)); err != nil {
			return fmt.Errorf("error unmarshaling '%s' text as %T: %w", value, dest, err)
		}

		return nil
	}

	// Everything comes in by pointer, dereference it
	v := reflect.Indirect(reflect.ValueOf(dest))

	// This is the basic type of the destination object.
	t := v.Type()

	if t.Kind() == reflect.Struct || t.Kind() == reflect.Map {
		// We've got a destination object, we'll create a JSON representation
		// of the input value, and let the json library deal with the unmarshaling
		parts, err := splitStyledParameter(style, opts.Explode, true, paramName, value)
		if err != nil {
			return err
		}

		return bindSplitPartsToDestinationStruct(paramName, parts, opts.Explode, dest)
	}

	if t.Kind() == reflect.Slice {
		if opts.Format == "byte" && isByteSlice(t) {
			parts, err := splitStyledParameter(style, opts.Explode, false, paramName, value)
			if err != nil {
				return fmt.Errorf("error splitting input '%s' into parts: %w", value, err)
			}
			if len(parts) != 1 {
				return fmt.Errorf("expected single base64 value for byte slice parameter '%s', got %d parts", paramName, len(parts))
			}
			decoded, err := base64Decode(parts[0])
			if err != nil {
				return fmt.Errorf("error decoding base64 parameter '%s': %w", paramName, err)
			}
			v.SetBytes(decoded)
			return nil
		}

		// Chop up the parameter into parts based on its style
		parts, err := splitStyledParameter(style, opts.Explode, false, paramName, value)
		if err != nil {
			return fmt.Errorf("error splitting input '%s' into parts: %w", value, err)
		}

		return bindSplitPartsToDestinationArray(parts, dest)
	}

	// For primitive types, only label and matrix styles need prefix stripping
	// via splitStyledParameter. Simple and form styles can bind the raw value
	// directly — splitting on commas would incorrectly reject primitive values
	// that contain commas (see https://github.com/oapi-codegen/runtime/issues/114).
	if style == "label" || style == "matrix" {
		parts, err := splitStyledParameter(style, opts.Explode, false, paramName, value)
		if err != nil {
			return fmt.Errorf("error splitting parameter '%s': %w", paramName, err)
		}
		if len(parts) != 1 {
			return fmt.Errorf("parameter '%s': expected single value, got %d parts", paramName, len(parts))
		}
		value = parts[0]
	}
	return BindStringToObject(value, dest)
}

// This is a complex set of operations, but each given parameter style can be
// packed together in multiple ways, using different styles of separators, and
// different packing strategies based on the explode flag. This function takes
// as input any parameter format, and unpacks it to a simple list of strings
// or key-values which we can then treat generically.
// Why, oh why, great Swagger gods, did you have to make this so complicated?
func splitStyledParameter(style string, explode bool, object bool, paramName string, value string) ([]string, error) {
	switch style {
	case "simple":
		// In the simple case, we always split on comma
		parts := strings.Split(value, ",")
		return parts, nil
	case "label":
		// In the label case, it's more tricky. In the no explode case, we have
		// /users/.3,4,5 for arrays
		// /users/.role,admin,firstName,Alex for objects
		// in the explode case, we have:
		// /users/.3.4.5
		// /users/.role=admin.firstName=Alex
		if explode {
			// In the exploded case, split everything on periods.
			parts := strings.Split(value, ".")
			// The first part should be an empty string because we have a
			// leading period.
			if parts[0] != "" {
				return nil, fmt.Errorf("invalid format for label parameter '%s', should start with '.'", paramName)
			}
			return parts[1:], nil

		} else {
			// In the unexploded case, we strip off the leading period.
			if value[0] != '.' {
				return nil, fmt.Errorf("invalid format for label parameter '%s', should start with '.'", paramName)
			}
			// The rest is comma separated.
			return strings.Split(value[1:], ","), nil
		}

	case "matrix":
		if explode {
			// In the exploded case, we break everything up on semicolon
			parts := strings.Split(value, ";")
			// The first part should always be empty string, since we started
			// with ;something
			if parts[0] != "" {
				return nil, fmt.Errorf("invalid format for matrix parameter '%s', should start with ';'", paramName)
			}
			parts = parts[1:]
			// Now, if we have an object, we just have a list of x=y statements.
			// for a non-object, like an array, we have id=x, id=y. id=z, etc,
			// so we need to strip the prefix from each of them.
			if !object {
				prefix := paramName + "="
				for i := range parts {
					parts[i] = strings.TrimPrefix(parts[i], prefix)
				}
			}
			return parts, nil
		} else {
			// In the unexploded case, parameters will start with ;paramName=
			prefix := ";" + paramName + "="
			if !strings.HasPrefix(value, prefix) {
				return nil, fmt.Errorf("expected parameter '%s' to start with %s", paramName, prefix)
			}
			str := strings.TrimPrefix(value, prefix)
			return strings.Split(str, ","), nil
		}
	case "form":
		var parts []string
		if explode {
			parts = strings.Split(value, "&")
			if !object {
				prefix := paramName + "="
				for i := range parts {
					parts[i] = strings.TrimPrefix(parts[i], prefix)
				}
			}
			return parts, nil
		} else {
			parts = strings.Split(value, ",")
			prefix := paramName + "="
			for i := range parts {
				parts[i] = strings.TrimPrefix(parts[i], prefix)
			}
		}
		return parts, nil
	}

	return nil, fmt.Errorf("unhandled parameter style: %s", style)
}

// Given a set of values as a slice, create a slice to hold them all, and
// assign to each one by one.
func bindSplitPartsToDestinationArray(parts []string, dest interface{}) error {
	// Everything comes in by pointer, dereference it
	v := reflect.Indirect(reflect.ValueOf(dest))

	// This is the basic type of the destination object.
	t := v.Type()

	// We've got a destination array, bind each object one by one.
	// This generates a slice of the correct element type and length to
	// hold all the parts.
	newArray := reflect.MakeSlice(t, len(parts), len(parts))
	for i, p := range parts {
		err := BindStringToObject(p, newArray.Index(i).Addr().Interface())
		if err != nil {
			return fmt.Errorf("error setting array element: %w", err)
		}
	}
	v.Set(newArray)
	return nil
}

// Given a set of chopped up parameter parts, bind them to a destination
// struct. The exploded parameter controls whether we send key value pairs
// in the exploded case, or a sequence of values which are interpreted as
// tuples.
// Given the struct Id { firstName string, role string }, as in the canonical
// swagger examples, in the exploded case, we would pass
// ["firstName=Alex", "role=admin"], where in the non-exploded case, we would
// pass "firstName", "Alex", "role", "admin"]
//
// We punt the hard work of binding these values to the object to the json
// library. We'll turn those arrays into JSON strings, and unmarshal
// into the struct.
func bindSplitPartsToDestinationStruct(paramName string, parts []string, explode bool, dest interface{}) error {
	// We've got a destination object, we'll create a JSON representation
	// of the input value, and let the json library deal with the unmarshaling
	var fields []string
	if explode {
		fields = make([]string, len(parts))
		for i, property := range parts {
			propertyParts := strings.Split(property, "=")
			if len(propertyParts) != 2 {
				return fmt.Errorf("parameter '%s' has invalid exploded format", paramName)
			}
			fields[i] = "\"" + propertyParts[0] + "\":\"" + propertyParts[1] + "\""
		}
	} else {
		if len(parts)%2 != 0 {
			return fmt.Errorf("parameter '%s' has invalid format, property/values need to be pairs", paramName)
		}
		fields = make([]string, len(parts)/2)
		for i := 0; i < len(parts); i += 2 {
			key := parts[i]
			value := parts[i+1]
			fields[i/2] = "\"" + key + "\":\"" + value + "\""
		}
	}
	jsonParam := "{" + strings.Join(fields, ",") + "}"
	err := json.Unmarshal([]byte(jsonParam), dest)
	if err != nil {
		return fmt.Errorf("error binding parameter %s fields: %w", paramName, err)
	}
	return nil
}

// BindQueryParameter works much like BindStyledParameter, however it takes a query argument
// input array from the url package, since query arguments come through a
// different path than the styled arguments. They're also exceptionally fussy.
// For example, consider the exploded and unexploded form parameter examples:
// (exploded) /users?role=admin&firstName=Alex
// (unexploded) /users?id=role,admin,firstName,Alex
//
// In the first case, we can pull the "id" parameter off the context,
// and unmarshal via json as an intermediate. Easy. In the second case, we
// don't have the id QueryParam present, but must find "role", and "firstName".
// what if there is another parameter similar to "ID" named "role"? We can't
// tell them apart. This code tries to fail, but the moral of the story is that
// you shouldn't pass objects via form styled query arguments, just use
// the Content parameter form.
//
// Deprecated: BindQueryParameter pre-decodes the query string via url.Values,
// which makes it impossible to distinguish literal commas from delimiter commas
// in form/explode=false parameters. Use BindRawQueryParameter instead, which
// operates on the raw query string and handles encoded delimiters correctly.
func BindQueryParameter(style string, explode bool, required bool, paramName string,
	queryParams url.Values, dest interface{}) error {
	return BindQueryParameterWithOptions(style, explode, required, paramName, queryParams, dest, BindQueryParameterOptions{})
}

// BindQueryParameterOptions defines optional arguments for BindQueryParameterWithOptions.
type BindQueryParameterOptions struct {
	// Type is the OpenAPI type of the parameter (e.g. "string", "integer").
	Type string
	// Format is the OpenAPI format of the parameter (e.g. "byte", "date-time").
	// When set to "byte" and the destination is []byte, the value is
	// base64-decoded rather than treated as a generic slice.
	Format string
	// AllowReserved, when true, indicates that the parameter value may
	// contain RFC 3986 reserved characters without percent-encoding.
	AllowReserved bool
}

// BindQueryParameterWithOptions works like BindQueryParameter with additional options.
func BindQueryParameterWithOptions(style string, explode bool, required bool, paramName string,
	queryParams url.Values, dest interface{}, opts BindQueryParameterOptions) error {

	// dv = destination value.
	dv := reflect.Indirect(reflect.ValueOf(dest))

	// intermediate value form which is either dv or dv dereferenced.
	v := dv

	// inner code will bind the string's value to this interface.
	var output interface{}

	// required params are never pointers, but it may happen that optional param
	// is not pointer as well if user decides to annotate it with
	// x-go-type-skip-optional-pointer
	var extraIndirect = !required && v.Kind() == reflect.Pointer
	if !extraIndirect {
		// If the parameter is required, then the generated code will pass us
		// a pointer to it: &int, &object, and so forth. We can directly set
		// them.
		output = dest
	} else {
		// For optional parameters, we have an extra indirect. An optional
		// parameter of type "int" will be *int on the struct. We pass that
		// in by pointer, and have **int.

		// If the destination, is a nil pointer, we need to allocate it.
		if v.IsNil() {
			t := v.Type()
			newValue := reflect.New(t.Elem())
			// for now, hang onto the output buffer separately from destination,
			// as we don't want to write anything to destination until we can
			// unmarshal successfully, and check whether a field is required.
			output = newValue.Interface()
		} else {
			// If the destination isn't nil, just use that.
			output = v.Interface()
		}

		// Get rid of that extra indirect as compared to the required case,
		// so the code below doesn't have to care.
		v = reflect.Indirect(reflect.ValueOf(output))
	}

	// This is the basic type of the destination object.
	t := v.Type()
	k := t.Kind()

	switch style {
	case "form", "spaceDelimited", "pipeDelimited":
		var parts []string
		if explode {
			// ok, the explode case in query arguments is very, very annoying,
			// because an exploded object, such as /users?role=admin&firstName=Alex
			// isn't actually present in the parameter array. We have to do
			// different things based on destination type.
			//
			// Note: spaceDelimited and pipeDelimited with explode=true are
			// serialized identically to form explode=true (each value is a
			// separate key=value pair), so we share this code path.
			values, found := queryParams[paramName]
			var err error

			switch k {
			case reflect.Slice:
				// In the slice case, we simply use the arguments provided by
				// http library.

				if !found {
					if required {
						return &RequiredParameterError{ParamName: paramName}
					} else {
						// If an optional parameter is not found, we do nothing,
						return nil
					}
				}
				if opts.Format == "byte" && isByteSlice(t) {
					if len(values) != 1 {
						return fmt.Errorf("expected single base64 value for byte slice parameter '%s', got %d values", paramName, len(values))
					}
					decoded, decErr := base64Decode(values[0])
					if decErr != nil {
						return fmt.Errorf("error decoding base64 parameter '%s': %w", paramName, decErr)
					}
					v.SetBytes(decoded)
				} else {
					err = bindSplitPartsToDestinationArray(values, output)
				}
			case reflect.Struct:
				// This case is really annoying, and error prone, but the
				// form style object binding doesn't tell us which arguments
				// in the query string correspond to the object's fields. We'll
				// try to bind field by field.
				var fieldsPresent bool
				fieldsPresent, err = bindParamsToExplodedObject(paramName, queryParams, output)
				// If no fields were set, and there is no error, we will not fall
				// through to assign the destination. An absent required
				// parameter is an error, matching the slice and primitive cases
				// above; this also covers scalar struct types such as
				// types.Date and time.Time.
				if !fieldsPresent {
					if required {
						return &RequiredParameterError{ParamName: paramName}
					}
					return nil
				}
			default:
				// Primitive object case. We expect to have 1 value to
				// unmarshal.
				if len(values) == 0 {
					if required {
						return &RequiredParameterError{ParamName: paramName}
					} else {
						return nil
					}
				}
				if len(values) != 1 {
					return fmt.Errorf("multiple values for single value parameter '%s'", paramName)
				}

				if !found {
					if required {
						return &RequiredParameterError{ParamName: paramName}
					} else {
						// If an optional parameter is not found, we do nothing,
						return nil
					}
				}
				err = BindStringToObject(values[0], output)
			}
			if err != nil {
				return err
			}
			// If the parameter is required (or relies on x-go-type-skip-optional-pointer),
			// and we've successfully unmarshaled it, this assigns the new object to the
			// pointer pointer.
			if extraIndirect {
				dv.Set(reflect.ValueOf(output))
			}
			return nil
		} else {
			values, found := queryParams[paramName]
			if !found {
				if required {
					return &RequiredParameterError{ParamName: paramName}
				} else {
					return nil
				}
			}
			if len(values) != 1 {
				return fmt.Errorf("parameter '%s' is not exploded, but is specified multiple times", paramName)
			}

			// For primitive types, the raw value should be used as-is
			// without splitting on commas. Per the OpenAPI specification,
			// explode has no effect on primitive types — the serialization
			// is the same regardless of the explode value. Comma splitting
			// is only meaningful for array and object types.
			// See: https://swagger.io/docs/specification/serialization/
			if k != reflect.Slice && k != reflect.Struct && k != reflect.Map {
				err := BindStringToObject(values[0], output)
				if err != nil {
					return err
				}
				if extraIndirect {
					dv.Set(reflect.ValueOf(output))
				}
				return nil
			}

			switch style {
			case "spaceDelimited":
				parts = strings.Split(values[0], " ")
			case "pipeDelimited":
				parts = strings.Split(values[0], "|")
			default:
				parts = strings.Split(values[0], ",")
			}
		}
		var err error
		switch k {
		case reflect.Slice:
			if opts.Format == "byte" && isByteSlice(t) {
				// For non-exploded form, the value was split on commas above.
				// Rejoin to get the original base64 string.
				raw := strings.Join(parts, ",")
				decoded, decErr := base64Decode(raw)
				if decErr != nil {
					return fmt.Errorf("error decoding base64 parameter '%s': %w", paramName, decErr)
				}
				v.SetBytes(decoded)
			} else {
				err = bindSplitPartsToDestinationArray(parts, output)
			}
		case reflect.Struct, reflect.Map:
			// Some struct types (e.g. types.Date, time.Time) are scalar values
			// that should be bound from a single string, not decomposed as
			// key-value objects. Detect these via the Binder and
			// TextUnmarshaler interfaces.
			switch v := output.(type) {
			case Binder:
				if len(parts) != 1 {
					return fmt.Errorf("multiple values for single value parameter '%s'", paramName)
				}
				err = v.Bind(parts[0])
			case encoding.TextUnmarshaler:
				if len(parts) != 1 {
					return fmt.Errorf("multiple values for single value parameter '%s'", paramName)
				}
				err = v.UnmarshalText([]byte(parts[0]))
			default:
				err = bindSplitPartsToDestinationStruct(paramName, parts, explode, output)
			}
		}
		if err != nil {
			return err
		}
		if extraIndirect {
			dv.Set(reflect.ValueOf(output))
		}
		return nil
	case "deepObject":
		if !explode {
			return errors.New("deepObjects must be exploded")
		}
		return unmarshalDeepObject(dest, paramName, queryParams, required)
	default:
		return fmt.Errorf("style '%s' on parameter '%s' is invalid", style, paramName)

	}
}

// findRawQueryParam extracts the raw (still-percent-encoded) values for a given
// parameter name from a raw query string, without URL-decoding the values.
// The parameter key is decoded for comparison purposes, but the returned values
// remain in their original encoded form.
func findRawQueryParam(rawQuery, paramName string) (values []string, found bool) {
	for rawQuery != "" {
		var part string
		if i := strings.IndexByte(rawQuery, '&'); i >= 0 {
			part = rawQuery[:i]
			rawQuery = rawQuery[i+1:]
		} else {
			part = rawQuery
			rawQuery = ""
		}
		if part == "" {
			continue
		}
		key := part
		var val string
		if i := strings.IndexByte(part, '='); i >= 0 {
			key = part[:i]
			val = part[i+1:]
		}
		decodedKey, err := url.QueryUnescape(key)
		if err != nil {
			// Skip malformed keys.
			continue
		}
		if decodedKey == paramName {
			values = append(values, val)
			found = true
		}
	}
	return values, found
}

// BindRawQueryParameter works like BindQueryParameter but operates on the raw
// (undecoded) query string instead of pre-parsed url.Values. This correctly
// handles form/explode=false parameters whose values contain literal commas
// encoded as %2C — something that BindQueryParameter cannot do because
// url.Values has already decoded %2C to ',' before we can split on the
// delimiter comma.
func BindRawQueryParameter(style string, explode bool, required bool, paramName string,
	rawQuery string, dest any) error {

	// dv = destination value.
	dv := reflect.Indirect(reflect.ValueOf(dest))

	// intermediate value form which is either dv or dv dereferenced.
	v := dv

	// inner code will bind the string's value to this interface.
	var output any

	// required params are never pointers, but it may happen that optional param
	// is not pointer as well if user decides to annotate it with
	// x-go-type-skip-optional-pointer
	var extraIndirect = !required && v.Kind() == reflect.Pointer
	if !extraIndirect {
		output = dest
	} else {
		if v.IsNil() {
			t := v.Type()
			newValue := reflect.New(t.Elem())
			output = newValue.Interface()
		} else {
			output = v.Interface()
		}
		v = reflect.Indirect(reflect.ValueOf(output))
	}

	// This is the basic type of the destination object.
	t := v.Type()
	k := t.Kind()

	switch style {
	case "form", "spaceDelimited", "pipeDelimited":
		if explode {
			// For the explode case, url.ParseQuery is fine — there are no
			// delimiter commas to confuse with literal commas.
			//
			// Note: spaceDelimited and pipeDelimited with explode=true are
			// serialized identically to form explode=true (each value is a
			// separate key=value pair), so we share this code path.
			queryParams, err := url.ParseQuery(rawQuery)
			if err != nil {
				return fmt.Errorf("error parsing query string: %w", err)
			}
			values, found := queryParams[paramName]

			switch k {
			case reflect.Slice:
				if !found {
					if required {
						return &RequiredParameterError{ParamName: paramName}
					}
					return nil
				}
				err = bindSplitPartsToDestinationArray(values, output)
			case reflect.Struct:
				var fieldsPresent bool
				fieldsPresent, err = bindParamsToExplodedObject(paramName, queryParams, output)
				// An absent required parameter is an error, matching the slice
				// and primitive cases above; this also covers scalar struct
				// types such as types.Date and time.Time.
				if !fieldsPresent {
					if required {
						return &RequiredParameterError{ParamName: paramName}
					}
					return nil
				}
			default:
				if len(values) == 0 {
					if required {
						return &RequiredParameterError{ParamName: paramName}
					}
					return nil
				}
				if len(values) != 1 {
					return fmt.Errorf("multiple values for single value parameter '%s'", paramName)
				}
				if !found {
					if required {
						return &RequiredParameterError{ParamName: paramName}
					}
					return nil
				}
				err = BindStringToObject(values[0], output)
			}
			if err != nil {
				return err
			}
			if extraIndirect {
				dv.Set(reflect.ValueOf(output))
			}
			return nil
		}

		// explode=false — use findRawQueryParam to get the still-encoded
		// value, split on the style-specific delimiter, then URL-decode
		// each resulting part individually.
		rawValues, found := findRawQueryParam(rawQuery, paramName)
		if !found {
			if required {
				return &RequiredParameterError{ParamName: paramName}
			}
			return nil
		}
		if len(rawValues) != 1 {
			return fmt.Errorf("parameter '%s' is not exploded, but is specified multiple times", paramName)
		}

		// For primitive types, decode the raw value as-is without splitting
		// on commas. Per the OpenAPI specification, explode has no effect on
		// primitive types. Comma splitting is only meaningful for array and
		// object types.
		if k != reflect.Slice && k != reflect.Struct && k != reflect.Map {
			decoded, err := url.QueryUnescape(rawValues[0])
			if err != nil {
				return fmt.Errorf("error decoding query parameter '%s' value %q: %w", paramName, rawValues[0], err)
			}
			err = BindStringToObject(decoded, output)
			if err != nil {
				return err
			}
			if extraIndirect {
				dv.Set(reflect.ValueOf(output))
			}
			return nil
		}

		var rawParts []string
		switch style {
		case "spaceDelimited":
			// Normalise all space representations to %20, then split.
			normalized := strings.ReplaceAll(rawValues[0], "+", "%20")
			normalized = strings.ReplaceAll(normalized, " ", "%20")
			rawParts = strings.Split(normalized, "%20")
		case "pipeDelimited":
			rawParts = strings.Split(rawValues[0], "|")
		default:
			rawParts = strings.Split(rawValues[0], ",")
		}

		parts := make([]string, len(rawParts))
		for i, rp := range rawParts {
			decoded, err := url.QueryUnescape(rp)
			if err != nil {
				return fmt.Errorf("error decoding query parameter '%s' part %q: %w", paramName, rp, err)
			}
			parts[i] = decoded
		}

		var err error
		switch k {
		case reflect.Slice:
			err = bindSplitPartsToDestinationArray(parts, output)
		case reflect.Struct:
			err = bindSplitPartsToDestinationStruct(paramName, parts, explode, output)
		}
		if err != nil {
			return err
		}
		if extraIndirect {
			dv.Set(reflect.ValueOf(output))
		}
		return nil

	case "deepObject":
		if !explode {
			return errors.New("deepObjects must be exploded")
		}
		queryParams, err := url.ParseQuery(rawQuery)
		if err != nil {
			return fmt.Errorf("error parsing query string: %w", err)
		}
		return UnmarshalDeepObject(dest, paramName, queryParams)
	default:
		return fmt.Errorf("style '%s' on parameter '%s' is invalid", style, paramName)
	}
}

// bindParamsToExplodedObject reflects the destination structure, and pulls the value for
// each settable field from the given parameters map. This is to deal with the
// exploded form styled object which may occupy any number of parameter names.
// We don't try to be smart here, if the field exists as a query argument,
// set its value. This function returns a boolean, telling us whether there was
// anything to bind. There will be nothing to bind if a parameter isn't found by name,
// or none of an exploded object's fields are present.
func bindParamsToExplodedObject(paramName string, values url.Values, dest interface{}) (bool, error) {
	// Dereference pointers to their destination values
	binder, v, t := indirect(dest)
	if binder != nil {
		_, found := values[paramName]
		if !found {
			return false, nil
		}
		return true, BindStringToObject(values.Get(paramName), dest)
	}
	if t.Kind() != reflect.Struct {
		return false, fmt.Errorf("unmarshaling query arg '%s' into wrong type", paramName)
	}

	fieldsPresent := false
	for i := 0; i < t.NumField(); i++ {
		fieldT := t.Field(i)

		// Skip unsettable fields, such as internal ones.
		if !v.Field(i).CanSet() {
			continue
		}

		// Find the json annotation on the field, and use the json specified
		// name if available, otherwise, just the field name.
		tag := fieldT.Tag.Get("json")
		fieldName := fieldT.Name
		if tag != "" {
			tagParts := strings.Split(tag, ",")
			name := tagParts[0]
			if name != "" {
				fieldName = name
			}
		}

		// At this point, we look up field name in the parameter list.
		fieldVal, found := values[fieldName]
		if found {
			if len(fieldVal) != 1 {
				return false, fmt.Errorf("field '%s' specified multiple times for param '%s'", fieldName, paramName)
			}
			err := BindStringToObject(fieldVal[0], v.Field(i).Addr().Interface())
			if err != nil {
				return false, fmt.Errorf("could not bind query arg '%s' to request object: %s'", paramName, err)
			}
			fieldsPresent = true
		}
	}
	return fieldsPresent, nil
}

// indirect
func indirect(dest interface{}) (interface{}, reflect.Value, reflect.Type) {
	v := reflect.ValueOf(dest)
	if v.Type().NumMethod() > 0 && v.CanInterface() {
		if u, ok := v.Interface().(Binder); ok {
			return u, reflect.Value{}, nil
		}
	}
	v = reflect.Indirect(v)
	t := v.Type()
	// special handling for custom types which might look like an object. We
	// don't want to use object binding on them, but rather treat them as
	// primitive types. time.Time{} is a unique case since we can't add a Binder
	// to it without changing the underlying generated code.
	if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
		return dest, reflect.Value{}, nil
	}
	if t.ConvertibleTo(reflect.TypeOf(types.Date{})) {
		return dest, reflect.Value{}, nil
	}
	return nil, v, t
}
