// provenance_shell.cpp — the REAPER reads behind provenance. See provenance_shell.h.
// Every REAPER symbol used here is verified against
// vendor/reaper-sdk/sdk/reaper_plugin_functions.h.
//
// Compiled into the reaper_reasampler module. Includes reaper_plugin_functions.h
// WITHOUT REAPERAPI_IMPLEMENT — main.cpp is the one TU that defines the API pointers.

#include "shell/capture/provenance_shell.h"

#include <vector>

#include "core/model/bank_book.h"        // BankBook, Bank, BankModel::all
#include "core/capture/capture_paths.h"    // resolveBankFile, normalizeSlashes
#include "shell/capture/track_guid.h"       // guidString — the ONE canonical GUID key formatter

#define REAPERAPI_MINIMAL
#define REAPERAPI_WANT_TrackFX_GetCount
#define REAPERAPI_WANT_TrackFX_GetFXName
#define REAPERAPI_WANT_TrackFX_GetFXGUID
#define REAPERAPI_WANT_TrackFX_GetEnabled
#define REAPERAPI_WANT_TakeFX_GetCount
#define REAPERAPI_WANT_TakeFX_GetFXName
#define REAPERAPI_WANT_TakeFX_GetFXGUID
#define REAPERAPI_WANT_TakeFX_GetEnabled
#define REAPERAPI_WANT_CountSelectedMediaItems
#define REAPERAPI_WANT_GetSelectedMediaItem
#define REAPERAPI_WANT_CountTrackMediaItems
#define REAPERAPI_WANT_GetTrackMediaItem
#define REAPERAPI_WANT_GetMediaItemInfo_Value
#define REAPERAPI_WANT_GetActiveTake
#define REAPERAPI_WANT_GetMediaItemTake_Source
#define REAPERAPI_WANT_GetMediaSourceFileName
#define REAPERAPI_WANT_CountTracks
#define REAPERAPI_WANT_GetTrack
#define REAPERAPI_WANT_guidToString
#include "reaper_plugin_functions.h"

namespace reasampler {

using capture::normalizeSlashes;
using capture::resolveBankFile;

std::string fxChainIdentityForTrack(MediaTrack* tr) {
    if (!tr) return fxChainIdentity({});
    std::vector<FxIdentityEntry> rows;
    const int n = TrackFX_GetCount(tr);
    rows.reserve(static_cast<std::size_t>(n < 0 ? 0 : n));
    for (int i = 0; i < n; ++i) {
        FxIdentityEntry e;
        char nameBuf[512] = {0};
        if (TrackFX_GetFXName(tr, i, nameBuf, static_cast<int>(sizeof(nameBuf))))
            e.name = nameBuf;
        // Per-instance GUID: the stable identity of THIS FX in the chain, so swapping
        // one FX for another of the same name registers as drift. guidToString needs a
        // >=64-char destination (SDK contract).
        if (GUID* g = TrackFX_GetFXGUID(tr, i)) {
            char gb[64] = {0};
            guidToString(g, gb);
            e.guid = gb;
        }
        e.enabled = TrackFX_GetEnabled(tr, i);
        rows.push_back(std::move(e));
    }
    return fxChainIdentity(rows);
}

std::string fxChainIdentityForItems(const std::vector<MediaItem*>& items) {
    // The owning track's chain is out of scope for an item capture (bypassed
    // during render) — TakeFX_* on the active take is the correct family here.
    std::vector<std::string> perItem;
    perItem.reserve(items.size());
    for (MediaItem* it : items) {
        if (!it) { perItem.push_back(fxChainIdentity({})); continue; }
        MediaItem_Take* take = GetActiveTake(it);
        if (!take) { perItem.push_back(fxChainIdentity({})); continue; }
        std::vector<FxIdentityEntry> rows;
        const int n = TakeFX_GetCount(take);
        rows.reserve(static_cast<std::size_t>(n < 0 ? 0 : n));
        for (int i = 0; i < n; ++i) {
            FxIdentityEntry e;
            char nameBuf[512] = {0};
            if (TakeFX_GetFXName(take, i, nameBuf, static_cast<int>(sizeof(nameBuf))))
                e.name = nameBuf;
            if (GUID* g = TakeFX_GetFXGUID(take, i)) {
                char gb[64] = {0};
                guidToString(g, gb);
                e.guid = gb;
            }
            e.enabled = TakeFX_GetEnabled(take, i);
            rows.push_back(std::move(e));
        }
        perItem.push_back(fxChainIdentity(rows));
    }
    return combineChainIdentities(perItem);
}

namespace {
// The active take source file of one item, normalized. Empty if unresolvable.
std::string itemSourceFile(MediaItem* it) {
    if (!it) return {};
    MediaItem_Take* take = GetActiveTake(it);
    if (!take) return {};  // empty (MIDI-less?) / no active take -> unresolvable
    PCM_source* src = GetMediaItemTake_Source(take);
    if (!src) return {};
    char buf[4096] = {0};
    GetMediaSourceFileName(src, buf, static_cast<int>(sizeof(buf)));
    return normalizeSlashes(std::string(buf));
}
}  // namespace

std::vector<std::string> selectedItemSourceFiles() {
    std::vector<std::string> files;
    const int n = CountSelectedMediaItems(nullptr);  // nullptr = active project
    for (int i = 0; i < n; ++i) {
        std::string f = itemSourceFile(GetSelectedMediaItem(nullptr, i));
        if (!f.empty()) files.push_back(std::move(f));  // omit unresolvable (never empty)
    }
    return files;
}

std::vector<std::string> trackItemSourceFiles(const std::vector<MediaTrack*>& tracks,
                                              double startSeconds, double endSeconds) {
    std::vector<std::string> files;
    for (MediaTrack* tr : tracks) {
        if (!tr) continue;
        const int n = CountTrackMediaItems(tr);
        for (int i = 0; i < n; ++i) {
            MediaItem* it = GetTrackMediaItem(tr, i);
            if (!it) continue;
            const double pos = GetMediaItemInfo_Value(it, "D_POSITION");
            const double len = GetMediaItemInfo_Value(it, "D_LENGTH");
            // Positive overlap with the capture range (a zero-length touch is not an
            // overlap): item [pos, pos+len) intersects [startSeconds, endSeconds).
            if (pos < endSeconds && (pos + len) > startSeconds) {
                std::string f = itemSourceFile(it);
                if (!f.empty()) files.push_back(std::move(f));
            }
        }
    }
    return files;
}

std::vector<BankFileRef> bankFileRefs(const BankBook& book, const std::string& projectDir) {
    std::vector<BankFileRef> refs;
    for (const Bank& b : book.banks()) {
        for (const Sample& s : b.index.all()) {
            BankFileRef ref;
            ref.sampleId = s.id;
            // Resolve to the same normalized absolute form selectedItemSourceFiles
            // produces, so detectParent compares like-for-like. Empty projectDir /
            // relativePath -> empty absolutePath (never a false match).
            ref.absolutePath = normalizeSlashes(resolveBankFile(projectDir, s.relativePath));
            refs.push_back(std::move(ref));
        }
    }
    return refs;
}

MediaTrack* trackByGuid(const std::string& guid) {
    if (guid.empty()) return nullptr;
    const int n = CountTracks(nullptr);  // nullptr = active project; excludes master
    for (int i = 0; i < n; ++i) {
        MediaTrack* tr = GetTrack(nullptr, i);
        if (!tr) continue;
        if (guidString(tr) == guid) return tr;
    }
    return nullptr;
}

}  // namespace reasampler
