// The one gated case that runs the WHOLE load->voice wire through REAL detection, closing the
// gap between two halves proven separately: testBuildSampleDataDetectsThirtyHertzSourcePeriod
// (test_sample_map.cpp, detectPeriod -> SampleData) never renders, and
// testSourcePeriodChangesTheRenderedStream (test_sampler_core.cpp, SampleData -> Voice -> audio)
// sets sourcePeriodFrames by hand rather than detecting it from PCM. Deliberately its own
// target: sample_map_tests and sampler_core_tests each keep their one-lib-only structural proof
// (map doesn't link the voice engine, the engine doesn't link period_detect), so bridging the
// two lives here instead of extending either.

#include "../src/core/instrument/map/sample_map.h"
#include "../src/core/instrument/engine/voice_engine.h"

#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <vector>

using namespace reasampler;
using namespace reasampler::instrument::engine;
using namespace reasampler::instrument::map;

static int g_fail = 0;
#define CHECK(cond) do { if(!(cond)) { \
    std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)

constexpr double kPi = 3.14159265358979323846;

// An ADSR that stays fully open (level 1) forever while held — isolates the render from
// envelope shaping (matches test_sampler_core.cpp's flatAdsr; not shared, both files stand
// alone).
static AdsrParams flatAdsr() {
    AdsrParams a;
    a.attackFrames = 0;
    a.decayFrames = 0;
    a.sustainLevel = 1.0;
    a.releaseFrames = 0;
    return a;
}

// FNV-1a over the raw float bits (matches test_sampler_core.cpp's hashStream).
static std::uint64_t hashStream(const std::vector<AudioSample>& v) {
    std::uint64_t h = 1469598103934665603ull;
    for (const AudioSample s : v) {
        std::uint32_t bits = 0;
        std::memcpy(&bits, &s, sizeof(bits));
        for (int b = 0; b < 4; ++b) {
            h ^= static_cast<std::uint64_t>((bits >> (8 * b)) & 0xffu);
            h *= 1099511628211ull;
        }
    }
    return h;
}

static void renderVoice(const SampleData& s, int note, std::int64_t window,
                        std::size_t outFrames, std::vector<AudioSample>& out) {
    Voice v;
    v.presizePreserveShifters(window);
    v.start(note, 127, s, /*declickTakeover=*/false, /*rate=*/1.0);
    out.resize(outFrames);
    for (std::size_t i = 0; i < outFrames; ++i) out[i] = v.renderFrame();
}

// 30 Hz @ 44.1k through the REAL wire: buildSampleData (sample_map.cpp:333-334) calls
// detectPeriod itself, so this proves the detected period actually reaches and moves the
// Preserve render — not just that a hand-set sourcePeriodFrames does (that is the sampler_core
// half; this is the missing map->engine seam).
static void testDetectedPeriodReachesAndMovesThePreserveRender() {
    const std::int64_t w = 2205;   // the product window at 44.1k
    const int rate = 44100;
    const std::size_t frames = 30000;
    std::vector<AudioSample> pcm(frames);
    for (std::size_t i = 0; i < frames; ++i) {
        pcm[i] = static_cast<float>(
            std::sin(2.0 * kPi * 30.0 * static_cast<double>(i) / rate));
    }

    SelectedSample ref;
    ref.relativePath = "b/a.wav";
    ref.rootNote = 60;
    SampleData on = buildSampleData(resolveCapture(ref, InstrumentParams{}),
                                    DecodedPcm{pcm, rate, {}});
    CHECK(std::fabs(on.sourcePeriodFrames - 1470.0) < 2.0);  // 44100 / 30 Hz, real detection

    on.play.adsr = flatAdsr();
    on.play.pitchEngine = PitchEngine::Preserve;
    SampleData off = on;
    off.sourcePeriodFrames = 0.0;   // the fixed-window fallback the pre-wire render used

    std::vector<AudioSample> outOn, outOff;
    renderVoice(on, /*note=*/67, w, 6000, outOn);    // +7 st: real splices
    renderVoice(off, 67, w, 6000, outOff);
    for (AudioSample v : outOn) CHECK(std::isfinite(v));
    CHECK(hashStream(outOn) != hashStream(outOff));  // the detected period actually moved the render
}

int main() {
    testDetectedPeriodReachesAndMovesThePreserveRender();

    if (g_fail == 0) {
        std::printf("all period_render_integration tests passed\n");
        return 0;
    }
    std::printf("%d period_render_integration check(s) failed\n", g_fail);
    return 1;
}
