#include "metric.hpp"

#include <algorithm>
#include <atomic>
#include <bit>
#include <cassert>
#include <cctype>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <limits>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <type_traits>
#include <unordered_map>
#include <vector>

#include <simdutf.h>

#include "format.hpp"

// WeaselDB Metrics System Design:
//
// THREADING MODEL:
// - Counters and Histograms: Per-thread storage, single writer per thread
// - Gauges: Global storage with mutex protection (multi-writer)
//
// PRECISION STRATEGY:
// - Use atomic<uint64_t> for lock-free storage
// - Store doubles using std::bit_cast to uint64_t (preserves full IEEE 754
// precision)
// - Single writer assumption allows simple load/store without CAS loops
//
// MEMORY MODEL:
// - Thread-local metrics auto-cleanup on thread destruction
// - Global metrics (gauges) persist for application lifetime
// - Histogram buckets are sorted, deduplicated, and include +Inf bucket

namespace metric {

// Validation helper that works in both debug and release builds
static void validate_or_abort(bool condition, const char *message,
                              const char *value) {
  if (!condition) {
    std::fprintf(stderr, "WeaselDB metric validation failed: %s: '%s'\n",
                 message, value);
    std::abort();
  }
}

// Labels key for second level of map
struct LabelsKey {
  std::vector<std::pair<std::string, std::string>> labels;

  LabelsKey(std::vector<std::pair<std::string, std::string>> l)
      : labels(std::move(l)) {
    // Validate all label keys and values
    for (const auto &[key, value] : labels) {
      validate_or_abort(is_valid_label_key(key), "invalid label key",
                        key.c_str());
      validate_or_abort(is_valid_label_value(value), "invalid label value",
                        value.c_str());
    }

    // Sort labels by key for Prometheus compatibility
    std::sort(labels.begin(), labels.end(),
              [](const auto &a, const auto &b) { return a.first < b.first; });
  }

  bool operator==(const LabelsKey &other) const {
    return labels == other.labels;
  }
};

} // namespace metric

namespace std {
template <> struct hash<metric::LabelsKey> {
  std::size_t operator()(const metric::LabelsKey &k) const {
    std::size_t hash_value = 0;
    for (const auto &[key, value] : k.labels) {
      // Combine hashes using a simple but effective method
      hash_value ^= std::hash<std::string>{}(key) + 0x9e3779b9 +
                    (hash_value << 6) + (hash_value >> 2);
      hash_value ^= std::hash<std::string>{}(value) + 0x9e3779b9 +
                    (hash_value << 6) + (hash_value >> 2);
    }
    return hash_value;
  }
};
} // namespace std

namespace metric {

using AtomicWord = std::atomic<uint64_t>;

// DESIGN: Store doubles in atomic<uint64_t> for lock-free operations
// - Preserves full IEEE 754 double precision (no truncation)
// - Allows atomic load/store without locks
// - Use std::bit_cast for safe conversion between double and uint64_t

// Family::State structures own the second-level maps (labels -> instances)
template <> struct Family<Counter>::State {
  std::string name;
  std::string help;

  struct PerThreadState {
    std::unordered_map<LabelsKey, std::unique_ptr<Counter::State>> instances;
  };
  std::unordered_map<std::thread::id, PerThreadState> perThreadState;

  // Callback-based metrics (global, not per-thread)
  std::unordered_map<LabelsKey, MetricCallback<Counter>> callbacks;
};

template <> struct Family<Gauge>::State {
  std::string name;
  std::string help;
  std::unordered_map<LabelsKey, std::unique_ptr<Gauge::State>> instances;

  // Callback-based metrics
  std::unordered_map<LabelsKey, MetricCallback<Gauge>> callbacks;
};

template <> struct Family<Histogram>::State {
  std::string name;
  std::string help;
  std::vector<double> buckets;

  struct PerThreadState {
    std::unordered_map<LabelsKey, std::unique_ptr<Histogram::State>> instances;
  };
  std::unordered_map<std::thread::id, PerThreadState> perThreadState;

  // Note: No callbacks map - histograms don't support callback-based metrics
};

// Counter: Thread-local, monotonically increasing, single writer per thread
struct Counter::State {
  AtomicWord value; // Stores double as uint64_t bits
  friend struct Metric;
};

// Gauge: Global, can increase/decrease, multiple writers (uses atomic CAS)
struct Gauge::State {
  AtomicWord value; // Stores double as uint64_t bits, lock-free
  friend struct Metric;
};

// Histogram: Thread-local buckets, single writer per thread
struct Histogram::State {
  std::vector<double>
      thresholds; // Bucket boundaries (sorted, deduplicated, includes +Inf)
  std::vector<AtomicWord> counts; // Count per bucket (uint64_t)
  AtomicWord sum; // Sum of observations (double stored as uint64_t bits)
  AtomicWord observations; // Total observation count (uint64_t)
  friend struct Metric;
};

struct Metric {
  static std::mutex mutex;

