// Copyright 2025 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "tools/codegen/core/gen_stats/gen_stats_utils.h"

#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <memory>
#include <numeric>
#include <optional>
#include <set>
#include <sstream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "absl/container/flat_hash_map.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "include/yaml-cpp/node/emit.h"
#include "include/yaml-cpp/node/node.h"
#include "include/yaml-cpp/node/parse.h"
#include "include/yaml-cpp/yaml.h"  // IWYU pragma: keep

namespace grpc_core {

// Helper to safely extract optional integer values from a YAML node.
void get_optional(const YAML::Node& node, const std::string& key,
                  std::optional<int>& out) {
  if (node[key]) {
    out = node[key].as<int>();
  }
}

// Converts snake_case to PascalCase
std::string SnakeToPascal(const std::string& name) {
  std::stringstream result;
  bool capitalize = true;
  for (char c : name) {
    if (c == '_') {
      capitalize = true;
    } else if (capitalize) {
      result << static_cast<char>(toupper(c));
      capitalize = false;
    } else {
      result << c;
    }
  }
  return result.str();
}

namespace {

inline std::string GetCopyright() {
  return R"(// Copyright 2025 gRPC authors.
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Automatically generated by tools/codegen/core/gen_stats_utils.cc
)";
}

std::string ToAsciiCStr(const std::string& s) {
  std::ostringstream result_stream;
  for (const char ch_char : s) {
    const unsigned char c = static_cast<const unsigned char>(ch_char);
    // Check if character is printable ASCII (32 to 126 inclusive).
    // AND not a backslash or double quote.
    if ((c >= 32 && c <= 126) && c != '\\' && c != '"') {
      result_stream << c;
    } else {
      // Escape with 3 digits, zero-padded, octal representation.
      result_stream << "\\" << std::oct << std::setw(3) << std::setfill('0')
                    << static_cast<int>(c);
      result_stream << std::dec;
    }
  }
  return result_stream.str();
}

// Reinterprets a double as a 64-bit unsigned integer.
uint64_t Dbl2U64(double d) {
  uint64_t u;
  std::memcpy(&u, &d, sizeof(double));
  return u;
}

// Reinterprets a 64-bit unsigned integer as a double.
double U642Dbl(uint64_t u) {
  double d;
  std::memcpy(&d, &u, sizeof(uint64_t));
  return d;
}

// ## Histogram Generation Logic

std::string ShapeSignature(const Shape& shape) {
  return absl::StrCat(std::to_string(shape.max), "_",
                      std::to_string(shape.buckets), "_",
                      std::to_string(shape.bits));
}

// Determines how much of a value table can be compressed with a given bit
// shift.
size_t ShiftWorksUntil(const std::vector<uint64_t>& mapped_bounds,
                       int shift_bits) {
  for (size_t i = 0; i < mapped_bounds.size() - 1; ++i) {
    if ((mapped_bounds[i] >> shift_bits) ==
        (mapped_bounds[i + 1] >> shift_bits)) {
      return i;
    }
  }
  return mapped_bounds.size();
}

// Finds the optimal bit shift to compress a lookup table.
std::optional<std::tuple<int, size_t, uint64_t>> FindIdealShift(
    const std::vector<uint64_t>& mapped_bounds, size_t max_size) {
  std::optional<std::tuple<int, size_t, uint64_t>> best;
  for (int shift_bits = 63; shift_bits >= 0; --shift_bits) {
    size_t n = ShiftWorksUntil(mapped_bounds, shift_bits);
    if (n == 0) {
      continue;
    }
    uint64_t table_size = mapped_bounds[n - 1] >> shift_bits;
    if (table_size > max_size) {
      continue;
    }
    if (!best.has_value() || std::get<1>(best.value()) < n) {
      best = {shift_bits, n, table_size};
    }
  }
  return best;
}

// Generates the lookup table for histogram bucketing.
std::vector<int> GenMapTable(
    std::vector<uint64_t> mapped_bounds,
    const std::tuple<int, size_t, uint64_t>& shift_data) {
  std::vector<int> tbl;
  int cur = 0;
  int shift_bits = std::get<0>(shift_data);
  for (uint64_t& x : mapped_bounds) {
    x >>= shift_bits;
  }
  size_t limit = mapped_bounds[std::get<1>(shift_data) - 1];
  for (size_t i = 0; i < limit; ++i) {
    while (cur < mapped_bounds.size() && i > mapped_bounds[cur]) {
      cur++;
    }
    tbl.push_back(cur);
  }
  return tbl;
}

// Determines the smallest unsigned integer type for a table.
std::string TypeForUintTable(const std::vector<int>& table) {
  int mv = 0;
  if (!table.empty()) {
    mv = *std::max_element(table.begin(), table.end());
  }
  if (mv < UINT8_MAX) return "uint8_t";
  if (mv < UINT16_MAX) return "uint16_t";
  if (mv < UINT32_MAX) return "uint32_t";
  return "uint64_t";
}

// Recursively builds a balanced if-else tree for efficient lookups.
std::string MergeCases(
    std::vector<std::pair<std::optional<int>, std::string>> cases) {
  if (cases.size() == 1) {
    return cases[0].second;
  }
  size_t l = cases.size();
  size_t left_len = l / 2;
  auto left_cases = std::vector<std::pair<std::optional<int>, std::string>>(
      cases.begin(), cases.begin() + left_len);
  auto right_cases = std::vector<std::pair<std::optional<int>, std::string>>(
      cases.begin() + left_len, cases.end());

  std::stringstream ss;
  ss << "if (value < " << left_cases.back().first.value() << ") {\n"
     << MergeCases(left_cases) << "\n} else {\n"
     << MergeCases(right_cases) << "\n}";
  return ss.str();
}

Shape HistogramShape(const StatDef& histogram, bool global_scope) {
  if (global_scope) {
    return Shape{histogram.max.value(), histogram.buckets.value(), 64};
  } else {
    return Shape{histogram.max.value(),
                 histogram.scope_buckets.value_or(histogram.buckets.value()),
                 histogram.scope_counter_bits.value()};
  }
}

std::string HistogramShapeSignature(const StatDef& histogram,
                                    bool global_scope) {
  return ShapeSignature(HistogramShape(histogram, global_scope));
}

class DefaultLocalScopedStatsCollectorGenerator
    : public LocalScopedStatsCollectorGeneratorInterface {
 public:
  DefaultLocalScopedStatsCollectorGenerator(std::string scope,
                                            std::string linked_global_scope)
      : scope_(scope), linked_global_scope_(linked_global_scope) {}
  void GenerateStatsCollector(
      const std::string& class_name,
      absl::flat_hash_map<StatType, std::vector<StatDef>> inst_map,
      std::string& output) override {
    absl::StrAppend(&output, " public:\n");
    absl::StrAppend(&output, "  const ", class_name,
                    "& View() const { return data_; };\n");
    for (auto& attr : inst_map[StatType::kCounter]) {
      if (attr.scope != scope_ && attr.scope != linked_global_scope_) {
        continue;
      }
      std::string increment_fn;
      if (attr.scope == linked_global_scope_) {
        increment_fn = absl::StrFormat(
            "  void Increment%s() { %s_stats().Increment%s(); }\n",
            SnakeToPascal(attr.name), linked_global_scope_,
            SnakeToPascal(attr.name));
      } else {
        increment_fn = absl::StrFormat(
            "  void Increment%s() { ++data_.%s; %s_stats().Increment%s(); "
            "}\n",
            SnakeToPascal(attr.name), attr.name, linked_global_scope_,
            SnakeToPascal(attr.name));
      }
      absl::StrAppend(&output, increment_fn);
    }

    for (auto& attr : inst_map[StatType::kHistogram]) {
      if (attr.scope != scope_ && attr.scope != linked_global_scope_) {
        continue;
      }
      std::string increment_fn;
      if (attr.scope == linked_global_scope_) {
        increment_fn = absl::StrFormat(
            "  void Increment%s(int value) { %s_stats().Increment%s(value); "
            "}\n",
            SnakeToPascal(attr.name), linked_global_scope_,
            SnakeToPascal(attr.name));
      } else {
        increment_fn = absl::StrFormat(
            "  void Increment%s(int value) { data_.%s.Increment(value); "
            "%s_stats().Increment%s(value); "
            "}\n",
            SnakeToPascal(attr.name), attr.name, linked_global_scope_,
            SnakeToPascal(attr.name));
      }
      absl::StrAppend(&output, increment_fn);
    }
    absl::StrAppend(&output, " private:\n");
    absl::StrAppend(&output, "  ", class_name, " data_;\n");
  };

 private:
  std::string scope_;
  std::string linked_global_scope_;
};

absl::StatusOr<std::vector<StatDef>> ParseStatsCommon(const YAML::Node& root) {
  std::vector<StatDef> stats;
  if (!root.IsSequence()) {
    return absl::InvalidArgumentError("Root of YAML is not a sequence.");
  }

  // Iterate over each scope block in the top-level sequence (e.g., global,
  // http2_global)
  for (const auto& scope_node : root) {
    std::string scope_name = scope_node["scope"].as<std::string>();
    std::string linked_global_scope;  // default to empty
    if (scope_node["global_scope"]) {
      linked_global_scope = scope_node["global_scope"].as<std::string>();
    } else {
      linked_global_scope = scope_name;
    }

    const YAML::Node& metrics = scope_node["metrics"];
    if (!metrics.IsSequence()) continue;

    // Iterate over each metric within the 'metrics' list
    for (const auto& metric_node : metrics) {
      StatDef def;
      def.scope = scope_name;
      def.linked_global_scope = linked_global_scope;

      // Determine the type (counter or histogram) and set the name
      if (metric_node["counter"]) {
        def.type = StatType::kCounter;
        def.name = metric_node["counter"].as<std::string>();
      } else if (metric_node["histogram"]) {
        def.type = StatType::kHistogram;
        def.name = metric_node["histogram"].as<std::string>();
      } else {
        continue;  // Skip if it's not a known type
      }

      // Extract required and optional fields
      def.doc = metric_node["doc"].as<std::string>();
      get_optional(metric_node, "max", def.max);
      get_optional(metric_node, "buckets", def.buckets);
      get_optional(metric_node, "scope_counter_bits", def.scope_counter_bits);
      get_optional(metric_node, "scope_buckets", def.scope_buckets);

      stats.push_back(def);
    }
  }
  return stats;
}

}  // namespace

