#ifndef SIMDJSON_GENERIC_ONDEMAND_VALUE_H

#ifndef SIMDJSON_CONDITIONAL_INCLUDE
#define SIMDJSON_GENERIC_ONDEMAND_VALUE_H
#include "simdjson/generic/ondemand/base.h"
#include "simdjson/generic/implementation_simdjson_result_base.h"
#include "simdjson/generic/ondemand/value_iterator.h"
#include "simdjson/generic/ondemand/deserialize.h"
#include <vector>
#endif // SIMDJSON_CONDITIONAL_INCLUDE

#include <type_traits>

namespace simdjson {

namespace SIMDJSON_IMPLEMENTATION {
namespace ondemand {
/**
 * An ephemeral JSON value returned during iteration. It is only valid for as long as you do
 * not access more data in the JSON document.
 */
class value {
public:
  /**
   * Create a new invalid value.
   *
   * Exists so you can declare a variable and later assign to it before use.
   */
  simdjson_inline value() noexcept = default;

  /**
   * Get this value as the given type.
   *
   * Supported types: object, array, raw_json_string, string_view, uint64_t, int64_t, double, bool
   *
   * You may use get_double(), get_bool(), get_uint64(), get_int64(),
   * get_object(), get_array(), get_raw_json_string(), or get_string() instead.
   * When SIMDJSON_SUPPORTS_CONCEPTS is set, custom types are also supported.
   *
   * @returns A value of the given type, parsed from the JSON.
   * @returns INCORRECT_TYPE If the JSON value is not the given type.
   */
  template <typename T>
  simdjson_inline simdjson_result<T> get()
#if SIMDJSON_SUPPORTS_CONCEPTS
    noexcept(custom_deserializable<T, value> ? nothrow_custom_deserializable<T, value> : true)
#else
    noexcept
#endif
  {
    static_assert(std::is_default_constructible<T>::value, "The specified type is not default constructible.");
    T out{};
    SIMDJSON_TRY(get<T>(out));
    return out;
  }


  /**
   * Get this value as the given type.
   *
   * Supported types: object, array, raw_json_string, string_view, uint64_t, int64_t, double, bool
   * If the macro SIMDJSON_SUPPORTS_CONCEPTS is set, then custom types are also supported.
   *
   * @param out This is set to a value of the given type, parsed from the JSON. If there is an error, this may not be initialized.
   * @returns INCORRECT_TYPE If the JSON value is not an object.
   * @returns SUCCESS If the parse succeeded and the out parameter was set to the value.
   */
  template <typename T>
  simdjson_warn_unused simdjson_inline error_code get(T &out)
#if SIMDJSON_SUPPORTS_CONCEPTS
    noexcept(custom_deserializable<T, value> ? nothrow_custom_deserializable<T, value> : true)
#else
    noexcept
#endif
 {
  #if SIMDJSON_SUPPORTS_CONCEPTS
  if constexpr (custom_deserializable<T, value>) {
      return deserialize(*this, out);
  } else if constexpr (concepts::optional_type<T>) {
      using value_type = typename std::remove_cvref_t<T>::value_type;

      // Check if the value is null
      bool is_null_value;
      SIMDJSON_TRY( is_null().get(is_null_value) );
      if (is_null_value) {
        out.reset(); // Set to nullopt
        return SUCCESS;
      }

      if (!out) {
        out.emplace();
      }
      return get<value_type>(out.value());
  } else {
    static_assert(!sizeof(T), "The get<T> method with type T is not implemented by the simdjson library. "
      "And you do not seem to have added support for it. Indeed, we have that "
      "simdjson::custom_deserializable<T> is false and the type T is not a default type "
      "such as ondemand::object, ondemand::array, raw_json_string, std::string_view, uint64_t, "
      "int64_t, double, or bool.");
    static_cast<void>(out); // to get rid of unused errors
    return UNINITIALIZED;
  }
#else // SIMDJSON_SUPPORTS_CONCEPTS
    // Unless the simdjson library or the user provides an inline implementation, calling this method should
    // immediately fail.
    static_assert(!sizeof(T), "The get method with given type is not implemented by the simdjson library. "
      "The supported types are ondemand::object, ondemand::array, raw_json_string, std::string_view, uint64_t, "
      "int64_t, double, and bool. We recommend you use get_double(), get_bool(), get_uint64(), get_int64(), "
      " get_object(), get_array(), get_raw_json_string(), or get_string() instead of the get template."
      " You may also add support for custom types, see our documentation.");
    static_cast<void>(out); // to get rid of unused errors
    return UNINITIALIZED;
#endif
  }

