#pragma once

// WeaselDB Metrics System
//
// High-performance metrics collection with Prometheus-compatible output.
//
// DESIGN PRINCIPLES:
// - Single-writer semantics: Each metric instance bound to creating thread
// - Lock-free operations using atomic<uint64_t> storage for doubles
// - Full IEEE 754 double precision preservation via bit reinterpretation
// - Single global registry: All metrics registered in one global namespace
//
// CRITICAL THREAD SAFETY CONSTRAINT:
// Each metric instance has exactly ONE writer thread (the creating thread).
// It is undefined behavior to call inc()/dec()/set()/observe() from a different
// thread.
//
// REGISTRY MODEL:
// This implementation uses a single global registry for all metrics, unlike
// typical Prometheus client libraries that support multiple registries.
// This design choice prioritizes simplicity and performance over flexibility.
//
// METRIC LIFECYCLE:
// Metrics are created once and persist for the application lifetime. There is
// no unregistration mechanism - this prevents accidental metric loss and
// simplifies the implementation.
//
// USAGE:
//   auto counter_family = metric::create_counter("requests_total", "Total
//   requests"); auto counter = counter_family.create({{"method", "GET"}});  //
//   Bound to this thread counter.inc(1.0);  // ONLY call from creating thread
//
//   auto histogram_family = metric::create_histogram("latency", "Request
//   latency", {0.1, 0.5, 1.0}); auto histogram =
//   histogram_family.create({{"endpoint", "/api"}});  // Bound to this thread
//   histogram.observe(0.25);  // ONLY call from creating thread

#include <functional>
#include <initializer_list>
#include <span>
#include <string>
#include <type_traits>
#include <vector>

#include "arena_allocator.hpp"

namespace metric {

// Forward declarations
template <typename T> struct Family;

// Callback function type for dynamic metric values
// Called during render() to get current metric value
// THREAD SAFETY: May be called from arbitrary thread, but serialized by
// render() mutex - no need to be thread-safe internally
template <typename T> using MetricCallback = std::function<double()>;

// Counter: A metric value that only increases.
//
// THREAD SAFETY RULES:
// 1. Do not call inc() on the same Counter object from multiple threads.
//    Each object must have only one writer thread.
// 2. To use Counters concurrently, each thread must create its own Counter
//    object.
// 3. When rendered, the values of all Counter objects with the same labels
//    are summed together into a single total.
struct Counter {
  void inc(double = 1.0); // Increment counter (must be >= 0)

private:
  Counter();
  friend struct Metric;
  template <class> friend struct Family;
  struct State;
  State *p;
};

// Gauge: A metric value that can be set, increased, or decreased.
//
// THREAD SAFETY RULES:
// 1. Do not call inc(), dec(), or set() on the same Gauge object from
//    multiple threads. Each object must have only one writer thread.
// 2. To use Gauges concurrently, each thread must create its own Gauge object.
// 3. If multiple Gauge objects are created with the same labels, their
//    operations are combined. For example, increments from different objects
//    are cumulative.
// 4. For independent gauges, create them with unique labels.
struct Gauge {
  void inc(double = 1.0);
  void dec(double = 1.0);
  void set(double);

private:
  Gauge();
  friend struct Metric;
  template <class> friend struct Family;
  struct State;
  State *p;
};

// Histogram: A metric that samples observations into buckets.
//
// THREAD SAFETY RULES:
// 1. Do not call observe() on the same Histogram object from multiple
//    threads. Each object must have only one writer thread.
// 2. To use Histograms concurrently, each thread must create its own
//    Histogram object.
// 3. When rendered, the observations from all Histogram objects with the
//    same labels are combined into a single histogram.
struct Histogram {
  void observe(double); // Record observation in appropriate bucket

private:
  Histogram();
  friend struct Metric;
  template <class> friend struct Family;
  struct State;
  State *p;
};

// Family: Factory for creating metric instances with different label
// combinations Each family represents one metric name with varying labels
template <class T> struct Family {
  static_assert(std::is_same_v<T, Counter> || std::is_same_v<T, Gauge> ||
                std::is_same_v<T, Histogram>);

  // Create metric instance with specific labels
  // Labels are sorted by key for Prometheus compatibility
  // ERROR: Will abort if labels already registered via register_callback()
  // OK: Multiple calls with same labels return same instance (idempotent)
  T create(std::vector<std::pair<std::string, std::string>> labels);

  // Register callback-based metric (Counter and Gauge only)
  // Validates that label set isn't already taken
  void
  register_callback(std::vector<std::pair<std::string, std::string>> labels,
                    MetricCallback<T> callback);

private:
  Family();
  friend struct Metric;
  friend Family<Counter> create_counter(std::string, std::string);
  friend Family<Gauge> create_gauge(std::string, std::string);
  friend Family<Histogram> create_histogram(std::string, std::string,
                                            std::span<const double>);

  struct State;
  State *p;
};

// Factory functions for creating metric families
// IMPORTANT: name and help must point to static memory (string literals)

// Create counter family (monotonically increasing values)
// ERROR: Aborts if family with same name is registered with different help
// text.
Family<Counter> create_counter(std::string name, std::string help);

// Create gauge family (can increase/decrease)
// ERROR: Aborts if family with same name is registered with different help
// text.
Family<Gauge> create_gauge(std::string name, std::string help);

// Create histogram family with custom buckets
// Buckets will be sorted, deduplicated, and +Inf will be added automatically
// ERROR: Aborts if family with same name is registered with different help text
// or buckets.
Family<Histogram> create_histogram(std::string name, std::string help,
                                   std::span<const double> buckets);

// Helper functions for generating standard histogram buckets
// Following Prometheus client library conventions

// Generate linear buckets: start, start+width, start+2*width, ...,
// start+(count-1)*width Example: linear_buckets(0, 10, 5) = {0, 10, 20, 30, 40}
std::vector<double> linear_buckets(double start, double width, int count);

// Generate exponential buckets: start, start*factor, start*factor^2, ...,
// start*factor^(count-1) Example: exponential_buckets(1, 2, 5) = {1, 2, 4, 8,
// 16}
std::vector<double> exponential_buckets(double start, double factor, int count);

// Render all metrics in Prometheus text format
// Returns chunks of Prometheus exposition format (includes # HELP and # TYPE
// lines) Each string_view may contain multiple lines separated by '\n' String
// views are NOT null-terminated - use .size() for length All string data
// allocated in provided arena for zero-copy efficiency
// TODO: Implement Prometheus text exposition format
// THREAD SAFETY: Serialized by global mutex - callbacks need not be thread-safe
std::span<std::string_view> render(ArenaAllocator &arena);

// Validation functions for Prometheus compatibility
bool is_valid_metric_name(const std::string &name);
bool is_valid_label_key(const std::string &key);
bool is_valid_label_value(const std::string &value);

// Note: Histograms do not support callbacks due to their multi-value nature
// (buckets + sum + count). Use static histogram metrics only.

} // namespace metric