#pragma once
// The per-FX offline snapshot's key and its restore resolution: what a captured
// FX state is keyed BY, and how that key resolves against the chain as it stands
// at restore time. Pure — the FX identity is an opaque string the shell reads
// from REAPER (TrackFX_GetFXGUID) and hands in.

#include <string>
#include <vector>

namespace reasampler {

// How a set of per-FX entries is keyed. `Slot` is the position-addressed shape:
// a park plan (every live slot, by construction) or a snapshot lifted from a
// project saved before identity was recorded. A live snapshot is always
// `Identity` — a chain reordered while the track is parked makes a slot a lie.
enum class FxKeying { Identity, Slot };

// One FX's captured offline state. Under Identity keying `fxGuid` is that FX's
// own durable identity; under Slot keying it is empty and the entry's POSITION
// in the snapshot is the slot it was captured from.
struct FxOfflineState {
    std::string fxGuid;
    int         offline = 0;  // int, not bool — mirrors TrackSnapshot's defensive contract

    bool operator==(const FxOfflineState& o) const {
        return fxGuid == o.fxGuid && offline == o.offline;
    }
};

// One per-FX offline write as PLANNED. The keying travels with the op; see
// FxKeying above and resolveFxRestore below for why a missing identity is
// never re-pointed at a slot.
struct FxOfflineOp {
    std::string guid;                        // track GUID
    FxKeying    keying = FxKeying::Identity;
    std::string fxGuid;                      // FX identity, under Identity keying
    int         slot = 0;                    // write target under Slot keying only
    bool        offline = false;

    bool operator==(const FxOfflineOp& o) const {
        return guid == o.guid && keying == o.keying && fxGuid == o.fxGuid &&
               slot == o.slot && offline == o.offline;
    }
};

// One resolved write: TrackFX_SetOffline(track, fxIndex, offline).
struct FxOfflineWrite {
    int  fxIndex = 0;
    bool offline = false;

    bool operator==(const FxOfflineWrite& o) const {
        return fxIndex == o.fxIndex && offline == o.offline;
    }
};

// Captured state a restore could not apply. All three counts mean the same
// act: the entry was dropped and no FX was written in its place.
struct FxRestoreDrops {
    int missingIdentity = 0;  // identity-keyed entry with no live FX carrying that GUID
    int unidentified    = 0;  // identity-keyed entry whose captured fxGuid was itself empty
    int slotOutOfRange  = 0;  // slot-keyed entry whose capture-time slot no longer exists

    int total() const { return missingIdentity + unidentified + slotOutOfRange; }

    void add(const FxRestoreDrops& o) {
        missingIdentity += o.missingIdentity;
        unidentified    += o.unidentified;
        slotOutOfRange  += o.slotOutOfRange;
    }

    bool operator==(const FxRestoreDrops& o) const {
        return missingIdentity == o.missingIdentity && unidentified == o.unidentified &&
               slotOutOfRange == o.slotOutOfRange;
    }
};

struct FxRestoreResolution {
    std::vector<FxOfflineWrite> writes;  // in planned order
    FxRestoreDrops              drops;
};

// Resolves each planned op against the live chain, where `liveFxGuids[i]` is the
// identity of the FX at slot `i` right now (empty when REAPER reported none).
// An identity that is not live is DROPPED, never re-pointed at a slot; a live FX
// no op names is left entirely alone.
FxRestoreResolution resolveFxRestore(const std::vector<FxOfflineOp>& planned,
                                     const std::vector<std::string>& liveFxGuids);

// One console line describing a whole apply's drops, or "" when nothing was
// dropped — the degrade is reported rather than swallowed.
std::string describeFxRestoreDrops(const FxRestoreDrops& drops, int trackCount);

}  // namespace reasampler
