// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package attribute

import (
	"cmp"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"math"
	"reflect"
	"slices"
	"strconv"
	"strings"
	"unicode/utf8"

	attribute "go.opentelemetry.io/otel/attribute/internal"
)

//go:generate stringer -type=Type

// Type describes the kind of data a [Value] holds.
type Type int // nolint: revive  // redefines builtin Type.

// Value represents the value in key-value pairs.
//
// Note that the zero value is a valid empty value.
type Value struct {
	vtype    Type
	numeric  uint64
	stringly string
	slice    any
}

const (
	// EMPTY identifies a Value with no data.
	EMPTY Type = iota
	// BOOL identifies a Value containing a bool.
	BOOL
	// INT64 identifies a Value containing an int64.
	INT64
	// FLOAT64 identifies a Value containing a float64.
	FLOAT64
	// STRING identifies a Value containing a string.
	STRING
	// BOOLSLICE identifies a Value containing a []bool.
	BOOLSLICE
	// INT64SLICE identifies a Value containing a []int64.
	INT64SLICE
	// FLOAT64SLICE identifies a Value containing a []float64.
	FLOAT64SLICE
	// STRINGSLICE identifies a Value containing a []string.
	STRINGSLICE
	// BYTESLICE identifies a Value containing a []byte.
	BYTESLICE
	// SLICE identifies a Value containing a []Value.
	SLICE
	// MAP identifies a Value containing a []KeyValue representation of a map.
	//
	// Note that MAP values may contain duplicate keys if duplicate keys are
	// provided when creating the value.
	MAP
	// INVALID is used for a Value with no value set.
	//
	// Deprecated: Use EMPTY instead as an empty value is a valid value.
	INVALID = EMPTY
)

// BoolValue returns a [Value] for a bool value.
func BoolValue(v bool) Value {
	return Value{
		vtype:   BOOL,
		numeric: boolToRaw(v),
	}
}

// BoolSliceValue returns a [Value] for a []bool value.
//
// Note that many observability backends are not optimized to query, index, or
// aggregate complex attribute values. Complex values may also carry
// additional performance overhead. Prefer primitive values when
// possible.
func BoolSliceValue(v []bool) Value {
	return Value{vtype: BOOLSLICE, slice: attribute.SliceValue(v)}
}

// IntValue returns a [Value] for an int value.
//
// It is provided as a convenience for [Int64Value].
func IntValue(v int) Value {
	return Int64Value(int64(v))
}

// IntSliceValue returns a [Value] for a []int value.
//
// It is provided as a convenience for [Int64SliceValue].
//
// Note that many observability backends are not optimized to query, index, or
// aggregate complex attribute values. Complex values may also carry
// additional performance overhead. Prefer primitive values when
// possible.
func IntSliceValue(v []int) Value {
	val := Value{vtype: INT64SLICE}

	// Avoid the common tiny-slice cases from allocating a new slice.
	switch len(v) {
	case 0:
		val.slice = [0]int64{}
	case 1:
		val.slice = [1]int64{int64(v[0])}
	case 2:
		val.slice = [2]int64{int64(v[0]), int64(v[1])}
	case 3:
		val.slice = [3]int64{int64(v[0]), int64(v[1]), int64(v[2])}
	default:
		// Fallback to a new slice for larger slices.
		cp := make([]int64, len(v))
		for i, val := range v {
			cp[i] = int64(val)
		}
		val.slice = attribute.SliceValue(cp)
	}

	return val
}

// Int64Value returns a [Value] for an int64 value.
func Int64Value(v int64) Value {
	return Value{
		vtype:   INT64,
		numeric: int64ToRaw(v),
	}
}

// Int64SliceValue returns a [Value] for a []int64 value.
//
// Note that many observability backends are not optimized to query, index, or
// aggregate complex attribute values. Complex values may also carry
// additional performance overhead. Prefer primitive values when
// possible.
func Int64SliceValue(v []int64) Value {
	return Value{vtype: INT64SLICE, slice: attribute.SliceValue(v)}
}

// Float64Value returns a [Value] for a float64 value.
func Float64Value(v float64) Value {
	return Value{
		vtype:   FLOAT64,
		numeric: float64ToRaw(v),
	}
}

// Float64SliceValue returns a [Value] for a []float64 value.
//
// Note that many observability backends are not optimized to query, index, or
// aggregate complex attribute values. Complex values may also carry
// additional performance overhead. Prefer primitive values when
// possible.
func Float64SliceValue(v []float64) Value {
	return Value{vtype: FLOAT64SLICE, slice: attribute.SliceValue(v)}
}

// StringValue returns a [Value] for a string value.
func StringValue(v string) Value {
	return Value{
		vtype:    STRING,
		stringly: v,
	}
}

// StringSliceValue returns a [Value] for a []string value.
//
// Note that many observability backends are not optimized to query, index, or
// aggregate complex attribute values. Complex values may also carry
// additional performance overhead. Prefer primitive values when
// possible.
func StringSliceValue(v []string) Value {
	return Value{vtype: STRINGSLICE, slice: attribute.SliceValue(v)}
}