  /**
   * Cast this JSON value to an array.
   *
   * @returns An object that can be used to iterate the array.
   * @returns INCORRECT_TYPE If the JSON value is not an array.
   */
  simdjson_inline simdjson_result<array> get_array() noexcept;

  /**
   * Cast this JSON value to an object.
   *
   * @returns An object that can be used to look up or iterate fields.
   * @returns INCORRECT_TYPE If the JSON value is not an object.
   */
  simdjson_inline simdjson_result<object> get_object() noexcept;

  /**
   * Cast this JSON value to an unsigned integer.
   *
   * @returns A unsigned 64-bit integer.
   * @returns INCORRECT_TYPE If the JSON value is not a 64-bit unsigned integer.
   */
  simdjson_inline simdjson_result<uint64_t> get_uint64() noexcept;

  /**
   * Cast this JSON value (inside string) to a unsigned integer.
   *
   * @returns A unsigned 64-bit integer.
   * @returns INCORRECT_TYPE If the JSON value is not a 64-bit unsigned integer.
   */
  simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept;

  /**
   * Cast this JSON value to a signed integer.
   *
   * @returns A signed 64-bit integer.
   * @returns INCORRECT_TYPE If the JSON value is not a 64-bit integer.
   */
  simdjson_inline simdjson_result<int64_t> get_int64() noexcept;

  /**
   * Cast this JSON value (inside string) to a signed integer.
   *
   * @returns A signed 64-bit integer.
   * @returns INCORRECT_TYPE If the JSON value is not a 64-bit integer.
   */
  simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;

  /**
   * Cast this JSON value to a double.
   *
   * @returns A double.
   * @returns INCORRECT_TYPE If the JSON value is not a valid floating-point number.
   */
  simdjson_inline simdjson_result<double> get_double() noexcept;

  /**
   * Cast this JSON value (inside string) to a double
   *
   * @returns A double.
   * @returns INCORRECT_TYPE If the JSON value is not a valid floating-point number.
   */
  simdjson_inline simdjson_result<double> get_double_in_string() noexcept;

  /**
   * Cast this JSON value to a string.
   *
   * The string is guaranteed to be valid UTF-8.
   *
   * Equivalent to get<std::string_view>().
   *
   * Important: a value should be consumed once. Calling get_string() twice on the same value
   * is an error.
   *
   * In some instances, you may want to allow replacement of invalid Unicode sequences.
   * You may do so by passing the allow_replacement parameter as true. In the following
   * example, the string "431924697b\udff0L\u0001Y" is not valid Unicode. By passing true
   * to get_string, we allow the replacement of the invalid Unicode sequences with the Unicode
   * replacement character (U+FFFD).
   *
   *   simdjson::ondemand::parser parser;
   *   auto json = R"({"deviceId":"431924697b\udff0L\u0001Y"})"_padded;
   *   simdjson::ondemand::document doc = parser.iterate(json);
   *   auto view = doc["deviceId"].get_string(true);
   *
   * @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next
   *          time it parses a document or when it is destroyed.
   * @returns INCORRECT_TYPE if the JSON value is not a string.
   */
  simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;

  /**
   * Attempts to fill the provided std::string reference with the parsed value of the current string.
   *
   * The string is guaranteed to be valid UTF-8.
   *
   * Important: a value should be consumed once. Calling get_string() twice on the same value
   * is an error.
   *
   * Performance: This method may be slower than get_string() or get_string(bool) because it may need to allocate memory.
   * We recommend you avoid allocating an std::string unless you need to.
   *
   * @returns INCORRECT_TYPE if the JSON value is not a string. Otherwise, we return SUCCESS.
   */
  template <typename string_type>
  simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept;