absl::StatusOr<std::vector<StatDef>> ParseStatsFromYamlString(
    const std::string& contents) {
  return ParseStatsCommon(YAML::Load(contents));
}

// Parses the YAML file and returns a vector of StatDef structs.
absl::StatusOr<std::vector<StatDef>> ParseStatsFromFile(
    const std::string& filename) {
  return ParseStatsCommon(YAML::LoadFile(filename));
}

// Implementation of the generator methods

absl::Status WriteToFile(const std::string& output_file,
                         const std::string& contents) {
  std::ofstream outfile(output_file);  // Open the file for writing
  if (outfile.is_open()) {
    // Write data to the file
    outfile << contents;
    outfile.close();
    // Check if the file was closed successfully
    if (!outfile.good()) {
      LOG(ERROR) << "Error: Failed to close file: " << output_file;
      return absl::InternalError("Failed to close file: " + output_file);
    }
  } else {
    LOG(ERROR) << "Error: Failed to open file: " << output_file;
    return absl::InternalError("Failed to open file: " + output_file);
  }
  return absl::OkStatus();
}

void StatsDataGenerator::ProcessAttrs(const std::vector<StatDef>& attrs) {
  inst_map_[StatType::kCounter] = {};
  inst_map_[StatType::kHistogram] = {};
  std::set<std::string> scope_set;

  for (const auto& attr : attrs) {
    StatDef::Validate(attr);
    inst_map_[attr.type].push_back(attr);
    scope_set.insert(attr.scope);
    linked_global_scopes_[attr.scope] = attr.linked_global_scope;
  }

  for (const auto& histogram : inst_map_[StatType::kHistogram]) {
    shapes_.insert(HistogramShape(histogram, true));
    if (histogram.scope != histogram.linked_global_scope) {
      shapes_.insert(HistogramShape(histogram, false));
    }
  }
  // Check that "global" scope is present.
  CHECK_EQ(scope_set.count("global"), 1);
  std::vector<std::string> global_scopes;
  std::vector<std::string> local_scopes;
  for (const auto& scope : scope_set) {
    if (scope == "global") continue;
    if (scope == linked_global_scopes_[scope]) {
      global_scopes.push_back(scope);
    } else {
      local_scopes.push_back(scope);
    }
  }
  scopes_.push_back("global");
  std::sort(global_scopes.begin(), global_scopes.end());
  std::sort(local_scopes.begin(), local_scopes.end());
  scopes_.insert(scopes_.end(), global_scopes.begin(), global_scopes.end());
  scopes_.insert(scopes_.end(), local_scopes.begin(), local_scopes.end());
}

