#pragma once
// ext_state_read — the GetProjExtState grow-loop retry policy, shared by the
// extension's persist/usage-scan shells and the instrument's bridge so the
// retry/termination rules cannot drift between them.
//
// GetProjExtState writes into a caller-supplied buffer with no query-the-size
// call, so a large value must be read by growing a buffer until it fits
// strictly inside it. Termination taxonomy (each caller folds differently):
//
//   * Absent   — the API returned <= 0 on some attempt: the key holds no value.
//                (persist -> "" empty bank; usage_scan / bridge -> nullopt)
//   * Complete — the written C string fits STRICTLY inside the buffer (size+1 <
//                cap), so it cannot have been clipped: `value` is the whole value.
//   * Overflow — the value never fit under the 16 MB ceiling: unreadable WHOLE,
//                NOT the same as absent. (persist warns on console; usage_scan
//                folds it to the prune fail-safe abort)
//
// `read` is one GetProjExtState-shaped attempt: int read(char* buf, int cap).
// Template, statically dispatched per call site — no virtual calls, no
// std::function (hot-path guardrail); the caller binds project/namespace/key
// in a lambda.

#include <cstddef>
#include <string>
#include <vector>

namespace reasampler::wire {

struct GrowingExtStateRead {
    enum class Status { Absent, Complete, Overflow };
    Status status = Status::Absent;
    int apiReturn = 0;  // the FINAL attempt's return (<= 0 iff Absent); feeds
                        // decodeGetProjExtState on the bridge path unchanged
    std::string value;  // the whole value; meaningful only when Complete
};

template <class ReadFn>
GrowingExtStateRead readProjExtStateGrowing(ReadFn&& read) {
    // Start generous; grow ×4 if REAPER reports the value may have been clipped
    // (the return is the value length; equal-to-capacity-minus-NUL is ambiguous,
    // so only a strict fit terminates). Ceiling 16 MB — give up rather than loop
    // forever on a pathological value.
    GrowingExtStateRead result;
    for (int cap = 1 << 16; cap <= (1 << 24); cap <<= 2) {
        std::vector<char> buf(static_cast<std::size_t>(cap), '\0');
        const int rv = read(buf.data(), cap);
        result.apiReturn = rv;
        if (rv <= 0) {
            result.status = GrowingExtStateRead::Status::Absent;
            return result;
        }
        buf[static_cast<std::size_t>(cap) - 1] = '\0';  // guard a read() that ignores NUL-termination within cap
        std::string s(buf.data());
        if (static_cast<int>(s.size()) + 1 < cap) {
            result.status = GrowingExtStateRead::Status::Complete;
            result.value = std::move(s);
            return result;
        }
        // else: possibly truncated -> grow and retry.
    }
    result.status = GrowingExtStateRead::Status::Overflow;
    return result;
}

}  // namespace reasampler::wire
