// Standalone tests for reasampler::vst::embed_strip — no VST3, no REAPER, no framework.
// Same fast assert loop as the sibling pure tests (editor_geometry et al.): assert the
// embedded TCP/MCP strip's layout math + zone hit-testing + level fill directly.
//
// Covers: layoutEmbed splitting a normal area into keymap + level band, a tiny area
// (band yields to the keymap minimum, no inversion), and a zero area (all empty);
// zoneSegmentRect mapping the 128-key span linearly, tiling adjacent zones seamlessly,
// clamping out-of-range/inverted notes; zoneAtPoint hitting the covering zone, first-match
// on overlap, missing on uncovered keys and off-band, and rejecting a null/empty list;
// levelFillRect clamping 0..1 and its endpoints.

#include "../src/vst/embed_strip.h"

#include <cstdio>

using namespace reasampler::vst;

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

// --- layoutEmbed --------------------------------------------------------------

static void testLayoutNormalArea() {
    // A comfortable inline strip: keymap band on top, thin level band pinned to the bottom.
    const EmbedLayout L = layoutEmbed(300, 40);
    CHECK(L.keymap.left == 0 && L.keymap.top == 0 && L.keymap.right == 300);
    CHECK(L.levelBand.left == 0 && L.levelBand.right == 300);
    // Level band is the fixed height at the very bottom; keymap fills the rest, contiguous.
    CHECK(L.levelBand.height() == kEmbedLevelBandHeight);
    CHECK(L.levelBand.bottom == 40);
    CHECK(L.keymap.bottom == L.levelBand.top);
    CHECK(L.keymap.height() == 40 - kEmbedLevelBandHeight);
}

static void testLayoutTinyAreaKeepsKeymap() {
    // A very short area: the level band must yield so the keymap keeps its minimum, and no
    // rect inverts.
    const EmbedLayout L = layoutEmbed(300, 8);
    CHECK(L.keymap.height() >= 0);
    CHECK(L.levelBand.height() >= 0);
    CHECK(L.keymap.bottom == L.levelBand.top);
    CHECK(L.levelBand.bottom == 8);
    // The keymap is not starved below its floor when the area allows it.
    CHECK(L.keymap.height() >= kEmbedKeymapMinHeight || 8 < kEmbedKeymapMinHeight);
}

static void testLayoutZeroArea() {
    const EmbedLayout L = layoutEmbed(0, 0);
    CHECK(L.keymap.width() <= 0 && L.keymap.height() <= 0);
    CHECK(L.levelBand.width() <= 0 && L.levelBand.height() <= 0);
    // Negative dimensions clamp to a zero-area, non-inverted rect.
    const EmbedLayout N = layoutEmbed(-50, -50);
    CHECK(N.keymap.right >= N.keymap.left && N.keymap.bottom >= N.keymap.top);
}

// --- zoneSegmentRect ----------------------------------------------------------

static void testZoneSegmentFullSpan() {
    // A zone covering the whole keyboard spans the entire keymap band width.
    const EmbedLayout L = layoutEmbed(256, 40);
    const Rect r = zoneSegmentRect(L, 0, 127);
    CHECK(r.left == L.keymap.left);
    CHECK(r.right == L.keymap.right);
    CHECK(r.top == L.keymap.top && r.bottom == L.keymap.bottom);
}

static void testAdjacentZonesTileSeamlessly() {
    // 256px band, 128 keys -> 2px/key. Zones 0..59 and 60..127 must abut with no gap or
    // overlap: the low zone's right == the high zone's left.
    const EmbedLayout L = layoutEmbed(256, 40);
    const Rect lo = zoneSegmentRect(L, 0, 59);
    const Rect hi = zoneSegmentRect(L, 60, 127);
    CHECK(lo.left == L.keymap.left);
    CHECK(hi.right == L.keymap.right);
    CHECK(lo.right == hi.left);           // seamless tile — the load-bearing assertion
    CHECK(lo.right == L.keymap.left + 60 * 2);  // 60 keys * 2px
}

