// Standalone tests for reasampler::batch_capture — no REAPER, no test framework.
// Same fast loop as the sibling pure tests (render_settings et al.): assert the batch
// planning (selected-set -> ordered capture units) and result aggregation directly.
//
// Covers (M11 brief §test cases):
//   * selected-set -> capture-request-list planning: items; razor areas; empty set;
//     multiple razor areas across tracks (order preserved, ordinals contiguous);
//     invalid (empty/inverted) ranges dropped.
//   * batch result aggregation: all-success; partial-failure ORDERING (failed
//     ordinals reported in unit order); all-failed; single-unit noun singular.

#include "../src/core/capture/batch_capture.h"

#include <cstdio>
#include <string>
#include <vector>

using namespace reasampler;
using namespace reasampler::capture;

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

// --- planCaptureUnits: items -------------------------------------------------

// Three selected items -> three units, order preserved, ordinals 1,2,3, bounds exact.
static void testPlanItemsPreservesOrderAndBounds() {
    std::vector<BatchRange> in = {{0.0, 1.5}, {2.0, 2.25}, {10.0, 12.0}};
    auto units = planCaptureUnits(in);
    CHECK(units.size() == 3);
    CHECK(units[0].ordinal == 1 && units[0].startSeconds == 0.0 && units[0].endSeconds == 1.5);
    CHECK(units[1].ordinal == 2 && units[1].startSeconds == 2.0 && units[1].endSeconds == 2.25);
    CHECK(units[2].ordinal == 3 && units[2].startSeconds == 10.0 && units[2].endSeconds == 12.0);
}

// --- planCaptureUnits: empty set ---------------------------------------------

// No selected item / no razor area -> empty plan (shell reports nothing-to-batch).
static void testPlanEmptySet() {
    CHECK(planCaptureUnits({}).empty());
}

// --- planCaptureUnits: invalid ranges dropped, ordinals stay contiguous ------

// An empty (end==start) and an inverted (end<start) range are DROPPED; the kept
// units are renumbered 1..k with no gap, so a dropped neighbor never leaves a hole
// in the reported ordinals.
static void testPlanDropsInvalidAndRenumbers() {
    std::vector<BatchRange> in = {
        {0.0, 1.0},   // kept -> #1
        {5.0, 5.0},   // empty  -> dropped
        {3.0, 2.0},   // inverted -> dropped
        {8.0, 9.0},   // kept -> #2
    };
    auto units = planCaptureUnits(in);
    CHECK(units.size() == 2);
    CHECK(units[0].ordinal == 1 && units[0].startSeconds == 0.0 && units[0].endSeconds == 1.0);
    CHECK(units[1].ordinal == 2 && units[1].startSeconds == 8.0 && units[1].endSeconds == 9.0);
}

// --- planCaptureUnits: multiple razor areas across tracks --------------------

// The shell concatenates every track's parsed razor areas in track order; planning
// preserves that concatenation order and assigns contiguous ordinals. Two areas on
// track A followed by one on track B -> ordinals 1,2,3 in that exact order.
static void testPlanRazorAreasAcrossTracksOrdered() {
    std::vector<BatchRange> in = {
        {0.0, 0.5},   // track A, area 1
        {1.0, 1.5},   // track A, area 2
        {4.0, 6.0},   // track B, area 1
    };
    auto units = planCaptureUnits(in);
    CHECK(units.size() == 3);
    CHECK(units[0].startSeconds == 0.0 && units[0].endSeconds == 0.5);
    CHECK(units[1].startSeconds == 1.0 && units[1].endSeconds == 1.5);
    CHECK(units[2].startSeconds == 4.0 && units[2].endSeconds == 6.0);
    CHECK(units[0].ordinal == 1 && units[1].ordinal == 2 && units[2].ordinal == 3);
}

// --- BatchOutcome: all-success -----------------------------------------------

static void testOutcomeAllSuccess() {
    BatchOutcome o;
    o.record(1, true);
    o.record(2, true);
    o.record(3, true);
    CHECK(o.total() == 3);
    CHECK(o.succeeded() == 3);
    CHECK(o.failed() == 0);
    CHECK(o.failures().empty());
    CHECK(o.summaryLine("item") == "ReaSampler batch capture: 3 items captured.");
}

// --- BatchOutcome: partial-failure ordering ----------------------------------

// Failures are reported in UNIT ORDER (#2, #4), not record-count order or sorted —
// the summary must let the user retry exactly the right units. This assertion fails
// if the aggregation reordered, deduped, or dropped a failure.
static void testOutcomePartialFailureOrdering() {
    BatchOutcome o;
    o.record(1, true);
    o.record(2, false, "render produced no output file");
    o.record(3, true);
    o.record(4, false, "capture range is empty");
    o.record(5, true);
    CHECK(o.total() == 5);
    CHECK(o.succeeded() == 3);
    CHECK(o.failed() == 2);

    auto fails = o.failures();
    CHECK(fails.size() == 2);
    CHECK(fails[0].ordinal == 2 && !fails[0].ok);
    CHECK(fails[0].detail == "render produced no output file");
    CHECK(fails[1].ordinal == 4);

    CHECK(o.summaryLine("item") ==
          "ReaSampler batch capture: 3 of 5 items captured (2 failed: #2, #4).");
}

// --- BatchOutcome: all-failed ------------------------------------------------

static void testOutcomeAllFailed() {
    BatchOutcome o;
    o.record(1, false, "x");
    o.record(2, false, "y");
    CHECK(o.succeeded() == 0);
    CHECK(o.failed() == 2);
    CHECK(o.summaryLine("razor area") ==
          "ReaSampler batch capture: 0 of 2 razor areas captured (2 failed: #1, #2).");
}

// --- BatchOutcome: single unit uses singular noun ----------------------------

static void testOutcomeSingularNoun() {
    BatchOutcome o;
    o.record(1, true);
    CHECK(o.summaryLine("item") == "ReaSampler batch capture: 1 item captured.");
}

// --- BatchOutcome: empty (no units planned) ----------------------------------

static void testOutcomeEmpty() {
    BatchOutcome o;
    CHECK(o.total() == 0);
    CHECK(o.summaryLine("item") == "ReaSampler batch capture: nothing to capture.");
}

int main() {
    testPlanItemsPreservesOrderAndBounds();
    testPlanEmptySet();
    testPlanDropsInvalidAndRenumbers();
    testPlanRazorAreasAcrossTracksOrdered();

    testOutcomeAllSuccess();
    testOutcomePartialFailureOrdering();
    testOutcomeAllFailed();
    testOutcomeSingularNoun();
    testOutcomeEmpty();

    if (g_fail == 0) std::printf("All tests passed.\n");
    return g_fail ? 1 : 0;
}
