// Standalone tests for reasampler::instrument::ui::capture_browser — no VST3, no REAPER, no framework.
// Same fast assert loop as the sibling pure tests (embed_strip / editor_geometry): assert
// the capture-first browser's card-grid + bank-filter-tab layout and hit-testing directly.
//
// Covers: layoutBrowser splitting an area into the tab strip + card grid and deriving the
// column count; a tiny/zero area (no inversion, columns >= 1); cardCellRect / cardContentRect
// / cardThumbnailRect / cardLabelRect tiling row-major across columns with the gutter inset
// and the thumbnail band above the label; cardHitTest landing on the card content (and MISSING
// in the inter-card gutter, past the last card, and on the tab strip); filterTabRect dividing
// the strip into equal segments with the last tab absorbing the remainder; filterTabHitTest
// hitting each tab and missing off-strip.

#include "../src/core/instrument/ui/capture_browser.h"

#include <cstdio>

using namespace reasampler;
using namespace reasampler::instrument::ui;

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

// --- layoutBrowser ------------------------------------------------------------

static void testLayoutNormalArea() {
    // Wide enough for several columns of the fixed-width card.
    const BrowserLayout L = layoutBrowser(560, 300);
    CHECK(L.tabStrip.x == 0 && L.tabStrip.y == 0 && L.tabStrip.right() == 560);
    CHECK(L.tabStrip.height == kBrowserTabHeight);
    // The grid starts right below the tab strip and fills the rest, contiguous.
    CHECK(L.grid.y == L.tabStrip.bottom());
    CHECK(L.grid.bottom() == 300 && L.grid.right() == 560);
    // columns = grid.width / cardWidth (>= 1).
    CHECK(L.columns == 560 / kBrowserCardWidth);
    CHECK(L.columns >= 1);
}

static void testLayoutNarrowAreaSingleColumn() {
    // Narrower than one card: still a single column, no inversion.
    const BrowserLayout L = layoutBrowser(kBrowserCardWidth - 10, 200);
    CHECK(L.columns == 1);
    CHECK(L.grid.width >= 0);
    CHECK(L.tabStrip.height == kBrowserTabHeight);
}

static void testLayoutZeroArea() {
    const BrowserLayout L = layoutBrowser(0, 0);
    CHECK(L.tabStrip.width == 0 && L.tabStrip.height == 0);
    CHECK(L.grid.width == 0);
    CHECK(L.columns == 1);  // never zero (avoids a divide-by-zero in card layout)
}

static void testLayoutTinyHeightClampsTabStrip() {
    // A height below the tab band: the tab strip clamps to the area, the grid is empty.
    const BrowserLayout L = layoutBrowser(560, kBrowserTabHeight - 6);
    CHECK(L.tabStrip.height == kBrowserTabHeight - 6);
    CHECK(L.grid.height <= 0);  // no room left for cards
}

// --- card rects ---------------------------------------------------------------

static void testCardCellsTileRowMajor() {
    const BrowserLayout L = layoutBrowser(560, 300);
    const int cols = L.columns;
    // Card 0 is top-left of the grid.
    const Rect c0 = cardCellRect(L, 0);
    CHECK(c0.x == L.grid.x && c0.y == L.grid.y);
    CHECK(c0.width == kBrowserCardWidth && c0.height == kBrowserCardHeight);
    // Card 1 is one card-width to the right, same row.
    const Rect c1 = cardCellRect(L, 1);
    CHECK(c1.x == L.grid.x + kBrowserCardWidth);
    CHECK(c1.y == c0.y);
    // The first card of the SECOND row wraps back to the left, one card-height down.
    const Rect wrap = cardCellRect(L, cols);
    CHECK(wrap.x == L.grid.x);
    CHECK(wrap.y == L.grid.y + kBrowserCardHeight);
}

static void testCardCellNegativeIndex() {
    const BrowserLayout L = layoutBrowser(560, 300);
    const Rect r = cardCellRect(L, -1);
    CHECK(r.x == 0 && r.y == 0 && r.right() == 0 && r.bottom() == 0);
}

static void testCardContentInsetByGutter() {
    const BrowserLayout L = layoutBrowser(560, 300);
    const Rect cell = cardCellRect(L, 0);
    const Rect content = cardContentRect(L, 0);
    CHECK(content.x == cell.x + kBrowserCardGutter);
    CHECK(content.y == cell.y + kBrowserCardGutter);
    CHECK(content.right() == cell.right() - kBrowserCardGutter);
    CHECK(content.bottom() == cell.bottom() - kBrowserCardGutter);
}

