// Package metrics provides Prometheus collectors and helpers.
package metrics

import (
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

// Recorder describes the metrics operations used across the server.
type Recorder interface {
	IncHTTPInFlight(tenantID string)
	DecHTTPInFlight(tenantID string)
	ObserveHTTPRequest(tenantID, method, route string, status int, duration time.Duration)
	ObserveOperation(tenantID, channel, operation, result string, duration time.Duration)
	ObserveReadyz(status string)
	IncUserCreated(tenantID, kind string)
	IncOrgCreated(tenantID string)
	IncRepoCreated(tenantID string)
	IncFirstGitPushUser(tenantID string)
	ObserveUserActivationDuration(tenantID string, duration time.Duration)
	ObserveRepoActivationDuration(tenantID string, duration time.Duration)
	SetActiveUsersDaily(tenantID string, value float64)
	SetActiveReposDaily(tenantID string, value float64)
	IncGitPush(tenantID string)
	IncGitRead(tenantID string)
	IncSearch(tenantID string)
	IncStarCreated(tenantID string)
	IncReleasePublished(tenantID string)
	IncIssueOpened(tenantID string)
	IncIssueClosed(tenantID string)
	IncIssueReopened(tenantID string)
	IncIssueCommentCreated(tenantID string)
	ObserveIssueCloseDuration(tenantID string, duration time.Duration)
	IncFrontendEvent(tenantID, event, source, page, platform, location, success, trafficSource string)
}

type noopRecorder struct{}

func (noopRecorder) IncHTTPInFlight(string)                                         {}
func (noopRecorder) DecHTTPInFlight(string)                                         {}
func (noopRecorder) ObserveHTTPRequest(string, string, string, int, time.Duration)  {}
func (noopRecorder) ObserveOperation(string, string, string, string, time.Duration) {}
func (noopRecorder) ObserveReadyz(string)                                           {}
func (noopRecorder) IncUserCreated(string, string)                                  {}
func (noopRecorder) IncOrgCreated(string)                                           {}
func (noopRecorder) IncRepoCreated(string)                                          {}
func (noopRecorder) IncFirstGitPushUser(string)                                     {}
func (noopRecorder) ObserveUserActivationDuration(string, time.Duration)            {}
func (noopRecorder) ObserveRepoActivationDuration(string, time.Duration)            {}
func (noopRecorder) SetActiveUsersDaily(string, float64)                            {}
func (noopRecorder) SetActiveReposDaily(string, float64)                            {}
func (noopRecorder) IncGitPush(string)                                              {}
func (noopRecorder) IncGitRead(string)                                              {}
func (noopRecorder) IncSearch(string)                                               {}
func (noopRecorder) IncStarCreated(string)                                          {}
func (noopRecorder) IncReleasePublished(string)                                     {}
func (noopRecorder) IncIssueOpened(string)                                          {}
func (noopRecorder) IncIssueClosed(string)                                          {}
func (noopRecorder) IncIssueReopened(string)                                        {}
func (noopRecorder) IncIssueCommentCreated(string)                                  {}
func (noopRecorder) ObserveIssueCloseDuration(string, time.Duration)                {}
func (noopRecorder) IncFrontendEvent(string, string, string, string, string, string, string, string) {
}

// PrometheusMetrics collects application metrics via Prometheus.
type PrometheusMetrics struct {
	HTTPRequestsTotal        *prometheus.CounterVec
	HTTPRequestDuration      *prometheus.HistogramVec
	HTTPRequestsInFlight     *prometheus.GaugeVec
	OperationsTotal          *prometheus.CounterVec
	OperationDuration        *prometheus.HistogramVec
	ReadyzTotal              *prometheus.CounterVec
	UserCreatedTotal         *prometheus.CounterVec
	OrgCreatedTotal          *prometheus.CounterVec
	RepoCreatedTotal         *prometheus.CounterVec
	FirstGitPushUserTotal    *prometheus.CounterVec
	UserActivationDuration   *prometheus.HistogramVec
	RepoActivationDuration   *prometheus.HistogramVec
	ActiveUsersDaily         *prometheus.GaugeVec
	ActiveReposDaily         *prometheus.GaugeVec
	GitPushTotal             *prometheus.CounterVec
	GitReadTotal             *prometheus.CounterVec
	SearchTotal              *prometheus.CounterVec
	StarCreatedTotal         *prometheus.CounterVec
	ReleasePublishedTotal    *prometheus.CounterVec
	IssueOpenedTotal         *prometheus.CounterVec
	IssueClosedTotal         *prometheus.CounterVec
	IssueReopenedTotal       *prometheus.CounterVec
	IssueCommentCreatedTotal *prometheus.CounterVec
	IssueCloseDuration       *prometheus.HistogramVec
	FrontendEventsTotal      *prometheus.CounterVec
}

// NewPrometheusMetrics registers and returns the Prometheus collectors.
func NewPrometheusMetrics(reg prometheus.Registerer) *PrometheusMetrics {
	registerDefaultCollectors(reg)

	m := &PrometheusMetrics{
		HTTPRequestsTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "http",
				Name:      "requests_total",
				Help:      "Total number of HTTP requests.",
			},
			[]string{"tenant_id", "method", "route", "status"},
		),
		HTTPRequestDuration: prometheus.NewHistogramVec(
			prometheus.HistogramOpts{
				Namespace: "gh_server",
				Subsystem: "http",
				Name:      "request_duration_seconds",
				Help:      "Duration of HTTP requests in seconds.",
				Buckets:   prometheus.DefBuckets,
			},
			[]string{"tenant_id", "method", "route", "status"},
		),
		HTTPRequestsInFlight: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: "gh_server",
				Subsystem: "http",
				Name:      "requests_in_flight",
				Help:      "Current number of in-flight HTTP requests.",
			},
			[]string{"tenant_id"},
		),
		OperationsTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Name:      "operation_total",
				Help:      "Total number of business operations.",
			},
			[]string{"tenant_id", "channel", "operation", "result"},
		),
		OperationDuration: prometheus.NewHistogramVec(
			prometheus.HistogramOpts{
				Namespace: "gh_server",
				Name:      "operation_duration_seconds",
				Help:      "Duration of business operations in seconds.",
				Buckets:   prometheus.DefBuckets,
			},
			[]string{"tenant_id", "channel", "operation", "result"},
		),
		ReadyzTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "readyz",
				Name:      "results_total",
				Help:      "Readiness probe results.",
			},
			[]string{"status"},
		),
		UserCreatedTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "user_created_total",
				Help:      "Total number of created user accounts by kind.",
			},
			[]string{"tenant_id", "kind"},
		),
		OrgCreatedTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "org_created_total",
				Help:      "Total number of created organization accounts.",
			},
			[]string{"tenant_id"},
		),
		RepoCreatedTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "repo_created_total",
				Help:      "Total number of created repositories.",
			},
			[]string{"tenant_id"},
		),
		FirstGitPushUserTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "first_git_push_user_total",
				Help:      "Total number of users who completed their first successful Git push.",
			},
			[]string{"tenant_id"},
		),
		UserActivationDuration: prometheus.NewHistogramVec(
			prometheus.HistogramOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "user_activation_duration_seconds",
				Help:      "Seconds from user creation to first successful Git push.",
				Buckets:   []float64{60, 300, 900, 1800, 3600, 10800, 21600, 43200, 86400, 604800, 2592000},
			},
			[]string{"tenant_id"},
		),
		RepoActivationDuration: prometheus.NewHistogramVec(
			prometheus.HistogramOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "repo_activation_duration_seconds",
				Help:      "Seconds from repository creation to first successful Git push.",
				Buckets:   []float64{60, 300, 900, 1800, 3600, 10800, 21600, 43200, 86400, 604800, 2592000},
			},
			[]string{"tenant_id"},
		),
		ActiveUsersDaily: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "active_users_daily",
				Help:      "Number of users with tracked business activity today.",
			},
			[]string{"tenant_id"},
		),
		ActiveReposDaily: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "active_repos_daily",
				Help:      "Number of repositories with tracked business activity today.",
			},
			[]string{"tenant_id"},
		),
		GitPushTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "git_push_total",
				Help:      "Total number of successful Git pushes that changed refs.",
			},
			[]string{"tenant_id"},
		),
		GitReadTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "git_read_total",
				Help:      "Total number of successful Git upload-pack requests.",
			},
			[]string{"tenant_id"},
		),
		SearchTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "search_total",
				Help:      "Total number of successful search requests.",
			},
			[]string{"tenant_id"},
		),
		StarCreatedTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "star_created_total",
				Help:      "Total number of newly created repository stars.",
			},
			[]string{"tenant_id"},
		),
		ReleasePublishedTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "release_published_total",
				Help:      "Total number of non-draft releases published.",
			},
			[]string{"tenant_id"},
		),
		IssueOpenedTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "issue_opened_total",
				Help:      "Total number of issues opened.",
			},
			[]string{"tenant_id"},
		),
		IssueClosedTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "issue_closed_total",
				Help:      "Total number of issues closed.",
			},
			[]string{"tenant_id"},
		),
		IssueReopenedTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "issue_reopened_total",
				Help:      "Total number of issues reopened.",
			},
			[]string{"tenant_id"},
		),
		IssueCommentCreatedTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "issue_comment_created_total",
				Help:      "Total number of issue comments created.",
			},
			[]string{"tenant_id"},
		),
		IssueCloseDuration: prometheus.NewHistogramVec(
			prometheus.HistogramOpts{
				Namespace: "gh_server",
				Subsystem: "business",
				Name:      "issue_close_duration_seconds",
				Help:      "Seconds from issue creation to close.",
				Buckets:   []float64{60, 300, 900, 1800, 3600, 10800, 21600, 43200, 86400, 604800, 2592000},
			},
			[]string{"tenant_id"},
		),
		FrontendEventsTotal: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "gh_server",
				Subsystem: "frontend",
				Name:      "events_total",
				Help:      "Total number of frontend analytics events received.",
			},
			[]string{"tenant_id", "event", "source", "page", "platform", "location", "success", "traffic_source"},
		),
	}

	reg.MustRegister(
		m.HTTPRequestsTotal,
		m.HTTPRequestDuration,
		m.HTTPRequestsInFlight,
		m.OperationsTotal,
		m.OperationDuration,
		m.ReadyzTotal,
		m.UserCreatedTotal,
		m.OrgCreatedTotal,
		m.RepoCreatedTotal,
		m.FirstGitPushUserTotal,
		m.UserActivationDuration,
		m.RepoActivationDuration,
		m.ActiveUsersDaily,
		m.ActiveReposDaily,
		m.GitPushTotal,
		m.GitReadTotal,
		m.SearchTotal,
		m.StarCreatedTotal,
		m.ReleasePublishedTotal,
		m.IssueOpenedTotal,
		m.IssueClosedTotal,
		m.IssueReopenedTotal,
		m.IssueCommentCreatedTotal,
		m.IssueCloseDuration,
		m.FrontendEventsTotal,
	)

	return m
}

