#pragma once
// assignment_request — the pure core of the S8 ingest assignment-request seam.
//
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO VST3,
// NO vendor/ includes. Standard library only. Unit-tested outside the DAW — the same
// "small pure type + length-prefixed round-trip" pattern as provenance / owned_manifest.
//
// -- What it is --------------------------------------------------------------
//
// When the EXTENSION ingests a sample (S8: arrange capture / Media-Explorer import /
// drop-onto-panel) it writes an ASSIGNMENT REQUEST to its own "reasampler" ext-state
// namespace: "the active sampler instance should now play THIS sample." The value
// names the ingested sample by (bankId, sampleId) plus a monotonic `generation` the
// reader compares to decide the request is NEW (a fresh ingest, even of the same id).
//
// This module owns ONLY the value's WIRE FORMAT — build/parse round-trip. Writing it
// to ext-state is the persist shell's job; READING it is the instrument's job in a
// LATER dispatch (S8 instrument-side follow-up, after S10 merges). This is why the
// format is documented here in the header, not just in code: the reader lands elsewhere
// and must decode exactly what this writer produced.
//
// -- The data-ownership boundary (load-bearing) ------------------------------
//
// The EXTENSION writes this; the instrument only READS it. That does not violate the
// instrument's read-only-over-the-bank rule: the assignment request is the extension
// writing its OWN namespace (a request FROM the extension TO the instrument), never the
// instrument writing back into the bank. The instrument, on reading a new generation,
// updates its OWN component-state selection (the same selection S4 persists) and reloads.
//
// -- Why `generation` -------------------------------------------------------
//
// Instances reference sample IDs, so re-assigning the SAME id (e.g. a recapture, or a
// re-drop of the same file) would be indistinguishable from a stale value without a
// changing field. `generation` is a monotonic disambiguator (the ingest writer supplies
// a wall-clock unix-epoch stamp today — see the writer shell) so the reader can tell
// "assigned again just now" from "already saw this." It is DELIBERATELY the same shape
// the S9 bank-generation counter will use, but it is NOT that counter — S9 is a separate
// point; this field is self-contained to the request and does not depend on S9 landing.

#include <cstdint>
#include <optional>
#include <string>

namespace reasampler {

// One assignment request: the ingested sample's identity + a monotonic disambiguator.
//   bankId     — the bank the sample was ingested into (the active/target bank).
//   sampleId   — the ingested Sample's stable id (BankIndex key).
//   generation — a monotonic value the reader compares to detect a NEW request. The
//                writer supplies a unix-epoch-seconds stamp; the reader treats it as an
//                opaque "did this change?" token, not a wall-clock it interprets.
struct AssignmentRequest {
    std::string  bankId;
    std::string  sampleId;
    std::int64_t generation = 0;

    bool operator==(const AssignmentRequest& o) const {
        return bankId == o.bankId && sampleId == o.sampleId &&
               generation == o.generation;
    }
    bool operator!=(const AssignmentRequest& o) const { return !(*this == o); }
};

// Encode an assignment request to the wire string. Length-prefixed fields behind a
// magic+version tag ("rsassign1"), so arbitrary bytes in an id (a GUID, a display-
// derived id) round-trip whole with no escaping ambiguity — the same idiom provenance
// uses. Deterministic: the same request always yields the same string.
//
// FORMAT (documented for the LATER instrument-side reader):
//   "rsassign1" <len>':'<bankId> <len>':'<sampleId> <len>':'<generation-decimal>
// where each <len> is the decimal byte length of the field that follows the ':'.
std::string encodeAssignmentRequest(const AssignmentRequest& req);

// Parse a wire string produced by encodeAssignmentRequest. std::nullopt on any
// malformed / truncated / trailing-garbage input (never UB, never a partial value) —
// the reader shell treats absence/malformed as "no pending request." Round-trips:
// decodeAssignmentRequest(encodeAssignmentRequest(x)) == x.
//
// READER REQUIREMENT (instrument-side, S8 follow-up dispatch): after successfully
// decoding a request, the reader MUST verify that (bankId, sampleId) resolves to an
// existing sample before acting on it. An undo on the extension side rolls back the
// `banks` ext-state key (removing the sample) but cannot atomically clear the
// `assign_request` key if the write happened outside the undo block. Even with the
// undo-grouping fix (Major 2), the reader must guard against this: treat an
// unresolvable (bankId, sampleId) pair as a stale/no-op request and discard it
// silently, never crashing or selecting a nonexistent entry.
std::optional<AssignmentRequest> decodeAssignmentRequest(const std::string& wire);

} // namespace reasampler