// ByteSliceValue returns a [Value] for a []byte value.
//
// Note that many observability backends are not optimized to query, index, or
// aggregate complex attribute values. Complex values may also carry
// additional performance overhead. Prefer primitive values when
// possible.
func ByteSliceValue(v []byte) Value {
	return Value{
		vtype:    BYTESLICE,
		stringly: string(v),
	}
}

// SliceValue returns a [Value] for a []Value value.
//
// Note that many observability backends are not optimized to query, index, or
// aggregate complex attribute values. Complex values may also carry
// additional performance overhead. Prefer primitive values when
// possible.
func SliceValue(v ...Value) Value {
	return Value{vtype: SLICE, slice: sliceValue(v)}
}

// MapValue returns a [Value] for a []KeyValue value.
//
// Note that many observability backends are not optimized to query, index, or
// aggregate complex attribute values. Complex values may also carry
// additional performance overhead. Prefer primitive values when
// possible.
//
// Users should avoid providing duplicate keys; many receivers handle maps
// containing duplicate keys unpredictably.
//
// The order of v is not preserved.
func MapValue(v ...KeyValue) Value {
	return Value{vtype: MAP, slice: mapValue(v)}
}

// Type returns v's type.
func (v Value) Type() Type {
	return v.vtype
}

// AsBool returns the bool value. Make sure that the Value's type is
// BOOL.
func (v Value) AsBool() bool {
	return rawToBool(v.numeric)
}

// AsBoolSlice returns the []bool value. Make sure that the Value's type is
// BOOLSLICE.
func (v Value) AsBoolSlice() []bool {
	if v.vtype != BOOLSLICE {
		return nil
	}
	return v.asBoolSlice()
}

func (v Value) asBoolSlice() []bool {
	return attribute.AsSlice[bool](v.slice)
}

// AsInt64 returns the int64 value. Make sure that the Value's type is
// INT64.
func (v Value) AsInt64() int64 {
	return rawToInt64(v.numeric)
}

// AsInt64Slice returns the []int64 value. Make sure that the Value's type is
// INT64SLICE.
func (v Value) AsInt64Slice() []int64 {
	if v.vtype != INT64SLICE {
		return nil
	}
	return v.asInt64Slice()
}

func (v Value) asInt64Slice() []int64 {
	return attribute.AsSlice[int64](v.slice)
}

// AsFloat64 returns the float64 value. Make sure that the Value's
// type is FLOAT64.
func (v Value) AsFloat64() float64 {
	return rawToFloat64(v.numeric)
}

// AsFloat64Slice returns the []float64 value. Make sure that the Value's type is
// FLOAT64SLICE.
func (v Value) AsFloat64Slice() []float64 {
	if v.vtype != FLOAT64SLICE {
		return nil
	}
	return v.asFloat64Slice()
}

func (v Value) asFloat64Slice() []float64 {
	return attribute.AsSlice[float64](v.slice)
}

// AsString returns the string value. Make sure that the Value's type
// is STRING.
func (v Value) AsString() string {
	return v.stringly
}

// AsStringSlice returns the []string value. Make sure that the Value's type is
// STRINGSLICE.
func (v Value) AsStringSlice() []string {
	if v.vtype != STRINGSLICE {
		return nil
	}
	return v.asStringSlice()
}

func (v Value) asStringSlice() []string {
	return attribute.AsSlice[string](v.slice)
}

// AsSlice returns the []Value value. Make sure that the Value's type is
// SLICE.
func (v Value) AsSlice() []Value {
	if v.vtype != SLICE {
		return nil
	}
	return v.asSlice()
}

func (v Value) asSlice() []Value {
	switch vals := v.slice.(type) {
	case [0]Value:
		return []Value{}
	case [1]Value:
		return []Value{vals[0]}
	case [2]Value:
		return []Value{vals[0], vals[1]}
	case [3]Value:
		return []Value{vals[0], vals[1], vals[2]}
	case [4]Value:
		return []Value{vals[0], vals[1], vals[2], vals[3]}
	case [5]Value:
		return []Value{vals[0], vals[1], vals[2], vals[3], vals[4]}
	default:
		return asValueSliceReflect(v.slice)
	}
}

func asValueSliceReflect(v any) []Value {
	rv := reflect.ValueOf(v)
	if !rv.IsValid() || rv.Kind() != reflect.Array || rv.Type().Elem() != reflect.TypeFor[Value]() {
		return nil
	}
	cpy := make([]Value, rv.Len())
	if len(cpy) > 0 {
		_ = reflect.Copy(reflect.ValueOf(cpy), rv)
	}
	return cpy
}