  /**
   * Cast this JSON value to a "wobbly" string.
   *
   * The string is may not be a valid UTF-8 string.
   * See https://simonsapin.github.io/wtf-8/
   *
   * Important: a value should be consumed once. Calling get_wobbly_string() twice on the same value
   * is an error.
   *
   * @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next
   *          time it parses a document or when it is destroyed.
   * @returns INCORRECT_TYPE if the JSON value is not a string.
   */
  simdjson_inline simdjson_result<std::string_view> get_wobbly_string() noexcept;
  /**
   * Cast this JSON value to a raw_json_string.
   *
   * The string is guaranteed to be valid UTF-8, and may have escapes in it (e.g. \\ or \n).
   *
   * @returns A pointer to the raw JSON for the given string.
   * @returns INCORRECT_TYPE if the JSON value is not a string.
   */
  simdjson_inline simdjson_result<raw_json_string> get_raw_json_string() noexcept;

  /**
   * Cast this JSON value to a bool.
   *
   * @returns A bool value.
   * @returns INCORRECT_TYPE if the JSON value is not true or false.
   */
  simdjson_inline simdjson_result<bool> get_bool() noexcept;

  /**
   * Checks if this JSON value is null. If and only if the value is
   * null, then it is consumed (we advance). If we find a token that
   * begins with 'n' but is not 'null', then an error is returned.
   *
   * @returns Whether the value is null.
   * @returns INCORRECT_TYPE If the JSON value begins with 'n' and is not 'null'.
   */
  simdjson_inline simdjson_result<bool> is_null() noexcept;

#if SIMDJSON_EXCEPTIONS
  /**
   * Cast this JSON value to an instance of type T. The programmer is responsible for
   * providing an implementation of get<T> for the type T, if T is not one of the types
   * supported by the library (object, array, raw_json_string, string_view, uint64_t, etc.).
   *
   * See https://github.com/simdjson/simdjson/blob/master/doc/basics.md#adding-support-for-custom-types
   *
   * @returns An instance of type T
   */
  template <class T>
  explicit simdjson_inline operator T() noexcept(false);
  /**
   * Cast this JSON value to an array.
   *
   * @returns An object that can be used to iterate the array.
   * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not an array.
   */
  simdjson_inline operator array() noexcept(false);
  /**
   * Cast this JSON value to an object.
   *
   * @returns An object that can be used to look up or iterate fields.
   * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not an object.
   */
  simdjson_inline operator object() noexcept(false);
  /**
   * Cast this JSON value to an unsigned integer.
   *
   * @returns A signed 64-bit integer.
   * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit unsigned integer.
   */
  simdjson_inline operator uint64_t() noexcept(false);
  /**
   * Cast this JSON value to a signed integer.
   *
   * @returns A signed 64-bit integer.
   * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit integer.
   */
  simdjson_inline operator int64_t() noexcept(false);
  /**
   * Cast this JSON value to a double.
   *
   * @returns A double.
   * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a valid floating-point number.
   */
  simdjson_inline operator double() noexcept(false);
  /**
   * Cast this JSON value to a string.
   *
   * The string is guaranteed to be valid UTF-8.
   *
   * Equivalent to get<std::string_view>().
   *
   * @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next
   *          time it parses a document or when it is destroyed.
   * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string.
   */
  simdjson_inline operator std::string_view() noexcept(false);
  /**
   * Cast this JSON value to a raw_json_string.
   *
   * The string is guaranteed to be valid UTF-8, and may have escapes in it (e.g. \\ or \n).
   *
   * @returns A pointer to the raw JSON for the given string.
   * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string.
   */
  simdjson_inline operator raw_json_string() noexcept(false);
  /**
   * Cast this JSON value to a bool.
   *
   * @returns A bool value.
   * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not true or false.
   */
  simdjson_inline operator bool() noexcept(false);
#endif

