package pb

import (
	"fmt"
	"math"
	"strconv"
	"sync"
	"time"

	"github.com/VictoriaMetrics/easyproto"

	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
	"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutil"
)

// DecodeMetricsOptions defines options for DecodeMetricsData
type DecodeMetricsOptions struct {
	DisableScopeMetadata      bool
	DisableResourceAttributes bool
	// ResourceAttributesList stored a list of resource attributes to ignore or promote based on the value of DisableResourceAttributes.
	ResourceAttributesList map[string]struct{}
}

// MetricPusher must push the parsed samples and metric metadata to the underlying storage.
type MetricPusher interface {
	// PushSample must store a sample with the given args.
	//
	// The PushSample must copy labels, since they become invalid after returning from the func.
	PushSample(mm *MetricMetadata, suffix string, ls *promutil.Labels, timestampNsecs uint64, value float64, flags uint32)

	// PushMetricMetadata must store mm.
	//
	// The PushMetricMetadata must copy mm contents, since it becomes invalid after returning from the func.
	PushMetricMetadata(mm *MetricMetadata)
}

// MetricMetadata contains metric metadata
type MetricMetadata struct {
	// Name is metric name
	Name string

	// Unit is metric unit
	Unit string

	// Description is metric description
	Description string

	// Type is metric type
	Type prompb.MetricType
}

func (mm *MetricMetadata) reset() {
	mm.Name = ""
	mm.Unit = ""
	mm.Description = ""
	mm.Type = 0
}

// MetricsData represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L56
type MetricsData struct {
	ResourceMetrics []*ResourceMetrics
}

// MarshalProtobuf marshals r to protobuf message, appends it to dst and returns the result.
func (r *MetricsData) MarshalProtobuf(dst []byte) []byte {
	m := mp.Get()
	r.marshalProtobuf(m.MessageMarshaler())
	dst = m.Marshal(dst)
	mp.Put(m)
	return dst
}

var mp easyproto.MarshalerPool

func (r *MetricsData) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	for _, rm := range r.ResourceMetrics {
		rm.marshalProtobuf(mm.AppendMessage(1))
	}
}

// DecodeMetricsData decodes metricsData with given options from src and sends the decoded data to mp.
func DecodeMetricsData(src []byte, mp MetricPusher, options DecodeMetricsOptions) (err error) {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L56
	//
	// message MetricsData {
	//   repeated ResourceMetrics resource_metrics = 1;
	// }

	dctx := getDecoderContext(mp)
	defer putDecoderContext(dctx)

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		switch fc.FieldNum {
		case 1:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read ResourceMetrics data")
			}
			if err := dctx.decodeResourceMetrics(data, options); err != nil {
				return fmt.Errorf("cannot unmarshal ResourceMetrics: %w", err)
			}
		}
	}
	return nil
}

// ResourceMetrics represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L66
type ResourceMetrics struct {
	Resource     *Resource
	ScopeMetrics []*ScopeMetrics
}

func (rm *ResourceMetrics) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	if rm.Resource != nil {
		rm.Resource.marshalProtobuf(mm.AppendMessage(1))
	}
	for _, sm := range rm.ScopeMetrics {
		sm.marshalProtobuf(mm.AppendMessage(2))
	}
}

func (dctx *decoderContext) decodeResourceMetrics(src []byte, options DecodeMetricsOptions) error {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L66
	//
	// message ResourceMetrics {
	//   Resource resource = 1;
	//   repeated ScopeMetrics scope_metrics = 2;
	// }

	dctx.ls.Reset()
	dctx.fb.reset()

	resourceData, ok, err := easyproto.GetMessageData(src, 1)
	if err != nil {
		return fmt.Errorf("cannot read Resource data: %w", err)
	}
	if ok {
		if err := dctx.decodeResource(resourceData, options.DisableResourceAttributes, options.ResourceAttributesList); err != nil {
			return fmt.Errorf("cannot decode Resource: %w", err)
		}
	}

	dctxSnapshot := dctx.getSnapshot()

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		switch fc.FieldNum {
		case 2:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read ScopeMetrics data")
			}

			if err := dctx.decodeScopeMetrics(data, options.DisableScopeMetadata); err != nil {
				return fmt.Errorf("cannot unmarshal ScopeMetrics: %w", err)
			}

			dctx.restoreFromSnapshot(dctxSnapshot)
		}
	}
	return nil
}

// Resource represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/resource/v1/resource.proto#L28
type Resource struct {
	Attributes []*KeyValue
}

func (r *Resource) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	for _, a := range r.Attributes {
		a.marshalProtobuf(mm.AppendMessage(1))
	}
}

func (dctx *decoderContext) decodeResource(src []byte, disableResourceAttributes bool, attributeKeys map[string]struct{}) (err error) {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/resource/v1/resource.proto#L28
	//
	// message Resource {
	//   repeated KeyValue attributes = 1;
	// }

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		switch fc.FieldNum {
		case 1:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read Attributes")
			}
			keySuffix, ok, err := easyproto.GetString(data, 1)
			if err != nil {
				return fmt.Errorf("cannot find Key in KeyValue: %w", err)
			}
			if !ok {
				// Key is missing, skip it.
				// See https://github.com/VictoriaMetrics/VictoriaLogs/issues/869#issuecomment-3631307996
				continue
			}
			if _, ok := attributeKeys[keySuffix]; ok != disableResourceAttributes {
				// Skip the attribute if:
				// 1. it is in the list of ignore attributes when disableResourceAttributes is false,
				// 2. it isn't in the list of promote attributes when disableResourceAttributes is true.
				continue
			}
			key := dctx.fb.formatSubFieldName("", keySuffix)

			// Decode value
			value, ok, err := easyproto.GetMessageData(data, 2)
			if err != nil {
				return fmt.Errorf("cannot find Value in KeyValue: %w", err)
			}
			if !ok {
				// Value is null, skip it.
				continue
			}

			if err := decodeAnyValue(value, &dctx.ls, &dctx.fb, key); err != nil {
				return fmt.Errorf("cannot unmarshal Attributes: %w", err)
			}
		}
	}
	return nil
}