// IncHTTPInFlight increments the in-flight HTTP requests gauge.
func (m *PrometheusMetrics) IncHTTPInFlight(tenantID string) {
	m.HTTPRequestsInFlight.WithLabelValues(tenantLabel(tenantID)).Inc()
}

// DecHTTPInFlight decrements the in-flight HTTP requests gauge.
func (m *PrometheusMetrics) DecHTTPInFlight(tenantID string) {
	m.HTTPRequestsInFlight.WithLabelValues(tenantLabel(tenantID)).Dec()
}

// ObserveHTTPRequest records a completed HTTP request.
func (m *PrometheusMetrics) ObserveHTTPRequest(tenantID, method, route string, status int, duration time.Duration) {
	if method == "" {
		method = "UNKNOWN"
	}
	if route == "" {
		route = "unmatched"
	}
	if status == 0 {
		status = http.StatusOK
	}
	statusLabel := strconv.Itoa(status)
	tenantID = tenantLabel(tenantID)
	m.HTTPRequestsTotal.WithLabelValues(tenantID, method, route, statusLabel).Inc()
	m.HTTPRequestDuration.WithLabelValues(tenantID, method, route, statusLabel).Observe(duration.Seconds())
}

// ObserveOperation records a completed business operation.
func (m *PrometheusMetrics) ObserveOperation(tenantID, channel, operation, result string, duration time.Duration) {
	if channel == "" {
		channel = "unknown"
	}
	if operation == "" {
		operation = "unknown"
	}
	if result == "" {
		result = "unknown"
	}
	tenantID = tenantLabel(tenantID)
	m.OperationsTotal.WithLabelValues(tenantID, channel, operation, result).Inc()
	m.OperationDuration.WithLabelValues(tenantID, channel, operation, result).Observe(duration.Seconds())
}

