// Code generated by mdatagen. DO NOT EDIT.

package {{ .Package }}

import (
	{{- if .Metrics | parseImportsRequired }}
	"strconv"
	"fmt"
	{{- end }}
	"time"

	"go.opentelemetry.io/collector/component"
	"go.opentelemetry.io/collector/pdata/pcommon"
	"go.opentelemetry.io/collector/pdata/pmetric"
	"go.opentelemetry.io/collector/receiver"
	{{- if .SemConvVersion }}
	conventions "go.opentelemetry.io/collector/semconv/v{{ .SemConvVersion }}"
	{{- end }}
)

{{ range $name, $info := .Attributes }}
{{- if $info.Enum -}}
// Attribute{{ $name.Render }} specifies the a value {{ $name }} attribute.
type Attribute{{ $name.Render }} int

const (
	_ Attribute{{ $name.Render }} = iota
	{{- range $info.Enum }}
	Attribute{{ $name.Render }}{{ . | publicVar }}
	{{- end }}
)

// String returns the string representation of the Attribute{{ $name.Render }}.
func (av Attribute{{ $name.Render }}) String() string {
	switch av {
	{{- range $info.Enum }}
	case Attribute{{ $name.Render }}{{ . | publicVar }}:
		return "{{ . }}"
	{{- end }}
	}
	return ""
}

// MapAttribute{{ $name.Render }} is a helper map of string to Attribute{{ $name.Render }} attribute value.
var MapAttribute{{ $name.Render }} = map[string]Attribute{{ $name.Render }}{
	{{- range $info.Enum }}
	"{{ . }}": Attribute{{ $name.Render }}{{ . | publicVar }},
	{{- end }}
}

{{ end }}
{{- end }}

{{ range $name, $metric := .Metrics -}}
type metric{{ $name.Render }} struct {
	data     pmetric.Metric // data buffer for generated metric.
	config   MetricConfig   // metric config provided by user.
	capacity int            // max observed number of data points added to the metric.
}

// init fills {{ $name }} metric with initial data.
func (m *metric{{ $name.Render }}) init() {
	m.data.SetName("{{ $name }}")
	m.data.SetDescription("{{ $metric.Description }}")
	m.data.SetUnit("{{ $metric.Unit }}")
	m.data.SetEmpty{{ $metric.Data.Type }}()
	{{- if $metric.Data.HasMonotonic }}
	m.data.{{ $metric.Data.Type }}().SetIsMonotonic({{ $metric.Data.Monotonic }})
	{{- end }}
	{{- if $metric.Data.HasAggregated }}
	m.data.{{ $metric.Data.Type }}().SetAggregationTemporality(pmetric.AggregationTemporality{{ $metric.Data.AggregationTemporality }})
	{{- end }}
	{{- if $metric.Attributes }}
	m.data.{{ $metric.Data.Type }}().DataPoints().EnsureCapacity(m.capacity)
	{{- end }}
}

func (m *metric{{ $name.Render }}) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val {{ $metric.Data.MetricValueType.BasicType }}
{{- range $metric.Attributes -}}, {{ .RenderUnexported }}AttributeValue {{ (attributeInfo .).Type.Primitive }}{{ end }}) {
	if !m.config.Enabled {
		return
	}
	dp := m.data.{{ $metric.Data.Type }}().DataPoints().AppendEmpty()
	dp.SetStartTimestamp(start)
	dp.SetTimestamp(ts)
	dp.Set{{ $metric.Data.MetricValueType }}Value(val)
	{{- range $metric.Attributes }}
	{{- if eq (attributeInfo .).Type.Primitive "[]byte" }}
	dp.Attributes().PutEmptyBytes("{{ (attributeInfo .).Name }}").FromRaw({{ .RenderUnexported }}AttributeValue)
	{{- else if eq (attributeInfo .).Type.Primitive "[]any" }}
	dp.Attributes().PutEmptySlice("{{ (attributeInfo .).Name }}").FromRaw({{ .RenderUnexported }}AttributeValue)
	{{- else if eq (attributeInfo .).Type.Primitive "map[string]any" }}
	dp.Attributes().PutEmptyMap("{{ (attributeInfo .).Name }}").FromRaw({{ .RenderUnexported }}AttributeValue)
	{{- else }}
	dp.Attributes().Put{{ (attributeInfo .).Type }}("{{ (attributeInfo .).Name }}", {{ .RenderUnexported }}AttributeValue)
	{{- end }}
	{{- end }}
}

// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metric{{ $name.Render }}) updateCapacity() {
	if m.data.{{ $metric.Data.Type }}().DataPoints().Len() > m.capacity {
		m.capacity = m.data.{{ $metric.Data.Type }}().DataPoints().Len()
	}
}

// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metric{{ $name.Render }}) emit(metrics pmetric.MetricSlice) {
	if m.config.Enabled && m.data.{{ $metric.Data.Type }}().DataPoints().Len() > 0 {
		m.updateCapacity()
		m.data.MoveTo(metrics.AppendEmpty())
		m.init()
	}
}

