// core/wire — the ONE length-prefixed ext-state wire codec, `<decimal-len>':'
// <bytes>`, shared across every ext-state seam. Pure: standard library only —
// no REAPER, no SWELL, no VST3. Hardening carried everywhere:
//   - length digit-run capped at 20 (SIZE_MAX's decimal width) so a crafted
//     digit run cannot accumulate past SIZE_MAX via repeated multiply;
//   - overflow guard on every accumulate (multiply+add checked BEFORE applied);
//   - subtraction-first bounds check so a huge len cannot wrap `start + len`;
//   - fieldInt/fieldInt64 parse sign+digits manually with an INT64 overflow
//     guard and an int range check — an out-of-range field FAILS the parse.
//
// Wire format is FROZEN: encode is byte-identical to the writers this
// replaced (std::to_string length + ':' + bytes); decode never UB, never a
// partial value.

#pragma once

#include <cstddef>
#include <cstdint>
#include <string>

namespace reasampler::wire {

// Append one length-prefixed field:  <decimal-len> ':' <bytes>
void putField(std::string& out, const std::string& field);

// Whole-string, non-negative decimal parse without exceptions or locale
// surprises. False on empty, any non-digit (incl. leading '+'/'-'), or
// overflow past INT64_MAX; overflow-guarded so a long digit run can never
// wrap into a bogus small value.
bool parseUnsignedDecimal(const std::string& s, std::int64_t& out);

// Bounds-checked cursor over an encoded string. All reads are bounds-checked;
// any short read fails the whole parse (ok_ latches false — every subsequent
// read also fails, so a caller may check ok() once at the end).
class Cursor {
public:
    explicit Cursor(const std::string& s) : s_(s) {}

    bool ok() const { return ok_; }
    bool atEnd() const { return pos_ >= s_.size(); }

    // Consumes an exact literal at the cursor (the magic tag). Fails if absent.
    bool literal(const char* lit);

    // Fails on a missing ':', an empty/non-numeric length, an overflow past
    // SIZE_MAX, or a length running past the end.
    bool field(std::string& out);

    // Length-prefixed signed 64-bit decimal. Digit run capped at 19; a
    // 20-digit negative (only INT64_MIN) is conservatively rejected too —
    // house writers emit generation timestamps and small enums, never that.
    bool fieldInt64(std::int64_t& out);

    // fieldInt64 narrowed to int; out-of-[INT_MIN, INT_MAX] FAILS the parse.
    bool fieldInt(int& out);

    // Length-prefixed unsigned decimal (element counts). Callers still apply
    // their own count-vs-wire-size sanity bound BEFORE any reserve().
    bool fieldSizeT(std::size_t& out);

    // Length-prefixed %.17g double. Deliberately no errno/ERANGE rejection:
    // writers emit %.17g of live doubles (incl. "inf"), which must decode back.
    bool fieldDouble(double& out);

private:
    bool fail() { ok_ = false; return false; }

    const std::string& s_;
    std::size_t pos_ = 0;
    bool ok_ = true;
};

}  // namespace reasampler::wire