// ObserveReadyz records a readiness probe result.
func (m *PrometheusMetrics) ObserveReadyz(status string) {
	if status == "" {
		status = "unknown"
	}
	m.ReadyzTotal.WithLabelValues(status).Inc()
}

// IncUserCreated records a created user account.
func (m *PrometheusMetrics) IncUserCreated(tenantID, kind string) {
	if kind == "" {
		kind = "unknown"
	}
	m.UserCreatedTotal.WithLabelValues(tenantLabel(tenantID), kind).Inc()
}

// IncOrgCreated records a created organization account.
func (m *PrometheusMetrics) IncOrgCreated(tenantID string) {
	m.OrgCreatedTotal.WithLabelValues(tenantLabel(tenantID)).Inc()
}

// IncRepoCreated records a created repository.
func (m *PrometheusMetrics) IncRepoCreated(tenantID string) {
	m.RepoCreatedTotal.WithLabelValues(tenantLabel(tenantID)).Inc()
}

// IncFirstGitPushUser records a user's first successful Git push.
func (m *PrometheusMetrics) IncFirstGitPushUser(tenantID string) {
	m.FirstGitPushUserTotal.WithLabelValues(tenantLabel(tenantID)).Inc()
}

// ObserveUserActivationDuration records user activation latency.
func (m *PrometheusMetrics) ObserveUserActivationDuration(tenantID string, duration time.Duration) {
	if duration >= 0 {
		m.UserActivationDuration.WithLabelValues(tenantLabel(tenantID)).Observe(duration.Seconds())
	}
}

