#pragma once
// instrument_drop — the PURE blob-construction core of S17 drop-and-load.
//
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO VST3 SDK,
// NO vendor/ includes. Standard library only (+ the pure sample_map it reuses). Unit-tested
// outside the DAW — the same "small pure builder + round-trip proof" pattern as
// assignment_request / provenance.
//
// -- What it is (the S17 seam, extension side) --------------------------------
//
// S17 drops a bank capture onto a track's FX button, which instantiates ReaSampler 9000 on
// that track ALREADY PLAYING that capture. The SETTLED mechanism (PLAN.md §S17, mechanism
// (B) — VST3 component-state injection) is: after TrackFX_AddByName creates the instance, the
// extension writes the instance's component state directly via
//   TrackFX_SetNamedConfigParm(track, fx, "vst_chunk", <base64 blob>)
// with the dragged capture PRE-SELECTED.
//
// LOAD-BEARING CAVEAT (PLAN.md §S17): "vst_chunk" is the plugin's OWN base64-encoded
// serialized chunk — the exact bytes ReaSampler 9000's setState/getState round-trips — NOT a
// neutral representation REAPER re-marshals. So the extension must construct EXACTLY the
// instrument's own state-blob bytes. This module does that WITHOUT hand-rolling a parallel
// byte writer: it calls the instrument's OWN serializer, sample_map::serializeComponentState
// (the single source of truth for the byte layout — the same function the processor's
// getState calls), then base64-encodes the result. The shared-writer requirement (both
// artifacts live in this repo → reuse the exact same code) is satisfied structurally: if the
// instrument's format changes, this module changes with it because it CALLS it.
//
// The base64 encoding is what REAPER's vst_chunk write-parm documents it accepts (see
// reaper_plugin_functions.h: "vst_chunk[_program] : base64-encoded VST-specific chunk").

#include <cstdint>
#include <string>
#include <vector>

namespace reasampler {

// Build the base64 blob the extension writes to TrackFX_SetNamedConfigParm(..., "vst_chunk").
// `sampleId` is the dragged capture's stable bank id — the ONLY thing the drop pre-selects.
// The resulting ComponentState is the instrument's default face with just this one capture
// picked: {selectionId = sampleId, no zones, mono, lastConsumedAssignGeneration = 0} — exactly
// what a fresh instance would hold after the user clicked that capture in the browser. The
// keymap builds under the product defaults (Gate + Preserve) from the bank's own S2 intrinsics,
// so the sample plays MIDI-triggered immediately (the S17 "loaded, selected, playable" verify).
//
// An EMPTY sampleId yields the empty-state blob ({"", no zones}) — a drop of nothing selects
// nothing (the S10 silent empty state); the shell guards against this upstream, but the pure
// contract is defined.
//
// Deterministic: the same sampleId always yields the same blob (base64 of the same bytes).
std::string buildInstrumentDropChunk(const std::string& sampleId);

// -- FX-drop-target classification (S-VIEW-BUG-1) ------------------------------
//
// Pure classifier for GetThingFromPoint's info string: is the point over a surface where an
// instrument drop should instantiate ReaSampler 9000 on the resolved track? This is string
// logic (no REAPER types), so it lives here and is unit-tested outside the DAW — the shell
// (instrument_drop_win) only supplies the info bytes GetThingFromPoint filled.
//
// THE BUG (S-VIEW-BUG-1): the original shell predicate matched ONLY "fx_" — but per the SDK
// (reaper_plugin_functions.h §GetThingFromPoint) "fx_chain"/"fx_N" are the FX-CHAIN and
// FLOATING-FX windows; a hit on the TCP/MCP FX button (where the intuitive "drop onto the
// track's FX chain" gesture lands) reports a string in the "tcp.*"/"mcp.*" family —
// specifically "tcp.fx" / "mcp.fx". So dropping on the TCP FX button never armed the drop;
// the gesture fell through and the file dropped to arrange as audio.
//
// The fix: two documented FX-bearing surfaces are hotspots (SDK §GetThingFromPoint):
//   * "fx_chain" / "fx_N"  — the FX-chain and floating-FX windows (prefix "fx_")
//   * "tcp.fx" / "mcp.fx"  — the TCP / MCP FX button (exact token)
// Bare "tcp"/"mcp" and any other "tcp.*"/"mcp.*" sub-element (e.g. "tcp.mute") are track-panel
// hits on non-FX surfaces — they must NOT trigger an instrument drop. Only the FX button does.
bool infoNamesFxHotspot(const std::string& info);

// The raw (pre-base64) component-state bytes — exposed so the round-trip test can decode them
// back through the instrument's OWN reader (sample_map::deserializeComponentState) and assert
// the capture is selected, proving buildInstrumentDropChunk feeds the instrument exactly what
// its setState expects. Not called by the shell (which uses the base64 form).
std::vector<std::uint8_t> instrumentDropStateBytes(const std::string& sampleId);

// Standard base64 encode/decode (RFC 4648, '+' '/' alphabet, '=' padding). Exposed so the
// round-trip test can decode buildInstrumentDropChunk's output. decodeBase64 returns the
// decoded bytes; on malformed input (bad length / illegal char) it returns an EMPTY vector
// (never throws) — the test asserts a clean decode, and the shell never decodes.
std::string encodeBase64(const std::vector<std::uint8_t>& bytes);
std::vector<std::uint8_t> decodeBase64(const std::string& b64);

}  // namespace reasampler