void StatsDataGenerator::RegisterLocalScopedStatsGenerator(
    std::string scope, std::string linked_global_scope,
    std::unique_ptr<LocalScopedStatsCollectorGeneratorInterface> generator) {
  std::string combined_scope = absl::StrCat(scope, "_", linked_global_scope);
  auto it = local_scoped_stats_generators_.find(combined_scope);
  if (it != local_scoped_stats_generators_.end()) {
    LOG(FATAL) << "Local scoped stats generator already registered for scope: "
               << combined_scope;
  }
  local_scoped_stats_generators_[combined_scope] = std::move(generator);
}

int StatsDataGenerator::DeclStaticTable(const std::vector<int>& values,
                                        const std::string& type) {
  auto v = std::make_pair(type, values);
  for (size_t i = 0; i < static_tables_.size(); ++i) {
    if (static_tables_[i] == v) return i;
  }
  static_tables_.push_back(v);
  return static_tables_.size() - 1;
}

std::pair<std::string, int> StatsDataGenerator::GenBucketCode(
    const Shape& shape) {
  std::vector<int> bounds = {0, 1};
  bool done_trivial = false;
  int first_nontrivial = -1;
  std::string output;

  while (bounds.size() < static_cast<size_t>(shape.buckets + 1)) {
    uint64_t nextb;
    if (bounds.size() == static_cast<size_t>(shape.buckets)) {
      nextb = shape.max;
    } else {
      double mul = std::pow(static_cast<double>(shape.max) / bounds.back(),
                            1.0 / (shape.buckets + 1 - bounds.size()));
      nextb = static_cast<long long>(std::ceil(bounds.back() * mul));
    }
    if (nextb <= bounds.back() + 1) {
      nextb = bounds.back() + 1;
    } else if (!done_trivial) {
      done_trivial = true;
      first_nontrivial = bounds.size();
    }
    bounds.push_back(nextb);
  }

  int bounds_idx = DeclStaticTable(bounds, "int");

  if (first_nontrivial == -1) {
    absl::StrAppend(&output, "return grpc_core::Clamp(value, 0, ", shape.max,
                    ");\n");
    return {output, bounds_idx};
  }

  std::vector<std::pair<std::optional<int>, std::string>> cases;
  cases.push_back({0, "return 0;"});
  cases.push_back({first_nontrivial, "return value;"});

  uint64_t first_nontrivial_code =
      Dbl2U64(static_cast<double>(first_nontrivial));
  uint64_t last_code = first_nontrivial_code;

  // This section generates optimized lookup tables for parts of the range.
  while (true) {
    output.clear();
    std::vector<uint64_t> code_bounds;
    int code_bounds_index = -1;
    double current_first = U642Dbl(first_nontrivial_code);
    for (size_t i = 0; i < bounds.size(); ++i) {
      if (bounds[i] > current_first) {
        if (code_bounds_index == -1) code_bounds_index = i;
      }
      code_bounds.push_back(Dbl2U64(static_cast<double>(bounds[i])) -
                            first_nontrivial_code);
    }
    if (code_bounds_index == -1) break;

    std::vector<uint64_t> code_bounds_sliced(
        code_bounds.begin() + code_bounds_index, code_bounds.end());
    auto shift_data = FindIdealShift(code_bounds_sliced, 65536);
    if (!shift_data.has_value()) {
      break;
    }
    auto map_table = GenMapTable(code_bounds_sliced, shift_data.value());
    if (map_table.empty() || map_table.back() < 5) {
      break;
    }

    for (auto& val : map_table) {
      val += code_bounds_index;
    }
    int map_table_idx = DeclStaticTable(map_table, TypeForUintTable(map_table));

    last_code = ((map_table.size() - 1) << std::get<0>(shift_data.value())) +
                first_nontrivial_code;

    absl::StrAppend(&output, "DblUint val;\n");
    absl::StrAppend(&output, "val.dbl = value;\n");
    absl::StrAppend(&output, "const int bucket = kStatsTable", map_table_idx,
                    "[((val.uint - ", first_nontrivial_code, "ull) >> ",
                    std::get<0>(shift_data.value()), ")];\n");
    absl::StrAppend(&output, "return bucket - (value < kStatsTable", bounds_idx,
                    "[bucket]);");
    cases.push_back({static_cast<int>(U642Dbl(last_code)) + 1, output});
    first_nontrivial_code = last_code;
  }

  double last = U642Dbl(last_code) + 1.0;
  for (size_t i = 0; i < bounds.size() - 2; ++i) {
    if (bounds[i + 1] < last) {
      continue;
    }
    cases.push_back({bounds[i + 1], absl::StrCat("return ", i, ";")});
  }

  cases.push_back(
      {std::nullopt, absl::StrCat("return ", bounds.size() - 2, ";")});
  return {MergeCases(cases), bounds_idx};
}