// AsMap returns the []KeyValue value. Make sure that the Value's type is
// MAP.
//
// The returned slice is sorted by key and may differ from the order
// provided when creating the map value.
//
// The returned slice may contain duplicate keys if duplicate keys were
// provided when creating the map value. Callers should not assume the returned
// keys are unique.
func (v Value) AsMap() []KeyValue {
	if v.vtype != MAP {
		return nil
	}
	return v.asMap()
}

func (v Value) asMap() []KeyValue {
	switch vals := v.slice.(type) {
	case [0]KeyValue:
		return []KeyValue{}
	case [1]KeyValue:
		return []KeyValue{vals[0]}
	case [2]KeyValue:
		return []KeyValue{vals[0], vals[1]}
	case [3]KeyValue:
		return []KeyValue{vals[0], vals[1], vals[2]}
	case [4]KeyValue:
		return []KeyValue{vals[0], vals[1], vals[2], vals[3]}
	case [5]KeyValue:
		return []KeyValue{vals[0], vals[1], vals[2], vals[3], vals[4]}
	default:
		return asKeyValueSliceReflect(v.slice)
	}
}

func asKeyValueSliceReflect(v any) []KeyValue {
	rv := reflect.ValueOf(v)
	if !rv.IsValid() || rv.Kind() != reflect.Array || rv.Type().Elem() != reflect.TypeFor[KeyValue]() {
		return nil
	}
	cpy := make([]KeyValue, rv.Len())
	if len(cpy) > 0 {
		_ = reflect.Copy(reflect.ValueOf(cpy), rv)
	}
	return cpy
}

// AsByteSlice returns the bytes value. Make sure that the Value's type
// is BYTESLICE.
func (v Value) AsByteSlice() []byte {
	if v.vtype != BYTESLICE {
		return nil
	}
	return v.asByteSlice()
}

func (v Value) asByteSlice() []byte {
	return []byte(v.stringly)
}

type unknownValueType struct{}

// AsInterface returns Value's data as any.
func (v Value) AsInterface() any {
	switch v.Type() {
	case BOOL:
		return v.AsBool()
	case BOOLSLICE:
		return v.asBoolSlice()
	case INT64:
		return v.AsInt64()
	case INT64SLICE:
		return v.asInt64Slice()
	case FLOAT64:
		return v.AsFloat64()
	case FLOAT64SLICE:
		return v.asFloat64Slice()
	case STRING:
		return v.stringly
	case STRINGSLICE:
		return v.asStringSlice()
	case BYTESLICE:
		return v.asByteSlice()
	case SLICE:
		return v.asSlice()
	case MAP:
		return v.asMap()
	case EMPTY:
		return nil
	}
	return unknownValueType{}
}

// String returns a string representation of Value using the
// [OpenTelemetry AnyValue representation for non-OTLP protocols] rules.
//
// Strings are returned as-is without JSON quoting, booleans and integers use
// JSON literals, floating-point values use JSON numbers except that NaN and
// ±Inf are rendered as NaN, Infinity, and -Infinity, byte slices are
// base64-encoded, empty values are the empty string, slices are encoded as JSON
// arrays, and maps are encoded as JSON objects. String, byte, and special
// floating-point values inside arrays and maps are encoded as JSON strings, and
// empty values inside arrays and maps are encoded as null.
//
// [OpenTelemetry AnyValue representation for non-OTLP protocols]: https://opentelemetry.io/docs/specs/otel/common/#anyvalue-representation-for-non-otlp-protocols
func (v Value) String() string {
	switch v.Type() {
	case BOOL:
		return strconv.FormatBool(v.AsBool())
	case BOOLSLICE:
		return formatBoolSliceValue(v.slice)
	case INT64:
		return strconv.FormatInt(v.AsInt64(), 10)
	case INT64SLICE:
		return formatInt64SliceValue(v.slice)
	case FLOAT64:
		return formatFloat64(v.AsFloat64())
	case FLOAT64SLICE:
		return formatFloat64SliceValue(v.slice)
	case STRING:
		return v.stringly
	case STRINGSLICE:
		return formatStringSliceValue(v.slice)
	case BYTESLICE:
		return formatByteSlice(v.stringly)
	case SLICE:
		return formatValueSliceValue(v.slice)
	case MAP:
		return formatMapValue(v.slice)
	case EMPTY:
		return ""
	default:
		return "unknown"
	}
}

// Emit returns a string representation of Value's data.
//
// Deprecated: Use [Value.String] instead.
func (v Value) Emit() string {
	switch v.Type() {
	case BOOLSLICE:
		return fmt.Sprint(v.asBoolSlice())
	case BOOL:
		return strconv.FormatBool(v.AsBool())
	case INT64SLICE:
		j, err := json.Marshal(v.asInt64Slice())
		if err != nil {
			return fmt.Sprintf("invalid: %v", v.asInt64Slice())
		}
		return string(j)
	case INT64:
		return strconv.FormatInt(v.AsInt64(), 10)
	case FLOAT64SLICE:
		j, err := json.Marshal(v.asFloat64Slice())
		if err != nil {
			return fmt.Sprintf("invalid: %v", v.asFloat64Slice())
		}
		return string(j)
	case FLOAT64:
		return fmt.Sprint(v.AsFloat64())
	case STRINGSLICE:
		j, err := json.Marshal(v.asStringSlice())
		if err != nil {
			return fmt.Sprintf("invalid: %v", v.asStringSlice())
		}
		return string(j)
	case STRING:
		return v.stringly
	case BYTESLICE:
		return formatByteSlice(v.stringly)
	case SLICE:
		return formatValueSliceValue(v.slice)
	case MAP:
		return formatMapValue(v.slice)
	case EMPTY:
		return ""
	default:
		return "unknown"
	}
}

