//     __ _____ _____ _____
//  __|  |   __|     |   | |  JSON for Modern C++ (supporting code)
// |  |  |__   |  |  | | | |  version 3.11.3
// |_____|_____|_____|_|___|  https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT

// cmake/test.cmake selects the C++ standard versions with which to build a
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
// When using macros that are only defined for particular versions of the standard
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
// version macro in a comment close by, like this:
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)

#include "doctest_compatibility.h"

#define _azure_JSON_TESTS_PRIVATE
#include <azure/core/internal/json/json.hpp>
using Azure::Core::Json::_internal::json;

#if JSON_HAS_THREE_WAY_COMPARISON
// this can be replaced with the doctest stl extension header in version 2.5
namespace doctest {
template <> struct StringMaker<std::partial_ordering>
{
  static String convert(const std::partial_ordering& order)
  {
    if (order == std::partial_ordering::less)
    {
      return "std::partial_ordering::less";
    }
    if (order == std::partial_ordering::equivalent)
    {
      return "std::partial_ordering::equivalent";
    }
    if (order == std::partial_ordering::greater)
    {
      return "std::partial_ordering::greater";
    }
    if (order == std::partial_ordering::unordered)
    {
      return "std::partial_ordering::unordered";
    }
    return "{?}";
  }
};
} // namespace doctest

#endif

namespace {
// helper function to check std::less<json::value_t>
// see https://en.cppreference.com/w/cpp/utility/functional/less
template <typename A, typename B, typename U = std::less<json::value_t>> bool f(A a, B b, U u = U())
{
  return u(a, b);
}
} // namespace