func newMetric{{ $name.Render }}(cfg MetricConfig) metric{{ $name.Render }} {
	m := metric{{ $name.Render }}{config: cfg}
	if cfg.Enabled {
		m.data = pmetric.NewMetric()
		m.init()
	}
	return m
}

{{ end -}}

// MetricsBuilder provides an interface for scrapers to report metrics while taking care of all the transformations
// required to produce metric representation defined in metadata and user config.
type MetricsBuilder struct {
	config          MetricsBuilderConfig // config of the metrics builder.
	startTime       pcommon.Timestamp    // start time that will be applied to all recorded data points.
	metricsCapacity int                  // maximum observed number of metrics per resource.
	metricsBuffer   pmetric.Metrics      // accumulates metrics data before emitting.
	buildInfo       component.BuildInfo  // contains version information.
	{{- range $name, $metric := .Metrics }}
	metric{{ $name.Render }} metric{{ $name.Render }}
	{{- end }}
}

// metricBuilderOption applies changes to default metrics builder.
type metricBuilderOption func(*MetricsBuilder)

// WithStartTime sets startTime on the metrics builder.
func WithStartTime(startTime pcommon.Timestamp) metricBuilderOption {
	return func(mb *MetricsBuilder) {
		mb.startTime = startTime
	}
}

func NewMetricsBuilder(mbc MetricsBuilderConfig, settings receiver.CreateSettings, options ...metricBuilderOption) *MetricsBuilder {
	{{- range $name, $metric := .Metrics }}
	{{- if $metric.Warnings.IfEnabled }}
	if mbc.Metrics.{{ $name.Render }}.Enabled {
		settings.Logger.Warn("[WARNING] `{{ $name }}` should not be enabled: {{ $metric.Warnings.IfEnabled }}")
	}
	{{- end }}
	{{- if $metric.Warnings.IfEnabledNotSet }}
	if !mbc.Metrics.{{ $name.Render }}.enabledSetByUser {
		settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `{{ $name }}`: {{ $metric.Warnings.IfEnabledNotSet }}")
	}
	{{- end }}
	{{- if $metric.Warnings.IfConfigured }}
	if mbc.Metrics.{{ $name.Render }}.enabledSetByUser {
		settings.Logger.Warn("[WARNING] `{{ $name }}` should not be configured: {{ $metric.Warnings.IfConfigured }}")
	}
	{{- end }}
	{{- end }}
	{{- range $name, $attr := .ResourceAttributes }}
	{{- if $attr.Warnings.IfEnabled }}
	if mbc.ResourceAttributes.{{ $name.Render }}.Enabled {
		settings.Logger.Warn("[WARNING] `{{ $name }}` should not be enabled: {{ $attr.Warnings.IfEnabled }}")
	}
	{{- end }}
	{{- if $attr.Warnings.IfEnabledNotSet }}
	if !mbc.ResourceAttributes.{{ $name.Render }}.enabledSetByUser {
		settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `{{ $name }}`: {{ $attr.Warnings.IfEnabledNotSet }}")
	}
	{{- end }}
	{{- if $attr.Warnings.IfConfigured }}
	if mbc.ResourceAttributes.{{ $name.Render }}.enabledSetByUser {
		settings.Logger.Warn("[WARNING] `{{ $name }}` should not be configured: {{ $attr.Warnings.IfConfigured }}")
	}
	{{- end }}
	{{- end }}
	mb := &MetricsBuilder{
		config:        mbc,
		startTime:     pcommon.NewTimestampFromTime(time.Now()),
		metricsBuffer: pmetric.NewMetrics(),
		buildInfo:     settings.BuildInfo,
		{{- range $name, $metric := .Metrics }}
		metric{{ $name.Render }}: newMetric{{ $name.Render }}(mbc.Metrics.{{ $name.Render }}),
		{{- end }}
	}
	for _, op := range options {
		op(mb)
	}
	return mb
}

{{- if .ResourceAttributes }}
// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted metrics.
func (mb *MetricsBuilder) NewResourceBuilder() *ResourceBuilder {
	return NewResourceBuilder(mb.config.ResourceAttributes)
}
{{- end }}

// updateCapacity updates max length of metrics and resource attributes that will be used for the slice capacity.
func (mb *MetricsBuilder) updateCapacity(rm pmetric.ResourceMetrics) {
	if mb.metricsCapacity < rm.ScopeMetrics().At(0).Metrics().Len() {
		mb.metricsCapacity = rm.ScopeMetrics().At(0).Metrics().Len()
	}
}

// ResourceMetricsOption applies changes to provided resource metrics.
type ResourceMetricsOption func(pmetric.ResourceMetrics)

