// Shared correctness checks + benchmark plumbing for every step.
//
// The contract each step signs:
//   1. Provide  parse_all_materialized(path) -> vector<vector<string>>
//      (fields fully unescaped). It runs against edge.csv and must reproduce
//      EXPECTED_EDGE exactly, or the program exits 1 before any benchmark.
//   2. Time its native parse of bench.csv and report through print_bench(),
//      which emits one machine-readable line bench.sh can grep.
//
// This header also counts heap allocations by overriding global operator
// new/delete. That is how step 3 PROVES "zero-copy" instead of asserting it:
// parse the same file twice and show the second pass allocates nothing.
#pragma once
#include <atomic>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <new>
#include <string>
#include <vector>
#include <chrono>

// ---- allocation counter -----------------------------------------------------
// Single-TU programs, so defining these here is safe. Every heap allocation in
// the process ticks the counter -- there is nowhere for a hidden copy to hide.
inline std::atomic<unsigned long long> g_allocs{0};

void* operator new(std::size_t n) {
    g_allocs.fetch_add(1, std::memory_order_relaxed);
    if (void* p = std::malloc(n)) return p;
    throw std::bad_alloc{};
}
void* operator new[](std::size_t n) { return operator new(n); }
void operator delete(void* p) noexcept { std::free(p); }
void operator delete[](void* p) noexcept { std::free(p); }
void operator delete(void* p, std::size_t) noexcept { std::free(p); }
void operator delete[](void* p, std::size_t) noexcept { std::free(p); }

inline unsigned long long allocs_now() { return g_allocs.load(std::memory_order_relaxed); }

// ---- edge.csv expectations ---------------------------------------------------
// The exact parse of edge.csv (written by gen_data.py): 7 records, 4 fields
// each, quotes stripped, doubled quotes collapsed, the embedded CRLF kept.
inline std::vector<std::vector<std::string>> expected_edge() {
    return {
        {"id", "name", "notes", "value"},
        {"1", "plain", "simple", "3.14"},
        {"2", "quoted, with comma", "line1\r\nline2", "2.71"},
        {"3", "she said \"hi\"", "bare", "1"},
        {"4", "", "", "0"},
        {"5", " leading and trailing ", "x", "42"},
        {"6", "end", "no-trailing-newline", "7"},
    };
}

template <typename ParseAll>
bool run_edge_checks(const char* edge_path, ParseAll parse_all) {
    auto got = parse_all(edge_path);
    auto want = expected_edge();
    if (got.size() != want.size()) {
        std::fprintf(stderr, "EDGE FAIL: %zu records, want %zu\n", got.size(), want.size());
        return false;
    }
    for (size_t r = 0; r < want.size(); ++r) {
        if (got[r].size() != want[r].size()) {
            std::fprintf(stderr, "EDGE FAIL: record %zu has %zu fields, want %zu\n",
                         r, got[r].size(), want[r].size());
            return false;
        }
        for (size_t c = 0; c < want[r].size(); ++c) {
            if (got[r][c] != want[r][c]) {
                std::fprintf(stderr, "EDGE FAIL: [%zu][%zu] = \"%s\", want \"%s\"\n",
                             r, c, got[r][c].c_str(), want[r][c].c_str());
                return false;
            }
        }
    }
    std::puts("edge.csv: all 7 records exact -- PASS");
    return true;
}

// ---- benchmark plumbing --------------------------------------------------------
struct BenchResult {
    double best_secs = 1e30;
    unsigned long long rows = 0, fields = 0, byte_sum = 0;
    unsigned long long last_pass_allocs = 0;
};

inline double now_secs() {
    using namespace std::chrono;
    return duration<double>(steady_clock::now().time_since_epoch()).count();
}

inline void print_bench(const char* step, const BenchResult& r, double file_mb) {
    // human line
    std::printf("%s: %.3f s  |  %.1f Mrows/s  |  %.0f MB/s  |  allocs last pass: %llu\n",
                step, r.best_secs, r.rows / r.best_secs / 1e6, file_mb / r.best_secs,
                r.last_pass_allocs);
    // machine line for bench.sh
    std::printf("BENCH step=%s secs=%.4f rows=%llu fields=%llu mbps=%.0f allocs=%llu bytesum=%llu\n",
                step, r.best_secs, r.rows, r.fields, file_mb / r.best_secs,
                r.last_pass_allocs, r.byte_sum);
}