const (
	jsonArrayBracketsLen   = len("[]")
	boolArrayElemMaxLen    = len("false")
	int64ArrayElemMaxLen   = len("-9223372036854775808")
	float64ArrayElemMaxLen = len("-1.7976931348623157e+308")
	commaLen               = len(",")
)

func sliceValue(v []Value) any {
	switch len(v) {
	case 0:
		return [0]Value{}
	case 1:
		return [1]Value{v[0]}
	case 2:
		return [2]Value{v[0], v[1]}
	case 3:
		return [3]Value{v[0], v[1], v[2]}
	case 4:
		return [4]Value{v[0], v[1], v[2], v[3]}
	case 5:
		return [5]Value{v[0], v[1], v[2], v[3], v[4]}
	default:
		return sliceValueReflect(v)
	}
}

func sliceValueReflect(v []Value) any {
	cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[Value]())).Elem()
	reflect.Copy(cp, reflect.ValueOf(v))
	return cp.Interface()
}

func mapValue(v []KeyValue) any {
	switch len(v) {
	case 0:
		return [0]KeyValue{}
	case 1:
		return [1]KeyValue{v[0]}
	case 2:
		vals := [2]KeyValue{v[0], v[1]}
		sortKeyValues(vals[:])
		return vals
	case 3:
		vals := [3]KeyValue{v[0], v[1], v[2]}
		sortKeyValues(vals[:])
		return vals
	case 4:
		vals := [4]KeyValue{v[0], v[1], v[2], v[3]}
		sortKeyValues(vals[:])
		return vals
	case 5:
		vals := [5]KeyValue{v[0], v[1], v[2], v[3], v[4]}
		sortKeyValues(vals[:])
		return vals
	default:
		return mapValueReflect(v)
	}
}

func mapValueReflect(v []KeyValue) any {
	cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[KeyValue]())).Elem()
	reflect.Copy(cp, reflect.ValueOf(v))
	vals := cp.Slice(0, len(v)).Interface().([]KeyValue)
	sortKeyValues(vals)
	return cp.Interface()
}

func sortKeyValues(vals []KeyValue) {
	slices.SortStableFunc(vals, func(a, b KeyValue) int {
		return cmp.Compare(a.Key, b.Key)
	})
}

func formatBoolSliceValue(v any) string {
	switch vals := v.(type) {
	case [0]bool:
		return "[]"
	case [1]bool:
		return formatBoolSlice(vals[:])
	case [2]bool:
		return formatBoolSlice(vals[:])
	case [3]bool:
		return formatBoolSlice(vals[:])
	default:
		return formatBoolSliceReflect(v)
	}
}

func formatBoolSlice(vals []bool) string {
	var b strings.Builder
	appendBoolSlice(&b, vals)
	return b.String()
}

func formatBoolSliceReflect(v any) string {
	var b strings.Builder
	appendBoolSliceReflect(&b, reflect.ValueOf(v))
	return b.String()
}

func appendBoolSliceValue(dst *strings.Builder, v any) {
	switch vals := v.(type) {
	case [0]bool:
		_, _ = dst.WriteString("[]")
	case [1]bool:
		appendBoolSlice(dst, vals[:])
	case [2]bool:
		appendBoolSlice(dst, vals[:])
	case [3]bool:
		appendBoolSlice(dst, vals[:])
	default:
		appendBoolSliceReflect(dst, reflect.ValueOf(v))
	}
}

func appendBoolSlice(dst *strings.Builder, vals []bool) {
	dst.Grow(jsonArrayBracketsLen + len(vals)*(boolArrayElemMaxLen+commaLen))
	_ = dst.WriteByte('[')
	for i, val := range vals {
		if i > 0 {
			_ = dst.WriteByte(',')
		}
		if val {
			_, _ = dst.WriteString("true")
		} else {
			_, _ = dst.WriteString("false")
		}
	}
	_ = dst.WriteByte(']')
}

func appendBoolSliceReflect(dst *strings.Builder, rv reflect.Value) {
	dst.Grow(jsonArrayBracketsLen + rv.Len()*(boolArrayElemMaxLen+commaLen))
	_ = dst.WriteByte('[')
	for i := 0; i < rv.Len(); i++ {
		if i > 0 {
			_ = dst.WriteByte(',')
		}
		if rv.Index(i).Bool() {
			_, _ = dst.WriteString("true")
		} else {
			_, _ = dst.WriteString("false")
		}
	}
	_ = dst.WriteByte(']')
}