// KeyValue represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/common/v1/common.proto#L66
type KeyValue struct {
	Key   string
	Value *AnyValue
}

func (kv *KeyValue) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	if kv.Key != "" {
		mm.AppendString(1, kv.Key)
	}
	if kv.Value != nil {
		kv.Value.marshalProtobuf(mm.AppendMessage(2))
	}
}

func decodeKeyValue(src []byte, ls *promutil.Labels, fb *fmtBuffer, keyPrefix string) error {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/common/v1/common.proto#L66
	//
	// message KeyValue {
	//   string key = 1;
	//   AnyValue value = 2;
	// }

	// Decode key
	keySuffix, ok, err := easyproto.GetString(src, 1)
	if err != nil {
		return fmt.Errorf("cannot find Key in KeyValue: %w", err)
	}
	if !ok {
		// Key is missing, skip it.
		// See https://github.com/VictoriaMetrics/VictoriaLogs/issues/869#issuecomment-3631307996
		return nil
	}
	key := fb.formatSubFieldName(keyPrefix, keySuffix)

	// Decode value
	valueData, ok, err := easyproto.GetMessageData(src, 2)
	if err != nil {
		return fmt.Errorf("cannot find Value in KeyValue: %w", err)
	}
	if !ok {
		// Value is null, skip it.
		return nil
	}

	if err := decodeAnyValue(valueData, ls, fb, key); err != nil {
		return fmt.Errorf("cannot decode AnyValue: %w", err)
	}
	return nil
}

// AnyValue represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/common/v1/common.proto#L28
type AnyValue struct {
	StringValue  *string
	BoolValue    *bool
	IntValue     *int64
	DoubleValue  *float64
	ArrayValue   *ArrayValue
	KeyValueList *KeyValueList
	BytesValue   *[]byte
}

func (av *AnyValue) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	if av == nil {
		return
	}
	switch {
	case av.StringValue != nil:
		mm.AppendString(1, *av.StringValue)
	case av.BoolValue != nil:
		mm.AppendBool(2, *av.BoolValue)
	case av.IntValue != nil:
		mm.AppendInt64(3, *av.IntValue)
	case av.DoubleValue != nil:
		mm.AppendDouble(4, *av.DoubleValue)
	case av.ArrayValue != nil:
		av.ArrayValue.marshalProtobuf(mm.AppendMessage(5))
	case av.KeyValueList != nil:
		av.KeyValueList.marshalProtobuf(mm.AppendMessage(6))
	case av.BytesValue != nil:
		mm.AppendBytes(7, *av.BytesValue)
	}
}

func decodeAnyValue(src []byte, ls *promutil.Labels, fb *fmtBuffer, key string) (err error) {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/common/v1/common.proto#L28
	//
	// message AnyValue {
	//   oneof value {
	//     string string_value = 1;
	//     bool bool_value = 2;
	//     int64 int_value = 3;
	//     double double_value = 4;
	//     ArrayValue array_value = 5;
	//     KeyValueList kvlist_value = 6;
	//     bytes bytes_value = 7;
	//   }
	// }

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		switch fc.FieldNum {
		case 1:
			stringValue, ok := fc.String()
			if !ok {
				return fmt.Errorf("cannot read StringValue")
			}
			ls.Add(key, stringValue)
		case 2:
			boolValue, ok := fc.Bool()
			if !ok {
				return fmt.Errorf("cannot read BoolValue")
			}
			boolValueStr := strconv.FormatBool(boolValue)
			ls.Add(key, boolValueStr)
		case 3:
			intValue, ok := fc.Int64()
			if !ok {
				return fmt.Errorf("cannot read IntValue")
			}
			intValueStr := fb.formatInt(intValue)
			ls.Add(key, intValueStr)
		case 4:
			doubleValue, ok := fc.Double()
			if !ok {
				return fmt.Errorf("cannot read DoubleValue")
			}
			doubleValueStr := fb.formatFloat(doubleValue)
			ls.Add(key, doubleValueStr)
		case 5:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read ArrayValue")
			}
			a := jsonArenaPool.Get()
			// Encode arrays as JSON to match the behavior of /insert/jsonline
			arr, err := decodeArrayValueToJSON(data, a, fb)
			if err != nil {
				jsonArenaPool.Put(a)
				return fmt.Errorf("cannot decode ArrayValue: %w", err)
			}

			encodedArr := fb.encodeJSONValue(arr)
			jsonArenaPool.Put(a)

			ls.Add(key, encodedArr)
		case 6:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read KeyValueList")
			}
			if err := decodeKeyValueList(data, ls, fb, key); err != nil {
				return fmt.Errorf("cannot decode KeyValueList: %w", err)
			}
		case 7:
			bytesValue, ok := fc.Bytes()
			if !ok {
				return fmt.Errorf("cannot read BytesValue")
			}
			v := fb.formatBase64(bytesValue)
			ls.Add(key, v)
		}
	}
	return nil
}

// ArrayValue represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/common/v1/common.proto#L44
type ArrayValue struct {
	Values []*AnyValue
}

func (av *ArrayValue) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	for _, v := range av.Values {
		v.marshalProtobuf(mm.AppendMessage(1))
	}
}

// KeyValueList represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/common/v1/common.proto#L54
type KeyValueList struct {
	Values []*KeyValue
}

func (kvl *KeyValueList) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	for _, v := range kvl.Values {
		v.marshalProtobuf(mm.AppendMessage(1))
	}
}

func decodeKeyValueList(src []byte, ls *promutil.Labels, fb *fmtBuffer, keyPrefix string) (err error) {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/common/v1/common.proto#L54
	//
	// message KeyValueList {
	//   repeated KeyValue values = 1;
	// }

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		switch fc.FieldNum {
		case 1:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read KeyValue data")
			}
			if err := decodeKeyValue(data, ls, fb, keyPrefix); err != nil {
				return fmt.Errorf("cannot decode KeyValue: %w", err)
			}
		}
	}
	return nil
}