// ObserveRepoActivationDuration records repository activation latency.
func (m *PrometheusMetrics) ObserveRepoActivationDuration(tenantID string, duration time.Duration) {
	if duration >= 0 {
		m.RepoActivationDuration.WithLabelValues(tenantLabel(tenantID)).Observe(duration.Seconds())
	}
}

// SetActiveUsersDaily sets today's active user gauge.
func (m *PrometheusMetrics) SetActiveUsersDaily(tenantID string, value float64) {
	m.ActiveUsersDaily.WithLabelValues(tenantLabel(tenantID)).Set(value)
}

// SetActiveReposDaily sets today's active repository gauge.
func (m *PrometheusMetrics) SetActiveReposDaily(tenantID string, value float64) {
	m.ActiveReposDaily.WithLabelValues(tenantLabel(tenantID)).Set(value)
}

// IncGitPush records a successful Git push that changed refs.
func (m *PrometheusMetrics) IncGitPush(tenantID string) {
	m.GitPushTotal.WithLabelValues(tenantLabel(tenantID)).Inc()
}

// IncGitRead records a successful upload-pack request.
func (m *PrometheusMetrics) IncGitRead(tenantID string) {
	m.GitReadTotal.WithLabelValues(tenantLabel(tenantID)).Inc()
}

// IncSearch records a successful search request.
func (m *PrometheusMetrics) IncSearch(tenantID string) {
	m.SearchTotal.WithLabelValues(tenantLabel(tenantID)).Inc()
}

