#include "rendercontext.h"
#include "timing/arrangement.h"
#include "timing/global_loop.h"
#include "timing/operators.h"
#include "timing/piecewise_linear.h"
#include "timing/warp.h"
#include <catch2/catch.hpp>
#include <emscripten/bind.h>
#include <emscripten/console.h>

EMSCRIPTEN_DECLARE_VAL_TYPE(TimingAutomationArray);
EMSCRIPTEN_DECLARE_VAL_TYPE(WarpMarkerArray);

std::shared_ptr<time_transform::TimelineTempoMap>
js_TimelineTempoMap_fromArray(double bps, const TimingAutomationArray &array) {
  assert(array.isArray());

  auto point_arr =
      std::vector<time_transform::TimelineTempoMap::LinearSegment>();
  for (const auto &item : array) {
    auto beats = item["beats"].as<double>();
    auto value = item["value"].as<double>();
    auto curve = item["curve"].as<double>();
    assert(curve == 0.0);
    assert(std::isfinite(beats));
    assert(std::isfinite(value));
    point_arr.emplace_back(time_units::Beats(beats), value);
  }
  return std::make_shared<time_transform::TimelineTempoMap>(
      bps, std::move(point_arr));
}

std::shared_ptr<time_transform::WarpMap<double>>
js_WarpMap_fromArray(const WarpMarkerArray &array) {
  assert(array.isArray());
  auto markers = std::vector<time_transform::WarpMap<double>::WarpMarker>();
  for (const auto &item : array) {
    auto timeInOutput = item["timeInOutput"].as<double>();
    auto timeInUnderlyingBuffer = item["timeInUnderlyingBuffer"].as<double>();
    markers.emplace_back(time_units::Beats(timeInOutput),
                         time_units::Seconds(timeInUnderlyingBuffer));
    assert(std::isfinite(timeInOutput));
    assert(std::isfinite(timeInUnderlyingBuffer));
  }
  return std::make_shared<time_transform::WarpMap<double>>(std::move(markers));
}

EMSCRIPTEN_BINDINGS(timing) {
  using namespace emscripten;

  register_optional<double>();

  register_type<TimingAutomationArray>(
      "{ beats: number, value: number, curve: number }[]");

  register_type<WarpMarkerArray>(
      "{ timeInOutput: number, timeInUnderlyingBuffer: number }[]");

  class_<time_transform::TimelineTempoMap>("TimelineTempoMap")
      .smart_ptr<std::shared_ptr<time_transform::TimelineTempoMap>>(
          "TimelineTempoMap")
      .class_function("fromArray(defaultBps, automationPoints)",
                      &js_TimelineTempoMap_fromArray, nonnull<ret_val>())
      .property("defaultBps", &time_transform::TimelineTempoMap::getDefaultDpcu)
      .function("transformBeatsToSeconds(beats)", &time_transform::TimelineTempoMap::js_transformBeatsToSeconds);

  class_<time_transform::WarpMap<double>>("WarpMap")
      .smart_ptr<std::shared_ptr<time_transform::WarpMap<double>>>("WarpMap")
      .class_function("fromArray(warpMarkers)", &js_WarpMap_fromArray,
                      nonnull<ret_val>());
};

template <time_transform::IsTimeTransformer T>
static void
requireMapPoint(const T &map, const typename T::DomainTime beats,
                const std::optional<typename T::CodomainTime> expected,
                const time_transform::SegmentMarks::MaskType expectedMask =
                    time_transform::SegmentMarks::NONE) {
  auto seg = map.getSegmentIteratorAt(beats);
  REQUIRE(seg != map.end());
  auto result = seg->map_point(beats);
  // Log segment, result, and expected values for debugging
  emscripten_console_log(("Segment: " + seg->toString()).c_str());
  if (result.has_value()) {
    emscripten_console_log(("Result: " + result->toString()).c_str());
  } else {
    emscripten_console_log("Result: nullopt");
  }
  emscripten_console_log(
      ("Expected: " + (expected.has_value() ? expected->toString() : "nullopt"))
          .c_str());
  if (expected.has_value()) {
    REQUIRE(result.has_value());
    REQUIRE(*result == *expected);
  }
  REQUIRE(seg->get_mask() == expectedMask);
};