// ScopeMetrics represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L86
type ScopeMetrics struct {
	Scope   *InstrumentationScope
	Metrics []*Metric
}

func (sm *ScopeMetrics) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	if sm.Scope != nil {
		sm.Scope.marshalProtobuf(mm.AppendMessage(1))
	}
	for _, m := range sm.Metrics {
		m.marshalProtobuf(mm.AppendMessage(2))
	}
}

func (dctx *decoderContext) decodeScopeMetrics(src []byte, disableScopeMetadata bool) error {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L86
	//
	// message ScopeMetrics {
	//   InstrumentationScope scope = 1;
	//   repeated Metric metrics = 2;
	// }

	if !disableScopeMetadata {
		scopeData, ok, err := easyproto.GetMessageData(src, 1)
		if err != nil {
			return fmt.Errorf("cannot read InstrumentationScope: %w", err)
		}
		if ok {
			if err := dctx.decodeInstrumentationScope(scopeData); err != nil {
				return fmt.Errorf("cannot decode InstrumentationScope: %w", err)
			}
		}
	}

	dctxSnapshot := dctx.getSnapshot()

	var fc easyproto.FieldContext
	var err error
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		switch fc.FieldNum {
		case 2:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read Metric data")
			}

			if err := dctx.decodeMetric(data); err != nil {
				return fmt.Errorf("cannot unmarshal Metric: %w", err)
			}

			dctx.restoreFromSnapshot(dctxSnapshot)
		}
	}
	return nil
}

// InstrumentationScope represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/common/v1/common.proto#L76
type InstrumentationScope struct {
	Name       *string
	Version    *string
	Attributes []*KeyValue
}

func (is *InstrumentationScope) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	if is.Name != nil {
		mm.AppendString(1, *is.Name)
	}
	if is.Version != nil {
		mm.AppendString(2, *is.Version)
	}
	for _, a := range is.Attributes {
		a.marshalProtobuf(mm.AppendMessage(3))
	}
}

func (dctx *decoderContext) decodeInstrumentationScope(src []byte) error {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/a5f0eac5b802f7ae51dfe41e5116fe5548955e64/opentelemetry/proto/common/v1/common.proto#L76
	//
	// message InstrumentationScope {
	//   string name = 1;
	//   string version = 2;
	//   repeated KeyValue attributes = 3;
	// }

	nameStr, ok, err := easyproto.GetString(src, 1)
	if err != nil {
		return fmt.Errorf("cannot read name: %w", err)
	}
	name := "unknown"
	if ok {
		name = nameStr
	}
	dctx.ls.Add("scope.name", name)

	versionStr, ok, err := easyproto.GetString(src, 2)
	if err != nil {
		return fmt.Errorf("cannot read version: %w", err)
	}
	version := "unknown"
	if ok {
		version = versionStr
	}
	dctx.ls.Add("scope.version", version)

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		switch fc.FieldNum {
		case 3:
			attributesData, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read Attributes data")
			}
			if err := decodeKeyValue(attributesData, &dctx.ls, &dctx.fb, "scope.attributes"); err != nil {
				return fmt.Errorf("cannot decode Attributes: %w", err)
			}
		}
	}

	return nil
}

// Metric represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L188
type Metric struct {
	Name                 string
	Description          string
	Unit                 string
	Gauge                *Gauge
	Sum                  *Sum
	Histogram            *Histogram
	ExponentialHistogram *ExponentialHistogram
	Summary              *Summary

	Metadata []*KeyValue
}

func (m *Metric) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	mm.AppendString(1, m.Name)
	if m.Description != "" {
		mm.AppendString(2, m.Description)
	}
	if m.Unit != "" {
		mm.AppendString(3, m.Unit)
	}
	switch {
	case m.Gauge != nil:
		m.Gauge.marshalProtobuf(mm.AppendMessage(5))
	case m.Sum != nil:
		m.Sum.marshalProtobuf(mm.AppendMessage(7))
	case m.Histogram != nil:
		m.Histogram.marshalProtobuf(mm.AppendMessage(9))
	case m.ExponentialHistogram != nil:
		m.ExponentialHistogram.marshalProtobuf(mm.AppendMessage(10))
	case m.Summary != nil:
		m.Summary.marshalProtobuf(mm.AppendMessage(11))
	}
	for _, md := range m.Metadata {
		md.marshalProtobuf(mm.AppendMessage(12))
	}
}

func (dctx *decoderContext) decodeMetric(src []byte) error {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L188
	//
	// message Metric {
	//   string name = 1;
	//   string description = 2;
	//   string unit = 3;
	//   oneof data {
	//     Gauge gauge = 5;
	//     Sum sum = 7;
	//     Histogram histogram = 9;
	//     ExponentialHistogram exponential_histogram = 10;
	//     Summary summary = 11;
	//   }
	//   repeated opentelemetry.proto.common.v1.KeyValue metadata = 12;
	// }

	dctx.mm.reset()

	metricName, ok, err := easyproto.GetString(src, 1)
	if err != nil {
		return fmt.Errorf("cannot read metric name: %w", err)
	}
	if !ok {
		return fmt.Errorf("missing metric name")
	}
	dctx.mm.Name = metricName

	unit, ok, err := easyproto.GetString(src, 3)
	if err != nil {
		return fmt.Errorf("cannot read metric unit: %w", err)
	}
	if ok {
		dctx.mm.Unit = unit
	}

	dctx.mm.Type = prompb.MetricTypeUnknown

	lsMetadata := promutil.GetLabels()
	defer promutil.PutLabels(lsMetadata)

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		var ok bool
		switch fc.FieldNum {
		case 2:
			dctx.mm.Description, ok = fc.String()
			if !ok {
				return fmt.Errorf("cannot read metric description")
			}
		case 5:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read Gauge data")
			}
			if err := dctx.decodeGauge(data); err != nil {
				return fmt.Errorf("cannot unmarshal Gauge: %w", err)
			}
		case 7:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read Sum data")
			}
			if err := dctx.decodeSum(data); err != nil {
				return fmt.Errorf("cannot unmarshal Sum: %w", err)
			}
		case 9:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read Histogram data")
			}
			if err := dctx.decodeHistogram(data); err != nil {
				return fmt.Errorf("cannot unmarshal Histogram: %w", err)
			}
		case 10:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read ExponentialHistogram data")
			}
			if err := dctx.decodeExponentialHistogram(data); err != nil {
				return fmt.Errorf("cannot unmarshal ExponentialHistogram: %w", err)
			}
		case 11:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read Summary data")
			}
			if err := dctx.decodeSummary(data); err != nil {
				return fmt.Errorf("cannot unmarshal Summary: %w", err)
			}
		case 12:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read metadata attributes")
			}
			if err := decodeKeyValue(data, lsMetadata, &dctx.fb, ""); err != nil {
				return fmt.Errorf("cannot unmarshal metadata attributes: %w", err)
			}
		}
	}

	switch lsMetadata.Get("prometheus.type") {
	case "unknown":
		dctx.mm.Type = prompb.MetricTypeUnknown
	case "info":
		dctx.mm.Type = prompb.MetricTypeInfo
	case "stateset":
		dctx.mm.Type = prompb.MetricTypeStateset
	}

	dctx.mp.PushMetricMetadata(&dctx.mm)

	return nil
}