func formatInt64SliceValue(v any) string {
	switch vals := v.(type) {
	case [0]int64:
		return "[]"
	case [1]int64:
		return formatInt64Slice(vals[:])
	case [2]int64:
		return formatInt64Slice(vals[:])
	case [3]int64:
		return formatInt64Slice(vals[:])
	default:
		return formatInt64SliceReflect(v)
	}
}

func formatInt64Slice(vals []int64) string {
	var b strings.Builder
	appendInt64Slice(&b, vals)
	return b.String()
}

func formatInt64SliceReflect(v any) string {
	var b strings.Builder
	appendInt64SliceReflect(&b, reflect.ValueOf(v))
	return b.String()
}

func appendInt64SliceValue(dst *strings.Builder, v any) {
	switch vals := v.(type) {
	case [0]int64:
		_, _ = dst.WriteString("[]")
	case [1]int64:
		appendInt64Slice(dst, vals[:])
	case [2]int64:
		appendInt64Slice(dst, vals[:])
	case [3]int64:
		appendInt64Slice(dst, vals[:])
	default:
		appendInt64SliceReflect(dst, reflect.ValueOf(v))
	}
}

func appendInt64Slice(dst *strings.Builder, vals []int64) {
	dst.Grow(jsonArrayBracketsLen + len(vals)*(int64ArrayElemMaxLen+commaLen))
	_ = dst.WriteByte('[')

	var buf [int64ArrayElemMaxLen]byte
	for i, val := range vals {
		if i > 0 {
			_ = dst.WriteByte(',')
		}
		out := strconv.AppendInt(buf[:0], val, 10)
		_, _ = dst.Write(out)
	}

	_ = dst.WriteByte(']')
}

func appendInt64SliceReflect(dst *strings.Builder, rv reflect.Value) {
	dst.Grow(jsonArrayBracketsLen + rv.Len()*(int64ArrayElemMaxLen+commaLen))
	_ = dst.WriteByte('[')

	var scratch [int64ArrayElemMaxLen]byte
	for i := 0; i < rv.Len(); i++ {
		if i > 0 {
			_ = dst.WriteByte(',')
		}
		out := strconv.AppendInt(scratch[:0], rv.Index(i).Int(), 10)
		_, _ = dst.Write(out)
	}

	_ = dst.WriteByte(']')
}

func formatFloat64(v float64) string {
	switch {
	case math.IsNaN(v):
		return "NaN"
	case math.IsInf(v, 1):
		return "Infinity"
	case math.IsInf(v, -1):
		return "-Infinity"
	default:
		return strconv.FormatFloat(v, 'g', -1, 64)
	}
}

func formatFloat64SliceValue(v any) string {
	switch vals := v.(type) {
	case [0]float64:
		return "[]"
	case [1]float64:
		return formatFloat64Slice(vals[:])
	case [2]float64:
		return formatFloat64Slice(vals[:])
	case [3]float64:
		return formatFloat64Slice(vals[:])
	default:
		return formatFloat64SliceReflect(v)
	}
}

func formatFloat64Slice(vals []float64) string {
	var b strings.Builder
	appendFloat64Slice(&b, vals)
	return b.String()
}

func formatFloat64SliceReflect(v any) string {
	var b strings.Builder
	appendFloat64SliceReflect(&b, reflect.ValueOf(v))
	return b.String()
}

func appendFloat64SliceValue(dst *strings.Builder, v any) {
	switch vals := v.(type) {
	case [0]float64:
		_, _ = dst.WriteString("[]")
	case [1]float64:
		appendFloat64Slice(dst, vals[:])
	case [2]float64:
		appendFloat64Slice(dst, vals[:])
	case [3]float64:
		appendFloat64Slice(dst, vals[:])
	default:
		appendFloat64SliceReflect(dst, reflect.ValueOf(v))
	}
}

func appendFloat64Slice(dst *strings.Builder, vals []float64) {
	dst.Grow(jsonArrayBracketsLen + len(vals)*(float64ArrayElemMaxLen+commaLen))
	_ = dst.WriteByte('[')

	var buf [float64ArrayElemMaxLen]byte
	for i, val := range vals {
		if i > 0 {
			_ = dst.WriteByte(',')
		}

		switch {
		case math.IsNaN(val):
			_, _ = dst.WriteString(`"NaN"`)
		case math.IsInf(val, 1):
			_, _ = dst.WriteString(`"Infinity"`)
		case math.IsInf(val, -1):
			_, _ = dst.WriteString(`"-Infinity"`)
		default:
			out := strconv.AppendFloat(buf[:0], val, 'g', -1, 64)
			_, _ = dst.Write(out)
		}
	}

	_ = dst.WriteByte(']')
}