// IncStarCreated records a newly created star.
func (m *PrometheusMetrics) IncStarCreated(tenantID string) {
	m.StarCreatedTotal.WithLabelValues(tenantLabel(tenantID)).Inc()
}

// IncReleasePublished records a non-draft release publication.
func (m *PrometheusMetrics) IncReleasePublished(tenantID string) {
	m.ReleasePublishedTotal.WithLabelValues(tenantLabel(tenantID)).Inc()
}

// IncIssueOpened records an opened issue.
func (m *PrometheusMetrics) IncIssueOpened(tenantID string) {
	m.IssueOpenedTotal.WithLabelValues(tenantLabel(tenantID)).Inc()
}

// IncIssueClosed records a closed issue.
func (m *PrometheusMetrics) IncIssueClosed(tenantID string) {
	m.IssueClosedTotal.WithLabelValues(tenantLabel(tenantID)).Inc()
}

// IncIssueReopened records a reopened issue.
func (m *PrometheusMetrics) IncIssueReopened(tenantID string) {
	m.IssueReopenedTotal.WithLabelValues(tenantLabel(tenantID)).Inc()
}

// IncIssueCommentCreated records a created issue comment.
func (m *PrometheusMetrics) IncIssueCommentCreated(tenantID string) {
	m.IssueCommentCreatedTotal.WithLabelValues(tenantLabel(tenantID)).Inc()
}

// ObserveIssueCloseDuration records issue close latency.
func (m *PrometheusMetrics) ObserveIssueCloseDuration(tenantID string, duration time.Duration) {
	if duration >= 0 {
		m.IssueCloseDuration.WithLabelValues(tenantLabel(tenantID)).Observe(duration.Seconds())
	}
}

// IncFrontendEvent records a normalized frontend analytics event.
func (m *PrometheusMetrics) IncFrontendEvent(tenantID, event, source, page, platform, location, success, trafficSource string) {
	m.FrontendEventsTotal.WithLabelValues(
		tenantLabel(tenantID),
		unknownIfEmpty(event),
		unknownIfEmpty(source),
		unknownIfEmpty(page),
		unknownIfEmpty(platform),
		unknownIfEmpty(location),
		unknownIfEmpty(success),
		unknownIfEmpty(trafficSource),
	).Inc()
}

var (
	defaultRecorder   Recorder = noopRecorder{}
	defaultPrometheus *PrometheusMetrics
	defaultHandler    http.Handler
	initOnce          sync.Once
)

// Init registers metrics with the default registry and returns the handler.
func Init() http.Handler {
	initOnce.Do(func() {
		defaultPrometheus = NewPrometheusMetrics(prometheus.DefaultRegisterer)
		defaultRecorder = defaultPrometheus
		defaultHandler = promhttp.Handler()
	})
	return defaultHandler
}

// DefaultPrometheus returns the default Prometheus collector set, if initialized.
func DefaultPrometheus() *PrometheusMetrics {
	return defaultPrometheus
}

// IncHTTPInFlight increments the in-flight HTTP requests gauge.
func IncHTTPInFlight(tenantID string) {
	defaultRecorder.IncHTTPInFlight(tenantID)
}

// DecHTTPInFlight decrements the in-flight HTTP requests gauge.
func DecHTTPInFlight(tenantID string) {
	defaultRecorder.DecHTTPInFlight(tenantID)
}

// ObserveHTTPRequest records a completed HTTP request.
func ObserveHTTPRequest(tenantID, method, route string, status int, duration time.Duration) {
	defaultRecorder.ObserveHTTPRequest(tenantID, method, route, status, duration)
}

// ObserveOperation records a completed business operation.
func ObserveOperation(tenantID, channel, operation, result string, duration time.Duration) {
	defaultRecorder.ObserveOperation(tenantID, channel, operation, result, duration)
}

// ObserveReadyz records readiness probe results.
func ObserveReadyz(status string) {
	defaultRecorder.ObserveReadyz(status)
}