TEST_CASE("lexicographical comparison operators")
{
  constexpr auto f_ = false;
  constexpr auto _t = true;
  constexpr auto nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
#if JSON_HAS_THREE_WAY_COMPARISON
  constexpr auto lt = std::partial_ordering::less;
  constexpr auto gt = std::partial_ordering::greater;
  constexpr auto eq = std::partial_ordering::equivalent;
  constexpr auto un = std::partial_ordering::unordered;
#endif

#if JSON_HAS_THREE_WAY_COMPARISON
  INFO("using 3-way comparison");
#endif

#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
  INFO("using legacy comparison");
#endif

  // REQUIRE(std::numeric_limits<json::number_float_t>::has_quiet_NaN);
  REQUIRE(std::isnan(nan));

  SECTION("types")
  {
    std::vector<json::value_t> j_types
        = {json::value_t::null,
           json::value_t::boolean,
           json::value_t::number_integer,
           json::value_t::number_unsigned,
           json::value_t::number_float,
           json::value_t::object,
           json::value_t::array,
           json::value_t::string,
           json::value_t::binary,
           json::value_t::discarded};

    std::vector<std::vector<bool>> expected_lt = {
        // 0   1   2   3   4   5   6   7   8   9
        {f_, _t, _t, _t, _t, _t, _t, _t, _t, f_}, //  0
        {f_, f_, _t, _t, _t, _t, _t, _t, _t, f_}, //  1
        {f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, //  2
        {f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, //  3
        {f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, //  4
        {f_, f_, f_, f_, f_, f_, _t, _t, _t, f_}, //  5
        {f_, f_, f_, f_, f_, f_, f_, _t, _t, f_}, //  6
        {f_, f_, f_, f_, f_, f_, f_, f_, _t, f_}, //  7
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  8
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  9
    };

    SECTION("comparison: less")
    {
      REQUIRE(expected_lt.size() == j_types.size());
      for (size_t i = 0; i < j_types.size(); ++i)
      {
        REQUIRE(expected_lt[i].size() == j_types.size());
        for (size_t j = 0; j < j_types.size(); ++j)
        {
          CAPTURE(i)
          CAPTURE(j)
          // check precomputed values
#if JSON_HAS_THREE_WAY_COMPARISON
          // JSON_HAS_CPP_20 (do not remove; see note at top of file)
          CHECK((j_types[i] < j_types[j]) == expected_lt[i][j]);
#else
          CHECK(operator<(j_types[i], j_types[j]) == expected_lt[i][j]);
#endif
          CHECK(f(j_types[i], j_types[j]) == expected_lt[i][j]);
        }
      }
    }
#if JSON_HAS_THREE_WAY_COMPARISON
    // JSON_HAS_CPP_20 (do not remove; see note at top of file)
    SECTION("comparison: 3-way")
    {
      std::vector<std::vector<std::partial_ordering>> expected = {
          // 0   1   2   3   4   5   6   7   8   9
          {eq, lt, lt, lt, lt, lt, lt, lt, lt, un}, //  0
          {gt, eq, lt, lt, lt, lt, lt, lt, lt, un}, //  1
          {gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, //  2
          {gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, //  3
          {gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, //  4
          {gt, gt, gt, gt, gt, eq, lt, lt, lt, un}, //  5
          {gt, gt, gt, gt, gt, gt, eq, lt, lt, un}, //  6
          {gt, gt, gt, gt, gt, gt, gt, eq, lt, un}, //  7
          {gt, gt, gt, gt, gt, gt, gt, gt, eq, un}, //  8
          {un, un, un, un, un, un, un, un, un, un}, //  9
      };

      // check expected partial_ordering against expected boolean
      REQUIRE(expected.size() == expected_lt.size());
      for (size_t i = 0; i < expected.size(); ++i)
      {
        REQUIRE(expected[i].size() == expected_lt[i].size());
        for (size_t j = 0; j < expected[i].size(); ++j)
        {
          CAPTURE(i)
          CAPTURE(j)
          CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
        }
      }

      // check 3-way comparison against expected partial_ordering
      REQUIRE(expected.size() == j_types.size());
      for (size_t i = 0; i < j_types.size(); ++i)
      {
        REQUIRE(expected[i].size() == j_types.size());
        for (size_t j = 0; j < j_types.size(); ++j)
        {
          CAPTURE(i)
          CAPTURE(j)
          CHECK((j_types[i] <=> j_types[j]) == expected[i][j]); // *NOPAD*
        }
      }
    }
#endif
  }

  SECTION("values")
  {
    json j_values = {
        nullptr,
        nullptr, // 0 1
        -17,
        42, // 2 3
        8u,
        13u, // 4 5
        3.14159,
        23.42, // 6 7
        nan,
        nan, // 8 9
        "foo",
        "bar", // 10 11
        true,
        false, // 12 13
        {1, 2, 3},
        {"one", "two", "three"}, // 14 15
        {{"first", 1}, {"second", 2}},
        {{"a", "A"}, {"b", {"B"}}}, // 16 17
        json::binary({1, 2, 3}),
        json::binary({1, 2, 4}), // 18 19
        json(json::value_t::discarded),
        json(json::value_t::discarded) // 20 21
    };

    std::vector<std::vector<bool>> expected_eq = {
        // 0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21
        {_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  0
        {_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  1
        {f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  2
        {f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  3
        {f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  4
        {f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  5
        {f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  6
        {f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  7
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  8
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  9
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 10
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 11
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 12
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_}, // 13
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_}, // 14
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_}, // 15
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_}, // 16
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_}, // 17
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_}, // 18
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 19
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
    };

    std::vector<std::vector<bool>> expected_lt = {
        // 0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21
        {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t,
         _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, //  0
        {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t,
         _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, //  1
        {f_, f_, f_, _t, _t, _t, _t, _t, f_, f_, _t,
         _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, //  2
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t,
         _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, //  3
        {f_, f_, f_, _t, f_, _t, f_, _t, f_, f_, _t,
         _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, //  4
        {f_, f_, f_, _t, f_, f_, f_, _t, f_, f_, _t,
         _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, //  5
        {f_, f_, f_, _t, _t, _t, f_, _t, f_, f_, _t,
         _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, //  6
        {f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, _t,
         _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, //  7
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t,
         _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, //  8
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t,
         _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, //  9
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 10
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t,
         f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 11
        {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t,
         _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 12
        {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t,
         _t, _t, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 13
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t,
         _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_}, // 14
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t,
         _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 15
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t,
         _t, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_}, // 16
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t,
         _t, f_, f_, _t, _t, _t, f_, _t, _t, f_, f_}, // 17
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 18
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 19
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
        {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
         f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
    };

    SECTION("compares unordered")
    {
      std::vector<std::vector<bool>> expected = {
          // 0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, //  0
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, //  1
          {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, //  2
          {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, //  3
          {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, //  4
          {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, //  5
          {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, //  6
          {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, //  7
          {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, //  8
          {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, //  9
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 10
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 11
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 12
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 13
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 14
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 15
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 16
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 17
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 18
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 19
          {_t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t,
           _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t}, // 20
          {_t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t,
           _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t}, // 21
      };

      // check if two values compare unordered as expected
      REQUIRE(expected.size() == j_values.size());
      for (size_t i = 0; i < j_values.size(); ++i)
      {
        REQUIRE(expected[i].size() == j_values.size());
        for (size_t j = 0; j < j_values.size(); ++j)
        {
          CAPTURE(i)
          CAPTURE(j)
          CHECK(json::compares_unordered(j_values[i], j_values[j]) == expected[i][j]);
        }
      }
    }

#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
    SECTION("compares unordered (inverse)")
    {
      std::vector<std::vector<bool>> expected = {
          // 0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  0
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  1
          {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  2
          {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  3
          {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  4
          {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  5
          {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  6
          {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  7
          {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  8
          {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, //  9
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 10
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 11
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 12
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 13
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 14
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 15
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 16
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 17
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 18
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 19
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
          {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_,
           f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
      };

      // check that two values compare unordered as expected (with legacy-mode enabled)
      REQUIRE(expected.size() == j_values.size());
      for (size_t i = 0; i < j_values.size(); ++i)
      {
        REQUIRE(expected[i].size() == j_values.size());
        for (size_t j = 0; j < j_values.size(); ++j)
        {
          CAPTURE(i)
          CAPTURE(j)
          CAPTURE(j_values[i])
          CAPTURE(j_values[j])
          CHECK(json::compares_unordered(j_values[i], j_values[j], true) == expected[i][j]);
        }
      }
    }
#endif

    SECTION("comparison: equal")
    {
      // check that two values compare equal
      REQUIRE(expected_eq.size() == j_values.size());
      for (size_t i = 0; i < j_values.size(); ++i)
      {
        REQUIRE(expected_eq[i].size() == j_values.size());
        for (size_t j = 0; j < j_values.size(); ++j)
        {
          CAPTURE(i)
          CAPTURE(j)
          CHECK((j_values[i] == j_values[j]) == expected_eq[i][j]);
        }
      }

      // compare with null pointer
      json j_null;
      CHECK(j_null == nullptr);
      CHECK(nullptr == j_null);
    }

    SECTION("comparison: not equal")
    {
      // check that two values compare unequal as expected
      for (size_t i = 0; i < j_values.size(); ++i)
      {
        for (size_t j = 0; j < j_values.size(); ++j)
        {
          CAPTURE(i)
          CAPTURE(j)

          if (json::compares_unordered(j_values[i], j_values[j], true))
          {
            // if two values compare unordered,
            // check that the boolean comparison result is always false
            CHECK_FALSE(j_values[i] != j_values[j]);
          }
          else
          {
            // otherwise, check that they compare according to their definition
            // as the inverse of equal
            CHECK((j_values[i] != j_values[j]) == !(j_values[i] == j_values[j]));
          }
        }
      }

      // compare with null pointer
      const json j_null;
      CHECK((j_null != nullptr) == false);
      CHECK((nullptr != j_null) == false);
      CHECK((j_null != nullptr) == !(j_null == nullptr));
      CHECK((nullptr != j_null) == !(nullptr == j_null));
    }

    SECTION("comparison: less")
    {
      // check that two values compare less than as expected
      REQUIRE(expected_lt.size() == j_values.size());
      for (size_t i = 0; i < j_values.size(); ++i)
      {
        REQUIRE(expected_lt[i].size() == j_values.size());
        for (size_t j = 0; j < j_values.size(); ++j)
        {
          CAPTURE(i)
          CAPTURE(j)
          CHECK((j_values[i] < j_values[j]) == expected_lt[i][j]);
        }
      }
    }

    SECTION("comparison: less than or equal equal")
    {
      // check that two values compare less than or equal as expected
      for (size_t i = 0; i < j_values.size(); ++i)
      {
        for (size_t j = 0; j < j_values.size(); ++j)
        {
          CAPTURE(i)
          CAPTURE(j)
          if (json::compares_unordered(j_values[i], j_values[j], true))
          {
            // if two values compare unordered,
            // check that the boolean comparison result is always false
            CHECK_FALSE(j_values[i] <= j_values[j]);
          }
          else
          {
            // otherwise, check that they compare according to their definition
            // as the inverse of less than with the operand order reversed
            CHECK((j_values[i] <= j_values[j]) == !(j_values[j] < j_values[i]));
          }
        }
      }
    }

    SECTION("comparison: greater than")
    {
      // check that two values compare greater than as expected
      for (size_t i = 0; i < j_values.size(); ++i)
      {
        for (size_t j = 0; j < j_values.size(); ++j)
        {
          CAPTURE(i)
          CAPTURE(j)
          if (json::compares_unordered(j_values[i], j_values[j]))
          {
            // if two values compare unordered,
            // check that the boolean comparison result is always false
            CHECK_FALSE(j_values[i] > j_values[j]);
          }
          else
          {
            // otherwise, check that they compare according to their definition
            // as the inverse of less than or equal which is defined as
            // the inverse of less than with the operand order reversed
            CHECK((j_values[i] > j_values[j]) == !(j_values[i] <= j_values[j]));
            CHECK((j_values[i] > j_values[j]) == !!(j_values[j] < j_values[i]));
          }
        }
      }
    }

    SECTION("comparison: greater than or equal")
    {
      // check that two values compare greater than or equal as expected
      for (size_t i = 0; i < j_values.size(); ++i)
      {
        for (size_t j = 0; j < j_values.size(); ++j)
        {
          CAPTURE(i)
          CAPTURE(j)
          if (json::compares_unordered(j_values[i], j_values[j], true))
          {
            // if two values compare unordered,
            // check that the boolean result is always false
            CHECK_FALSE(j_values[i] >= j_values[j]);
          }
          else
          {
            // otherwise, check that they compare according to their definition
            // as the inverse of less than
            CHECK((j_values[i] >= j_values[j]) == !(j_values[i] < j_values[j]));
          }
        }
      }
    }

#if JSON_HAS_THREE_WAY_COMPARISON
    // JSON_HAS_CPP_20 (do not remove; see note at top of file)
    SECTION("comparison: 3-way")
    {
      std::vector<std::vector<std::partial_ordering>> expected = {
          // 0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21
          {eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt,
           lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, //  0
          {eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt,
           lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, //  1
          {gt, gt, eq, lt, lt, lt, lt, lt, un, un, lt,
           lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, //  2
          {gt, gt, gt, eq, gt, gt, gt, gt, un, un, lt,
           lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, //  3
          {gt, gt, gt, lt, eq, lt, gt, lt, un, un, lt,
           lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, //  4
          {gt, gt, gt, lt, gt, eq, gt, lt, un, un, lt,
           lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, //  5
          {gt, gt, gt, lt, lt, lt, eq, lt, un, un, lt,
           lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, //  6
          {gt, gt, gt, lt, gt, gt, gt, eq, un, un, lt,
           lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, //  7
          {gt, gt, un, un, un, un, un, un, un, un, lt,
           lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, //  8
          {gt, gt, un, un, un, un, un, un, un, un, lt,
           lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, //  9
          {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq,
           gt, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 10
          {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt,
           eq, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 11
          {gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt,
           lt, eq, gt, lt, lt, lt, lt, lt, lt, un, un}, // 12
          {gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt,
           lt, lt, eq, lt, lt, lt, lt, lt, lt, un, un}, // 13
          {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt,
           lt, gt, gt, eq, lt, gt, gt, lt, lt, un, un}, // 14
          {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt,
           lt, gt, gt, gt, eq, gt, gt, lt, lt, un, un}, // 15
          {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt,
           lt, gt, gt, lt, lt, eq, gt, lt, lt, un, un}, // 16
          {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt,
           lt, gt, gt, lt, lt, lt, eq, lt, lt, un, un}, // 17
          {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt,
           gt, gt, gt, gt, gt, gt, gt, eq, lt, un, un}, // 18
          {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt,
           gt, gt, gt, gt, gt, gt, gt, gt, eq, un, un}, // 19
          {un, un, un, un, un, un, un, un, un, un, un,
           un, un, un, un, un, un, un, un, un, un, un}, // 20
          {un, un, un, un, un, un, un, un, un, un, un,
           un, un, un, un, un, un, un, un, un, un, un}, // 21
      };

      // check expected partial_ordering against expected booleans
      REQUIRE(expected.size() == expected_eq.size());
      REQUIRE(expected.size() == expected_lt.size());
      for (size_t i = 0; i < expected.size(); ++i)
      {
        REQUIRE(expected[i].size() == expected_eq[i].size());
        REQUIRE(expected[i].size() == expected_lt[i].size());
        for (size_t j = 0; j < expected[i].size(); ++j)
        {
          CAPTURE(i)
          CAPTURE(j)
          CHECK(std::is_eq(expected[i][j]) == expected_eq[i][j]);
          CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
          if (std::is_gt(expected[i][j]))
          {
            CHECK((!expected_eq[i][j] && !expected_lt[i][j]));
          }
        }
      }

      // check that two values compare according to their expected ordering
      REQUIRE(expected.size() == j_values.size());
      for (size_t i = 0; i < j_values.size(); ++i)
      {
        REQUIRE(expected[i].size() == j_values.size());
        for (size_t j = 0; j < j_values.size(); ++j)
        {
          CAPTURE(i)
          CAPTURE(j)
          CHECK((j_values[i] <=> j_values[j]) == expected[i][j]); // *NOPAD*
        }
      }
    }
#endif
  }

#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
  SECTION("parser callback regression")
  {
    SECTION("filter specific element")
    {
      const auto* s_object = R"(
                {
                    "foo": 2,
                    "bar": {
                        "baz": 1
                    }
                }
            )";
      const auto* s_array = R"(
                [1,2,[3,4,5],4,5]
            )";

      json j_object = json::parse(
          s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json& j) noexcept {
            // filter all number(2) elements
            return j != json(2);
          });

      CHECK(j_object == json({{"bar", {{"baz", 1}}}}));

      json j_array = json::parse(
          s_array, [](int /*unused*/, json::parse_event_t /*unused*/, const json& j) noexcept {
            return j != json(2);
          });

      CHECK(j_array == json({1, {3, 4, 5}, 4, 5}));
    }
  }
#endif
}
