#pragma once
// sample_usage — pure core of the instance-usage wire: ReaSampler 9000 instances
// count as usage for the prune. Each live instance publishes the captures it
// holds to a per-instance ext-state key ("rsusage_<instanceGuid>"); the
// extension reads every record at prune-scan time, keeps only the ones backed
// by a live FX instance, and folds the surviving paths into the prune's
// `referenced` set — a file any live instance holds can never be an orphan.
//
// No REAPER/VST3/SWELL/vendor includes. Mirror of assignment_request on the
// instrument->extension direction: the wire format and the two safety-critical
// decisions (what to write on publish, which records count at prune time) are
// pure and provable without a DAW; shells only move strings.
//
// The INSTRUMENT writes usage keys, the EXTENSION only reads them — one of the
// two sanctioned instrument->ext-state writes (bake_wire's request key is the
// other). It does not weaken the read-only-bank invariant: the instrument
// publishes only its own per-instance key, never banks/view/tail/assign; the
// bridge's write entry point structurally accepts only "rsusage_"-prefixed keys.
//
// Every failure, ambiguity, or uncertainty here fails safe toward PROTECT (the
// territory-wide asymmetry, stated in core/tracking/CLAUDE.md). Three folds enforce
// it here:
//   * sibling-collision -> UNION, never clean-replace over a foreign writer;
//   * zero-identified   -> records exist but no instance was identified live ->
//                          protect ALL records' paths (a matcher failure must
//                          never degrade toward delete);
//   * unreadable record -> ABORT the prune entirely (a record we cannot read
//                          may protect anything; halting deletes nothing).
//                          Residual: readReasamplerExtState returning nullopt
//                          for a >16 MB value is indistinguishable from
//                          "absent" at the publish site, so that narrow case
//                          takes the fresh-write branch, not remint.
//
// Liveness is decided extension-side at read time, not by teardown clearing
// (REAPER destroys the plugin instance when an FX goes offline, including
// Design View's CPU-park, so a terminate-time clear would strip a still-live
// instance's record) or challenge/response (a closed-editor instance could
// never answer a prune-time challenge). Publishing is eager instead (on load
// + every play-set change).
//
// The liveness rule (usageHeldPaths): a record counts iff its track still
// hosts >= 1 instance (offline included — a parked instance still protects
// its holds). A record with no resolvable track GUID counts while ANY
// instance exists (fail-safe fallback). Zero instances identified anywhere ->
// EVERY record's paths protected.
//
// Identity & the copy problem (planUsagePublish): the publishing key is a
// per-instance GUID persisted in ComponentState — inherently copyable (FX
// copy / track duplication clones it byte-for-byte), so two live instances can
// share one key, and same-track copies converge on byte-identical wires, so
// "existing == what I last wrote" is not a sound ownership test. Two in-wire
// facts close this:
//   * ownerNonce — a per-lifetime nonce, minted fresh in memory, NEVER
//     persisted (a persisted nonce would clone with the state). Proves
//     "exactly this incarnation wrote the key last."
//   * unioned — a sticky poison flag: once a sibling's holds are unioned in,
//     the record refuses clean replace forever (every subsequent write unions,
//     holds only accumulate) — over-protect residual, accepted.
// Publish resolution, always leaning over-protect:
//   * ownerNonce matches mine AND not unioned -> clean replace (sole writer;
//     released holds drop).
//   * same track with a foreign nonce, OR unioned -> UNION, written unioned=true.
//   * foreign nonce, different track -> RE-MINT under a fresh key; the
//     original's record is untouched and dies later by the liveness rule if
//     abandoned.

#include <optional>
#include <string>
#include <unordered_set>
#include <vector>

namespace reasampler::wire {

// One held capture: the bank sample id (attribution/debugging) + the project-relative
// WAV path (the prune-protection payload — compared by EXACT string against the prune
// core's `present` spelling, which both sides source from the same bank-blob spelling).
struct UsageHold {
    std::string sampleId;
    std::string relativePath;

    bool operator==(const UsageHold& o) const {
        return sampleId == o.sampleId && relativePath == o.relativePath;
    }
};

// One instance's published usage: the track GUID it was hosted on at publish
// time (empty if unresolvable), the writing incarnation's ownerNonce, the
// sticky `unioned` poison flag, plus every capture it holds — self-contained,
// the extension needs nothing beyond this value and the live FX enumeration.
struct UsageRecord {
    std::string trackGuid;
    std::string ownerNonce;
    bool unioned = false;
    std::vector<UsageHold> holds;