  /**
   * Begin array iteration.
   *
   * Part of the std::iterable interface.
   *
   * @returns INCORRECT_TYPE If the JSON value is not an array.
   */
  simdjson_inline simdjson_result<array_iterator> begin() & noexcept;
  /**
   * Sentinel representing the end of the array.
   *
   * Part of the std::iterable interface.
   */
  simdjson_inline simdjson_result<array_iterator> end() & noexcept;
  /**
   * This method scans the array and counts the number of elements.
   * The count_elements method should always be called before you have begun
   * iterating through the array: it is expected that you are pointing at
   * the beginning of the array.
   * The runtime complexity is linear in the size of the array. After
   * calling this function, if successful, the array is 'rewinded' at its
   * beginning as if it had never been accessed. If the JSON is malformed (e.g.,
   * there is a missing comma), then an error is returned and it is no longer
   * safe to continue.
   *
   * Performance hint: You should only call count_elements() as a last
   * resort as it may require scanning the document twice or more.
   */
  simdjson_inline simdjson_result<size_t> count_elements() & noexcept;
  /**
   * This method scans the object and counts the number of key-value pairs.
   * The count_fields method should always be called before you have begun
   * iterating through the object: it is expected that you are pointing at
   * the beginning of the object.
   * The runtime complexity is linear in the size of the object. After
   * calling this function, if successful, the object is 'rewinded' at its
   * beginning as if it had never been accessed. If the JSON is malformed (e.g.,
   * there is a missing comma), then an error is returned and it is no longer
   * safe to continue.
   *
   * To check that an object is empty, it is more performant to use
   * the is_empty() method on the object instance.
   *
   * Performance hint: You should only call count_fields() as a last
   * resort as it may require scanning the document twice or more.
   */
  simdjson_inline simdjson_result<size_t> count_fields() & noexcept;
  /**
   * Get the value at the given index in the array. This function has linear-time complexity.
   * This function should only be called once on an array instance since the array iterator is not reset between each call.
   *
   * @return The value at the given index, or:
   *         - INDEX_OUT_OF_BOUNDS if the array index is larger than an array length
   */
  simdjson_inline simdjson_result<value> at(size_t index) noexcept;
  /**
   * Look up a field by name on an object (order-sensitive). By order-sensitive, we mean that
   * fields must be accessed in the order they appear in the JSON text (although you can
   * skip fields). See find_field_unordered() and operator[] for an order-insensitive version.
   *
   * The following code reads z, then y, then x, and thus will not retrieve x or y if fed the
   * JSON `{ "x": 1, "y": 2, "z": 3 }`:
   *
   * ```cpp
   * simdjson::ondemand::parser parser;
   * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded);
   * double z = obj.find_field("z");
   * double y = obj.find_field("y");
   * double x = obj.find_field("x");
   * ```
   * If you have multiple fields with a matching key ({"x": 1,  "x": 1}) be mindful
   * that only one field is returned.

   * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys.
   * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`.
   *
   * @param key The key to look up.
   * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object.
   */
  simdjson_inline simdjson_result<value> find_field(std::string_view key) noexcept;
  /** @overload simdjson_inline simdjson_result<value> find_field(std::string_view key) noexcept; */
  simdjson_inline simdjson_result<value> find_field(const char *key) noexcept;

  /**
   * Look up a field by name on an object, without regard to key order.
   *
   * **Performance Notes:** This is a bit less performant than find_field(), though its effect varies
   * and often appears negligible. It starts out normally, starting out at the last field; but if
   * the field is not found, it scans from the beginning of the object to see if it missed it. That
   * missing case has a non-cache-friendly bump and lots of extra scanning, especially if the object
   * in question is large. The fact that the extra code is there also bumps the executable size.
   *
   * We default operator[] on find_field_unordered() for convenience.
   * It is the default because it would be highly surprising (and hard to debug) if the
   * default behavior failed to look up a field just because it was in the wrong order--and many
   * APIs assume this. Therefore, you must be explicit if you want to treat objects as out of order.
   *
   * If you have multiple fields with a matching key ({"x": 1,  "x": 1}) be mindful
   * that only one field is returned.
   *
   * Use find_field() if you are sure fields will be in order (or are willing to treat it as if the
   * field as not there when they are not in order).
   *
   * @param key The key to look up.
   * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object.
   */
  simdjson_inline simdjson_result<value> find_field_unordered(std::string_view key) noexcept;
  /** @overload simdjson_inline simdjson_result<value> find_field_unordered(std::string_view key) noexcept; */
  simdjson_inline simdjson_result<value> find_field_unordered(const char *key) noexcept;
  /** @overload simdjson_inline simdjson_result<value> find_field_unordered(std::string_view key) noexcept; */
  simdjson_inline simdjson_result<value> operator[](std::string_view key) noexcept;
  /** @overload simdjson_inline simdjson_result<value> find_field_unordered(std::string_view key) noexcept; */
  simdjson_inline simdjson_result<value> operator[](const char *key) noexcept;
  simdjson_result<value> operator[](int) noexcept = delete;

