namespace Prometheus
{
    /// <summary>
    /// Static class for easy creation of metrics. Acts as the entry point to the prometheus-net metrics recording API.
    /// 
    /// Some built-in metrics are registered by default in the default collector registry. If these default metrics are
    /// not desired, call <see cref="SuppressDefaultMetrics()"/> to remove them before registering your own.
    /// </summary>
    public static class Metrics
    {
        /// <summary>
        /// The default registry where all metrics are registered by default.
        /// </summary>
        public static CollectorRegistry DefaultRegistry { get; private set; }
        
        /// <summary>
        /// The default metric factory used to create collectors in the default registry.
        /// </summary>
        public static MetricFactory DefaultFactory { get; private set; }

        /// <summary>
        /// Creates a new registry. You may want to use multiple registries if you want to
        /// export different sets of metrics via different exporters (e.g. on different URLs).
        /// </summary>
        public static CollectorRegistry NewCustomRegistry() => new CollectorRegistry();

        /// <summary>
        /// Returns an instance of <see cref="MetricFactory" /> that you can use to register metrics in a custom registry.
        /// </summary>
        public static MetricFactory WithCustomRegistry(CollectorRegistry registry) =>
            new MetricFactory(registry);

        /// <summary>
        /// Adds the specified static labels to all metrics created using the returned factory.
        /// </summary>
        public static IMetricFactory WithLabels(IDictionary<string, string> labels) =>
            new MetricFactory(DefaultRegistry, LabelSequence.From(labels));

        /// <summary>
        /// Returns a factory that creates metrics with a managed lifetime.
        /// </summary>
        /// <param name="expiresAfter">
        /// Metrics created from this factory will expire after this time span elapses, enabling automatic deletion of unused metrics.
        /// The expiration timer is reset to zero for the duration of any active lifetime-extension lease that is taken on a specific metric.
        /// </param>
        public static IManagedLifetimeMetricFactory WithManagedLifetime(TimeSpan expiresAfter) =>
            DefaultFactory.WithManagedLifetime(expiresAfter);

        /// <summary>
        /// Counters only increase in value and reset to zero when the process restarts.
        /// </summary>
        public static Counter CreateCounter(string name, string help, CounterConfiguration? configuration = null) =>
            DefaultFactory.CreateCounter(name, help, configuration);

        /// <summary>
        /// Gauges can have any numeric value and change arbitrarily.
        /// </summary>
        public static Gauge CreateGauge(string name, string help, GaugeConfiguration? configuration = null) =>
            DefaultFactory.CreateGauge(name, help, configuration);

        /// <summary>
        /// Summaries track the trends in events over time (10 minutes by default).
        /// </summary>
        public static Summary CreateSummary(string name, string help, SummaryConfiguration? configuration = null) =>
            DefaultFactory.CreateSummary(name, help, configuration);

        /// <summary>
        /// Histograms track the size and number of events in buckets.
        /// </summary>
        public static Histogram CreateHistogram(string name, string help, HistogramConfiguration? configuration = null) =>
            DefaultFactory.CreateHistogram(name, help, configuration);

        /// <summary>
        /// Counters only increase in value and reset to zero when the process restarts.
        /// </summary>
        public static Counter CreateCounter(string name, string help, string[] labelNames, CounterConfiguration? configuration = null) =>
            DefaultFactory.CreateCounter(name, help, labelNames, configuration);

        /// <summary>
        /// Gauges can have any numeric value and change arbitrarily.
        /// </summary>
        public static Gauge CreateGauge(string name, string help, string[] labelNames, GaugeConfiguration? configuration = null) =>
            DefaultFactory.CreateGauge(name, help, labelNames, configuration);

        /// <summary>
        /// Summaries track the trends in events over time (10 minutes by default).
        /// </summary>
        public static Summary CreateSummary(string name, string help, string[] labelNames, SummaryConfiguration? configuration = null) =>
            DefaultFactory.CreateSummary(name, help, labelNames, configuration);

        /// <summary>
        /// Histograms track the size and number of events in buckets.
        /// </summary>
        public static Histogram CreateHistogram(string name, string help, string[] labelNames, HistogramConfiguration? configuration = null) =>
            DefaultFactory.CreateHistogram(name, help, labelNames, configuration);

        /// <summary>
        /// Counters only increase in value and reset to zero when the process restarts.
        /// </summary>
        public static Counter CreateCounter(string name, string help, params string[] labelNames) =>
            DefaultFactory.CreateCounter(name, help, labelNames);

        /// <summary>
        /// Gauges can have any numeric value and change arbitrarily.
        /// </summary>
        public static Gauge CreateGauge(string name, string help, params string[] labelNames) =>
            DefaultFactory.CreateGauge(name, help, labelNames);

        /// <summary>
        /// Summaries track the trends in events over time (10 minutes by default).
        /// </summary>
        public static Summary CreateSummary(string name, string help, params string[] labelNames) =>
            DefaultFactory.CreateSummary(name, help, labelNames);

        /// <summary>
        /// Histograms track the size and number of events in buckets.
        /// </summary>
        public static Histogram CreateHistogram(string name, string help, params string[] labelNames) =>
            DefaultFactory.CreateHistogram(name, help, labelNames);

        static Metrics()
        {
            DefaultRegistry = new CollectorRegistry();

            // Configures defaults to their default behaviors, can be overridden by user if they desire (before first collection).
            SuppressDefaultMetrics(SuppressDefaultMetricOptions.SuppressNone);

            DefaultFactory = new MetricFactory(DefaultRegistry);
        }

        /// <summary>
        /// Suppresses the registration of the default sample metrics from the default registry.
        /// Has no effect if not called on startup (it will not remove metrics from a registry already in use).
        /// </summary>
        public static void SuppressDefaultMetrics() => SuppressDefaultMetrics(SuppressDefaultMetricOptions.SuppressAll);

        /// <summary>
        /// Suppresses the registration of the default sample metrics from the default registry.
        /// Has no effect if not called on startup (it will not remove metrics from a registry already in use).
        /// </summary>
        public static void SuppressDefaultMetrics(SuppressDefaultMetricOptions options)
        {
            options ??= SuppressDefaultMetricOptions.SuppressAll;

            // Only has effect if called before the registry is collected from. Otherwise a no-op.
            DefaultRegistry.SetBeforeFirstCollectCallback(delegate
            {
                var configureCallbacks = new SuppressDefaultMetricOptions.ConfigurationCallbacks()
                {
#if NET
                    ConfigureEventCounterAdapter = _configureEventCounterAdapterCallback,
#endif
#if NET6_0_OR_GREATER
                    ConfigureMeterAdapter = _configureMeterAdapterOptions
#endif
                };

                options.ApplyToDefaultRegistry(configureCallbacks);
            });
        }

#if NET
        private static Action<EventCounterAdapterOptions> _configureEventCounterAdapterCallback = delegate { };

        /// <summary>
        /// Configures the event counter adapter that is enabled by default on startup.
        /// </summary>
        public static void ConfigureEventCounterAdapter(Action<EventCounterAdapterOptions> callback) => _configureEventCounterAdapterCallback = callback;
#endif

#if NET6_0_OR_GREATER
        private static Action<MeterAdapterOptions> _configureMeterAdapterOptions = delegate { };

        /// <summary>
        /// Configures the meter adapter that is enabled by default on startup.
        /// </summary>
        public static void ConfigureMeterAdapter(Action<MeterAdapterOptions> callback) => _configureMeterAdapterOptions = callback;
#endif
    }
}