#include "core/view/fx_offline.h"

#include <cstddef>
#include <map>

namespace reasampler {

namespace {

// Identity -> current slot. First occurrence wins: REAPER mints one GUID per FX
// instance, so a repeat can only come from a corrupt/hand-edited chain, and
// picking one deterministically beats writing twice.
std::map<std::string, int> slotByIdentity(const std::vector<std::string>& liveFxGuids) {
    std::map<std::string, int> byGuid;
    for (std::size_t i = 0; i < liveFxGuids.size(); ++i) {
        if (liveFxGuids[i].empty()) continue;  // unidentifiable FX is not a restore target
        byGuid.emplace(liveFxGuids[i], static_cast<int>(i));
    }
    return byGuid;
}

}  // namespace

FxRestoreResolution resolveFxRestore(const std::vector<FxOfflineOp>& planned,
                                     const std::vector<std::string>& liveFxGuids) {
    FxRestoreResolution res;
    const std::map<std::string, int> byGuid = slotByIdentity(liveFxGuids);
    const int liveCount = static_cast<int>(liveFxGuids.size());

    for (const FxOfflineOp& op : planned) {
        if (op.keying == FxKeying::Identity) {
            if (op.fxGuid.empty()) {
                // REAPER reported no GUID at capture time — distinct from a real
                // captured identity going missing: the FX may still be live, we
                // just never had a name for it. Counted separately so the report
                // never claims it was deleted (see describeFxRestoreDrops).
                ++res.drops.unidentified;
                continue;
            }
            auto it = byGuid.find(op.fxGuid);
            if (it == byGuid.end()) {
                ++res.drops.missingIdentity;
                continue;
            }
            res.writes.push_back(FxOfflineWrite{it->second, op.offline});
        } else {
            if (op.slot < 0 || op.slot >= liveCount) {
                ++res.drops.slotOutOfRange;
                continue;
            }
            res.writes.push_back(FxOfflineWrite{op.slot, op.offline});
        }
    }
    return res;
}

std::string describeFxRestoreDrops(const FxRestoreDrops& drops, int trackCount) {
    if (drops.total() <= 0) return {};

    std::string msg = "ReaSampler: Design View restore dropped " +
                      std::to_string(drops.total()) + " captured FX offline state(s) on " +
                      std::to_string(trackCount) + " track(s) -- ";

    std::vector<std::string> clauses;
    if (drops.missingIdentity > 0) {
        clauses.push_back(std::to_string(drops.missingIdentity) +
               " FX no longer in the chain (deleted or replaced while parked)");
    }
    if (drops.unidentified > 0) {
        clauses.push_back(std::to_string(drops.unidentified) +
               " FX REAPER could not identify at capture time (no GUID reported), "
               "so it could not be matched now");
    }
    if (drops.slotOutOfRange > 0) {
        clauses.push_back(std::to_string(drops.slotOutOfRange) +
               " from a project saved before FX identity was recorded, whose slot no longer exists");
    }
    for (std::size_t i = 0; i < clauses.size(); ++i) {
        if (i) msg += ", ";
        msg += clauses[i];
    }

    // Every dropped entry is still sitting exactly where park left it — offline
    // — because the restore that would have flipped it back never ran. Say
    // that, not the reassuring-but-wrong "left as it was" (park itself was the
    // change; restore is what didn't happen for these).
    msg += ". Each was left offline, as park left it, with no snapshot left to "
           "restore it -- switch it back on by hand.\n";
    return msg;
}

}  // namespace reasampler