// Gauge represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L232
type Gauge struct {
	DataPoints []*NumberDataPoint
}

func (g *Gauge) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	for _, dp := range g.DataPoints {
		dp.marshalProtobuf(mm.AppendMessage(1))
	}
}

func (dctx *decoderContext) decodeGauge(src []byte) (err error) {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L232
	//
	// message Gauge {
	//   repeated NumberDataPoint data_points = 1;
	// }

	dctx.mm.Type = prompb.MetricTypeGauge

	dctxSnapshot := dctx.getSnapshot()

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		switch fc.FieldNum {
		case 1:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read NumberDataPoint data")
			}
			if err := dctx.decodeNumberDataPoint(data); err != nil {
				return fmt.Errorf("cannot unmarshal NumberDataPoint: %w", err)
			}

			dctx.restoreFromSnapshot(dctxSnapshot)
		}
	}
	return nil
}

// NumberDataPoint represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L385
type NumberDataPoint struct {
	Attributes   []*KeyValue
	TimeUnixNano uint64
	DoubleValue  *float64
	IntValue     *int64
	Flags        uint32
}

func (ndp *NumberDataPoint) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	for _, a := range ndp.Attributes {
		a.marshalProtobuf(mm.AppendMessage(7))
	}
	mm.AppendFixed64(3, ndp.TimeUnixNano)
	switch {
	case ndp.DoubleValue != nil:
		mm.AppendDouble(4, *ndp.DoubleValue)
	case ndp.IntValue != nil:
		mm.AppendSfixed64(6, *ndp.IntValue)
	}
	mm.AppendUint32(8, ndp.Flags)
}

func (dctx *decoderContext) decodeNumberDataPoint(src []byte) (err error) {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L385
	//
	// message NumberDataPoint {
	//   repeated KeyValue attributes = 7;
	//   fixed64 time_unix_nano = 3;
	//   oneof value {
	//     double as_double = 4;
	//     sfixed64 as_int = 6;
	//   }
	//   uint32 flags = 8;
	// }

	var (
		timestamp uint64
		value     float64
		flags     uint32
	)

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		var ok bool
		switch fc.FieldNum {
		case 7:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read Attributes")
			}
			if err := decodeKeyValue(data, &dctx.ls, &dctx.fb, ""); err != nil {
				return fmt.Errorf("cannot unmarshal Attributes: %w", err)
			}
		case 3:
			timestamp, ok = fc.Fixed64()
			if !ok {
				return fmt.Errorf("cannot read TimeUnixNano")
			}
		case 4:
			value, ok = fc.Double()
			if !ok {
				return fmt.Errorf("cannot read DoubleValue")
			}
		case 6:
			intValue, ok := fc.Sfixed64()
			if !ok {
				return fmt.Errorf("cannot read IntValue")
			}
			value = float64(intValue)
		case 8:
			flags, ok = fc.Uint32()
			if !ok {
				return fmt.Errorf("cannot read Flags")
			}
		}
	}

	dctx.mp.PushSample(&dctx.mm, "", &dctx.ls, timestamp, value, flags)

	return nil
}

// Sum represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L240
type Sum struct {
	DataPoints  []*NumberDataPoint
	IsMonotonic bool
}

func (s *Sum) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	for _, dp := range s.DataPoints {
		dp.marshalProtobuf(mm.AppendMessage(1))
	}
	mm.AppendBool(3, s.IsMonotonic)
}

func (dctx *decoderContext) decodeSum(src []byte) (err error) {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L240
	//
	// message Sum {
	//   repeated NumberDataPoint data_points = 1;
	//   bool is_monotonic = 3;
	// }

	isMonotonic, _, err := easyproto.GetBool(src, 3)
	if err != nil {
		return fmt.Errorf("cannot obtain isMonotonic: %w", err)
	}
	if isMonotonic {
		dctx.mm.Type = prompb.MetricTypeCounter
	} else {
		dctx.mm.Type = prompb.MetricTypeGauge
	}

	dctxSnapshot := dctx.getSnapshot()

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		switch fc.FieldNum {
		case 1:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read NumberDataPoint data")
			}
			if err := dctx.decodeNumberDataPoint(data); err != nil {
				return fmt.Errorf("cannot unmarshal NumberDataPoint: %w", err)
			}

			dctx.restoreFromSnapshot(dctxSnapshot)
		}
	}
	return nil
}

// Histogram represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L255
type Histogram struct {
	DataPoints []*HistogramDataPoint
}

func (h *Histogram) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	for _, dp := range h.DataPoints {
		dp.marshalProtobuf(mm.AppendMessage(1))
	}
}