  // Two-level map: name -> Family
  static std::unordered_map<std::string,
                            std::unique_ptr<Family<Counter>::State>>
      counterFamilies;
  static std::unordered_map<std::string, std::unique_ptr<Family<Gauge>::State>>
      gaugeFamilies;
  static std::unordered_map<std::string,
                            std::unique_ptr<Family<Histogram>::State>>
      histogramFamilies;

  // Thread cleanup for per-family thread-local storage
  struct ThreadInit {
    ThreadInit() {
      // Thread registration happens lazily when metrics are created
    }
    ~ThreadInit() {
      // Clean up this thread's storage from all families
      std::unique_lock<std::mutex> _{mutex};
      auto thread_id = std::this_thread::get_id();

      // Clean up counter families
      for (auto &[name, family] : counterFamilies) {
        family->perThreadState.erase(thread_id);
      }

      // Clean up histogram families
      for (auto &[name, family] : histogramFamilies) {
        family->perThreadState.erase(thread_id);
      }

      // Gauges are global, no per-thread cleanup needed
    }
  };
  static thread_local ThreadInit thread_init;

  // Thread cleanup now handled by ThreadInit RAII

  static Counter create_counter_instance(
      Family<Counter> *family,
      const std::vector<std::pair<std::string, std::string>> &labels) {
    std::unique_lock<std::mutex> _{mutex};
    LabelsKey key{labels};

    // Validate that labels aren't already registered as callback
    validate_or_abort(
        family->p->callbacks.find(key) == family->p->callbacks.end(),
        "labels already registered as callback",
        key.labels.empty() ? "(no labels)" : key.labels[0].first.c_str());

    auto &ptr =
        family->p->perThreadState[std::this_thread::get_id()].instances[key];
    if (!ptr) {
      ptr = std::make_unique<Counter::State>();
      ptr->value.store(0, std::memory_order_relaxed);
    }
    Counter result;
    result.p = ptr.get();
    return result;
  }

  static Gauge create_gauge_instance(
      Family<Gauge> *family,
      const std::vector<std::pair<std::string, std::string>> &labels) {
    std::unique_lock<std::mutex> _{mutex};
    LabelsKey key{labels};

    // Validate that labels aren't already registered as callback
    validate_or_abort(
        family->p->callbacks.find(key) == family->p->callbacks.end(),
        "labels already registered as callback",
        key.labels.empty() ? "(no labels)" : key.labels[0].first.c_str());

    auto &ptr = family->p->instances[key];
    if (!ptr) {
      ptr = std::make_unique<Gauge::State>();
      ptr->value.store(0, std::memory_order_relaxed);
    }
    Gauge result;
    result.p = ptr.get();
    return result;
  }