void StatsDataGenerator::GenStatsDataHdr(const std::string& prefix,
                                         std::string& output) {
  absl::StrAppend(&output, GetCopyright(), "\n");
  absl::StrAppend(&output, "#ifndef GRPC_SRC_CORE_TELEMETRY_STATS_DATA_H\n");
  absl::StrAppend(&output, "#define GRPC_SRC_CORE_TELEMETRY_STATS_DATA_H\n\n");
  absl::StrAppend(&output, "#include <grpc/support/port_platform.h>\n");
  absl::StrAppend(&output, "#include <atomic>\n");
  absl::StrAppend(&output, "#include <memory>\n");
  absl::StrAppend(&output, "#include <stdint.h>\n");
  absl::StrAppend(&output,
                  "#include \"src/core/telemetry/histogram_view.h\"\n");
  absl::StrAppend(&output, "#include \"", prefix,
                  "absl/strings/string_view.h\"\n");
  absl::StrAppend(&output, "#include \"src/core/util/per_cpu.h\"\n");
  absl::StrAppend(&output, "#include \"src/core/util/no_destruct.h\"\n\n");
  absl::StrAppend(&output, "namespace grpc_core {\n");

  for (const auto& scope : scopes_) {
    absl::StrAppend(&output, "class ", SnakeToPascal(scope),
                    "StatsCollector;\n");
  }

  for (const auto& shape : shapes_) {
    std::string sig = ShapeSignature(shape);
    if (shape.bits == 64) {
      absl::StrAppend(&output, "class HistogramCollector_", sig, ";\n");
    }
    absl::StrAppend(&output, "class Histogram_", sig, " {\n");
    absl::StrAppend(&output, " public:\n");
    absl::StrAppend(&output, "  static int BucketFor(int value);\n");
    absl::StrAppend(
        &output, absl::StrFormat(
                     "  const uint%d_t* buckets() const { return buckets_; }\n",
                     shape.bits));
    absl::StrAppend(&output, "  size_t bucket_count() const { return ",
                    shape.buckets, "; }\n");
    absl::StrAppend(&output, "  void Increment(int value) {\n");
    if (shape.bits == 64) {
      absl::StrAppend(
          &output,
          absl::StrFormat("    ++buckets_[Histogram_%s::BucketFor(value)];\n",
                          sig));
    } else {
      absl::StrAppend(
          &output,
          absl::StrFormat(
              "    auto& bucket = buckets_[Histogram_%s::BucketFor(value)];\n",
              sig));
      absl::StrAppend(
          &output, absl::StrFormat("    if (GPR_UNLIKELY(bucket == "
                                   "std::numeric_limits<uint%d_t>::max())) {\n",
                                   shape.bits));
      absl::StrAppend(&output,
                      absl::StrFormat("      for (size_t i=0; i<%d; ++i) {\n",
                                      shape.buckets));
      absl::StrAppend(&output, "        buckets_[i] /= 2;\n");
      absl::StrAppend(&output, "      }\n");
      absl::StrAppend(&output, "    }\n");
      absl::StrAppend(&output, "    ++bucket;\n");
    }
    absl::StrAppend(&output, "  }\n");
    if (shape.bits == 64) {
      absl::StrAppend(
          &output,
          absl::StrFormat(
              "  friend Histogram_%s operator-(const Histogram_%s& left,"
              " const Histogram_%s& right);\n",
              sig, sig, sig));
    }
    absl::StrAppend(&output, " private:\n");
    if (shape.bits == 64) {
      absl::StrAppend(
          &output,
          absl::StrFormat("  friend class HistogramCollector_%s;\n", sig));
    }
    absl::StrAppend(&output, absl::StrFormat("  uint%d_t buckets_[%d]{};\n};\n",
                                             shape.bits, shape.buckets));

    if (shape.bits == 64) {
      absl::StrAppend(&output,
                      absl::StrFormat("class HistogramCollector_%s {\n", sig));
      absl::StrAppend(&output, " public:\n");
      absl::StrAppend(&output, "  void Increment(int value) {\n");
      absl::StrAppend(
          &output, absl::StrFormat(
                       "    buckets_[Histogram_%s::BucketFor(value)]\n", sig));
      absl::StrAppend(&output,
                      "        .fetch_add(1, std::memory_order_relaxed);\n");
      absl::StrAppend(&output, "  }\n");
      absl::StrAppend(
          &output, absl::StrFormat(
                       "  void Collect(Histogram_%s* result) const;\n", sig));
      absl::StrAppend(&output, " private:\n");
      absl::StrAppend(
          &output, absl::StrFormat("  std::atomic<uint64_t> buckets_[%d]{};\n",
                                   shape.buckets));
      absl::StrAppend(&output, "};\n");
    }
  }

  auto include_attr = [](const StatDef& def, std::string scope) -> bool {
    return def.scope == scope || def.linked_global_scope == scope;
  };

  for (const auto& scope : scopes_) {
    std::string linked_global_scope = linked_global_scopes_[scope];
    std::string class_name = absl::StrCat(SnakeToPascal(scope), "Stats");
    absl::StrAppend(&output, absl::StrFormat("struct %s {\n", class_name));
    absl::StrAppend(&output, "  enum class Counter {\n");
    for (const auto& attr : inst_map_[StatType::kCounter]) {
      if (!include_attr(attr, scope)) {
        continue;
      }
      absl::StrAppend(&output,
                      absl::StrFormat("    k%s,\n", SnakeToPascal(attr.name)));
    }
    absl::StrAppend(&output, "    COUNT\n");
    absl::StrAppend(&output, "  };\n");
    absl::StrAppend(&output, "  enum class Histogram {\n");
    for (const auto& attr : inst_map_[StatType::kHistogram]) {
      if (!include_attr(attr, scope)) {
        continue;
      }
      absl::StrAppend(&output,
                      absl::StrFormat("    k%s,\n", SnakeToPascal(attr.name)));
    }
    absl::StrAppend(&output, "    COUNT\n");
    absl::StrAppend(&output, "  };\n");
    absl::StrAppend(&output, absl::StrFormat("  %s();\n", class_name));
    absl::StrAppend(&output,
                    "  static const absl::string_view"
                    " counter_name[static_cast<int>(Counter::COUNT)];\n");
    absl::StrAppend(&output,
                    "  static const absl::string_view"
                    " histogram_name[static_cast<int>(Histogram::COUNT)];\n");
    absl::StrAppend(&output,
                    "  static const absl::string_view"
                    " counter_doc[static_cast<int>(Counter::COUNT)];\n");
    absl::StrAppend(&output,
                    "  static const absl::string_view"
                    " histogram_doc[static_cast<int>(Histogram::COUNT)];\n");
    absl::StrAppend(&output, "  union {\n");
    absl::StrAppend(&output, "    struct {\n");
    for (const auto& attr : inst_map_[StatType::kCounter]) {
      if (!include_attr(attr, scope)) {
        continue;
      }
      absl::StrAppend(&output,
                      absl::StrFormat("    uint64_t %s;\n", attr.name));
    }
    absl::StrAppend(&output, "    };\n");
    absl::StrAppend(
        &output, "    uint64_t counters[static_cast<int>(Counter::COUNT)];\n");
    absl::StrAppend(&output, "  };\n");
    for (const auto& attr : inst_map_[StatType::kHistogram]) {
      if (!include_attr(attr, scope)) {
        continue;
      }
      absl::StrAppend(&output,
                      absl::StrFormat("  Histogram_%s %s;\n",
                                      HistogramShapeSignature(
                                          attr, scope == linked_global_scope),
                                      attr.name));
    }
    // scope is of type 'global'
    if (scope == linked_global_scope) {
      absl::StrAppend(&output,
                      "  HistogramView histogram(Histogram which) const;\n");
      absl::StrAppend(
          &output, absl::StrFormat(
                       "  std::unique_ptr<%s> Diff(const %s& other) const;\n",
                       class_name, class_name));
    }
    absl::StrAppend(&output, "};\n");

    absl::StrAppend(&output,
                    absl::StrFormat("class %sCollector {\n", class_name));

    if (scope == linked_global_scope) {
      absl::StrAppend(&output, " public:\n");
      absl::StrAppend(
          &output, absl::StrFormat("  std::unique_ptr<%s> Collect() const;\n",
                                   class_name));
      bool is_private = false;
      auto set_private = [&](bool yes) {
        if (is_private == yes) {
          return;
        }
        is_private = yes;
        if (yes) {
          absl::StrAppend(&output, " private:\n");
        } else {
          absl::StrAppend(&output, " public:\n");
        }
      };

      for (const auto& attr : inst_map_[StatType::kCounter]) {
        if (!include_attr(attr, scope)) {
          continue;
        }
        set_private(attr.scope != scope);
        absl::StrAppend(
            &output,
            absl::StrFormat(
                "  void Increment%s() { data_.this_cpu().%s.fetch_add(1,"
                " std::memory_order_relaxed); }\n",
                SnakeToPascal(attr.name), attr.name));
      }
      for (const auto& attr : inst_map_[StatType::kHistogram]) {
        if (!include_attr(attr, scope)) {
          continue;
        }
        set_private(attr.scope != scope);
        absl::StrAppend(
            &output,
            absl::StrFormat("  void Increment%s(int value) { "
                            "data_.this_cpu().%s.Increment(value); }\n",
                            SnakeToPascal(attr.name), attr.name));
      }

      set_private(true);
      for (const auto& other_scope : scopes_) {
        if (other_scope == scope) {
          continue;
        }
        absl::StrAppend(&output,
                        absl::StrFormat("  friend class %sStatsCollector;\n",
                                        SnakeToPascal(other_scope)));
      }
      absl::StrAppend(&output, "  struct Data {\n");
      for (const auto& attr : inst_map_[StatType::kCounter]) {
        if (!include_attr(attr, scope)) {
          continue;
        }
        absl::StrAppend(
            &output,
            absl::StrFormat("    std::atomic<uint64_t> %s{0};\n", attr.name));
      }
      for (const auto& attr : inst_map_[StatType::kHistogram]) {
        if (!include_attr(attr, scope)) {
          continue;
        }
        absl::StrAppend(&output,
                        absl::StrFormat("    HistogramCollector_%s %s;\n",
                                        HistogramShapeSignature(
                                            attr, scope == linked_global_scope),
                                        attr.name));
      }
      absl::StrAppend(&output, "  };\n");
      absl::StrAppend(
          &output,
          "  PerCpu<Data>"
          " data_{PerCpuOptions().SetCpusPerShard(4).SetMaxShards(32)};\n");
    } else {  // not global scope
      auto it = local_scoped_stats_generators_.find(
          absl::StrCat(scope, "_", linked_global_scope));
      LocalScopedStatsCollectorGeneratorInterface* generator = nullptr;
      if (it != local_scoped_stats_generators_.end()) {
        generator = it->second.get();
      } else {
        std::unique_ptr<LocalScopedStatsCollectorGeneratorInterface>
            local_default_generator =
                std::make_unique<DefaultLocalScopedStatsCollectorGenerator>(
                    scope, linked_global_scope);
        generator = local_default_generator.get();
        local_scoped_stats_generators_[absl::StrCat(scope, "_",
                                                    linked_global_scope)] =
            std::move(local_default_generator);
      }
      CHECK_NE(generator, nullptr);
      generator->GenerateStatsCollector(class_name, inst_map_, output);
    }
    absl::StrAppend(&output, "};\n");
    if (scope == linked_global_scope) {
      absl::StrAppend(&output,
                      absl::StrFormat("inline %sStatsCollector& %s_stats() {\n",
                                      SnakeToPascal(scope), scope));
      absl::StrAppend(
          &output,
          absl::StrFormat("  return"
                          " *NoDestructSingleton<%sStatsCollector>::Get();\n",
                          SnakeToPascal(scope)));
      absl::StrAppend(&output, "}\n");
    }
  }
  absl::StrAppend(&output, "}\n\n");
  absl::StrAppend(&output, "#endif // GRPC_SRC_CORE_TELEMETRY_STATS_DATA_H\n");
}