  /**
   * Get the type of this JSON value. It does not validate or consume the value.
   * E.g., you must still call "is_null()" to check that a value is null even if
   * "type()" returns json_type::null.
   *
   * NOTE: If you're only expecting a value to be one type (a typical case), it's generally
   * better to just call .get_double, .get_string, etc. and check for INCORRECT_TYPE (or just
   * let it throw an exception).
   *
   * @return The type of JSON value (json_type::array, json_type::object, json_type::string,
   *     json_type::number, json_type::boolean, or json_type::null).
   * @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse".
   */
  simdjson_inline simdjson_result<json_type> type() noexcept;

  /**
   * Checks whether the value is a scalar (string, number, null, Boolean).
   * Returns false when there it is an array or object.
   *
   * @returns true if the type is string, number, null, Boolean
   * @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse".
   */
  simdjson_inline simdjson_result<bool> is_scalar() noexcept;
  /**
   * Checks whether the value is a string.
   *
   * @returns true if the type is string
   * @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse".
   */
  simdjson_inline simdjson_result<bool> is_string() noexcept;

  /**
   * Checks whether the value is a negative number.
   *
   * @returns true if the number if negative.
   */
  simdjson_inline bool is_negative() noexcept;
  /**
   * Checks whether the value is an integer number. Note that
   * this requires to partially parse the number string. If
   * the value is determined to be an integer, it may still
   * not parse properly as an integer in subsequent steps
   * (e.g., it might overflow).
   *
   * Performance note: if you call this function systematically
   * before parsing a number, you may have fallen for a performance
   * anti-pattern.
   *
   * @returns true if the number if negative.
   */
  simdjson_inline simdjson_result<bool> is_integer() noexcept;
  /**
   * Determine the number type (integer or floating-point number) as quickly
   * as possible. This function does not fully validate the input. It is
   * useful when you only need to classify the numbers, without parsing them.
   *
   * If you are planning to retrieve the value or you need full validation,
   * consider using the get_number() method instead: it will fully parse
   * and validate the input, and give you access to the type:
   * get_number().get_number_type().
   *
   * get_number_type() is number_type::unsigned_integer if we have
   * an integer greater or equal to 9223372036854775808.
   * get_number_type() is number_type::signed_integer if we have an
   * integer that is less than 9223372036854775808.
   * get_number_type() is number_type::big_integer for integers that do not fit in 64 bits,
   * in which case the digit_count is set to the length of the big integer string.
   * Otherwise, get_number_type() has value number_type::floating_point_number.
   *
   * This function requires processing the number string, but it is expected
   * to be faster than get_number().get_number_type() because it is does not
   * parse the number value.
   *
   * @returns the type of the number
   */
  simdjson_inline simdjson_result<number_type> get_number_type() noexcept;

  /**
   * Attempt to parse an ondemand::number. An ondemand::number may
   * contain an integer value or a floating-point value, the simdjson
   * library will autodetect the type. Thus it is a dynamically typed
   * number. Before accessing the value, you must determine the detected
   * type.
   *
   * number.get_number_type() is number_type::signed_integer if we have
   * an integer in [-9223372036854775808,9223372036854775808)
   * You can recover the value by calling number.get_int64() and you
   * have that number.is_int64() is true.
   *
   * number.get_number_type() is number_type::unsigned_integer if we have
   * an integer in [9223372036854775808,18446744073709551616)
   * You can recover the value by calling number.get_uint64() and you
   * have that number.is_uint64() is true.
   *
   * For integers that do not fit in 64 bits, the function returns BIGINT_ERROR error code.
   *
   * Otherwise, number.get_number_type() has value number_type::floating_point_number
   * and we have a binary64 number.
   * You can recover the value by calling number.get_double() and you
   * have that number.is_double() is true.
   *
   * You must check the type before accessing the value: it is an error
   * to call "get_int64()" when number.get_number_type() is not
   * number_type::signed_integer and when number.is_int64() is false.
   *
   * Performance note: this is designed with performance in mind. When
   * calling 'get_number()', you scan the number string only once, determining
   * efficiently the type and storing it in an efficient manner.
   */
  simdjson_warn_unused simdjson_inline simdjson_result<number> get_number() noexcept;