    bool operator==(const UsageRecord& o) const {
        return trackGuid == o.trackGuid && ownerNonce == o.ownerNonce &&
               unioned == o.unioned && holds == o.holds;
    }
};

// Length-prefixed fields behind a magic tag ("rsusage1"), same idiom as
// assignment_request, so arbitrary bytes in a GUID or path round-trip whole.
//   "rsusage1" <len>':'<trackGuid> <len>':'<ownerNonce> <len>':'<unioned "0"|"1">
//              <len>':'<holdCount> then per hold: <len>':'<sampleId> <len>':'<relativePath>
std::string encodeUsageRecord(const UsageRecord& rec);

// std::nullopt on malformed/truncated/trailing-garbage input (never UB, never
// a partial value). The prune scan treats an undecodable record as unreadable
// and aborts (foldUsageRecords) rather than proceed with protection it cannot read.
std::optional<UsageRecord> decodeUsageRecord(const std::string& wire);

// The publish decision computed before a write.
//   * remint    — the existing key belongs to a live foreign instance on
//                 another track: mint a fresh instance GUID, write under it.
//   * skipWrite — the write would change nothing that matters (byte-identical,
//                 or a union over an already-unioned record adding no holds).
//   * wire      — the encoded value to write (mine, or the same-track union).
struct UsagePublishPlan {
    bool remint = false;
    bool skipWrite = false;
    std::string wire;
};

// Decide what to write for `mine` given the key's current value, in order:
//   * absent/empty            -> write mine (unioned=false).
//   * undecodable              -> REMINT under a fresh key rather than overwrite
//     the corrupt value — overwriting would silently clear the prune-side abort
//     that is currently protecting a same-key sibling's unreadable holds.
//   * nonce match, !unioned   -> clean replace (released holds drop).
//   * same track, or unioned  -> union(existing, mine), written unioned=true —
//     a sibling's holds are never dropped.
//   * foreign nonce, other track -> remint (fresh un-poisoned key).
UsagePublishPlan planUsagePublish(const std::optional<std::string>& existing,
                                  const UsageRecord& mine);

// The prune-side liveness fold: every project-relative path held by a live
// instance, de-duped, in (record, hold) order. A record counts iff its
// trackGuid is present in `liveTrackGuids`, or its trackGuid is empty and
// `anyInstanceLive` is true. FAIL-SAFE NET: when `records` is non-empty and
// `anyInstanceLive` is false, EVERY record's paths are returned (protect-all;
// see the safety property above). Holds with an empty relativePath are skipped.
std::vector<std::string> usageHeldPaths(
    const std::vector<UsageRecord>& records,
    const std::unordered_set<std::string>& liveTrackGuids,
    bool anyInstanceLive);

// One enumerated rsusage_* key and what came back from it: nullopt = present but
// unreadable/undecodable.
struct DecodedUsage {
    std::string key;  // "rsusage_<guid>"
    std::optional<UsageRecord> record;
};

// A record that counted toward heldPaths, still attributed to its key. Lets a
// consumer ask "who holds this path" — the flattened path list cannot.
struct CountedUsage {
    std::string key;
    UsageRecord record;
};

// The prune-side entry fold. ANY unreadable record sets abortPrune (halt, delete
// nothing) and names its key; otherwise delegates to usageHeldPaths (including its
// protect-all net) and reports which records counted.
struct UsageFoldResult {
    bool abortPrune = false;
    std::vector<std::string> offendingKeys;  // non-empty iff abortPrune
    std::vector<std::string> heldPaths;
    std::vector<CountedUsage> counted;       // empty on abort
};
UsageFoldResult foldUsageRecords(
    const std::vector<DecodedUsage>& decoded,
    const std::unordered_set<std::string>& liveTrackGuids,
    bool anyInstanceLive);

// FX-identity match for the live-instance enumeration (pure so the matcher is
// testable; the shell supplies REAPER's identity strings). `identity` is an
// FX's "fx_ident" or "original_name" parm; the needles are the UPPERCASED
// channel constants — uidHexUpper (32-hex class UID), nameUpper (factory
// display name), outputNameUpper (.vst3 filename base, the form fx_ident is
// guaranteed to embed). Substring, case-insensitive. Deliberate beta-substring
// over-protect: stable needles are substrings of the beta ones, so a stable
// extension matches beta instances too — a wider protected set only, never a
// delete risk.
bool identityMatches(const std::string& identity, const std::string& uidHexUpper,
                     const std::string& nameUpper, const std::string& outputNameUpper);

// ASCII-only uppercase (shared by the matcher and the shell's needle preparation).
std::string toUpperAscii(const std::string& s);

}  // namespace reasampler::wire
