// instrument_drop — pure implementation. See instrument_drop.h.
// NO REAPER / SWELL / VST3 SDK / vendor. Reuses sample_map's ComponentState serializer.

#include "instrument_drop.h"

#include "vst/sample_map.h"  // ComponentState + serializeComponentState (the SHARED writer)

namespace reasampler {

namespace {

constexpr char kB64Alphabet[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// -1 = not a base64 char; index by unsigned byte. Built once.
int b64Value(unsigned char c) {
    if (c >= 'A' && c <= 'Z') return c - 'A';
    if (c >= 'a' && c <= 'z') return c - 'a' + 26;
    if (c >= '0' && c <= '9') return c - '0' + 52;
    if (c == '+') return 62;
    if (c == '/') return 63;
    return -1;
}

}  // namespace

std::vector<std::uint8_t> instrumentDropStateBytes(const std::string& sampleId) {
    // The ONE fact the drop carries: this capture is the instance's selection. Everything
    // else stays at the fresh-instance defaults (no zones, mono, generation 0) — the same
    // ComponentState a browser click would produce. serializeComponentState is the
    // instrument's own writer (the single source of truth for the byte layout), so this is
    // NOT a parallel encoder — it IS the instrument's encoder.
    ComponentState cs;
    cs.selectionId = sampleId;
    return serializeComponentState(cs);
}

std::string buildInstrumentDropChunk(const std::string& sampleId) {
    return encodeBase64(instrumentDropStateBytes(sampleId));
}

bool infoNamesFxHotspot(const std::string& info) {
    // See the header contract. Two documented FX-bearing surfaces (SDK §GetThingFromPoint):
    //   "fx_chain" / "fx_N"  — the FX-chain and floating-FX windows.
    //   "tcp.fx" / "mcp.fx"  — the TCP / MCP FX button specifically.
    // Bare "tcp" / "mcp" and any other "tcp.*" / "mcp.*" sub-element (e.g. "tcp.mute",
    // "tcp.vol") are track-panel hits that are NOT on the FX button — those must not trigger
    // an instrument drop. Exact-string match for the two .fx tokens; prefix-match for fx_.
    if (info == "tcp.fx" || info == "mcp.fx") return true;
    auto startsWith = [&info](const char* p) { return info.rfind(p, 0) == 0; };
    return startsWith("fx_");
}

std::string encodeBase64(const std::vector<std::uint8_t>& bytes) {
    std::string out;
    out.reserve(((bytes.size() + 2) / 3) * 4);
    std::size_t i = 0;
    const std::size_t n = bytes.size();
    while (i + 3 <= n) {
        const std::uint32_t triple = (static_cast<std::uint32_t>(bytes[i]) << 16) |
                                     (static_cast<std::uint32_t>(bytes[i + 1]) << 8) |
                                     static_cast<std::uint32_t>(bytes[i + 2]);
        out.push_back(kB64Alphabet[(triple >> 18) & 0x3F]);
        out.push_back(kB64Alphabet[(triple >> 12) & 0x3F]);
        out.push_back(kB64Alphabet[(triple >> 6) & 0x3F]);
        out.push_back(kB64Alphabet[triple & 0x3F]);
        i += 3;
    }
    const std::size_t rem = n - i;
    if (rem == 1) {
        const std::uint32_t triple = static_cast<std::uint32_t>(bytes[i]) << 16;
        out.push_back(kB64Alphabet[(triple >> 18) & 0x3F]);
        out.push_back(kB64Alphabet[(triple >> 12) & 0x3F]);
        out.push_back('=');
        out.push_back('=');
    } else if (rem == 2) {
        const std::uint32_t triple = (static_cast<std::uint32_t>(bytes[i]) << 16) |
                                     (static_cast<std::uint32_t>(bytes[i + 1]) << 8);
        out.push_back(kB64Alphabet[(triple >> 18) & 0x3F]);
        out.push_back(kB64Alphabet[(triple >> 12) & 0x3F]);
        out.push_back(kB64Alphabet[(triple >> 6) & 0x3F]);
        out.push_back('=');
    }
    return out;
}

std::vector<std::uint8_t> decodeBase64(const std::string& b64) {
    std::vector<std::uint8_t> out;
    if (b64.size() % 4 != 0) return out;  // malformed length -> empty (never throws)
    out.reserve((b64.size() / 4) * 3);
    for (std::size_t i = 0; i < b64.size(); i += 4) {
        const char c0 = b64[i], c1 = b64[i + 1], c2 = b64[i + 2], c3 = b64[i + 3];
        const int v0 = b64Value(static_cast<unsigned char>(c0));
        const int v1 = b64Value(static_cast<unsigned char>(c1));
        if (v0 < 0 || v1 < 0) return {};  // illegal char in a non-pad position -> empty
        // Padding is only legal in the last two positions of the last quad.
        const bool pad2 = (c2 == '=');
        const bool pad3 = (c3 == '=');
        if ((pad2 || pad3) && i + 4 != b64.size()) return {};  // pad before the final quad
        if (pad2 && !pad3) return {};                          // "=X" is malformed
        std::uint32_t triple = (static_cast<std::uint32_t>(v0) << 18) |
                               (static_cast<std::uint32_t>(v1) << 12);
        out.push_back(static_cast<std::uint8_t>((triple >> 16) & 0xFF));
        if (!pad2) {
            const int v2 = b64Value(static_cast<unsigned char>(c2));
            if (v2 < 0) return {};
            triple |= static_cast<std::uint32_t>(v2) << 6;
            out.push_back(static_cast<std::uint8_t>((triple >> 8) & 0xFF));
            if (!pad3) {
                const int v3 = b64Value(static_cast<unsigned char>(c3));
                if (v3 < 0) return {};
                triple |= static_cast<std::uint32_t>(v3);
                out.push_back(static_cast<std::uint8_t>(triple & 0xFF));
            }
        }
    }
    return out;
}

}  // namespace reasampler