// IncUserCreated records a created user account.
func IncUserCreated(tenantID, kind string) {
	defaultRecorder.IncUserCreated(tenantID, kind)
}

// IncOrgCreated records a created organization account.
func IncOrgCreated(tenantID string) {
	defaultRecorder.IncOrgCreated(tenantID)
}

// IncRepoCreated records a created repository.
func IncRepoCreated(tenantID string) {
	defaultRecorder.IncRepoCreated(tenantID)
}

// IncFirstGitPushUser records a user's first successful Git push.
func IncFirstGitPushUser(tenantID string) {
	defaultRecorder.IncFirstGitPushUser(tenantID)
}

// ObserveUserActivationDuration records user activation latency.
func ObserveUserActivationDuration(tenantID string, duration time.Duration) {
	defaultRecorder.ObserveUserActivationDuration(tenantID, duration)
}

// ObserveRepoActivationDuration records repository activation latency.
func ObserveRepoActivationDuration(tenantID string, duration time.Duration) {
	defaultRecorder.ObserveRepoActivationDuration(tenantID, duration)
}

// SetActiveUsersDaily sets today's active user gauge.
func SetActiveUsersDaily(tenantID string, value float64) {
	defaultRecorder.SetActiveUsersDaily(tenantID, value)
}

// SetActiveReposDaily sets today's active repository gauge.
func SetActiveReposDaily(tenantID string, value float64) {
	defaultRecorder.SetActiveReposDaily(tenantID, value)
}

// IncGitPush records a successful Git push that changed refs.
func IncGitPush(tenantID string) {
	defaultRecorder.IncGitPush(tenantID)
}

// IncGitRead records a successful upload-pack request.
func IncGitRead(tenantID string) {
	defaultRecorder.IncGitRead(tenantID)
}

// IncSearch records a successful search request.
func IncSearch(tenantID string) {
	defaultRecorder.IncSearch(tenantID)
}

// IncStarCreated records a newly created star.
func IncStarCreated(tenantID string) {
	defaultRecorder.IncStarCreated(tenantID)
}

// IncReleasePublished records a non-draft release publication.
func IncReleasePublished(tenantID string) {
	defaultRecorder.IncReleasePublished(tenantID)
}

// IncIssueOpened records an opened issue.
func IncIssueOpened(tenantID string) {
	defaultRecorder.IncIssueOpened(tenantID)
}

// IncIssueClosed records a closed issue.
func IncIssueClosed(tenantID string) {
	defaultRecorder.IncIssueClosed(tenantID)
}

// IncIssueReopened records a reopened issue.
func IncIssueReopened(tenantID string) {
	defaultRecorder.IncIssueReopened(tenantID)
}

// IncIssueCommentCreated records a created issue comment.
func IncIssueCommentCreated(tenantID string) {
	defaultRecorder.IncIssueCommentCreated(tenantID)
}

// ObserveIssueCloseDuration records issue close latency.
func ObserveIssueCloseDuration(tenantID string, duration time.Duration) {
	defaultRecorder.ObserveIssueCloseDuration(tenantID, duration)
}

// IncFrontendEvent records a normalized frontend analytics event.
func IncFrontendEvent(tenantID, event, source, page, platform, location, success, trafficSource string) {
	defaultRecorder.IncFrontendEvent(tenantID, event, source, page, platform, location, success, trafficSource)
}

func tenantLabel(value string) string {
	if value == "" {
		return "unresolved"
	}
	return value
}

func unknownIfEmpty(value string) string {
	if value == "" {
		return "unknown"
	}
	return value
}

func registerDefaultCollectors(reg prometheus.Registerer) {
	registerIfAbsent(reg, prometheus.NewGoCollector())
	registerIfAbsent(reg, prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
}

func registerIfAbsent(reg prometheus.Registerer, collector prometheus.Collector) {
	if err := reg.Register(collector); err != nil {
		if _, ok := err.(prometheus.AlreadyRegisteredError); ok {
			return
		}
		panic(err)
	}
}