static void testThumbnailAboveLabel() {
    const BrowserLayout L = layoutBrowser(560, 300);
    const Rect content = cardContentRect(L, 0);
    const Rect thumb = cardThumbnailRect(L, 0);
    const Rect label = cardLabelRect(L, 0);
    // Thumbnail is the top band of the content; the label is the remainder below it, contiguous.
    CHECK(thumb.x == content.x && thumb.right() == content.right());
    CHECK(thumb.y == content.y);
    CHECK(thumb.height == kBrowserThumbHeight);
    CHECK(label.y == thumb.bottom());
    CHECK(label.bottom() == content.bottom());
    CHECK(label.x == content.x && label.right() == content.right());
}

// --- cardHitTest --------------------------------------------------------------

static void testCardHitCenterOfCard() {
    const BrowserLayout L = layoutBrowser(560, 300);
    const Rect content = cardContentRect(L, 3);
    const int cx = content.x + content.width / 2;
    const int cy = content.y + content.height / 2;
    CHECK(cardHitTest(L, 12, cx, cy) == 3);
}

static void testCardHitMissesGutter() {
    const BrowserLayout L = layoutBrowser(560, 300);
    // A point in the gutter between the content and the cell edge (top-left corner of cell 0)
    // is a miss — only the card CONTENT counts.
    const Rect cell = cardCellRect(L, 0);
    CHECK(cardHitTest(L, 12, cell.x, cell.y) == -1);
}

static void testCardHitMissesPastLastCard() {
    const BrowserLayout L = layoutBrowser(560, 300);
    // Only 2 cards exist; a point on where card 5 WOULD be is a miss.
    const Rect content = cardContentRect(L, 5);
    const int cx = content.x + content.width / 2;
    const int cy = content.y + content.height / 2;
    CHECK(cardHitTest(L, 2, cx, cy) == -1);
}

static void testCardHitMissesTabStrip() {
    const BrowserLayout L = layoutBrowser(560, 300);
    CHECK(cardHitTest(L, 12, 10, L.tabStrip.y + 2) == -1);
}

static void testCardHitZeroCards() {
    const BrowserLayout L = layoutBrowser(560, 300);
    CHECK(cardHitTest(L, 0, 20, 40) == -1);
}

// --- filter tabs --------------------------------------------------------------

static void testFilterTabsTileStrip() {
    const BrowserLayout L = layoutBrowser(560, 300);
    const int n = 4;  // "All" + 3 banks
    const Rect t0 = filterTabRect(L, n, 0);
    const Rect tLast = filterTabRect(L, n, n - 1);
    CHECK(t0.x == L.tabStrip.x);
    // Adjacent tabs share an exact edge (no gap).
    CHECK(filterTabRect(L, n, 0).right() == filterTabRect(L, n, 1).x);
    CHECK(filterTabRect(L, n, 1).right() == filterTabRect(L, n, 2).x);
    // The last tab reaches the strip's right edge exactly (absorbs the remainder).
    CHECK(tLast.right() == L.tabStrip.right());
    // All tabs share the strip's height.
    CHECK(t0.y == L.tabStrip.y && t0.bottom() == L.tabStrip.bottom());
}

static void testFilterTabOutOfRange() {
    const BrowserLayout L = layoutBrowser(560, 300);
    CHECK(filterTabRect(L, 3, -1).width == 0);
    CHECK(filterTabRect(L, 3, 3).width == 0);
    CHECK(filterTabRect(L, 0, 0).width == 0);
}

static void testFilterTabHit() {
    const BrowserLayout L = layoutBrowser(560, 300);
    const int n = 3;
    for (int i = 0; i < n; ++i) {
        const Rect t = filterTabRect(L, n, i);
        const int cx = t.x + t.width / 2;
        const int cy = t.y + t.height / 2;
        CHECK(filterTabHitTest(L, n, cx, cy) == i);
    }
    // Below the strip (in the grid) -> no tab.
    CHECK(filterTabHitTest(L, n, 20, L.grid.y + 4) == -1);
}

int main() {
    testLayoutNormalArea();
    testLayoutNarrowAreaSingleColumn();
    testLayoutZeroArea();
    testLayoutTinyHeightClampsTabStrip();
    testCardCellsTileRowMajor();
    testCardCellNegativeIndex();
    testCardContentInsetByGutter();
    testThumbnailAboveLabel();
    testCardHitCenterOfCard();
    testCardHitMissesGutter();
    testCardHitMissesPastLastCard();
    testCardHitMissesTabStrip();
    testCardHitZeroCards();
    testFilterTabsTileStrip();
    testFilterTabOutOfRange();
    testFilterTabHit();

    if (g_fail == 0) std::printf("capture_browser: all tests passed\n");
    return g_fail != 0;
}