func (dctx *decoderContext) decodeHistogram(src []byte) (err error) {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L255
	//
	// message Histogram {
	//   repeated HistogramDataPoint data_points = 1;
	// }

	dctx.mm.Type = prompb.MetricTypeHistogram

	dctxSnapshot := dctx.getSnapshot()

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		switch fc.FieldNum {
		case 1:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read HistogramDataPoint")
			}
			if err := dctx.decodeHistogramDataPoint(data); err != nil {
				return fmt.Errorf("cannot unmarshal HistogramDataPoint: %w", err)
			}

			dctx.restoreFromSnapshot(dctxSnapshot)
		}
	}
	return nil
}

// ExponentialHistogram represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L267
type ExponentialHistogram struct {
	DataPoints []*ExponentialHistogramDataPoint
}

func (h *ExponentialHistogram) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	for _, dp := range h.DataPoints {
		dp.marshalProtobuf(mm.AppendMessage(1))
	}
}

func (dctx *decoderContext) decodeExponentialHistogram(src []byte) (err error) {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L267
	//
	// message ExponentialHistogram {
	//   repeated ExponentialHistogramDataPoint data_points = 1;
	// }

	dctx.mm.Type = prompb.MetricTypeHistogram

	dctxSnapshot := dctx.getSnapshot()

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		switch fc.FieldNum {
		case 1:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read ExponentialHistogramDataPoint")
			}
			if err := dctx.decodeExponentialHistogramDataPoint(data); err != nil {
				return fmt.Errorf("cannot unmarshal ExponentialHistogramDataPoint: %w", err)
			}

			dctx.restoreFromSnapshot(dctxSnapshot)
		}
	}
	return nil
}

// Summary represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L286
type Summary struct {
	DataPoints []*SummaryDataPoint
}

func (s *Summary) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	for _, dp := range s.DataPoints {
		dp.marshalProtobuf(mm.AppendMessage(1))
	}
}

func (dctx *decoderContext) decodeSummary(src []byte) (err error) {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L286
	//
	// message Summary {
	//   repeated SummaryDataPoint data_points = 1;
	// }

	dctx.mm.Type = prompb.MetricTypeSummary

	dctxSnapshot := dctx.getSnapshot()

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		switch fc.FieldNum {
		case 1:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read SummaryDataPoint")
			}
			if err := dctx.decodeSummaryDataPoint(data); err != nil {
				return fmt.Errorf("cannot unmarshal SummaryDataPoint: %w", err)
			}

			dctx.restoreFromSnapshot(dctxSnapshot)
		}
	}
	return nil
}

// HistogramDataPoint represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L434
type HistogramDataPoint struct {
	Attributes     []*KeyValue
	TimeUnixNano   uint64
	Count          uint64
	Sum            *float64
	BucketCounts   []uint64
	ExplicitBounds []float64
	Flags          uint32
}

func (dp *HistogramDataPoint) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	for _, a := range dp.Attributes {
		a.marshalProtobuf(mm.AppendMessage(9))
	}
	mm.AppendFixed64(3, dp.TimeUnixNano)
	mm.AppendFixed64(4, dp.Count)
	if dp.Sum != nil {
		mm.AppendDouble(5, *dp.Sum)
	}
	mm.AppendFixed64s(6, dp.BucketCounts)
	mm.AppendDoubles(7, dp.ExplicitBounds)
	mm.AppendUint32(10, dp.Flags)
}

func (dctx *decoderContext) decodeHistogramDataPoint(src []byte) (err error) {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L434
	//
	// message HistogramDataPoint {
	//   repeated KeyValue attributes = 9;
	//   fixed64 time_unix_nano = 3;
	//   fixed64 count = 4;
	//   optional double sum = 5;
	//   repeated fixed64 bucket_counts = 6;
	//   repeated double explicit_bounds = 7;
	//   uint32 flags = 10;
	// }

	hctx := getHistogramDataPointContext()
	defer putHistogramDataPointContext(hctx)

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}

		var ok bool
		switch fc.FieldNum {
		case 9:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read Attributes")
			}
			if err := decodeKeyValue(data, &dctx.ls, &dctx.fb, ""); err != nil {
				return fmt.Errorf("cannot unmarshal Attributes: %w", err)
			}
		case 3:
			hctx.timestamp, ok = fc.Fixed64()
			if !ok {
				return fmt.Errorf("cannot read TimeUnixNano")
			}
		case 4:
			hctx.count, ok = fc.Fixed64()
			if !ok {
				return fmt.Errorf("cannot read Count")
			}
		case 5:
			hctx.sum, ok = fc.Double()
			if !ok {
				return fmt.Errorf("cannot read Sum")
			}
			hctx.hasSum = true
		case 6:
			hctx.bucketCounts, ok = fc.UnpackFixed64s(hctx.bucketCounts)
			if !ok {
				return fmt.Errorf("cannot read BucketCounts")
			}
		case 7:
			hctx.explicitBounds, ok = fc.UnpackDoubles(hctx.explicitBounds)
			if !ok {
				return fmt.Errorf("cannot read ExplicitBounds")
			}
		case 10:
			hctx.flags, ok = fc.Uint32()
			if !ok {
				return fmt.Errorf("cannot read Flags")
			}
		}
	}

	hctx.pushSamples(dctx)

	return nil
}

type histogramDataPointContext struct {
	timestamp      uint64
	count          uint64
	sum            float64
	hasSum         bool
	bucketCounts   []uint64
	explicitBounds []float64
	flags          uint32
}

func (hctx *histogramDataPointContext) reset() {
	hctx.timestamp = 0
	hctx.count = 0
	hctx.sum = 0
	hctx.hasSum = false
	hctx.bucketCounts = hctx.bucketCounts[:0]
	hctx.explicitBounds = hctx.explicitBounds[:0]
	hctx.flags = 0
}