// WithResource sets the provided resource on the emitted ResourceMetrics.
// It's recommended to use ResourceBuilder to create the resource.
func WithResource(res pcommon.Resource) ResourceMetricsOption {
	return func(rm pmetric.ResourceMetrics) {
		res.CopyTo(rm.Resource())
	}
}

// WithStartTimeOverride overrides start time for all the resource metrics data points.
// This option should be only used if different start time has to be set on metrics coming from different resources.
func WithStartTimeOverride(start pcommon.Timestamp) ResourceMetricsOption {
	return func(rm pmetric.ResourceMetrics) {
		var dps pmetric.NumberDataPointSlice
		metrics := rm.ScopeMetrics().At(0).Metrics()
		for i := 0; i < metrics.Len(); i++ {
			switch metrics.At(i).Type() {
			case pmetric.MetricTypeGauge:
				dps = metrics.At(i).Gauge().DataPoints()
			case pmetric.MetricTypeSum:
				dps = metrics.At(i).Sum().DataPoints()
			}
			for j := 0; j < dps.Len(); j++ {
				dps.At(j).SetStartTimestamp(start)
			}
		}
	}
}

// EmitForResource saves all the generated metrics under a new resource and updates the internal state to be ready for
// recording another set of data points as part of another resource. This function can be helpful when one scraper
// needs to emit metrics from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceMetricsOption arguments.
func (mb *MetricsBuilder) EmitForResource(rmo ...ResourceMetricsOption) {
	rm := pmetric.NewResourceMetrics()
	{{- if .SemConvVersion }}
	rm.SetSchemaUrl(conventions.SchemaURL)
	{{- end }}
	ils := rm.ScopeMetrics().AppendEmpty()
	ils.Scope().SetName("{{ .ScopeName }}")
	ils.Scope().SetVersion(mb.buildInfo.Version)
	ils.Metrics().EnsureCapacity(mb.metricsCapacity)
	{{- range $name, $metric := .Metrics }}
	mb.metric{{- $name.Render }}.emit(ils.Metrics())
	{{- end }}

	for _, op := range rmo {
		op(rm)
	}
	if ils.Metrics().Len() > 0 {
		mb.updateCapacity(rm)
		rm.MoveTo(mb.metricsBuffer.ResourceMetrics().AppendEmpty())
	}
}

// Emit returns all the metrics accumulated by the metrics builder and updates the internal state to be ready for
// recording another set of metrics. This function will be responsible for applying all the transformations required to
// produce metric representation defined in metadata and user config, e.g. delta or cumulative.
func (mb *MetricsBuilder) Emit(rmo ...ResourceMetricsOption) pmetric.Metrics {
	mb.EmitForResource(rmo...)
	metrics := mb.metricsBuffer
	mb.metricsBuffer = pmetric.NewMetrics()
	return metrics
}

{{ range $name, $metric := .Metrics -}}
// Record{{ $name.Render }}DataPoint adds a data point to {{ $name }} metric.
func (mb *MetricsBuilder) Record{{ $name.Render }}DataPoint(ts pcommon.Timestamp
	{{- if $metric.Data.HasMetricInputType }}, inputVal {{ $metric.Data.MetricInputType.String }}
	{{- else }}, val {{ $metric.Data.MetricValueType.BasicType }}
	{{- end }}
	{{- range $metric.Attributes -}}
	, {{ .RenderUnexported }}AttributeValue {{ if (attributeInfo .).Enum }}Attribute{{ .Render }}{{ else }}{{ (attributeInfo .).Type.Primitive }}{{ end }}
	{{- end }})
	{{- if $metric.Data.HasMetricInputType }} error{{ end }} {
	{{- if $metric.Data.HasMetricInputType }}
	{{- if eq $metric.Data.MetricValueType.BasicType "float64" }}
	val, err := strconv.ParseFloat(inputVal, 64)
	{{- else if eq $metric.Data.MetricValueType.BasicType "int64" }}
	val, err := strconv.ParseInt(inputVal, 10, 64)
	{{- end }}
	if err != nil {
		return fmt.Errorf("failed to parse {{ $metric.Data.MetricValueType.BasicType }} for {{ $name.Render }}, value was %s: %w", inputVal, err)
	}
	{{- end }}
	mb.metric{{ $name.Render }}.recordDataPoint(mb.startTime, ts, val
		{{- range $metric.Attributes -}}
		, {{ .RenderUnexported }}AttributeValue{{ if (attributeInfo .).Enum }}.String(){{ end }}
		{{- end }})
	{{- if $metric.Data.HasMetricInputType }}
	return nil
	{{- end }}
}
{{ end }}

// Reset resets metrics builder to its initial state. It should be used when external metrics source is restarted,
// and metrics builder should update its startTime and reset it's internal state accordingly.
func (mb *MetricsBuilder) Reset(options ...metricBuilderOption) {
	mb.startTime = pcommon.NewTimestampFromTime(time.Now())
	for _, op := range options {
		op(mb)
	}
}