  /**
   * Get the raw JSON for this token.
   *
   * The string_view will always point into the input buffer.
   *
   * The string_view will start at the beginning of the token, and include the entire token
   * *as well as all spaces until the next token (or EOF).* This means, for example, that a
   * string token always begins with a " and is always terminated by the final ", possibly
   * followed by a number of spaces.
   *
   * The string_view is *not* null-terminated. However, if this is a scalar (string, number,
   * boolean, or null), the character after the end of the string_view is guaranteed to be
   * a non-space token.
   *
   * Tokens include:
   * - {
   * - [
   * - "a string (possibly with UTF-8 or backslashed characters like \\\")".
   * - -1.2e-100
   * - true
   * - false
   * - null
   *
   * See also value::raw_json().
   */
  simdjson_inline std::string_view raw_json_token() noexcept;

  /**
   * Get a string_view pointing at this value in the JSON document.
   * If this element is an array or an object, it consumes the array or the object
   * and returns a string_view instance corresponding to the
   * array as represented in JSON. It points inside the original document.
   * If this element is a scalar (string, number, Boolean, null), it returns what
   * raw_json_token() would return.
   */
  simdjson_inline simdjson_result<std::string_view> raw_json() noexcept;

  /**
   * Returns the current location in the document if in bounds.
   */
  simdjson_inline simdjson_result<const char *> current_location() noexcept;

  /**
   * Returns the current depth in the document if in bounds.
   *
   * E.g.,
   *  0 = finished with document
   *  1 = document root value (could be [ or {, not yet known)
   *  2 = , or } inside root array/object
   *  3 = key or value inside root array/object.
   */
  simdjson_inline int32_t current_depth() const noexcept;

  /**
   * Get the value associated with the given JSON pointer.  We use the RFC 6901
   * https://tools.ietf.org/html/rfc6901 standard.
   *
   *   ondemand::parser parser;
   *   auto json = R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded;
   *   auto doc = parser.iterate(json);
   *   doc.at_pointer("/foo/a/1") == 20
   *
   * It is allowed for a key to be the empty string:
   *
   *   ondemand::parser parser;
   *   auto json = R"({ "": { "a": [ 10, 20, 30 ] }})"_padded;
   *   auto doc = parser.iterate(json);
   *   doc.at_pointer("//a/1") == 20
   *
   * Note that at_pointer() called on the document automatically calls the document's rewind
   * method between each call. It invalidates all previously accessed arrays, objects and values
   * that have not been consumed.
   *
   * Calling at_pointer() on non-document instances (e.g., arrays and objects) is not
   * standardized (by RFC 6901). We provide some experimental support for JSON pointers
   * on non-document instances.  Yet it is not the case when calling at_pointer on an array
   * or an object instance: there is no rewind and no invalidation.
   *
   * You may only call at_pointer on an array after it has been created, but before it has
   * been first accessed. When calling at_pointer on an array, the pointer is advanced to
   * the location indicated by the JSON pointer (in case of success). It is no longer possible
   * to call at_pointer on the same array.
   *
   * You may call at_pointer more than once on an object, but each time the pointer is advanced
   * to be within the value matched by the key indicated by the JSON pointer query. Thus any preceding
   * key (as well as the current key) can no longer be used with following JSON pointer calls.
   *
   * Also note that at_pointer() relies on find_field() which implies that we do not unescape keys when matching
   *
   * @return The value associated with the given JSON pointer, or:
   *         - NO_SUCH_FIELD if a field does not exist in an object
   *         - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length
   *         - INCORRECT_TYPE if a non-integer is used to access an array
   *         - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed
   */
  simdjson_inline simdjson_result<value> at_pointer(std::string_view json_pointer) noexcept;