func (hctx *histogramDataPointContext) pushSamples(dctx *decoderContext) {
	if len(hctx.bucketCounts) == 0 {
		return
	}

	if len(hctx.bucketCounts) != len(hctx.explicitBounds)+1 {
		skippedSampleLogger.Warnf("opentelemetry: unexpected number of buckets for %s; got %d; want %d", &dctx.ls, len(hctx.bucketCounts), len(hctx.explicitBounds)+1)
		return
	}

	dctx.mp.PushSample(&dctx.mm, "_count", &dctx.ls, hctx.timestamp, float64(hctx.count), hctx.flags)

	// sum is optional, it will not be filled out when negative events are recorded,
	// see https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L465
	if hctx.hasSum {
		dctx.mp.PushSample(&dctx.mm, "_sum", &dctx.ls, hctx.timestamp, float64(hctx.sum), hctx.flags)
	}

	dctx.ls.Add("le", "")
	leValueP := &dctx.ls.Labels[len(dctx.ls.Labels)-1].Value

	var cumulative uint64
	for index, bound := range hctx.explicitBounds {
		cumulative += hctx.bucketCounts[index]
		*leValueP = dctx.fb.formatFloat(bound)
		dctx.mp.PushSample(&dctx.mm, "_bucket", &dctx.ls, hctx.timestamp, float64(cumulative), hctx.flags)
	}
	cumulative += hctx.bucketCounts[len(hctx.bucketCounts)-1]
	*leValueP = "+Inf"
	dctx.mp.PushSample(&dctx.mm, "_bucket", &dctx.ls, hctx.timestamp, float64(cumulative), hctx.flags)
}

var skippedSampleLogger = logger.WithThrottler("otlp_skipped_sample", 5*time.Second)

func getHistogramDataPointContext() *histogramDataPointContext {
	v := hctxPool.Get()
	if v == nil {
		return &histogramDataPointContext{}
	}
	return v.(*histogramDataPointContext)
}

func putHistogramDataPointContext(hctx *histogramDataPointContext) {
	hctx.reset()
	hctxPool.Put(hctx)
}

var hctxPool sync.Pool

// ExponentialHistogramDataPoint represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L521
type ExponentialHistogramDataPoint struct {
	Attributes    []*KeyValue
	TimeUnixNano  uint64
	Count         uint64
	Sum           *float64
	Scale         int32
	ZeroCount     uint64
	Positive      *Buckets
	Negative      *Buckets
	Flags         uint32
	Min           *float64
	Max           *float64
	ZeroThreshold float64
}

func (dp *ExponentialHistogramDataPoint) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	for _, a := range dp.Attributes {
		a.marshalProtobuf(mm.AppendMessage(1))
	}
	mm.AppendFixed64(3, dp.TimeUnixNano)
	mm.AppendFixed64(4, dp.Count)
	if dp.Sum != nil {
		mm.AppendDouble(5, *dp.Sum)
	}
	mm.AppendSint32(6, dp.Scale)
	mm.AppendFixed64(7, dp.ZeroCount)
	if dp.Positive != nil {
		dp.Positive.marshalProtobuf(mm.AppendMessage(8))
	}
	if dp.Negative != nil {
		dp.Negative.marshalProtobuf(mm.AppendMessage(9))
	}
	mm.AppendUint32(10, dp.Flags)
	if dp.Min != nil {
		mm.AppendDouble(12, *dp.Min)
	}
	if dp.Max != nil {
		mm.AppendDouble(13, *dp.Max)
	}
	mm.AppendDouble(14, dp.ZeroThreshold)
}

func (dctx *decoderContext) decodeExponentialHistogramDataPoint(src []byte) (err error) {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L521
	//
	// message ExponentialHistogramDataPoint {
	//   repeated KeyValue attributes = 1;
	//   fixed64 time_unix_nano = 3;
	//   fixed64 count = 4;
	//   optional double sum = 5;
	//   sint32 scale = 6;
	//   fixed64 zero_count = 7;
	//   Buckets positive = 8;
	//   uint32 flags = 10;
	//   optional double min = 12;
	//   optional double max = 13;
	//   double zero_threshold = 14;
	// }

	ehctx := getExponentialHistogramDataPointContext()
	defer putExponentialHistogramDataPointContext(ehctx)

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		var ok bool
		switch fc.FieldNum {
		case 1:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read Attributes")
			}
			if err := decodeKeyValue(data, &dctx.ls, &dctx.fb, ""); err != nil {
				return fmt.Errorf("cannot unmarshal Attributes: %w", err)
			}
		case 3:
			ehctx.timestamp, ok = fc.Fixed64()
			if !ok {
				return fmt.Errorf("cannot read TimeUnixNano")
			}
		case 4:
			ehctx.count, ok = fc.Fixed64()
			if !ok {
				return fmt.Errorf("cannot read Count")
			}
		case 5:
			ehctx.sum, ok = fc.Double()
			if !ok {
				return fmt.Errorf("cannot read Sum")
			}
			ehctx.hasSum = true
		case 6:
			ehctx.scale, ok = fc.Sint32()
			if !ok {
				return fmt.Errorf("cannot read Scale")
			}
		case 7:
			ehctx.zeroCount, ok = fc.Fixed64()
			if !ok {
				return fmt.Errorf("cannot read ZeroCount")
			}
		case 8:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read Positive buckets")
			}
			if err := ehctx.positive.decodeBuckets(data); err != nil {
				return fmt.Errorf("cannot unmarshal Positive: %w", err)
			}
		case 9:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read Negative buckets")
			}
			if err := ehctx.negative.decodeBuckets(data); err != nil {
				return fmt.Errorf("cannot unmarshal Negative: %w", err)
			}
		case 10:
			ehctx.flags, ok = fc.Uint32()
			if !ok {
				return fmt.Errorf("cannot read Flags")
			}
		case 12:
			ehctx.min, ok = fc.Double()
			if !ok {
				return fmt.Errorf("cannot read Min")
			}
			ehctx.hasMin = true
		case 13:
			ehctx.max, ok = fc.Double()
			if !ok {
				return fmt.Errorf("cannot read Max")
			}
			ehctx.hasMax = true
		case 14:
			ehctx.zeroThreshold, ok = fc.Double()
			if !ok {
				return fmt.Errorf("cannot read ZeroThreshold")
			}
		}
	}

	ehctx.pushSamples(dctx)

	return nil
}

