package metrics

import (
	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
)

type metric[T prometheus.Collector] struct {
	value T
	desc  Description
}

// Descriptions contains metric description suitable for further processing.
// The only reason for it to exist is `prometheus.Desc` disallowing field access directly.
// https://github.com/prometheus/client_golang/pull/326
// https://github.com/prometheus/client_golang/issues/516
// https://github.com/prometheus/client_golang/issues/222
type Description struct {
	Name           string            `json:"name"`
	Help           string            `json:"help"`
	Type           string            `json:"type"`
	ConstantLabels prometheus.Labels `json:"constant_labels,omitempty"`
	VariableLabels []string          `json:"variable_labels,omitempty"`
}

func newGauge(opts prometheus.GaugeOpts) metric[prometheus.Gauge] {
	return metric[prometheus.Gauge]{
		value: prometheus.NewGauge(opts),
		desc: Description{
			Name:           prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
			Type:           dto.MetricType_GAUGE.String(),
			Help:           opts.Help,
			ConstantLabels: opts.ConstLabels,
		},
	}
}

func newGaugeVec(opts prometheus.GaugeOpts, labelNames []string) metric[*prometheus.GaugeVec] {
	return metric[*prometheus.GaugeVec]{
		value: prometheus.NewGaugeVec(opts, labelNames),
		desc: Description{
			Name:           prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
			Type:           dto.MetricType_GAUGE.String(),
			Help:           opts.Help,
			ConstantLabels: opts.ConstLabels,
			VariableLabels: labelNames,
		},
	}
}

func newCounter(opts prometheus.CounterOpts) metric[prometheus.Counter] {
	return metric[prometheus.Counter]{
		value: prometheus.NewCounter(opts),
		desc: Description{
			Name:           prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
			Type:           dto.MetricType_COUNTER.String(),
			Help:           opts.Help,
			ConstantLabels: opts.ConstLabels,
		},
	}
}

// DescribeAll returns descriptions for all registered metrics.
func DescribeAll() ([]Description, error) {
	registeredDescriptionsMtx.Lock()
	defer registeredDescriptionsMtx.Unlock()

	ds := make([]Description, len(registeredDescriptions))
	copy(ds, registeredDescriptions)
	return ds, nil
}