  /**
   * Get the value associated with the given JSONPath expression. We only support
   * JSONPath queries that trivially convertible to JSON Pointer queries: key
   * names and array indices.
   *
   * @return The value associated with the given JSONPath expression, or:
   *         - INVALID_JSON_POINTER if the JSONPath to JSON Pointer conversion fails
   *         - NO_SUCH_FIELD if a field does not exist in an object
   *         - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length
   *         - INCORRECT_TYPE if a non-integer is used to access an array
   */
  simdjson_inline simdjson_result<value> at_path(std::string_view at_path) noexcept;

  /**
   * Get all values matching the given JSONPath expression with wildcard support.
   * Supports wildcard character (*) for arrays or ".*" for objects.
   *
   * @param json_path JSONPath expression with wildcards
   * @return Vector of values matching the wildcard pattern
   */
  simdjson_inline simdjson_result<std::vector<value>> at_path_with_wildcard(std::string_view json_path) noexcept;

protected:
  /**
   * Create a value.
   */
  simdjson_inline value(const value_iterator &iter) noexcept;

  /**
   * Skip this value, allowing iteration to continue.
   */
  simdjson_inline void skip() noexcept;

  /**
   * Start a value at the current position.
   *
   * (It should already be started; this is just a self-documentation method.)
   */
  static simdjson_inline value start(const value_iterator &iter) noexcept;

  /**
   * Resume a value.
   */
  static simdjson_inline value resume(const value_iterator &iter) noexcept;

  /**
   * Get the object, starting or resuming it as necessary
   */
  simdjson_inline simdjson_result<object> start_or_resume_object() noexcept;

  // simdjson_inline void log_value(const char *type) const noexcept;
  // simdjson_inline void log_error(const char *message) const noexcept;

  value_iterator iter{};

  friend class document;
  friend class array_iterator;
  friend class field;
  friend class object;
  friend struct simdjson_result<value>;
  friend struct simdjson_result<field>;
  friend class field;
};

} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson

namespace simdjson {

template<>
struct simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base<SIMDJSON_IMPLEMENTATION::ondemand::value> {
public:
  simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::value &&value) noexcept; ///< @private
  simdjson_inline simdjson_result(error_code error) noexcept; ///< @private
  simdjson_inline simdjson_result() noexcept = default;

  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array> get_array() noexcept;
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object> get_object() noexcept;

  simdjson_inline simdjson_result<uint64_t> get_uint64() noexcept;
  simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept;
  simdjson_inline simdjson_result<int64_t> get_int64() noexcept;
  simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
  simdjson_inline simdjson_result<double> get_double() noexcept;
  simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
  simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
  template <typename string_type>
  simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept;
  simdjson_inline simdjson_result<std::string_view> get_wobbly_string() noexcept;
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string> get_raw_json_string() noexcept;
  simdjson_inline simdjson_result<bool> get_bool() noexcept;
  simdjson_inline simdjson_result<bool> is_null() noexcept;

  template<typename T> simdjson_inline simdjson_result<T> get() noexcept;

  template<typename T> simdjson_inline error_code get(T &out) noexcept;

#if SIMDJSON_EXCEPTIONS
  template <class T>
  explicit simdjson_inline operator T() noexcept(false);
  simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::array() noexcept(false);
  simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::object() noexcept(false);
  simdjson_inline operator uint64_t() noexcept(false);
  simdjson_inline operator int64_t() noexcept(false);
  simdjson_inline operator double() noexcept(false);
  simdjson_inline operator std::string_view() noexcept(false);
  simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false);
  simdjson_inline operator bool() noexcept(false);
#endif
  simdjson_inline simdjson_result<size_t> count_elements() & noexcept;
  simdjson_inline simdjson_result<size_t> count_fields() & noexcept;
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at(size_t index) noexcept;
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> begin() & noexcept;
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> end() & noexcept;

  /**
   * Look up a field by name on an object (order-sensitive). By order-sensitive, we mean that
   * fields must be accessed in the order they appear in the JSON text (although you can
   * skip fields). See find_field_unordered() and operator[] for an order-insensitive version.
   *
   * The following code reads z, then y, then x, and thus will not retrieve x or y if fed the
   * JSON `{ "x": 1, "y": 2, "z": 3 }`:
   *
   * ```cpp
   * simdjson::ondemand::parser parser;
   * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded);
   * double z = obj.find_field("z");
   * double y = obj.find_field("y");
   * double x = obj.find_field("x");
   * ```
   *
   * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys.
   * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`.
   *
   * @param key The key to look up.
   * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object.
   */
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field(std::string_view key) noexcept;
  /** @overload simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field(std::string_view key) noexcept; */
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field(const char *key) noexcept;