type exponentialHistogramDataPointContext struct {
	timestamp     uint64
	count         uint64
	sum           float64
	hasSum        bool
	scale         int32
	zeroCount     uint64
	positive      buckets
	negative      buckets
	flags         uint32
	min           float64
	hasMin        bool
	max           float64
	hasMax        bool
	zeroThreshold float64
}

func (ehctx *exponentialHistogramDataPointContext) reset() {
	ehctx.timestamp = 0
	ehctx.count = 0
	ehctx.sum = 0
	ehctx.hasSum = false
	ehctx.scale = 0
	ehctx.zeroCount = 0
	ehctx.positive.reset()
	ehctx.negative.reset()
	ehctx.flags = 0
	ehctx.min = 0
	ehctx.hasMin = false
	ehctx.max = 0
	ehctx.hasMax = false
	ehctx.zeroThreshold = 0
}

type buckets struct {
	offset       int32
	bucketCounts []uint64
}

func (b *buckets) reset() {
	b.offset = 0
	b.bucketCounts = b.bucketCounts[:0]
}

func (ehctx *exponentialHistogramDataPointContext) pushSamples(dctx *decoderContext) {
	dctx.mp.PushSample(&dctx.mm, "_count", &dctx.ls, ehctx.timestamp, float64(ehctx.count), ehctx.flags)
	// sum is optional, it will not be filled out when negative events are recorded,
	// see https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L550
	if ehctx.hasSum {
		dctx.mp.PushSample(&dctx.mm, "_sum", &dctx.ls, ehctx.timestamp, float64(ehctx.sum), ehctx.flags)
	}

	dctx.ls.Add("vmrange", "")
	vmrangeValueP := &dctx.ls.Labels[len(dctx.ls.Labels)-1].Value

	if ehctx.zeroCount > 0 {
		// ZeroThreshold is optionally set to convey the width of the zero region.
		// When ZeroThreshold is set, all observations within the closed interval [-ZeroThreshold, +ZeroThreshold] go to the zero bucket rather than a regular bucket.
		*vmrangeValueP = dctx.fb.formatVmrange(-ehctx.zeroThreshold, ehctx.zeroThreshold)
		dctx.mp.PushSample(&dctx.mm, "_bucket", &dctx.ls, ehctx.timestamp, float64(ehctx.zeroCount), ehctx.flags)
	}

	ratio := math.Pow(2, -float64(ehctx.scale))
	base := math.Pow(2, ratio)
	positiveBound := math.Pow(2, float64(ehctx.positive.offset)*ratio)
	for i, count := range ehctx.positive.bucketCounts {
		if count <= 0 {
			continue
		}

		lowerBound := positiveBound * math.Pow(base, float64(i))
		upperBound := lowerBound * base
		*vmrangeValueP = dctx.fb.formatVmrange(lowerBound, upperBound)
		dctx.mp.PushSample(&dctx.mm, "_bucket", &dctx.ls, ehctx.timestamp, float64(count), ehctx.flags)
	}
	negativeBound := math.Pow(2, float64(ehctx.negative.offset)*ratio)
	for i, count := range ehctx.negative.bucketCounts {
		if count <= 0 {
			continue
		}

		lowerBound := negativeBound * math.Pow(base, float64(i))
		upperBound := lowerBound * base
		*vmrangeValueP = dctx.fb.formatVmrange(-upperBound, -lowerBound)
		dctx.mp.PushSample(&dctx.mm, "_bucket", &dctx.ls, ehctx.timestamp, float64(count), ehctx.flags)
	}
}

func getExponentialHistogramDataPointContext() *exponentialHistogramDataPointContext {
	v := ehctxPool.Get()
	if v == nil {
		return &exponentialHistogramDataPointContext{}
	}
	return v.(*exponentialHistogramDataPointContext)
}

func putExponentialHistogramDataPointContext(ehctx *exponentialHistogramDataPointContext) {
	ehctx.reset()
	ehctxPool.Put(ehctx)
}

var ehctxPool sync.Pool

// Buckets represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L592
type Buckets struct {
	Offset       int32
	BucketCounts []uint64
}

func (b *Buckets) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	mm.AppendSint32(1, b.Offset)
	for _, bc := range b.BucketCounts {
		mm.AppendUint64(2, bc)
	}
}

func (b *buckets) decodeBuckets(src []byte) (err error) {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L592
	//
	// message Buckets {
	//   sint32 offset = 1;
	//   repeated uint64 bucket_counts = 2;
	// }

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		var ok bool
		switch fc.FieldNum {
		case 1:
			b.offset, ok = fc.Sint32()
			if !ok {
				return fmt.Errorf("cannot read Offset")
			}
		case 2:
			b.bucketCounts, ok = fc.UnpackUint64s(b.bucketCounts)
			if !ok {
				return fmt.Errorf("cannot read BucketCounts")
			}
		}
	}
	return nil
}

// SummaryDataPoint represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L636
type SummaryDataPoint struct {
	Attributes     []*KeyValue
	TimeUnixNano   uint64
	Count          uint64
	Sum            float64
	QuantileValues []*ValueAtQuantile
	Flags          uint32
}

func (dp *SummaryDataPoint) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	for _, a := range dp.Attributes {
		a.marshalProtobuf(mm.AppendMessage(7))
	}
	mm.AppendFixed64(3, dp.TimeUnixNano)
	mm.AppendFixed64(4, dp.Count)
	mm.AppendDouble(5, dp.Sum)
	for _, v := range dp.QuantileValues {
		v.marshalProtobuf(mm.AppendMessage(6))
	}
	mm.AppendUint32(8, dp.Flags)
}

