#include "Manifest.h"
#include "Support.h"
#include <algorithm>
#include <map>
#include <set>

namespace deeppluck::benchmark
{
Values foundation()
{
    // Explicit Rev1 fixture, NOT product defaults or a mutable preset lookup.
    return { -3, 440, .003f, .320f, 0, .180f,
        1, 1800, .18f, 3, .35f, 2.50f, .002f, .280f, 0, .180f,
        1, -18, .012f, .55f, 1, -27, .120f, .35f,
        1, -15, 0, 0, -7, 2, 4, .35f,
        .25f, 0, 4, 0, 1, 0, 0, 0, 0, .50f, 0, .50f,
        1, .35f, .25f, .65f, .20f, 1, 1, .375f, 1, .32f, 6000, .22f,
        1, .55f, .018f, .55f, .18f, 0 };
}
namespace
{
constexpr std::array notes { 48,55,60,63,67,70,72,75,79,82,84,87,91,94,96,99 };
constexpr std::array velocities { 96,88,104,80,112,92,100,84,108,90,102,86,110,94,98,82 };
std::int64_t at (double seconds, int rate)
{
    return static_cast<std::int64_t> (std::floor (seconds * rate + .5));
}
std::int64_t grid (double seconds, int rate)
{
    return 512 * static_cast<std::int64_t> (std::ceil (seconds * rate / 512));
}
void param (Case& c, double t, int index, double value)
{
    c.events.push_back ({ grid (t, c.rate), Kind::parameter, index, value });
}
void note (Case& c, int number, int velocity, double on, double off)
{
    c.events.push_back ({ at (on, c.rate), Kind::noteOn, number, static_cast<double> (velocity) });
    c.events.push_back ({ at (off, c.rate), Kind::noteOff, number, 0 });
}
void chord (Case& c, int count, double on, double off, bool stress = false)
{
    for (int i = 0; i < count; ++i)
        note (c, notes[static_cast<size_t> (i)], stress ? 127 : velocities[static_cast<size_t> (i)], on, off);
}
void fx (Values& v, int state)
{
    v[44] = state == 1 || state == 4 ? 0.0f : 1.0f;
    v[49] = state == 2 || state == 4 ? 0.0f : 1.0f;
    v[56] = state == 3 || state == 4 ? 0.0f : 1.0f;
}
void filter (Values& v, bool high)
{
    v[6] = 1; v[7] = 1800; v[8] = high ? 1.0f : .18f; v[9] = high ? 18.0f : 3.0f;
}
void lfo (Values& v, int mode)
{
    v[32] = mode == 2 ? 12.0f : .25f;
    v[33] = 0; v[35] = mode == 2 ? 3.0f : 0.0f; v[36] = 1;
    v[37] = mode == 0 ? 0.0f : mode == 1 ? .20f : 2.0f;
    v[38] = mode == 0 ? 0.0f : mode == 1 ? 1.0f : 50.0f;
    v[39] = mode == 0 ? 0.0f : mode == 1 ? .10f : 1.0f;
}
void wave (Values& v, int waveform, bool stress)
{
    v[40] = v[42] = static_cast<float> (waveform);
    v[41] = waveform != 4 ? .5f : stress ? .05f : .25f;
    v[43] = waveform != 4 ? .5f : stress ? .95f : .75f;
}
Values profile (bool stress)
{
    auto v = foundation();
    if (stress)
    {
        filter (v, true); lfo (v, 2); v[5] = v[15] = 10;
        v[25] = 0; v[30] = 30; v[31] = 1;
        v[17] = 0; v[18] = .100f; v[19] = 1;
        v[21] = 0; v[22] = 1.500f; v[23] = 1;
        v[45] = 4; v[46] = v[47] = 1; v[48] = .5f;
        v[50] = 0; v[51] = 2; v[53] = .75f; v[54] = 18000; v[55] = .5f;
        v[57] = .92f; v[58] = .120f; v[59] = 0; v[60] = .5f;
    }
    return v;
}
Case make (const std::string& family, int rate, int block, double duration, const Values& values)
{
    Case c;
    c.family = family; c.rate = rate; c.block = block; c.requested = values;
    c.id = family + "-r" + std::to_string (rate) + "-b" + std::to_string (block);
    c.samples = ((at (duration, rate) + block - 1) / block) * block;
    return c;
}
void phases (Case& c, std::initializer_list<double> boundaries,
             std::initializer_list<const char*> names)
{
    std::vector<double> edges (boundaries);
    auto index = size_t { 0 };
    for (const auto* name : names)
    {
        const auto start = at (edges[index], c.rate);
        const auto end = index + 2 == edges.size() ? c.samples : at (edges[index + 1], c.rate);
        c.phases.push_back ({ name, start, end });
        ++index;
    }
}
void matrixLoad (Case& c, int polyphony, bool stress)
{
    c.polyphony = polyphony; c.sustainLoad = true;
    c.requested[4] = c.requested[14] = .5f;
    chord (c, polyphony, 0, 3, stress);
    phases (c, { 0,3,4 }, { "held-load", "release" });
}
void typicalMusic (Case& c)
{
    constexpr std::array<std::array<int, 4>, 4> chords {
        std::array {48,55,60,63}, {53,60,65,68}, {51,58,63,67}, {55,62,67,70} };
    for (int i = 0; i < 4; ++i)
        for (int n = 0; n < 4; ++n)
            note (c, chords[static_cast<size_t> (i)][static_cast<size_t> (n)],
                  velocities[static_cast<size_t> (n)], i, i + .125);
}
void addTransitions (std::vector<Case>& result, int rate, int block)
{
    auto f = foundation();
    const auto append = [&] (Case c)
    {
        if (c.phases.empty()) phases (c, {0,4,6}, {"events", "tail"});
        result.push_back (std::move (c));
    };
    for (int mode = 0; mode < 3; ++mode)
    {
        auto c = make ("T1", rate, block, 6, f); c.id += "-off" + std::to_string (mode);
        c.polyphony = 16; c.requested[4] = .5f; c.requested[29] = 4;
        chord (c, 16, 0, 1);
        note (c, 61, 100, .25, .5);
        c.events.push_back ({at (.75, rate), Kind::noteOn, 48, 96});
        if (mode != 0)
        {
            std::erase_if (c.events, [&] (const Event& e) { return e.sample == rate && e.kind == Kind::noteOff; });
            c.events.push_back ({rate, Kind::controller, mode == 1 ? 123 : 120, 0});
        }
        append (c);
    }
    {
        auto c = make ("T2", rate, block, 6, f);
        constexpr std::array on {0,63,65,128,255,512}, off {1,64,127,129,511,513};
        for (int i = 0; i < 6; ++i)
        {
            const auto n = i < 4 ? std::array {60,63,67,70}[static_cast<size_t> (i)] : i == 4 ? 72 : 75;
            c.events.push_back ({on[static_cast<size_t> (i)], Kind::noteOn, n, 96});
            c.events.push_back ({off[static_cast<size_t> (i)], Kind::noteOff, n, 0});
        }
        append (c);
    }
    {
        auto c = make ("T3", rate, block, 6, f); c.requested[5] = c.requested[15] = 2;
        note (c, 60, 96, 0, .75); note (c, 60, 96, 2, 2.1);
        for (int offset : {0,10})
        {
            param (c, .25, 4 + offset, .5); param (c, .5, 3 + offset, 1);
            param (c, 1, 5 + offset, 4); param (c, 1.5, 5 + offset, .05);
        }
        append (c);
    }
    auto held = f; held[4] = held[14] = .5f;
    {
        auto c = make ("T4", rate, block, 6, held); c.polyphony = 4; chord (c, 4, 0, 4);
        param (c,.5,7,20); param (c,.5,8,1); param (c,.5,9,18); param (c,1,7,20000);
        param (c,1.5,6,0); param (c,2,6,1); param (c,2,7,1800); param (c,2,8,.18); param (c,2,9,3);
        append (c);
    }
    for (int variant = 0; variant < 2; ++variant)
    {
        auto c = make ("T5", rate, block, 6, held); c.id += variant == 0 ? "-wave" : "-unison";
        c.polyphony = 4; chord (c,4,0,4);
        if (variant == 0)
        {
            for (int w = 0; w < 5; ++w)
            { param (c,.5*(w+1),40,w); param (c,.5*(w+1),42,w); }
            param (c,2.5,41,.05); param (c,2.5,43,.95);
            param (c,3,41,.95); param (c,3,43,.05);
        }
        else
        {
            for (int u = 1; u <= 4; ++u) param (c,.5*u,29,u);
            param (c,2.5,24,0); param (c,3,24,1);
        }
        append (c);
    }
    // Core LIGHT/OFF/STRESS trajectory plus all four waveforms / both restart
    // modes. Variant 4 is the exact core; 0..3 hold the selected test waveform.
    for (int restart = 0; restart < 2; ++restart)
        for (int w = 0; w < 5; ++w)
            for (int missing = 0; missing < 2; ++missing)
            {
                auto c = make ("T6",rate,block,6,held);
                c.id += "-restart" + std::to_string (restart) + "-wave" + std::to_string (w)
                      + (missing == 0 ? "-missing" : "-invalid");
                c.polyphony = 4; c.requested[36] = static_cast<float> (restart);
                if (w < 4) c.requested[35] = static_cast<float> (w);
                chord (c,4,0,4);
                for (int step = 0; step < 3; ++step)
                {
                    auto v = f; lfo (v, step == 0 ? 1 : step == 1 ? 0 : 2);
                    for (int p : {32,33,35,36,37,38,39})
                        param (c,.5*(step+1),p,p == 36 ? restart : p == 35 && w < 4 ? w : v[static_cast<size_t> (p)]);
                }
                param (c,2,33,1); param (c,2,50,1);
                c.events.push_back ({grid(2.5,rate),Kind::tempo,0,90});
                c.events.push_back ({grid(3,rate),Kind::tempo,0,135});
                // Finite but out of the host's legal tempo range for invalid.
                c.events.push_back ({grid(3.5,rate),Kind::tempo,missing == 0 ? 1 : 0,0});
                append (c);
            }
    auto b = held; fx (b,4); lfo (b,1);
    for (int variant = 0; variant < 5; ++variant)
    {
        auto c = make ("T7",rate,block,6,b); c.id += "-variant" + std::to_string (variant);
        c.polyphony = 4; chord (c,4,0,1);
        if (variant == 0)
            for (int order = 0; order < 6; ++order) param (c,.5*(order+1),61,order);
        else
            for (int effect = 0; effect < 3; ++effect)
                if (variant == 4 || effect == variant-1)
                {
                    const auto mix = std::array {48,55,60}[static_cast<size_t> (effect)];
                    const auto bypass = std::array {44,49,56}[static_cast<size_t> (effect)];
                    param(c,.5,mix,0); param(c,1,mix,1); param(c,1.5,mix,.5);
                    param(c,2,bypass,1); param(c,2.5,bypass,0);
                }
        append (c);
    }
    {
        auto values = f; fx(values,4); lfo(values,1);
        auto c = make ("T8",rate,block,6,values); c.polyphony=4; typicalMusic(c);
        c.events.push_back ({grid(1,rate),Kind::state,0,0});
        c.events.push_back ({grid(2,rate),Kind::state,1,0});
        c.events.push_back ({grid(3,rate),Kind::factory,0,0});
        append (c);
    }
    {
        auto c = make ("T9",rate,block,6,held); c.lifecycle = true;
        // The runner expands the documented rate/block/reset plan independently
        // of this nominal pair; each pair remains its own compatibility probe.
        append (c);
    }
}
}
std::vector<Case> makeCases()
{
    std::vector<Case> result;
    result.reserve (8000);
    for (const auto* family : {"M","W","X"})
        for (int rate : {44100,48000,96000}) for (int block : {64,128,256,512})
            for (int p : {1,4,8,16}) for (int u : {1,2,4})
            {
                const auto suffix = "-p" + std::to_string(p) + "-u" + std::to_string(u);
                if (std::string (family) == "X")
                {
                    for (int high : {0,1}) for (int light : {0,1}) for (int longRelease : {0,1})
                    {
                        auto v = foundation(); v[29] = static_cast<float>(u); fx(v,4); filter(v,high != 0); lfo(v,light);
                        v[5] = v[15] = longRelease == 1 ? 10.0f : .18f;
                        auto c = make(family,rate,block,4,v);
                        c.id += suffix + "-filter" + std::to_string(high) + "-lfo" + std::to_string(light)
                              + "-release" + std::to_string(longRelease);
                        matrixLoad(c,p,false); result.push_back(std::move(c));
                    }
                }
                else
                    for (int stress : {0,1}) for (int variant = 0; variant < 5; ++variant) for (int osc : {0,1})
                    {
                        auto v = profile(stress != 0); v[29] = static_cast<float>(u); v[24] = static_cast<float>(osc);
                        const auto isM = std::string(family) == "M";
                        fx(v,isM ? variant : 4); wave(v,isM ? (stress == 0 ? 0 : 4) : variant,stress != 0);
                        auto c = make(family,rate,block,4,v);
                        c.id += suffix + "-stress" + std::to_string(stress) + (isM ? "-fx" : "-wave")
                              + std::to_string(variant) + "-osc2" + std::to_string(osc);
                        matrixLoad(c,p,stress != 0); result.push_back(std::move(c));
                    }
            }
    for (int rate : {44100,48000,96000}) for (int block : {64,128,256,512})
    {
        for (int w = 0; w < 5; ++w)
        {
            auto a = foundation(); a[29]=1; a[24]=a[16]=a[20]=0; wave(a,w,false);
            auto c = make("A",rate,block,6,a); c.id += "-wave" + std::to_string(w);
            for (int i=0;i<4;++i) note(c,60,96,i,i+.125);
            phases(c,{0,3.125,6},{"musical","tail"}); result.push_back(c);
            auto b = foundation(); fx(b,4); lfo(b,1); wave(b,w,false);
            c=make("B",rate,block,6,b); c.id += "-wave" + std::to_string(w); c.polyphony=4; typicalMusic(c);
            phases(c,{0,3.125,6},{"musical","tail"}); result.push_back(c);
            b[29]=4; b[4]=b[14]=.5f; b[5]=b[15]=2;
            c=make("C",rate,block,8,b); c.id += "-wave" + std::to_string(w); c.polyphony=8;
            chord(c,8,0,2); phases(c,{0,2,4,8},{"held-load","release-window","late-tail"}); result.push_back(c);
            for (int order=0;order<6;++order)
            {
                auto d=profile(true); d[29]=4; d[4]=d[14]=1; d[61]=static_cast<float>(order); fx(d,4); wave(d,w,true);
                c=make("D",rate,block,16,d); c.id += "-wave"+std::to_string(w)+"-order"+std::to_string(order);
                c.polyphony=16; chord(c,16,0,4,true);
                phases(c,{0,4,14,16},{"held-load","release-window","late-tail"}); result.push_back(c);
            }
        }
        for (int variant=0;variant<4;++variant)
        {
            auto e=profile(true); e[29]=4; e[4]=e[14]=.5f; fx(e,variant==3 ? 0 : 4);
            auto c=make("E",rate,block,128,e); c.id += "-variant"+std::to_string(variant); c.polyphony=16;
            for (int i=0;i<16;++i) note(c,notes[static_cast<size_t>(i)],velocities[static_cast<size_t>(i)],i*.125,4);
            if (variant==1 || variant==2)
                for (int index : variant==1 ? std::array{44,49,56} : std::array{48,55,60})
                    c.events.push_back ({((4LL*rate+block-1)/block)*block,Kind::parameter,index,variant==1 ? 1.0 : 0.0});
            phases(c,{0,4,16,64,124,128},{"trigger-hold","voice-release-and-fx","late-tails","long-tails","idle-window"});
            result.push_back(c);
        }
        addTransitions(result,rate,block);
    }
    for (auto& c : result)
        std::stable_sort(c.events.begin(),c.events.end(),[](const Event& a,const Event& b)
        { return a.sample != b.sample ? a.sample < b.sample : a.kind < b.kind; });
    validateInventory(result);
    return result;
}
void validateInventory (const std::vector<Case>& cases)
{
    std::set<std::string> ids;
    std::map<std::string,int> counts;
    for (const auto& c : cases)
    {
        require(ids.insert(c.id).second,"Duplicate case ID"); ++counts[c.family];
        require(c.samples>0 && c.samples%c.block==0,"Invalid full-block render length");
        for (const auto v : c.requested) require(std::isfinite(v),"Nonfinite fixture");
        std::int64_t previous=-1;
        for (const auto& e : c.events)
        {
            require(e.sample>=previous && e.sample<c.samples && std::isfinite(e.value),"Invalid event"); previous=e.sample;
            if (e.kind==Kind::parameter)
                require(e.sample%c.block==0 && e.index>=0 && e.index<62,"Non-block-aligned or unknown parameter");
        }
        std::int64_t edge=0;
        for (const auto& phase : c.phases)
        { require(phase.begin==edge && phase.end>edge,"Incomplete phase coverage"); edge=phase.end; }
        require(edge==c.samples,"Missing phase samples");
        if (c.sustainLoad) require(c.requested[4]==.5f && c.requested[14]==.5f,"Invalid load sustain");
    }
    for (const auto& [family,count] : std::map<std::string,int>{{"M",2880},{"W",2880},{"X",1152},
             {"A",60},{"B",60},{"C",60},{"D",360},{"E",48},{"T1",36},{"T2",12},{"T3",12},
             {"T4",12},{"T5",24},{"T6",240},{"T7",60},{"T8",12},{"T9",12}})
        require(counts[family]==count,"Wrong inventory count for "+family);
    require(cases.size()==7920,"Unexpected full inventory size");
}
Case prefixWindow (const Case& source, double seconds)
{
    require(!source.lifecycle && std::isfinite(seconds) && seconds>0,"Invalid prefix window");
    auto c=source;
    c.samples=std::min(c.samples,((static_cast<std::int64_t>(std::ceil(seconds*c.rate))+c.block-1)/c.block)*c.block);
    std::erase_if(c.events,[&](const Event& event){return event.sample>=c.samples;});
    std::erase_if(c.phases,[&](const Phase& phase){return phase.begin>=c.samples;});
    for(auto& phase:c.phases) phase.end=std::min(phase.end,c.samples);
    require(!c.phases.empty() && c.phases.front().begin==0 && c.phases.back().end==c.samples,"Incomplete prefix phases");
    return c;
}
namespace
{
std::vector<Case> representativeCases (const std::vector<Case>& all)
{
    // User-approved 2026-09-03 scope: one-factor scaling, not a Cartesian product.
    // Derived from the preserved input fixtures; no product defaults are changed.
    std::set<std::string> wanted {"A-r48000-b128-wave0","B-r48000-b128-wave0","C-r48000-b128-wave0"};
    const auto matrixId=[](int rate,int block,int voices,int unison,int stress,int effect,int osc2)
    {
        return "M-r"+std::to_string(rate)+"-b"+std::to_string(block)+"-p"+std::to_string(voices)
            +"-u"+std::to_string(unison)+"-stress"+std::to_string(stress)+"-fx"+std::to_string(effect)+"-osc2"+std::to_string(osc2);
    };
    for(int voices:{1,4,8,16}) wanted.insert(matrixId(48000,128,voices,2,0,4,1));
    for(int unison:{1,2,4}) wanted.insert(matrixId(48000,128,8,unison,0,4,1));
    for(int block:{64,128,256,512}) wanted.insert(matrixId(48000,block,8,2,0,4,1));
    for(int rate:{44100,48000,96000}) wanted.insert(matrixId(rate,128,8,2,0,4,1));
    for(int effect:{0,1,2,3,4}) wanted.insert(matrixId(48000,128,8,2,0,effect,1));
    wanted.insert(matrixId(48000,128,8,2,0,4,0));
    wanted.insert(matrixId(48000,128,16,4,1,4,1));
    wanted.insert(matrixId(48000,128,16,4,1,0,1));
    wanted.insert(matrixId(48000,128,16,4,1,4,0));
    for(int waveform:{0,1,2,3,4})
    {
        wanted.insert("W-r48000-b128-p8-u2-stress0-wave"+std::to_string(waveform)+"-osc21");
        wanted.insert("D-r96000-b64-wave"+std::to_string(waveform)+"-order0");
    }
    for(int order:{1,2,3,4,5}) wanted.insert("D-r96000-b64-wave4-order"+std::to_string(order));
    wanted.insert("D-r48000-b128-wave4-order0");
    wanted.insert("D-r96000-b512-wave4-order0");
    require(wanted.size()==representativeCaseCount,"Representative selection count changed");
    std::vector<Case> result;
    for(const auto& original:all)
        if(wanted.contains(original.id))
        {
            auto c=prefixWindow(original,2.0);
            c.originId=original.id;
            c.id += "-short2";
            result.push_back(std::move(c));
        }
    require(result.size()==wanted.size(),"Missing representative source fixture");
    return result;
}
}
std::vector<Case> selectCases (const std::vector<Case>& all, const std::string& suite,
                             const std::string& caseId, int shardIndex, int shardCount)
{
    require(suite=="all" || suite=="matrix" || suite=="reference" || suite=="transitions" || suite=="quick" || suite=="representative","Unknown suite");
    require(shardCount>0 && shardIndex>=0 && shardIndex<shardCount,"Invalid shard");
    std::vector<Case> selected;
    const auto representative=suite=="representative" ? representativeCases(all) : std::vector<Case>{};
    const auto& inventory=suite=="representative" ? representative : all;
    for (const auto& c : inventory)
    {
        const auto matrix=c.family=="M" || c.family=="W" || c.family=="X";
        const auto transition=c.family.starts_with("T");
        const auto quick=(c.id=="B-r48000-b128-wave0" || c.id=="T4-r48000-b128" || c.id=="T2-r48000-b128");
        const auto inSuite=suite=="all" || suite=="representative" || (suite=="matrix" && matrix) || (suite=="reference" && !matrix && !transition)
                         || (suite=="transitions" && transition) || (suite=="quick" && quick);
        if (inSuite && (caseId.empty() || c.id==caseId)) selected.push_back(c);
    }
    require(!selected.empty(),"Unknown case ID or empty suite");
    std::vector<Case> shard;
    for (size_t i=0;i<selected.size();++i)
        if (i%static_cast<size_t>(shardCount)==static_cast<size_t>(shardIndex)) shard.push_back(selected[i]);
    require(!shard.empty(),"Empty shard");
    return shard;
}
juce::var describe (const Case& c)
{
    auto out=object(); put(out,"id",c.id); put(out,"family",c.family); put(out,"sample_rate",c.rate);
    put(out,"nominal_block_size",c.block); put(out,"frames",static_cast<juce::int64>(c.samples));
    put(out,"polyphony_requested",c.polyphony); put(out,"raw_requested",array(c.requested));
    put(out,"sustain_load",c.sustainLoad); put(out,"lifecycle_plan",c.lifecycle);
    if(!c.originId.empty())
    {
        put(out,"protocol",representativeProtocol); put(out,"origin_id",c.originId);
        put(out,"measurement_window_seconds",2.0); put(out,"warmup_window_seconds",0.5);
        put(out,"window_rule","unchanged initial samples, rounded upward to complete blocks; no retiming");
    }
    juce::Array<juce::var> events, phaseList;
    for (const auto& e : c.events)
    {
        auto v=object(); put(v,"sample",static_cast<juce::int64>(e.sample)); put(v,"kind",static_cast<int>(e.kind));
        put(v,"index",e.index); put(v,"value",e.value); events.add(v);
        put(events.getReference(events.size()-1),"block_index",static_cast<juce::int64>(e.sample/c.block));
        put(events.getReference(events.size()-1),"block_offset",static_cast<int>(e.sample%c.block));
    }
    for (const auto& p : c.phases)
    {
        auto v=object(); put(v,"name",p.name); put(v,"begin",static_cast<juce::int64>(p.begin));
        put(v,"end",static_cast<juce::int64>(p.end)); phaseList.add(v);
    }
    put(out,"events",events); put(out,"phases",phaseList); return out;
}
}
