// Step 3 -- stop touching the bytes: fields become views into the mapping.
//
// Step 2 still ran every byte of every field through `cur += c`. This step
// hands the caller {pointer, length} pairs -- std::string_view -- aimed
// straight into the mmap'd file. Nothing is copied, nothing is allocated,
// nothing is even written; the parser's only output is coordinates.
//
// The two decisions that make zero-copy work:
//
//   * Lifetime. A string_view into a mapping is valid exactly as long as the
//     mapping. Here the Mapped object outlives every use of every view, so
//     the borrow is safe BY SCOPE, not by discipline. (An API that let views
//     escape the mapping's lifetime would be a bug factory; ours processes
//     rows inside the parse call.)
//   * Escapes are deferred. A quoted field with doubled quotes ("she said
//     ""hi""") cannot be viewed verbatim -- unescaping needs a write
//     somewhere. So the view keeps the RAW bytes and a needs_unescape flag;
//     only a consumer that actually wants the cooked text pays for the copy
//     (unescape_into, used by the correctness check). In bench.csv that is
//     ~1% of fields; the other 99% are never rewritten at all.
//
// PROOF, not promise: the benchmark parses the file twice and reports heap
// allocations during the second pass. The number is zero -- every vector has
// reached capacity, and views allocate nothing.
//
// Build:  c++ -std=c++20 -O2 step3_zerocopy.cpp -o step3
#include "csv_test_common.hpp"
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string>
#include <string_view>
#include <vector>

struct Mapped {
    const char* data = nullptr;
    size_t size = 0;
    Mapped(const char* path) {
        int fd = ::open(path, O_RDONLY);
        if (fd < 0) { std::perror(path); std::exit(1); }
        struct stat st{};
        ::fstat(fd, &st);
        size = size_t(st.st_size);
        data = (const char*)::mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
        ::close(fd);
        if (data == MAP_FAILED) { std::perror("mmap"); std::exit(1); }
    }
    ~Mapped() { if (data) ::munmap((void*)data, size); }
};

struct Field {
    std::string_view raw;      // quoted fields: content between the quotes,
    bool needs_unescape;       // doubled quotes still doubled
};

// Collapse "" -> " into `out`. Only called when needs_unescape is set.
static void unescape_into(std::string_view raw, std::string& out) {
    out.clear();
    for (size_t i = 0; i < raw.size(); ++i) {
        out += raw[i];
        if (raw[i] == '"') ++i;          // skip the second quote of a pair
    }
}

// One pass; on_record receives the reused field vector for each record.
template <typename OnRecord>
static void parse_view(const char* p, const char* end, OnRecord on_record) {
    std::vector<Field> rec;
    rec.reserve(16);
    while (p < end) {
        rec.clear();
        for (;;) {                                   // one field per iteration
            bool quoted = (p < end && *p == '"');
            const char* begin;
            bool esc = false;
            if (quoted) {
                begin = ++p;                          // content starts after "
                while (p < end) {
                    if (*p == '"') {
                        if (p + 1 < end && p[1] == '"') { esc = true; p += 2; }
                        else break;                   // closing quote
                    } else ++p;
                }
                rec.push_back({{begin, size_t(p - begin)}, esc});
                if (p < end) ++p;                     // step over closing "
            } else {
                begin = p;
                while (p < end && *p != ',' && *p != '\r' && *p != '\n') ++p;
                rec.push_back({{begin, size_t(p - begin)}, false});
            }
            if (p >= end) break;                      // EOF ends the record
            char c = *p++;
            if (c == ',') continue;                   // next field
            if (c == '\r' && p < end && *p == '\n') ++p;
            break;                                    // \r, \r\n or \n: record done
        }
        on_record(rec);
    }
}

static std::vector<std::vector<std::string>> parse_all_materialized(const char* path) {
    Mapped m(path);
    std::vector<std::vector<std::string>> rows;
    std::string scratch;
    parse_view(m.data, m.data + m.size, [&](const std::vector<Field>& rec) {
        std::vector<std::string> out;
        for (const Field& f : rec) {
            if (f.needs_unescape) { unescape_into(f.raw, scratch); out.push_back(scratch); }
            else out.emplace_back(f.raw);
        }
        rows.push_back(std::move(out));
    });
    return rows;
}

int main() {
    if (!run_edge_checks("edge.csv", parse_all_materialized)) return 1;

    Mapped m("bench.csv");
    const double mb = m.size / 1e6;

    BenchResult res;
    for (int rep = 0; rep < 3; ++rep) {
        unsigned long long a0 = allocs_now();
        double t0 = now_secs();
        unsigned long long rows = 0, fields = 0, byte_sum = 0;
        parse_view(m.data, m.data + m.size, [&](const std::vector<Field>& rec) {
            ++rows;
            fields += rec.size();
            for (const Field& f : rec) byte_sum += f.raw.size();
        });
        double dt = now_secs() - t0;
        if (dt < res.best_secs) res.best_secs = dt;
        res.rows = rows; res.fields = fields; res.byte_sum = byte_sum;
        res.last_pass_allocs = allocs_now() - a0;    // second/third pass: 0
    }
    print_bench("step3_zerocopy", res, mb);
    return 0;
}