static void testZoneSegmentClampsBadNotes() {
    const EmbedLayout L = layoutEmbed(256, 40);
    // Out-of-range notes clamp into the band; an inverted zone (low > high) collapses to a
    // zero-or-positive-width rect, never inverts.
    const Rect over = zoneSegmentRect(L, -10, 200);
    CHECK(over.left == L.keymap.left && over.right == L.keymap.right);
    const Rect inv = zoneSegmentRect(L, 100, 20);
    CHECK(inv.right >= inv.left);
}

// --- zoneAtPoint --------------------------------------------------------------

static void testZoneAtPointHits() {
    const EmbedLayout L = layoutEmbed(256, 40);
    const EmbedZone zones[2] = {{0, 59}, {60, 127}};
    // A point inside the low zone's segment resolves to zone 0; inside the high zone, 1.
    const Rect lo = zoneSegmentRect(L, 0, 59);
    const Rect hi = zoneSegmentRect(L, 60, 127);
    const int yMid = (L.keymap.top + L.keymap.bottom) / 2;
    CHECK(zoneAtPoint(L, zones, 2, lo.left + 1, yMid) == 0);
    CHECK(zoneAtPoint(L, zones, 2, hi.right - 1, yMid) == 1);
}

static void testZoneAtPointFirstMatchOnOverlap() {
    const EmbedLayout L = layoutEmbed(256, 40);
    // Two overlapping zones; the FIRST in order must win the contested keys.
    const EmbedZone zones[2] = {{0, 127}, {40, 80}};
    const int yMid = (L.keymap.top + L.keymap.bottom) / 2;
    const Rect contested = zoneSegmentRect(L, 40, 80);
    CHECK(zoneAtPoint(L, zones, 2, contested.left + 1, yMid) == 0);  // zone 0 wins
}

static void testZoneAtPointMisses() {
    const EmbedLayout L = layoutEmbed(256, 40);
    const EmbedZone zones[1] = {{60, 72}};  // a narrow zone; most keys uncovered
    const int yMid = (L.keymap.top + L.keymap.bottom) / 2;
    // A key left of the zone is uncovered -> -1.
    CHECK(zoneAtPoint(L, zones, 1, L.keymap.left + 1, yMid) == -1);
    // A point in the level band (below the keymap) is off the keymap -> -1.
    CHECK(zoneAtPoint(L, zones, 1, L.levelBand.left + 4, L.levelBand.top) == -1);
    // Empty / null list -> -1.
    CHECK(zoneAtPoint(L, zones, 0, L.keymap.left + 1, yMid) == -1);
    CHECK(zoneAtPoint(L, nullptr, 3, L.keymap.left + 1, yMid) == -1);
}

// --- levelFillRect ------------------------------------------------------------

static void testLevelFillClamps() {
    const EmbedLayout L = layoutEmbed(200, 40);
    // Zero / negative -> empty.
    CHECK(levelFillRect(L, 0.0).width() <= 0);
    CHECK(levelFillRect(L, -1.0).width() <= 0);
    // Full / over-full -> the whole band width.
    CHECK(levelFillRect(L, 1.0).width() == L.levelBand.width());
    CHECK(levelFillRect(L, 5.0).width() == L.levelBand.width());
    // Half -> ~half the band, pinned to the band's left and vertical extent.
    const Rect half = levelFillRect(L, 0.5);
    CHECK(half.left == L.levelBand.left);
    CHECK(half.top == L.levelBand.top && half.bottom == L.levelBand.bottom);
    CHECK(half.width() == L.levelBand.width() / 2);
}

int main() {
    testLayoutNormalArea();
    testLayoutTinyAreaKeepsKeymap();
    testLayoutZeroArea();
    testZoneSegmentFullSpan();
    testAdjacentZonesTileSeamlessly();
    testZoneSegmentClampsBadNotes();
    testZoneAtPointHits();
    testZoneAtPointFirstMatchOnOverlap();
    testZoneAtPointMisses();
    testLevelFillClamps();

    if (g_fail == 0) std::printf("embed_strip: all tests passed\n");
    else std::printf("embed_strip: %d FAILED\n", g_fail);
    return g_fail == 0 ? 0 : 1;
}