  static Histogram create_histogram_instance(
      Family<Histogram> *family,
      const std::vector<std::pair<std::string, std::string>> &labels) {
    std::unique_lock<std::mutex> _{mutex};
    LabelsKey key{labels};
    auto &ptr =
        family->p->perThreadState[std::this_thread::get_id()].instances[key];
    if (!ptr) {
      ptr = std::make_unique<Histogram::State>();
      // DESIGN: Prometheus-compatible histogram buckets
      // Use buckets from family configuration
      ptr->thresholds = family->p->buckets; // Already sorted and deduplicated

      // DESIGN: std::atomic is not copy-constructible
      // Initialize vector with correct size, all atomics explicitly initialized
      // to 0
      ptr->counts = std::vector<AtomicWord>(ptr->thresholds.size());
      for (auto &count : ptr->counts) {
        count.store(0, std::memory_order_relaxed);
      }
      ptr->sum.store(0, std::memory_order_relaxed);
      ptr->observations.store(0, std::memory_order_relaxed);
    }
    Histogram result;
    result.p = ptr.get();
    return result;
  }
};

Counter::Counter() = default;

void Counter::inc(double x) {
  // DESIGN: Single writer per thread allows simple load-modify-store
  // No CAS loop needed since only one thread writes to this counter
  auto current_value =
      std::bit_cast<double>(p->value.load(std::memory_order_relaxed));
  auto new_value = current_value + x;

  // Validate monotonic property (counter never decreases)
  if (new_value < current_value) [[unlikely]] {
    validate_or_abort(false, "counter value overflow/wraparound detected",
                      std::to_string(new_value).c_str());
  }

  p->value.store(std::bit_cast<uint64_t>(new_value), std::memory_order_relaxed);
}

Gauge::Gauge() = default;

void Gauge::inc(double x) {
  // Lock-free increment using CAS loop
  uint64_t expected = p->value.load(std::memory_order_relaxed);
  uint64_t desired;
  do {
    double current_value = std::bit_cast<double>(expected);
    double new_value = current_value + x;
    desired = std::bit_cast<uint64_t>(new_value);
  } while (!p->value.compare_exchange_weak(expected, desired,
                                           std::memory_order_relaxed));
}
void Gauge::dec(double x) {
  // Lock-free decrement using CAS loop
  uint64_t expected = p->value.load(std::memory_order_relaxed);
  uint64_t desired;
  do {
    double current_value = std::bit_cast<double>(expected);
    double new_value = current_value - x;
    desired = std::bit_cast<uint64_t>(new_value);
  } while (!p->value.compare_exchange_weak(expected, desired,
                                           std::memory_order_relaxed));
}
void Gauge::set(double x) {
  // Simple atomic store for set operation
  p->value.store(std::bit_cast<uint64_t>(x), std::memory_order_relaxed);
}

Histogram::Histogram() = default;

void Histogram::observe(double x) {
  assert(p->thresholds.size() == p->counts.size());

  // Increment bucket counts (cumulative: each bucket counts all values <=
  // threshold)
  for (size_t i = 0; i < p->thresholds.size(); ++i) {
    p->counts[i].fetch_add(x <= p->thresholds[i], std::memory_order_relaxed);
  }

  // DESIGN: Single writer per thread allows simple load-modify-store for sum
  // No CAS loop needed since only one thread writes to this histogram
  auto current_sum =
      std::bit_cast<double>(p->sum.load(std::memory_order_relaxed));
  p->sum.store(std::bit_cast<uint64_t>(current_sum + x),
               std::memory_order_relaxed);

  p->observations.fetch_add(1, std::memory_order_relaxed);
}

template <> Family<Counter>::Family() = default;
template <> Family<Gauge>::Family() = default;
template <> Family<Histogram>::Family() = default;

template <>
Counter Family<Counter>::create(
    std::vector<std::pair<std::string, std::string>> labels) {
  return Metric::create_counter_instance(this, labels);
}

template <>
Gauge Family<Gauge>::create(
    std::vector<std::pair<std::string, std::string>> labels) {
  return Metric::create_gauge_instance(this, labels);
}

template <>
Histogram Family<Histogram>::create(
    std::vector<std::pair<std::string, std::string>> labels) {
  return Metric::create_histogram_instance(this, labels);
}

Family<Counter> create_counter(std::string name, std::string help) {
  validate_or_abort(is_valid_metric_name(name), "invalid counter name",
                    name.c_str());

  std::unique_lock<std::mutex> _{Metric::mutex};
  auto &familyPtr = Metric::counterFamilies[name];
  if (!familyPtr) {
    familyPtr = std::make_unique<Family<Counter>::State>();
    familyPtr->name = std::move(name);
    familyPtr->help = std::move(help);
  } else {
    validate_or_abort(
        familyPtr->help == help,
        "metric family already registered with different help text",
        name.c_str());
  }
  Family<Counter> family;
  family.p = familyPtr.get();
  return family;
}

Family<Gauge> create_gauge(std::string name, std::string help) {
  validate_or_abort(is_valid_metric_name(name), "invalid gauge name",
                    name.c_str());

  std::unique_lock<std::mutex> _{Metric::mutex};
  auto &familyPtr = Metric::gaugeFamilies[name];
  if (!familyPtr) {
    familyPtr = std::make_unique<Family<Gauge>::State>();
    familyPtr->name = std::move(name);
    familyPtr->help = std::move(help);
  } else {
    validate_or_abort(
        familyPtr->help == help,
        "metric family already registered with different help text",
        name.c_str());
  }
  Family<Gauge> family;
  family.p = familyPtr.get();
  return family;
}

Family<Histogram> create_histogram(std::string name, std::string help,
                                   std::span<const double> buckets) {
  validate_or_abort(is_valid_metric_name(name), "invalid histogram name",
                    name.c_str());

  std::unique_lock<std::mutex> _{Metric::mutex};
  auto &familyPtr = Metric::histogramFamilies[name];
  if (!familyPtr) {
    familyPtr = std::make_unique<Family<Histogram>::State>();
    familyPtr->name = std::move(name);
    familyPtr->help = std::move(help);

    // DESIGN: Prometheus-compatible histogram buckets
    familyPtr->buckets = std::vector<double>(buckets.begin(), buckets.end());
    std::sort(familyPtr->buckets.begin(), familyPtr->buckets.end());
    familyPtr->buckets.erase(
        std::unique(familyPtr->buckets.begin(), familyPtr->buckets.end()),
        familyPtr->buckets.end());
    // +Inf bucket captures all observations (Prometheus requirement)
    if (familyPtr->buckets.empty() ||
        familyPtr->buckets.back() != std::numeric_limits<double>::infinity()) {
      familyPtr->buckets.push_back(std::numeric_limits<double>::infinity());
    }
  } else {
    validate_or_abort(
        familyPtr->help == help,
        "metric family already registered with different help text",
        name.c_str());
    std::vector<double> new_buckets_vec(buckets.begin(), buckets.end());
    std::sort(new_buckets_vec.begin(), new_buckets_vec.end());
    new_buckets_vec.erase(
        std::unique(new_buckets_vec.begin(), new_buckets_vec.end()),
        new_buckets_vec.end());
    if (new_buckets_vec.empty() ||
        new_buckets_vec.back() != std::numeric_limits<double>::infinity()) {
      new_buckets_vec.push_back(std::numeric_limits<double>::infinity());
    }
    validate_or_abort(familyPtr->buckets == new_buckets_vec,
                      "metric family already registered with different buckets",
                      name.c_str());
  }
  Family<Histogram> family;
  family.p = familyPtr.get();
  return family;
}

std::vector<double> linear_buckets(double start, double width, int count) {
  validate_or_abort(width > 0, "linear bucket width must be positive",
                    std::to_string(width).c_str());
  validate_or_abort(count >= 0, "linear bucket count must be non-negative",
                    std::to_string(count).c_str());

  std::vector<double> buckets;
  buckets.reserve(count);

  for (int i = 0; i < count; ++i) {
    buckets.push_back(start + i * width);
  }

  return buckets;
}

std::vector<double> exponential_buckets(double start, double factor,
                                        int count) {
  validate_or_abort(start > 0, "exponential bucket start must be positive",
                    std::to_string(start).c_str());
  validate_or_abort(factor > 1, "exponential bucket factor must be > 1",
                    std::to_string(factor).c_str());
  validate_or_abort(count >= 0, "exponential bucket count must be non-negative",
                    std::to_string(count).c_str());

  std::vector<double> buckets;
  buckets.reserve(count);

  double current = start;
  for (int i = 0; i < count; ++i) {
    buckets.push_back(current);
    current *= factor;
  }

  return buckets;
}

// Prometheus validation functions
// Metric names must match [a-zA-Z_:][a-zA-Z0-9_:]*
bool is_valid_metric_name(const std::string &name) {
  if (name.empty())
    return false;

  // First character must be letter, underscore, or colon
  char first = name[0];
  if (!std::isalpha(first) && first != '_' && first != ':') {
    return false;
  }

  // Remaining characters must be alphanumeric, underscore, or colon
  for (size_t i = 1; i < name.size(); ++i) {
    char c = name[i];
    if (!std::isalnum(c) && c != '_' && c != ':') {
      return false;
    }
  }

  return true;
}

// Label keys must match [a-zA-Z_][a-zA-Z0-9_]*
bool is_valid_label_key(const std::string &key) {
  if (key.empty())
    return false;

  // First character must be letter or underscore
  char first = key[0];
  if (!std::isalpha(first) && first != '_') {
    return false;
  }

  // Remaining characters must be alphanumeric or underscore
  for (size_t i = 1; i < key.size(); ++i) {
    char c = key[i];
    if (!std::isalnum(c) && c != '_') {
      return false;
    }
  }

  // Label keys starting with __ are reserved for internal use
  if (key.size() >= 2 && key[0] == '_' && key[1] == '_') {
    return false;
  }

  return true;
}

// Label values can contain any UTF-8 characters (no specific restrictions)
bool is_valid_label_value(const std::string &value) {
  // Prometheus allows any UTF-8 string as label value
  // Validate UTF-8 encoding for correctness using simdutf
  return simdutf::validate_utf8(value.c_str(), value.size());
}

std::span<std::string_view> render(ArenaAllocator &arena) {
  std::unique_lock<std::mutex> _{Metric::mutex};

  std::vector<std::string_view> output;

  auto format_labels =
      [&](const std::vector<std::pair<std::string_view, std::string_view>>
              &labels) -> std::string_view {
    if (labels.empty()) {
      return "";
    }

    size_t required_size = 2; // {}
    for (const auto &[key, value] : labels) {
      required_size += key.length() + 3 + value.length(); // key="value"
      for (char c : value) {
        if (c == '\\' || c == '"' || c == '\n') {
          required_size++;
        }
      }
    }
    if (!labels.empty()) {
      required_size += labels.size() - 1; // commas
    }

    char *buf = arena.allocate<char>(required_size);
    char *p = buf;

    *p++ = '{';
    for (size_t i = 0; i < labels.size(); ++i) {
      if (i > 0)
        *p++ = ',';
      std::memcpy(p, labels[i].first.data(), labels[i].first.length());
      p += labels[i].first.length();
      *p++ = '=';
      *p++ = '"';
      for (char c : labels[i].second) {
        switch (c) {
        case '\\':
          *p++ = '\\';
          *p++ = '\\';
          break;
        case '"':
          *p++ = '\\';
          *p++ = '"';
          break;
        case '\n':
          *p++ = '\\';
          *p++ = 'n';
          break;
        default:
          *p++ = c;
          break;
        }
      }
      *p++ = '"';
    }
    *p++ = '}';
    return std::string_view(buf, p - buf);
  };

  // Render counters
  for (const auto &[name, family] : Metric::counterFamilies) {
    output.push_back(
        format(arena, "# HELP %s %s\n", name.c_str(), family->help.c_str()));
    output.push_back(format(arena, "# TYPE %s counter\n", name.c_str()));

    std::vector<std::pair<std::string_view, std::string_view>> labels_sv;
    for (const auto &[labels_key, callback] : family->callbacks) {
      auto value = callback();
      labels_sv.clear();
      for (const auto &l : labels_key.labels)
        labels_sv.push_back(l);
      auto labels = format_labels(labels_sv);
      output.push_back(format(arena, "%.*s%.*s %.17g\n",
                              static_cast<int>(name.length()), name.data(),
                              static_cast<int>(labels.length()), labels.data(),
                              value));
    }

    for (const auto &[thread_id, per_thread] : family->perThreadState) {
      for (const auto &[labels_key, instance] : per_thread.instances) {
        auto value = std::bit_cast<double>(
            instance->value.load(std::memory_order_relaxed));
        labels_sv.clear();
        for (const auto &l : labels_key.labels)
          labels_sv.push_back(l);
        auto labels = format_labels(labels_sv);
        output.push_back(format(arena, "%.*s%.*s %.17g\n",
                                static_cast<int>(name.length()), name.data(),
                                static_cast<int>(labels.length()),
                                labels.data(), value));
      }
    }
  }

  // Render gauges
  for (const auto &[name, family] : Metric::gaugeFamilies) {
    output.push_back(
        format(arena, "# HELP %s %s\n", name.c_str(), family->help.c_str()));
    output.push_back(format(arena, "# TYPE %s gauge\n", name.c_str()));

    std::vector<std::pair<std::string_view, std::string_view>> labels_sv;
    for (const auto &[labels_key, callback] : family->callbacks) {
      auto value = callback();
      labels_sv.clear();
      for (const auto &l : labels_key.labels)
        labels_sv.push_back(l);
      auto labels = format_labels(labels_sv);
      output.push_back(format(arena, "%.*s%.*s %.17g\n",
                              static_cast<int>(name.length()), name.data(),
                              static_cast<int>(labels.length()), labels.data(),
                              value));
    }

    for (const auto &[labels_key, instance] : family->instances) {
      auto value = std::bit_cast<double>(
          instance->value.load(std::memory_order_relaxed));
      labels_sv.clear();
      for (const auto &l : labels_key.labels)
        labels_sv.push_back(l);
      auto labels = format_labels(labels_sv);
      output.push_back(format(arena, "%.*s%.*s %.17g\n",
                              static_cast<int>(name.length()), name.data(),
                              static_cast<int>(labels.length()), labels.data(),
                              value));
    }
  }

  // Render histograms
  for (const auto &[name, family] : Metric::histogramFamilies) {
    output.push_back(
        format(arena, "# HELP %s %s\n", name.c_str(), family->help.c_str()));
    output.push_back(format(arena, "# TYPE %s histogram\n", name.c_str()));

    std::vector<std::pair<std::string_view, std::string_view>> bucket_labels_sv;
    for (const auto &[thread_id, per_thread] : family->perThreadState) {
      for (const auto &[labels_key, instance] : per_thread.instances) {
        for (size_t i = 0; i < instance->thresholds.size(); ++i) {
          bucket_labels_sv.clear();
          for (const auto &l : labels_key.labels)
            bucket_labels_sv.push_back(l);

          if (std::isinf(instance->thresholds[i])) {
            bucket_labels_sv.push_back({"le", "+Inf"});
          } else {
            bucket_labels_sv.push_back(
                {"le", format(arena, "%.17g", instance->thresholds[i])});
          }
          auto count = instance->counts[i].load(std::memory_order_relaxed);
          auto labels = format_labels(bucket_labels_sv);
          output.push_back(format(arena, "%s_bucket%.*s %llu\n", name.c_str(),
                                  static_cast<int>(labels.length()),
                                  labels.data(),
                                  static_cast<unsigned long long>(count)));
        }

        auto sum_value = std::bit_cast<double>(
            instance->sum.load(std::memory_order_relaxed));
        bucket_labels_sv.clear();
        for (const auto &l : labels_key.labels)
          bucket_labels_sv.push_back(l);
        auto labels = format_labels(bucket_labels_sv);
        output.push_back(format(arena, "%s_sum%.*s %.17g\n", name.c_str(),
                                static_cast<int>(labels.length()),
                                labels.data(), sum_value));

        auto count_value =
            instance->observations.load(std::memory_order_relaxed);
        output.push_back(format(arena, "%s_count%.*s %llu\n", name.c_str(),
                                static_cast<int>(labels.length()),
                                labels.data(),
                                static_cast<unsigned long long>(count_value)));
      }
    }
  }

  auto result = arena.allocate<std::string_view>(output.size());
  std::copy(output.begin(), output.end(), result);
  return std::span<std::string_view>(result, output.size());
}

// Template specialization implementations for register_callback
template <>
void Family<Counter>::register_callback(
    std::vector<std::pair<std::string, std::string>> labels,
    MetricCallback<Counter> callback) {
  std::unique_lock<std::mutex> _{Metric::mutex};
  LabelsKey key{std::move(labels)};

  // Validate that labels aren't already in use by create() calls
  for (const auto &[thread_id, per_thread] : p->perThreadState) {
    validate_or_abort(
        per_thread.instances.find(key) == per_thread.instances.end(),
        "labels already registered as static instance",
        key.labels.empty() ? "(no labels)" : key.labels[0].first.c_str());
  }

  // Validate that callback isn't already registered for these labels
  validate_or_abort(p->callbacks.find(key) == p->callbacks.end(),
                    "callback already registered for labels",
                    key.labels.empty() ? "(no labels)"
                                       : key.labels[0].first.c_str());

  p->callbacks[std::move(key)] = std::move(callback);
}

template <>
void Family<Gauge>::register_callback(
    std::vector<std::pair<std::string, std::string>> labels,
    MetricCallback<Gauge> callback) {
  std::unique_lock<std::mutex> _{Metric::mutex};
  LabelsKey key{std::move(labels)};

  // Validate that labels aren't already in use by create() calls
  validate_or_abort(p->instances.find(key) == p->instances.end(),
                    "labels already registered as static instance",
                    key.labels.empty() ? "(no labels)"
                                       : key.labels[0].first.c_str());

  // Validate that callback isn't already registered for these labels
  validate_or_abort(p->callbacks.find(key) == p->callbacks.end(),
                    "callback already registered for labels",
                    key.labels.empty() ? "(no labels)"
                                       : key.labels[0].first.c_str());

  p->callbacks[std::move(key)] = std::move(callback);
}

// Explicit template instantiations to provide member implementations
template void Family<Counter>::register_callback(
    std::vector<std::pair<std::string, std::string>>, MetricCallback<Counter>);

template void Family<Gauge>::register_callback(
    std::vector<std::pair<std::string, std::string>>, MetricCallback<Gauge>);

// Static member definitions
std::mutex Metric::mutex;
std::unordered_map<std::string, std::unique_ptr<Family<Counter>::State>>
    Metric::counterFamilies;
std::unordered_map<std::string, std::unique_ptr<Family<Gauge>::State>>
    Metric::gaugeFamilies;
std::unordered_map<std::string, std::unique_ptr<Family<Histogram>::State>>
    Metric::histogramFamilies;
thread_local Metric::ThreadInit Metric::thread_init;

} // namespace metric