void StatsDataGenerator::GenStatsDataSrc(std::string& output) {
  absl::StrAppend(&output, GetCopyright(), "\n");
  absl::StrAppend(&output, "#include <grpc/support/port_platform.h>\n\n");
  absl::StrAppend(&output, "#include \"src/core/telemetry/stats_data.h\"\n");
  absl::StrAppend(&output, "#include <stdint.h>\n\n");
  std::vector<std::string> hist_code;
  absl::flat_hash_map<Shape, int> hist_bucket_boundaries;
  for (const auto& shape : shapes_) {
    std::pair<std::string, int> bucket_code = GenBucketCode(shape);
    hist_code.push_back(bucket_code.first);
    hist_bucket_boundaries[shape] = bucket_code.second;
  }
  absl::StrAppend(&output, "namespace grpc_core {\n");
  absl::StrAppend(
      &output, "namespace { union DblUint { double dbl; uint64_t uint; }; }\n");

  for (const auto& shape : shapes_) {
    std::string sig = ShapeSignature(shape);
    if (shape.bits == 64) {
      absl::StrAppend(
          &output,
          absl::StrFormat(
              "void HistogramCollector_%s::Collect(Histogram_%s* result)"
              " const {\n",
              sig, sig));
      absl::StrAppend(&output, absl::StrFormat("  for (int i=0; i<%d; i++) {\n",
                                               shape.buckets));
      absl::StrAppend(&output,
                      "    result->buckets_[i] +="
                      " buckets_[i].load(std::memory_order_relaxed);\n");
      absl::StrAppend(&output, "  }\n");
      absl::StrAppend(&output, "}\n");
      absl::StrAppend(
          &output, absl::StrFormat(
                       "Histogram_%s operator-(const Histogram_%s& left, const"
                       " Histogram_%s& right) {\n",
                       sig, sig, sig));
      absl::StrAppend(&output,
                      absl::StrFormat("  Histogram_%s result;\n", sig));
      absl::StrAppend(&output, absl::StrFormat("  for (int i=0; i<%d; i++) {\n",
                                               shape.buckets));
      absl::StrAppend(
          &output,
          "    result.buckets_[i] = left.buckets_[i] - right.buckets_[i];\n");
      absl::StrAppend(&output, "  }\n");
      absl::StrAppend(&output, "  return result;\n");
      absl::StrAppend(&output, "}\n");
    }
  }
  absl::StrAppend(&output, "namespace {\n");
  for (int i = 0; i < static_tables_.size(); ++i) {
    std::string stats_tbl_str = absl::StrJoin(
        static_tables_[i].second.begin(), static_tables_[i].second.end(), ",");
    absl::StrAppend(
        &output,
        absl::StrFormat("const %s kStatsTable%d[%d] = {%s};\n",
                        static_tables_[i].first, i,
                        static_tables_[i].second.size(), stats_tbl_str));
  }
  absl::StrAppend(&output, "}  // namespace\n");
  int j = 0;
  for (const auto& shape : shapes_) {
    absl::StrAppend(
        &output,
        absl::StrFormat("int Histogram_%s::BucketFor(int value) {%s}\n",
                        ShapeSignature(shape), hist_code[j++]));
  }
  auto include_attr = [](const StatDef& def, std::string scope) -> bool {
    return def.scope == scope || def.linked_global_scope == scope;
  };
  auto process_instances = [&](const std::vector<StatDef>& attrs,
                               const std::string& class_name,
                               const std::string& scope, StatType type) {
    std::string type_name =
        type == StatType::kCounter ? "Counter" : "Histogram";
    absl::StrAppend(
        &output, absl::StrFormat(
                     "const absl::string_view"
                     " %s::%s_name[static_cast<int>(%s::COUNT)] = {\n",
                     class_name, absl::AsciiStrToLower(type_name), type_name));
    for (const auto& attr : attrs) {
      if (!include_attr(attr, scope)) {
        continue;
      }
      absl::StrAppend(&output,
                      absl::StrFormat("  \"%s\",\n", ToAsciiCStr(attr.name)));
    }
    absl::StrAppend(&output, "};\n");
    absl::StrAppend(
        &output, absl::StrFormat(
                     "const absl::string_view"
                     " %s::%s_doc[static_cast<int>(%s::COUNT)] = {\n",
                     class_name, absl::AsciiStrToLower(type_name), type_name));
    for (const auto& attr : attrs) {
      if (!include_attr(attr, scope)) {
        continue;
      }
      absl::StrAppend(&output,
                      absl::StrFormat("  \"%s\",\n", ToAsciiCStr(attr.doc)));
    }
    absl::StrAppend(&output, "};\n");
  };
  for (const auto& scope : scopes_) {
    std::string linked_global_scope = linked_global_scopes_[scope];
    std::string class_name = absl::StrCat(SnakeToPascal(scope), "Stats");
    process_instances(inst_map_[StatType::kCounter], class_name, scope,
                      StatType::kCounter);
    process_instances(inst_map_[StatType::kHistogram], class_name, scope,
                      StatType::kHistogram);
    std::string included_ctrs;
    for (int j = 0; j < inst_map_[StatType::kCounter].size(); ++j) {
      const auto& attr = inst_map_[StatType::kCounter][j];
      if (!include_attr(attr, scope)) {
        continue;
      }
      if (j > 0 && !included_ctrs.empty()) {
        absl::StrAppend(&included_ctrs, ",");
      }
      absl::StrAppend(&included_ctrs, absl::StrFormat("%s{0}", attr.name));
    }
    absl::StrAppend(&output, absl::StrFormat("%s::%s() : %s {}\n", class_name,
                                             class_name, included_ctrs));

    if (scope != linked_global_scope) {
      continue;
    }
    absl::StrAppend(
        &output, absl::StrFormat(
                     "HistogramView %s::histogram(Histogram which) const {\n",
                     class_name));
    absl::StrAppend(&output, "  switch (which) {\n");
    absl::StrAppend(
        &output,
        "    default: GPR_UNREACHABLE_CODE(return HistogramView());\n");
    for (const auto& attr : inst_map_[StatType::kHistogram]) {
      if (!include_attr(attr, scope)) {
        continue;
      }
      absl::StrAppend(&output, absl::StrFormat("    case Histogram::k%s:\n",
                                               SnakeToPascal(attr.name)));
      Shape shape = HistogramShape(attr, scope == linked_global_scope);
      absl::StrAppend(
          &output,
          absl::StrFormat("      return HistogramView{&Histogram_%s::BucketFor,"
                          " kStatsTable%d, %d, %s.buckets()};\n",
                          ShapeSignature(shape), hist_bucket_boundaries[shape],
                          shape.buckets, attr.name));
    }
    absl::StrAppend(&output, "  }\n");
    absl::StrAppend(&output, "}\n");
    absl::StrAppend(
        &output,
        absl::StrFormat("std::unique_ptr<%sStats> %sStatsCollector::Collect()"
                        " const {\n",
                        SnakeToPascal(scope), SnakeToPascal(scope)));

    absl::StrAppend(
        &output,
        absl::StrFormat("  auto result = std::make_unique<%sStats>();\n",
                        SnakeToPascal(scope)));
    absl::StrAppend(&output, "  for (const auto& data : data_) {\n");
    for (const auto& attr : inst_map_[StatType::kCounter]) {
      if (attr.scope != scope && linked_global_scopes_[attr.scope] != scope) {
        continue;
      }
      absl::StrAppend(
          &output,
          absl::StrFormat("    result->%s +="
                          " data.%s.load(std::memory_order_relaxed);\n",
                          attr.name, attr.name));
    }
    for (const auto& attr : inst_map_[StatType::kHistogram]) {
      if (attr.scope != scope && linked_global_scopes_[attr.scope] != scope) {
        continue;
      }
      absl::StrAppend(&output,
                      absl::StrFormat("    data.%s.Collect(&result->%s);\n",
                                      attr.name, attr.name));
    }
    absl::StrAppend(&output, "  }\n");
    absl::StrAppend(&output, "  return result;\n");
    absl::StrAppend(&output, "}\n");
    absl::StrAppend(
        &output, absl::StrFormat("std::unique_ptr<%sStats> %sStats::Diff(const"
                                 " %sStats& other) const {\n",
                                 SnakeToPascal(scope), SnakeToPascal(scope),
                                 SnakeToPascal(scope)));
    absl::StrAppend(
        &output,
        absl::StrFormat("  auto result = std::make_unique<%sStats>();\n",
                        SnakeToPascal(scope)));
    for (const auto& attr : inst_map_[StatType::kCounter]) {
      if (attr.scope != scope && linked_global_scopes_[attr.scope] != scope) {
        continue;
      }
      absl::StrAppend(
          &output, absl::StrFormat("  result->%s = %s - other.%s;\n", attr.name,
                                   attr.name, attr.name));
    }
    for (const auto& attr : inst_map_[StatType::kHistogram]) {
      if (attr.scope != scope && linked_global_scopes_[attr.scope] != scope) {
        continue;
      }
      absl::StrAppend(
          &output, absl::StrFormat("  result->%s = %s - other.%s;\n", attr.name,
                                   attr.name, attr.name));
    }
    absl::StrAppend(&output, "  return result;\n");
    absl::StrAppend(&output, "}\n");
  }
  absl::StrAppend(&output, "}  // namespace grpc_core\n");
}

}  // namespace grpc_core