func appendFloat64SliceReflect(dst *strings.Builder, rv reflect.Value) {
	dst.Grow(jsonArrayBracketsLen + rv.Len()*(float64ArrayElemMaxLen+commaLen))
	_ = dst.WriteByte('[')

	var scratch [float64ArrayElemMaxLen]byte
	for i := 0; i < rv.Len(); i++ {
		if i > 0 {
			_ = dst.WriteByte(',')
		}
		val := rv.Index(i).Float()
		switch {
		case math.IsNaN(val):
			_, _ = dst.WriteString(`"NaN"`)
		case math.IsInf(val, 1):
			_, _ = dst.WriteString(`"Infinity"`)
		case math.IsInf(val, -1):
			_, _ = dst.WriteString(`"-Infinity"`)
		default:
			out := strconv.AppendFloat(scratch[:0], val, 'g', -1, 64)
			_, _ = dst.Write(out)
		}
	}

	_ = dst.WriteByte(']')
}

func formatStringSliceValue(v any) string {
	switch vals := v.(type) {
	case [0]string:
		return "[]"
	case [1]string:
		return formatStringSlice(vals[:])
	case [2]string:
		return formatStringSlice(vals[:])
	case [3]string:
		return formatStringSlice(vals[:])
	default:
		return formatStringSliceReflect(v)
	}
}

func formatStringSlice(vals []string) string {
	var b strings.Builder
	appendStringSlice(&b, vals)
	return b.String()
}

func formatStringSliceReflect(v any) string {
	var b strings.Builder
	appendStringSliceReflect(&b, reflect.ValueOf(v))
	return b.String()
}

func appendStringSliceValue(dst *strings.Builder, v any) {
	switch vals := v.(type) {
	case [0]string:
		_, _ = dst.WriteString("[]")
	case [1]string:
		appendStringSlice(dst, vals[:])
	case [2]string:
		appendStringSlice(dst, vals[:])
	case [3]string:
		appendStringSlice(dst, vals[:])
	default:
		appendStringSliceReflect(dst, reflect.ValueOf(v))
	}
}

func appendStringSlice(dst *strings.Builder, vals []string) {
	size := jsonArrayBracketsLen
	for _, val := range vals {
		size += len(val) + commaLen + 2 // Account for JSON string quotes and comma.
	}

	dst.Grow(size)
	_ = dst.WriteByte('[')
	for i, val := range vals {
		if i > 0 {
			_ = dst.WriteByte(',')
		}
		appendJSONString(dst, val)
	}
	_ = dst.WriteByte(']')
}

func appendStringSliceReflect(dst *strings.Builder, rv reflect.Value) {
	size := jsonArrayBracketsLen
	for i := 0; i < rv.Len(); i++ {
		size += len(rv.Index(i).String()) + commaLen + 2 // Account for JSON string quotes and comma.
	}

	dst.Grow(size)
	_ = dst.WriteByte('[')
	for i := 0; i < rv.Len(); i++ {
		if i > 0 {
			_ = dst.WriteByte(',')
		}
		appendJSONString(dst, rv.Index(i).String())
	}
	_ = dst.WriteByte(']')
}

func formatByteSlice(v string) string {
	var b strings.Builder
	appendBase64(&b, v)
	return b.String()
}

func formatValueSliceValue(v any) string {
	switch vals := v.(type) {
	case [0]Value:
		return "[]"
	case [1]Value:
		return formatValueSlice(vals[:])
	case [2]Value:
		return formatValueSlice(vals[:])
	case [3]Value:
		return formatValueSlice(vals[:])
	case [4]Value:
		return formatValueSlice(vals[:])
	case [5]Value:
		return formatValueSlice(vals[:])
	default:
		return formatValueSliceReflect(v)
	}
}

func formatValueSlice(vals []Value) string {
	var b strings.Builder
	appendValueSlice(&b, vals)
	return b.String()
}

func formatValueSliceReflect(v any) string {
	var b strings.Builder
	appendValueSliceReflect(&b, reflect.ValueOf(v))
	return b.String()
}

func formatMapValue(v any) string {
	switch vals := v.(type) {
	case [0]KeyValue:
		return "{}"
	case [1]KeyValue:
		return formatMap(vals[:])
	case [2]KeyValue:
		return formatMap(vals[:])
	case [3]KeyValue:
		return formatMap(vals[:])
	case [4]KeyValue:
		return formatMap(vals[:])
	case [5]KeyValue:
		return formatMap(vals[:])
	default:
		return formatMapReflect(v)
	}
}

func formatMap(vals []KeyValue) string {
	var b strings.Builder
	appendMap(&b, vals)
	return b.String()
}

func formatMapReflect(v any) string {
	var b strings.Builder
	appendMapReflect(&b, reflect.ValueOf(v))
	return b.String()
}