func (dctx *decoderContext) decodeSummaryDataPoint(src []byte) (err error) {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L636
	//
	// message SummaryDataPoint {
	//   repeated KeyValue attributes = 7;
	//   fixed64 time_unix_nano = 3;
	//   fixed64 count = 4;
	//   double sum = 5;
	//   repeated ValueAtQuantile quantile_values = 6;
	//   uint32 flags = 8;
	// }

	sctx := getSummaryDataPointContext()
	defer putSummaryDataPointContext(sctx)

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return fmt.Errorf("cannot read the next field: %w", err)
		}
		var ok bool
		switch fc.FieldNum {
		case 7:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read Attributes")
			}
			if err := decodeKeyValue(data, &dctx.ls, &dctx.fb, ""); err != nil {
				return fmt.Errorf("cannot unmarshal Attributes: %w", err)
			}
		case 3:
			sctx.timestamp, ok = fc.Fixed64()
			if !ok {
				return fmt.Errorf("cannot read TimeUnixNano")
			}
		case 4:
			sctx.count, ok = fc.Fixed64()
			if !ok {
				return fmt.Errorf("cannot read Count")
			}
		case 5:
			sctx.sum, ok = fc.Double()
			if !ok {
				return fmt.Errorf("cannot read Sum")
			}
		case 6:
			data, ok := fc.MessageData()
			if !ok {
				return fmt.Errorf("cannot read ValueAtQuantile")
			}
			quantile, value, err := decodeValueAtQuantile(data)
			if err != nil {
				return fmt.Errorf("cannot unmarshal ValueAtQuantile: %w", err)
			}
			sctx.quantileValues = append(sctx.quantileValues, quantileValue{
				quantile: quantile,
				value:    value,
			})
		case 8:
			sctx.flags, ok = fc.Uint32()
			if !ok {
				return fmt.Errorf("cannot read Flags")
			}
		}
	}

	sctx.pushSamples(dctx)

	return nil
}

type summaryDataPointContext struct {
	timestamp      uint64
	count          uint64
	sum            float64
	quantileValues []quantileValue
	flags          uint32
}

type quantileValue struct {
	quantile float64
	value    float64
}

func (sctx *summaryDataPointContext) reset() {
	sctx.timestamp = 0
	sctx.count = 0
	sctx.sum = 0
	sctx.quantileValues = sctx.quantileValues[:0]
	sctx.flags = 0
}

func getSummaryDataPointContext() *summaryDataPointContext {
	v := sctxPool.Get()
	if v == nil {
		return &summaryDataPointContext{}
	}
	return v.(*summaryDataPointContext)
}

func putSummaryDataPointContext(sctx *summaryDataPointContext) {
	sctx.reset()
	sctxPool.Put(sctx)
}

var sctxPool sync.Pool

func (sctx *summaryDataPointContext) pushSamples(dctx *decoderContext) {
	dctx.mp.PushSample(&dctx.mm, "_count", &dctx.ls, sctx.timestamp, float64(sctx.count), sctx.flags)
	dctx.mp.PushSample(&dctx.mm, "_sum", &dctx.ls, sctx.timestamp, sctx.sum, sctx.flags)

	dctx.ls.Add("quantile", "")
	quantileValueP := &dctx.ls.Labels[len(dctx.ls.Labels)-1].Value

	for _, qv := range sctx.quantileValues {
		*quantileValueP = dctx.fb.formatFloat(qv.quantile)
		dctx.mp.PushSample(&dctx.mm, "", &dctx.ls, sctx.timestamp, qv.value, sctx.flags)
	}
}

// ValueAtQuantile represents the corresponding OTEL protobuf message
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L680
type ValueAtQuantile struct {
	Quantile float64
	Value    float64
}

func (v *ValueAtQuantile) marshalProtobuf(mm *easyproto.MessageMarshaler) {
	mm.AppendDouble(1, v.Quantile)
	mm.AppendDouble(2, v.Value)
}

func decodeValueAtQuantile(src []byte) (quantile float64, value float64, err error) {
	// See https://github.com/open-telemetry/opentelemetry-proto/blob/049d4332834935792fd4dbd392ecd31904f99ba2/opentelemetry/proto/metrics/v1/metrics.proto#L680
	//
	// message ValueAtQuantile {
	//   double quantile = 1;
	//   double value = 2;
	// }

	var fc easyproto.FieldContext
	for len(src) > 0 {
		src, err = fc.NextField(src)
		if err != nil {
			return 0, 0, fmt.Errorf("cannot read the next field: %w", err)
		}
		var ok bool
		switch fc.FieldNum {
		case 1:
			quantile, ok = fc.Double()
			if !ok {
				return 0, 0, fmt.Errorf("cannot read Quantile")
			}
		case 2:
			value, ok = fc.Double()
			if !ok {
				return 0, 0, fmt.Errorf("cannot read Value")
			}
		}
	}
	return quantile, value, nil
}

type decoderContext struct {
	ls promutil.Labels
	fb fmtBuffer

	mm MetricMetadata

	mp MetricPusher
}

func (dctx *decoderContext) reset() {
	// Explicitly clear all the ls.Labels up to its' capacity in order to remove possible references
	// to the original byte slices, so they could be cleared by Go GC.
	clear(dctx.ls.Labels[:cap(dctx.ls.Labels)])
	dctx.ls.Labels = dctx.ls.Labels[:0]

	dctx.ls.Reset()
	dctx.fb.reset()

	dctx.mm.reset()

	dctx.mp = nil
}

func (dctx *decoderContext) getSnapshot() decoderContextSnapshot {
	return decoderContextSnapshot{
		labelsLen: len(dctx.ls.Labels),
		fbLen:     len(dctx.fb.buf),
	}
}

func (dctx *decoderContext) restoreFromSnapshot(snapshot decoderContextSnapshot) {
	dctx.ls.Labels = dctx.ls.Labels[:snapshot.labelsLen]
	dctx.fb.buf = dctx.fb.buf[:snapshot.fbLen]
}

type decoderContextSnapshot struct {
	labelsLen int
	fbLen     int
}

func getDecoderContext(mp MetricPusher) *decoderContext {
	v := dctxPool.Get()
	if v == nil {
		v = &decoderContext{}
	}
	dctx := v.(*decoderContext)
	dctx.mp = mp

	return dctx
}

func putDecoderContext(dctx *decoderContext) {
	dctx.reset()
	dctxPool.Put(dctx)
}

var dctxPool sync.Pool