TEST_CASE("timing tests", "[timing]") {
  using namespace time_transform;
  using namespace time_units;

  SECTION("tempo map empty") {
    auto test_tempo_map = TimelineTempoMap(2.0, {});
    requireMapPoint(test_tempo_map, Beats(0.0), Seconds(0.0));
    requireMapPoint(test_tempo_map, Beats(10.0), Seconds(5.0));
    requireMapPoint(test_tempo_map, Beats(-10.0), Seconds(-5.0));

    // iterator should split domain into (-inf, 0) and [0, inf)
    auto it = test_tempo_map.begin();
    emscripten_console_log(it->toString().c_str());
    REQUIRE(it->source_range.start == Beats<double>::neg_inf());
    REQUIRE(it->source_range.end == Beats(0.0));
    REQUIRE(it->target_range.start == Seconds<double>::neg_inf());
    REQUIRE(it->target_range.end == Seconds(0.0));
    REQUIRE(!it->left_anchored);
    REQUIRE(it->slope == 0.5);
    ++it;
    REQUIRE(it->source_range.start == Beats(0.0));
    REQUIRE(it->source_range.end == Beats<double>::inf());
    REQUIRE(it->target_range.start == Seconds(0.0));
    REQUIRE(it->target_range.end == Seconds<double>::inf());
    REQUIRE(it->left_anchored);
    REQUIRE(it->slope == 0.5);
    ++it;
    REQUIRE(it == test_tempo_map.end());

    // test iterator at
    auto it2 = test_tempo_map.getSegmentIteratorAt(Beats(-1.0));
    auto it3 = test_tempo_map.getSegmentIteratorAt(Beats(0.0));
    REQUIRE(it2 == test_tempo_map.begin());
    REQUIRE(it3 == std::next(it2));

    REQUIRE(findDomainPointForCodomainDelta(test_tempo_map, Beats(0.0),
                                            SecondsDelta(1.0)) == Beats(2.0));
    REQUIRE(findDomainPointForCodomainDelta(test_tempo_map, Beats(2.0),
                                            SecondsDelta(1.0)) == Beats(4.0));
    emscripten_console_log(
        ("findDomainPointForCodomainDelta(Beats(0.0), SecondsDelta(-1.0)) == " +
         findDomainPointForCodomainDelta(test_tempo_map, Beats(0.0),
                                         SecondsDelta(-1.0))
             .toString())
            .c_str());
    REQUIRE(findDomainPointForCodomainDelta(test_tempo_map, Beats(0.0),
                                            SecondsDelta(-1.0)) == Beats(-2.0));
  }

  SECTION("tempo map one point") {
    // in this case, the bps of the single point is used for all domain times
    auto test_tempo_map = TimelineTempoMap(2.0, {{Beats(4.0), 3.0}});

    requireMapPoint(test_tempo_map, Beats(0.0), Seconds(0.0));
    requireMapPoint(test_tempo_map, Beats(3.0), Seconds(1.0));
    requireMapPoint(test_tempo_map, Beats(-3.0), Seconds(-1.0));
    requireMapPoint(test_tempo_map, Beats(30.0), Seconds(10.0));

    // iterator should split domain into (-inf, 4.0) and [4.0, inf)
    auto it = test_tempo_map.begin();
    REQUIRE(it->source_range.start == Beats<double>::neg_inf());
    REQUIRE(it->source_range.end == Beats(4.0));
    REQUIRE(!it->left_anchored);
    REQUIRE(it->slope == 1.0 / 3.0);
    ++it;
    REQUIRE(it->source_range.start == Beats(4.0));
    REQUIRE(it->source_range.end == Beats<double>::inf());
    REQUIRE(it->left_anchored);
    REQUIRE(it->slope == 1.0 / 3.0);
    ++it;
    REQUIRE(it == test_tempo_map.end());
  }

  SECTION("tempo map two points right of origin") {
    auto test_tempo_map =
        TimelineTempoMap(2.0, {{Beats(4.0), 3.0}, {Beats(9.0), 4.0}});
    requireMapPoint(test_tempo_map, Beats(0.0), Seconds(0.0));
    requireMapPoint(test_tempo_map, Beats(-3.0), Seconds(-1.0));
    requireMapPoint(test_tempo_map, Beats(9.0), Seconds(3.0));
    requireMapPoint(test_tempo_map, Beats(13.0), Seconds(4.0));
    requireMapPoint(test_tempo_map, Beats(17.0), Seconds(5.0));

    REQUIRE(findDomainPointForCodomainDelta(test_tempo_map, Beats(4.0),
                                            SecondsDelta(-0.0001)) <
            Beats(4.0));
  }

  SECTION("tempo map two points left of origin") {
    // This isn't symmetric with "two points right of origin" because rate
    // changes apply to all points after (right of) the anchor point
    auto test_tempo_map =
        TimelineTempoMap(2.0, {{Beats(-100.0), 4.0}, {Beats(-9.0), 3.0}});
    requireMapPoint(test_tempo_map, Beats(0.0), Seconds(0.0));
    requireMapPoint(test_tempo_map, Beats(3.0), Seconds(1.0));
    requireMapPoint(test_tempo_map, Beats(-9.0), Seconds(-3.0));
    requireMapPoint(test_tempo_map, Beats(-13.0), Seconds(-4.0));
    requireMapPoint(test_tempo_map, Beats(-17.0), Seconds(-5.0));
  }

  SECTION("tempo map three points straddling origin") {
    auto test_tempo_map = TimelineTempoMap(
        2.0, {{Beats(-100.0), 5.0}, {Beats(-3.0), 3.0}, {Beats(6.0), 4.0}});
    requireMapPoint(test_tempo_map, Beats(0.0), Seconds(0.0));
    requireMapPoint(test_tempo_map, Beats(-3.0), Seconds(-1.0));
    requireMapPoint(test_tempo_map, Beats(-13.0), Seconds(-3.0));
    requireMapPoint(test_tempo_map, Beats(6.0), Seconds(2.0));
    requireMapPoint(test_tempo_map, Beats(10.0), Seconds(3.0));
  }

  SECTION("tempo map stress test") {
    std::vector<time_transform::TimelineTempoMap::LinearSegment> segments;
    for (int i = 0; i < 1000; ++i) {
      segments.emplace_back(Beats((double)i), 1.0 + i / 1000.0);
    }
    auto test_tempo_map = TimelineTempoMap(2.0, std::move(segments));
    std::uniform_real_distribution<double> dist(0.0, 1000.0);
    std::mt19937 gen;
    for (int i = 0; i < 100; ++i) {
      auto rand_value = dist(gen);
      auto result = test_tempo_map.getSegmentIteratorAt(Beats(rand_value));
      REQUIRE(result != test_tempo_map.end());
      auto mapped = result->map_point(Beats(rand_value));
      REQUIRE(mapped.has_value());
      auto mapped_val = *mapped;
      while (result != test_tempo_map.end()) {
        REQUIRE(result->target_range.end >= mapped_val);
        ++result;
      }
    }
  }

  SECTION("loop map") {
    auto loop_map = GlobalLoopMap(Beats(1.0), Beats(2.0));
    auto it = loop_map.begin();
    auto it_by_map = loop_map.getSegmentIteratorAt(Beats(-123.0));
    REQUIRE(it == it_by_map);
    REQUIRE(it->source_range.start == Beats<double>::neg_inf());
    REQUIRE(it->source_range.end == Beats(1.0));
    REQUIRE(it->target_range.start == Beats<double>::neg_inf());
    REQUIRE(it->target_range.end == Beats(1.0));
    REQUIRE(!it->left_anchored);
    REQUIRE(it->slope == 1.0);
    REQUIRE(!it->has_mark(SegmentMarks::SEEK_TO_LOOP_START));
    ++it;
    it_by_map = loop_map.getSegmentIteratorAt(Beats(1.1));
    REQUIRE(it == it_by_map);
    REQUIRE(it->source_range.start == Beats(1.0));
    REQUIRE(it->source_range.end == Beats(2.0));
    REQUIRE(it->target_range.start == Beats(1.0));
    REQUIRE(it->target_range.end == Beats(2.0));
    REQUIRE(it->left_anchored);
    REQUIRE(it->slope == 1.0);
    REQUIRE(!it->has_mark(SegmentMarks::SEEK_TO_LOOP_START));
    ++it;
    it_by_map = loop_map.getSegmentIteratorAt(Beats(2.1));
    REQUIRE(it == it_by_map);
    REQUIRE(it->source_range.start == Beats(2.0));
    REQUIRE(it->source_range.end == Beats(3.0));
    REQUIRE(it->target_range.start == Beats(1.0));
    REQUIRE(it->target_range.end == Beats(2.0));
    REQUIRE(it->left_anchored);
    REQUIRE(it->slope == 1.0);
    REQUIRE(it->has_mark(SegmentMarks::SEEK_TO_LOOP_START));
  }

  SECTION("composed loop and tempo map") {
    auto test_loop_map = TimelineLoopMap(Beats(1.0), Beats(2.0));
    auto test_tempo_map = TimelineTempoMap(2.0, {{Beats(0.0), 2.0},
                                                 {Beats(1.5), 2.0},
                                                 {Beats(1.8), 2.0},
                                                 {Beats(2.1), 2.0}});
    auto composed_map = CompositionMap(test_loop_map, test_tempo_map);

    // Test iteration through the composed map
    auto it = composed_map.getSegmentIteratorAt(Beats(0.9));
    REQUIRE(it != composed_map.end());

    // First segment (before loop)
    REQUIRE(it->source_range.end == Beats(1.0));
    REQUIRE(it->target_range.end ==
            Seconds(0.5));     // 1.0 beats at 2.0 bpm = 0.5 seconds
    REQUIRE(it->slope == 0.5); // 1/2.0 bpm = 0.5 seconds per beat
    REQUIRE(!it->has_mark(SegmentMarks::SEEK_TO_LOOP_START));
    ++it;

    // Segments in first iteration of loop
    while (it != composed_map.end() && it->source_range.start < Beats(2.0)) {
      emscripten_console_log("step");
      emscripten_console_log(it->toString().c_str());
      REQUIRE(it->slope == 0.5);
      REQUIRE(!it->has_mark(SegmentMarks::SEEK_TO_LOOP_START));
      ++it;
    }

    // After loop segment
    emscripten_console_log("out");
    emscripten_console_log(it->toString().c_str());
    REQUIRE(it->source_range.start == Beats(2.0));
    REQUIRE(it->source_range.end > Beats(2.0));
    REQUIRE(it->target_range.start == Seconds(0.5));
    REQUIRE(it->target_range.end > Seconds(0.5));
    REQUIRE(it->slope == 0.5);
    REQUIRE(it->has_mark(SegmentMarks::SEEK_TO_LOOP_START));
    ++it;
  }

  SECTION("render context") {
    auto test_tempo_map = TimelineTempoMap(
        2.0, {{Beats(-100.0), 5.0}, {Beats(-3.0), 3.0}, {Beats(4.0), 4.0}});
    auto position = Beats(0.0);
    for (int i = 0; i < 30; i++) {
      auto newPosition = boundedSubdivide(
          [&](const time_transform::MappedSegment<time_units::BeatsTag,
                                                  time_units::SecondsTag,
                                                  double> &segment,
              time_units::Beats<double> position,
              time_units::Seconds<double> mappedPosition, size_t offset,
              size_t frameCount, time_units::BeatsDelta<double> segmentDuration)
              -> std::optional<time_units::Beats<double>> {
            emscripten_console_log(
                ("position: " + position.toString() +
                 " mappedPosition: " + mappedPosition.toString() +
                 " slope: " + std::to_string(segment.slope) +
                 " offset: " + std::to_string(offset) +
                 " frameCount: " + std::to_string(frameCount))
                    .c_str());
            return std::nullopt;
          },
          test_tempo_map, position, 4410, 256);
      emscripten_console_log(("position: " + position.toString() +
                              " -> newPosition: " + newPosition.toString())
                                 .c_str());
      position = newPosition;
    }
  }

  SECTION("arrangement map") {
    auto test_loop_map = time_transform::ArrangementBeatLoopMap(
        Beats(2.0), Beats(1.0), Beats(3.0));
    auto test_offset_map =
        time_transform::ArrangementBeatOffsetMap(Beats(10.0), Beats(20.0));
    auto test_arrangement_map =
        time_transform::ArrangementMap(test_offset_map, test_loop_map);
    requireMapPoint(test_arrangement_map, Beats(9.0), std::nullopt,
                    time_transform::SegmentMarks::INVALID_ARRANGEMENT_TIME);
    requireMapPoint(test_arrangement_map, Beats(21.0), std::nullopt,
                    time_transform::SegmentMarks::INVALID_ARRANGEMENT_TIME);
    requireMapPoint(test_arrangement_map, Beats(10.0), Beats(2.0));
    requireMapPoint(test_arrangement_map, Beats(10.5), Beats(2.5));
    requireMapPoint(test_arrangement_map, Beats(11.0), Beats(1.0));

    auto test_tempo_map = TimelineTempoMap(1.0);

    auto composed_map = CompositionMap(test_arrangement_map, test_tempo_map);

    auto position = Beats(9.0);
    for (int i = 0; i < 30; i++) {
      auto newPosition = boundedSubdivide(
          [&](const time_transform::MappedSegment<time_units::BeatsTag,
                                                  time_units::SecondsTag,
                                                  double> &segment,
              time_units::Beats<double> position,
              time_units::Seconds<double> mappedPosition, size_t offset,
              size_t frameCount, time_units::BeatsDelta<double> segmentDuration)
              -> std::optional<time_units::Beats<double>> {
            emscripten_console_log(
                ("position: " + position.toString() +
                 " mappedPosition: " + mappedPosition.toString() +
                 " slope: " + std::to_string(segment.slope) +
                 " offset: " + std::to_string(offset) +
                 " frameCount: " + std::to_string(frameCount))
                    .c_str());
            return std::nullopt;
          },
          composed_map, position, 4410, 256);
      emscripten_console_log(("position: " + position.toString() +
                              " -> newPosition: " + newPosition.toString())
                                 .c_str());
      position = newPosition;
    }
  }

  SECTION("warp map") {
    auto empty_warp_map = WarpMap<double>({});
    requireMapPoint(empty_warp_map, Beats(-1.0), Seconds(-1.0));
    requireMapPoint(empty_warp_map, Beats(0.0), Seconds(0.0));
    requireMapPoint(empty_warp_map, Beats(1.0), Seconds(1.0));
    requireMapPoint(empty_warp_map, Beats(2.0), Seconds(2.0));

    auto single_marker_warp_map = WarpMap<double>({{Beats(2.0), Seconds(5.0)}});
    requireMapPoint(single_marker_warp_map, Beats(1.0), Seconds(4.0));
    requireMapPoint(single_marker_warp_map, Beats(2.0), Seconds(5.0));
    requireMapPoint(single_marker_warp_map, Beats(3.0), Seconds(6.0));

    auto warp_map = WarpMap<double>(
        {{Beats(0.0), Seconds(0.0)}, {Beats(1.0), Seconds(1.0)}});
    requireMapPoint(warp_map, Beats(-1.0), Seconds(-1.0));
    requireMapPoint(warp_map, Beats(0.0), Seconds(0.0));
    requireMapPoint(warp_map, Beats(1.0), Seconds(1.0));
    requireMapPoint(warp_map, Beats(0.5), Seconds(0.5));
    requireMapPoint(warp_map, Beats(2.0), Seconds(2.0));

    auto warp_map_2 = WarpMap<double>({{Beats(3.0), Seconds(1.0)},
                                       {Beats(4.0), Seconds(11.0)},
                                       {Beats(5.0), Seconds(11.5)},
                                       {Beats(6.0), Seconds(6.0)}});
    requireMapPoint(warp_map_2, Beats(2.0), Seconds(-9.0));
    requireMapPoint(warp_map_2, Beats(3.0), Seconds(1.0));
    requireMapPoint(warp_map_2, Beats(3.2), Seconds(3.0));
    requireMapPoint(warp_map_2, Beats(4.0), Seconds(11.0));
    requireMapPoint(warp_map_2, Beats(4.5), Seconds(11.25));
    requireMapPoint(warp_map_2, Beats(5.0), Seconds(11.5));
    requireMapPoint(warp_map_2, Beats(5.1), Seconds(10.95));
    requireMapPoint(warp_map_2, Beats(6.0), Seconds(6.0));
    requireMapPoint(warp_map_2, Beats(7.0), Seconds(0.5));
  }

  SECTION("warp map iterator behavior") {
    SECTION("zero markers - identity map") {
      WarpMap<double> map({});
      auto it = map.begin();
      REQUIRE(it != map.end());

      // Segment 1: (-inf, 0) -> (-inf, 0), slope 1.0
      REQUIRE(it->source_range.start == Beats<double>::neg_inf());
      REQUIRE(it->source_range.end == Beats<double>(0.0));
      REQUIRE(it->target_range.start == Seconds<double>::neg_inf());
      REQUIRE(it->target_range.end == Seconds<double>(0.0));
      REQUIRE(it->slope == 1.0);
      REQUIRE(!it->left_anchored); // Right anchored at (0,0)

      auto it_prev = it;
      ++it;
      REQUIRE(it != map.end());
      REQUIRE(it != it_prev);
      --it;
      REQUIRE(it == it_prev); // Test decrement
      ++it;                   // advance again

      // Segment 2: [0, +inf) -> [0, +inf), slope 1.0
      REQUIRE(it->source_range.start == Beats<double>(0.0));
      REQUIRE(it->source_range.end == Beats<double>::inf());
      REQUIRE(it->target_range.start == Seconds<double>(0.0));
      REQUIRE(it->target_range.end == Seconds<double>::inf());
      REQUIRE(it->slope == 1.0);
      REQUIRE(it->left_anchored); // Left anchored at (0,0)

      it_prev = it;
      ++it;
      REQUIRE(it == map.end());
      --it;
      REQUIRE(it == it_prev); // Test decrement from end

      // Test getSegmentIteratorAt
      REQUIRE(map.getSegmentIteratorAt(Beats<double>(-10.0)) == map.begin());
      REQUIRE(map.getSegmentIteratorAt(Beats<double>(0.0)) ==
              std::next(map.begin()));
      REQUIRE(map.getSegmentIteratorAt(Beats<double>(10.0)) ==
              std::next(map.begin()));
      REQUIRE(map.getSegmentIteratorAt(Beats<double>::neg_inf()) ==
              map.begin());
      REQUIRE(map.getSegmentIteratorAt(Beats<double>::inf()) ==
              std::next(map.begin()));
    }

    SECTION("one marker - translation map") {
      WarpMap<double> map({{Beats<double>(5.0), Seconds<double>(10.0)}});
      auto it = map.begin();
      REQUIRE(it != map.end());

      // Segment 1: (-inf, 5.0) -> (-inf, 10.0), slope 1.0
      REQUIRE(it->source_range.start == Beats<double>::neg_inf());
      REQUIRE(it->source_range.end == Beats<double>(5.0));
      REQUIRE(it->target_range.start == Seconds<double>::neg_inf());
      REQUIRE(it->target_range.end == Seconds<double>(10.0));
      REQUIRE(it->slope == 1.0);
      REQUIRE(!it->left_anchored); // Right anchored at (5,10)

      auto it_prev = it;
      ++it;
      REQUIRE(it != map.end());
      REQUIRE(it != it_prev);
      --it;
      REQUIRE(it == it_prev);
      ++it;

      // Segment 2: [5.0, +inf) -> [10.0, +inf), slope 1.0
      REQUIRE(it->source_range.start == Beats<double>(5.0));
      REQUIRE(it->source_range.end == Beats<double>::inf());
      REQUIRE(it->target_range.start == Seconds<double>(10.0));
      REQUIRE(it->target_range.end == Seconds<double>::inf());
      REQUIRE(it->slope == 1.0);
      REQUIRE(it->left_anchored); // Left anchored at (5,10)

      it_prev = it;
      ++it;
      REQUIRE(it == map.end());
      --it;
      REQUIRE(it == it_prev);

      // Test getSegmentIteratorAt
      REQUIRE(map.getSegmentIteratorAt(Beats<double>(0.0)) == map.begin());
      REQUIRE(map.getSegmentIteratorAt(Beats<double>(5.0)) ==
              std::next(map.begin()));
      REQUIRE(map.getSegmentIteratorAt(Beats<double>(10.0)) ==
              std::next(map.begin()));
      REQUIRE(map.getSegmentIteratorAt(Beats<double>::neg_inf()) ==
              map.begin());
      REQUIRE(map.getSegmentIteratorAt(Beats<double>::inf()) ==
              std::next(map.begin()));
    }

    SECTION("two markers - basic warp") {
      WarpMap<double> map({{Beats<double>(0.0), Seconds<double>(0.0)},
                           {Beats<double>(10.0), Seconds<double>(5.0)}});
      auto it = map.begin();
      REQUIRE(it != map.end());

      // Segment 1: Extrapolation before first marker (-inf, 0) -> (-inf, 0),
      // slope 0.5
      REQUIRE(it->source_range.start == Beats<double>::neg_inf());
      REQUIRE(it->source_range.end == Beats<double>(0.0));
      REQUIRE(it->target_range.start == Seconds<double>::neg_inf());
      REQUIRE(it->target_range.end == Seconds<double>(0.0));
      REQUIRE(it->slope == 0.5);
      REQUIRE(!it->left_anchored); // Right anchored at (0,0)

      auto it_prev = it;
      ++it;
      REQUIRE(it != map.end());
      REQUIRE(it != it_prev);
      --it;
      REQUIRE(it == it_prev);
      ++it;

      // Segment 2: Interpolation between markers [0, 10) -> [0, 5), slope 0.5
      REQUIRE(it->source_range.start == Beats<double>(0.0));
      REQUIRE(it->source_range.end == Beats<double>(10.0));
      REQUIRE(it->target_range.start == Seconds<double>(0.0));
      REQUIRE(it->target_range.end == Seconds<double>(5.0));
      REQUIRE(it->slope == 0.5);
      REQUIRE(it->left_anchored); // Left anchored at (0,0)

      it_prev = it;
      ++it;
      REQUIRE(it != map.end());
      REQUIRE(it != it_prev);
      --it;
      REQUIRE(it == it_prev);
      ++it;

      // Segment 3: Extrapolation after last marker [10, +inf) -> [5, +inf),
      // slope 0.5
      REQUIRE(it->source_range.start == Beats<double>(10.0));
      REQUIRE(it->source_range.end == Beats<double>::inf());
      REQUIRE(it->target_range.start == Seconds<double>(5.0));
      REQUIRE(it->target_range.end == Seconds<double>::inf());
      REQUIRE(it->slope == 0.5);
      REQUIRE(it->left_anchored); // Left anchored at (10,5)

      it_prev = it;
      ++it;
      REQUIRE(it == map.end());
      --it;
      REQUIRE(it == it_prev);

      // Test getSegmentIteratorAt
      auto begin_it = map.begin();
      auto second_it = std::next(begin_it);
      auto third_it = std::next(second_it);

      REQUIRE(map.getSegmentIteratorAt(Beats<double>(-5.0)) == begin_it);
      REQUIRE(map.getSegmentIteratorAt(Beats<double>(0.0)) == second_it);
      REQUIRE(map.getSegmentIteratorAt(Beats<double>(5.0)) == second_it);
      REQUIRE(map.getSegmentIteratorAt(Beats<double>(10.0)) == third_it);
      REQUIRE(map.getSegmentIteratorAt(Beats<double>(15.0)) == third_it);
      REQUIRE(map.getSegmentIteratorAt(Beats<double>::neg_inf()) == begin_it);
      REQUIRE(map.getSegmentIteratorAt(Beats<double>::inf()) == third_it);
    }

    SECTION("iterator bidirectional check") {
      WarpMap<double> map({{Beats<double>(0.0), Seconds<double>(0.0)},
                           {Beats<double>(10.0), Seconds<double>(5.0)},
                           {Beats<double>(20.0), Seconds<double>(15.0)}});

      auto it = map.begin();
      REQUIRE(it != map.end());

      auto initial_it = it;
      ++it;
      REQUIRE(it != map.end());
      REQUIRE(it != initial_it);
      --it;
      REQUIRE(it == initial_it);

      // Check for std::prev(std::next(it)) == it, for non-end iterators
      if (it != map.end() && std::next(it) != map.end()) {
        auto next_it = std::next(it);
        REQUIRE(std::prev(next_it) == it);
      }

      // Iterate to the end and back
      std::vector<WarpMap<double>::iterator> visited_its;
      for (auto test_it = map.begin(); test_it != map.end(); ++test_it) {
        visited_its.push_back(test_it);
      }
      REQUIRE(!visited_its.empty());

      for (size_t i = visited_its.size(); i > 0; --i) {
        auto current_from_end = visited_its[i - 1];
        if (i <
            visited_its.size()) { // not the last element from forward iteration
          auto prev_from_end_iter = visited_its[i];
          // This doesn't test operator-- directly on map.end(), but on valid
          // iterators
          REQUIRE(std::prev(prev_from_end_iter) == current_from_end);
        }
      }

      // Test decrementing from map.end()
      auto end_it = map.end();
      auto last_valid_it = std::prev(end_it);
      REQUIRE(last_valid_it != map.end());
      REQUIRE(std::next(last_valid_it) == end_it);
    }
  }

  SECTION("tempo map codomain travel") {
    std::vector<TimelineTempoMap::LinearSegment> segments;
    for (int i = 0; i < 100; i++) {
      segments.push_back({Beats<double>(i), 1.0 / (i + 1)});
    }
    auto test_tempo_map = TimelineTempoMap(2.0, std::move(segments));

    for (int i = 1; i < 100; i++) {
      const double rateInThisSegment = 1.0 / i;
      const double delta = -0.032;
      const auto res = findDomainPointForCodomainDelta(
          test_tempo_map, Beats((double)i + 1e-15), SecondsDelta(delta));
      REQUIRE(res == Beats((double)i + 1e-15 + rateInThisSegment * delta));
    }
  }
}