func appendValueSliceValue(dst *strings.Builder, v any) {
	switch vals := v.(type) {
	case [0]Value:
		_, _ = dst.WriteString("[]")
	case [1]Value:
		appendValueSlice(dst, vals[:])
	case [2]Value:
		appendValueSlice(dst, vals[:])
	case [3]Value:
		appendValueSlice(dst, vals[:])
	case [4]Value:
		appendValueSlice(dst, vals[:])
	case [5]Value:
		appendValueSlice(dst, vals[:])
	default:
		appendValueSliceReflect(dst, reflect.ValueOf(v))
	}
}

func appendValueSlice(dst *strings.Builder, vals []Value) {
	// Estimate 10 bytes per value for small values and commas.
	dst.Grow(jsonArrayBracketsLen + len(vals)*commaLen + len(vals)*10)
	_ = dst.WriteByte('[')
	for i, val := range vals {
		if i > 0 {
			_ = dst.WriteByte(',')
		}
		appendJSONValue(dst, val)
	}
	_ = dst.WriteByte(']')
}

func appendValueSliceReflect(dst *strings.Builder, rv reflect.Value) {
	// Estimate 10 bytes per value for small values and commas.
	dst.Grow(jsonArrayBracketsLen + rv.Len()*commaLen + rv.Len()*10)
	_ = dst.WriteByte('[')
	for i := 0; i < rv.Len(); i++ {
		if i > 0 {
			_ = dst.WriteByte(',')
		}
		appendJSONValue(dst, rv.Index(i).Interface().(Value))
	}
	_ = dst.WriteByte(']')
}

func appendMapValue(dst *strings.Builder, v any) {
	switch vals := v.(type) {
	case [0]KeyValue:
		_, _ = dst.WriteString("{}")
	case [1]KeyValue:
		appendMap(dst, vals[:])
	case [2]KeyValue:
		appendMap(dst, vals[:])
	case [3]KeyValue:
		appendMap(dst, vals[:])
	case [4]KeyValue:
		appendMap(dst, vals[:])
	case [5]KeyValue:
		appendMap(dst, vals[:])
	default:
		appendMapReflect(dst, reflect.ValueOf(v))
	}
}

func appendMap(dst *strings.Builder, vals []KeyValue) {
	// Estimate 32 bytes per value for small values, plus key quotes, colon,
	// and commas. Escaped keys and larger values grow the builder as needed.
	size := len("{}") + len(vals)*commaLen + len(vals)*32
	for _, val := range vals {
		size += len(val.Key) + len(`"":`)
	}

	dst.Grow(size)
	_ = dst.WriteByte('{')
	for i, val := range vals {
		if i > 0 {
			_ = dst.WriteByte(',')
		}
		appendJSONString(dst, string(val.Key))
		_ = dst.WriteByte(':')
		appendJSONValue(dst, val.Value)
	}
	_ = dst.WriteByte('}')
}

func appendMapReflect(dst *strings.Builder, rv reflect.Value) {
	// Estimate 32 bytes per value for small values, plus key quotes, colon,
	// and commas. Escaped keys and larger values grow the builder as needed.
	size := len("{}") + rv.Len()*commaLen + rv.Len()*32
	for i := 0; i < rv.Len(); i++ {
		size += len(rv.Index(i).Field(0).String()) + len(`"":`)
	}

	dst.Grow(size)
	_ = dst.WriteByte('{')
	for i := 0; i < rv.Len(); i++ {
		if i > 0 {
			_ = dst.WriteByte(',')
		}
		val := rv.Index(i).Interface().(KeyValue)
		appendJSONString(dst, string(val.Key))
		_ = dst.WriteByte(':')
		appendJSONValue(dst, val.Value)
	}
	_ = dst.WriteByte('}')
}

func appendJSONValue(dst *strings.Builder, v Value) {
	switch v.Type() {
	case BOOL:
		if v.AsBool() {
			_, _ = dst.WriteString("true")
		} else {
			_, _ = dst.WriteString("false")
		}
	case BOOLSLICE:
		appendBoolSliceValue(dst, v.slice)
	case INT64:
		var buf [int64ArrayElemMaxLen]byte
		out := strconv.AppendInt(buf[:0], v.AsInt64(), 10)
		_, _ = dst.Write(out)
	case INT64SLICE:
		appendInt64SliceValue(dst, v.slice)
	case FLOAT64:
		val := v.AsFloat64()
		switch {
		case math.IsNaN(val):
			appendJSONString(dst, "NaN")
		case math.IsInf(val, 1):
			appendJSONString(dst, "Infinity")
		case math.IsInf(val, -1):
			appendJSONString(dst, "-Infinity")
		default:
			var buf [float64ArrayElemMaxLen]byte
			out := strconv.AppendFloat(buf[:0], val, 'g', -1, 64)
			_, _ = dst.Write(out)
		}
	case FLOAT64SLICE:
		appendFloat64SliceValue(dst, v.slice)
	case STRING:
		appendJSONString(dst, v.stringly)
	case STRINGSLICE:
		appendStringSliceValue(dst, v.slice)
	case BYTESLICE:
		_ = dst.WriteByte('"')
		appendBase64(dst, v.stringly)
		_ = dst.WriteByte('"')
	case SLICE:
		appendValueSliceValue(dst, v.slice)
	case MAP:
		appendMapValue(dst, v.slice)
	case EMPTY:
		_, _ = dst.WriteString("null")
	default:
		appendJSONString(dst, "unknown")
	}
}