  /**
   * Look up a field by name on an object, without regard to key order.
   *
   * **Performance Notes:** This is a bit less performant than find_field(), though its effect varies
   * and often appears negligible. It starts out normally, starting out at the last field; but if
   * the field is not found, it scans from the beginning of the object to see if it missed it. That
   * missing case has a non-cache-friendly bump and lots of extra scanning, especially if the object
   * in question is large. The fact that the extra code is there also bumps the executable size.
   *
   * We default operator[] on find_field_unordered() for convenience.
   * It is the default because it would be highly surprising (and hard to debug) if the
   * default behavior failed to look up a field just because it was in the wrong order--and many
   * APIs assume this. Therefore, you must be explicit if you want to treat objects as out of order.
   *
   * Use find_field() if you are sure fields will be in order (or are willing to treat it as if the
   * field as not there when they are not in order).
   *
   * @param key The key to look up.
   * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object.
   */
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field_unordered(std::string_view key) noexcept;
  /** @overload simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field_unordered(std::string_view key) noexcept; */
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field_unordered(const char *key) noexcept;
  /** @overload simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field_unordered(std::string_view key) noexcept; */
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> operator[](std::string_view key) noexcept;
  /** @overload simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field_unordered(std::string_view key) noexcept; */
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> operator[](const char *key) noexcept;
  simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> operator[](int) noexcept = delete;

  /**
   * Get the type of this JSON value. It does not validate or consume the value.
   * E.g., you must still call "is_null()" to check that a value is null even if
   * "type()" returns json_type::null.
   *
   * Given a valid JSON document, the answer can be one of
   * simdjson::ondemand::json_type::object,
   * simdjson::ondemand::json_type::array,
   * simdjson::ondemand::json_type::string,
   * simdjson::ondemand::json_type::number,
   * simdjson::ondemand::json_type::boolean,
   * simdjson::ondemand::json_type::null.
   *
   * Starting with simdjson 4.0, this function will return simdjson::ondemand::json_type::unknown
   * given a bad token.
   * This allows you to identify a case such as {"key": NaN} and identify the NaN value.
   * The simdjson::ondemand::json_type::unknown value should only happen with non-valid JSON.
   *
   * NOTE: If you're only expecting a value to be one type (a typical case), it's generally
   * better to just call .get_double, .get_string, etc. and check for INCORRECT_TYPE (or just
   * let it throw an exception).
   */
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::json_type> type() noexcept;
  simdjson_inline simdjson_result<bool> is_scalar() noexcept;
  simdjson_inline simdjson_result<bool> is_string() noexcept;
  simdjson_inline simdjson_result<bool> is_negative() noexcept;
  simdjson_inline simdjson_result<bool> is_integer() noexcept;
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::number_type> get_number_type() noexcept;
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::number> get_number() noexcept;

  /** @copydoc simdjson_inline std::string_view value::raw_json_token() const noexcept */
  simdjson_inline simdjson_result<std::string_view> raw_json_token() noexcept;
  simdjson_inline simdjson_result<std::string_view> raw_json() noexcept;

  /** @copydoc simdjson_inline simdjson_result<const char *> current_location() noexcept */
  simdjson_inline simdjson_result<const char *> current_location() noexcept;
  /** @copydoc simdjson_inline int32_t current_depth() const noexcept */
  simdjson_inline simdjson_result<int32_t> current_depth() const noexcept;
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer(std::string_view json_pointer) noexcept;
  simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_path(std::string_view json_path) noexcept;
  simdjson_inline simdjson_result<std::vector<SIMDJSON_IMPLEMENTATION::ondemand::value>> at_path_with_wildcard(std::string_view json_path) noexcept;
};

} // namespace simdjson

#endif // SIMDJSON_GENERIC_ONDEMAND_VALUE_H