// appendJSONString appends s to dst as a JSON string literal.
//
// This is adapted from the Go standard library's encoding/json
// [appendString implementation]. It keeps the same escaping behavior we need
// here, but writes directly into a strings.Builder and intentionally does not
// apply HTML escaping because the OpenTelemetry non-OTLP AnyValue representation
// only requires JSON array string encoding. We inline this instead of using
// encoding/json so slice formatting avoids allocations and reflection.
//
// [appendString implementation]: https://github.com/golang/go/blob/3b5954c6349d31465dca409b45ab6597e0942d9f/src/encoding/json/encode.go#L998-L1064
func appendJSONString(dst *strings.Builder, s string) {
	const hex = "0123456789abcdef" // For escaping bytes to hex.

	_ = dst.WriteByte('"')
	start := 0

	for i := 0; i < len(s); {
		if c := s[i]; c < utf8.RuneSelf {
			if c >= 0x20 && c != '\\' && c != '"' {
				i++
				continue
			}

			if start < i {
				_, _ = dst.WriteString(s[start:i])
			}

			switch c {
			case '\\', '"':
				_ = dst.WriteByte('\\')
				_ = dst.WriteByte(c)
			case '\b':
				_, _ = dst.WriteString(`\b`)
			case '\f':
				_, _ = dst.WriteString(`\f`)
			case '\n':
				_, _ = dst.WriteString(`\n`)
			case '\r':
				_, _ = dst.WriteString(`\r`)
			case '\t':
				_, _ = dst.WriteString(`\t`)
			default:
				_, _ = dst.WriteString(`\u00`)
				_ = dst.WriteByte(hex[c>>4])
				_ = dst.WriteByte(hex[c&0x0f])
			}

			i++
			start = i
			continue
		}

		r, size := utf8.DecodeRuneInString(s[i:])
		if r == utf8.RuneError && size == 1 {
			if start < i {
				_, _ = dst.WriteString(s[start:i])
			}
			// Match encoding/json by replacing invalid UTF-8 with U+FFFD.
			_, _ = dst.WriteString(`\ufffd`)
			i++
			start = i
			continue
		}

		if r == '\u2028' || r == '\u2029' {
			if start < i {
				_, _ = dst.WriteString(s[start:i])
			}
			// Escape JSONP-sensitive separators unconditionally, like encoding/json.
			_, _ = dst.WriteString(`\u202`)
			_ = dst.WriteByte(hex[r&0x0f])
			i += size
			start = i
			continue
		}

		i += size
	}

	if start < len(s) {
		_, _ = dst.WriteString(s[start:])
	}
	_ = dst.WriteByte('"')
}

// This is adapted from the Go standard library's encoding/base64
// [Encoding.Encode implementation]. It keeps the same encoding behavior we need
// here, but writes directly into a strings.Builder. We inline this instead of using
// encoding/base64 to avoid allocations.
//
// [Encoding.Encode implementation]: https://github.com/golang/go/blob/3b5954c6349d31465dca409b45ab6597e0942d9f/src/encoding/base64/base64.go#L139-L189
func appendBase64(dst *strings.Builder, s string) {
	const encode = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

	dst.Grow(base64.StdEncoding.EncodedLen(len(s)))

	i := 0
	for ; i+2 < len(s); i += 3 {
		n := uint32(s[i])<<16 | uint32(s[i+1])<<8 | uint32(s[i+2])
		_ = dst.WriteByte(encode[n>>18&0x3f])
		_ = dst.WriteByte(encode[n>>12&0x3f])
		_ = dst.WriteByte(encode[n>>6&0x3f])
		_ = dst.WriteByte(encode[n&0x3f])
	}

	switch len(s) - i {
	case 1:
		n := uint32(s[i]) << 16
		_ = dst.WriteByte(encode[n>>18&0x3f])
		_ = dst.WriteByte(encode[n>>12&0x3f])
		_ = dst.WriteByte('=')
		_ = dst.WriteByte('=')
	case 2:
		n := uint32(s[i])<<16 | uint32(s[i+1])<<8
		_ = dst.WriteByte(encode[n>>18&0x3f])
		_ = dst.WriteByte(encode[n>>12&0x3f])
		_ = dst.WriteByte(encode[n>>6&0x3f])
		_ = dst.WriteByte('=')
	}
}

// MarshalJSON returns the JSON encoding of the Value.
func (v Value) MarshalJSON() ([]byte, error) {
	var jsonVal struct {
		Type  string
		Value any
	}
	jsonVal.Type = v.Type().String()
	jsonVal.Value = v.AsInterface()
	return json.Marshal(jsonVal)
}
