// Glaze Library
// For the license information refer to glaze.hpp

#pragma once

#include <array>
#include <cstddef>
#include <iterator>
#include <optional>
#include <string>
#include <type_traits>
#include <vector>

#include "glaze/concepts/container_concepts.hpp"
#include "glaze/core/array_apply.hpp"
#include "glaze/core/cast.hpp"
#include "glaze/core/constraint.hpp"
#include "glaze/core/context.hpp"
#include "glaze/core/feature_test.hpp"
#include "glaze/core/meta.hpp"
#include "glaze/util/bit_array.hpp"
#include "glaze/util/expected.hpp"
#include "glaze/util/for_each.hpp"
#include "glaze/util/hash_map.hpp"
#include "glaze/util/help.hpp"
#include "glaze/util/string_literal.hpp"
#include "glaze/util/tuple.hpp"
#include "glaze/util/type_traits.hpp"
#include "glaze/util/utility.hpp"
#include "glaze/util/validate.hpp"
#include "glaze/util/variant.hpp"

namespace glz
{
   // write out a string like type without quoting it
   template <class T>
   struct raw_t
   {
      static constexpr bool glaze_wrapper = true;
      using value_type = T;
      T& val;
   };

   template <class... T>
   struct obj final
   {
      glz::tuple<std::conditional_t<std::is_convertible_v<std::decay_t<T>, sv>, sv, T>...> value;
      static constexpr auto glaze_reflect = false;
   };

   template <class... T>
   obj(T&&...) -> obj<T...>;

   template <class... T>
   struct obj_copy final
   {
      glz::tuple<T...> value;
      static constexpr auto glaze_reflect = false;
   };

   template <class... T>
   obj_copy(T...) -> obj_copy<T...>;

   template <class... T>
   struct arr final
   {
      glz::tuple<std::conditional_t<std::is_convertible_v<std::decay_t<T>, sv>, sv, T>...> value;
      static constexpr auto glaze_reflect = false;
   };

   template <class... T>
   arr(T&&...) -> arr<T...>;

   template <class... T>
   struct arr_copy final
   {
      glz::tuple<T...> value;
   };

   template <class... T>
   arr_copy(T...) -> arr_copy<T...>;

   // used to merge multiple JSON objects into a single JSON object
   template <class... T>
   struct merge final
   {
      glz::tuple<std::conditional_t<std::is_convertible_v<std::decay_t<T>, sv>, sv, T>...> value;
      static constexpr auto glaze_reflect = false;
   };

   template <class... T>
   merge(T&&...) -> merge<T...>;

   template <class... T>
   struct overload : T...
   {
      using T::operator()...;
   };

   struct hidden
   {};

   // hide class is a wrapper to denote that the exposed variable should be excluded or hidden for serialization output
   template <class T>
   struct hide final
   {
      T value;

      constexpr decltype(auto) operator()(auto&&) const { return hidden{}; }
   };

   template <class T>
   hide(T) -> hide<T>;

   struct skip
   {}; // to skip a keyed value in input

   template <class T>
   struct includer
   {
      T& value;

      static constexpr auto glaze_includer = true;
      static constexpr auto glaze_reflect = false;
   };

   template <class T>
   struct meta<includer<T>>
   {
      static constexpr std::string_view name = join_v<chars<"includer<">, name_v<T>, chars<">">>;
   };

   // Register this with an object to allow file including (direct writes) to the meta object
   struct file_include final
   {
      bool reflection_helper{}; // needed for count_members
      static constexpr auto glaze_includer = true;
      static constexpr auto glaze_reflect = false;

      constexpr decltype(auto) operator()(auto&& value) const noexcept
      {
         return includer<decay_keep_volatile_t<decltype(value)>>{value};
      }
   };

   template <class T>
   concept is_includer = requires(T t) { requires T::glaze_includer == true; };

   template <class T>
   concept is_member_function_pointer = std::is_member_function_pointer_v<T>;

   template <class T>
   using core_t = std::remove_cvref_t<T>;

   // Use std::stringview if you know the buffer is going to outlive this
   template <class string_type = std::string>
   struct basic_raw_json
   {
      string_type str;

      basic_raw_json() = default;

      template <class T>
         requires(!std::same_as<std::decay_t<T>, basic_raw_json> && std::constructible_from<string_type, T>)
      basic_raw_json(T&& s) : str(std::forward<T>(s))
      {}

      basic_raw_json(const basic_raw_json&) = default;
      basic_raw_json(basic_raw_json&&) = default;
      basic_raw_json& operator=(const basic_raw_json&) = default;
      basic_raw_json& operator=(basic_raw_json&&) = default;
   };

   using raw_json = basic_raw_json<std::string>;
   using raw_json_view = basic_raw_json<std::string_view>;

   template <class T>
   struct meta<basic_raw_json<T>>
   {
      static constexpr std::string_view name = "raw_json";
   };

   // glz::text (i.e. glz::basic_text<std::string>) just reads in the entire contents of the message or writes out
   // whatever contents it holds it avoids JSON reading/writing and is a pass through mechanism
   template <class string_type>
   struct basic_text
   {
      string_type str;

      basic_text() = default;

      template <class T>
         requires(!std::same_as<std::decay_t<T>, basic_text>)
      basic_text(T&& s) : str(std::forward<T>(s))
      {}

      basic_text(const basic_text&) = default;
      basic_text(basic_text&&) = default;
      basic_text& operator=(const basic_text&) = default;
      basic_text& operator=(basic_text&&) = default;
   };

   using text = basic_text<std::string>;
   using text_view = basic_text<std::string_view>;

   template <class T>
   concept constructible = requires { meta<std::decay_t<T>>::construct; } || local_construct_t<std::decay_t<T>>;

   template <class T>
   concept meta_value_t = glaze_t<std::decay_t<T>>;

   // this concept requires that T is just a view
   template <class T>
   concept string_view_t = std::same_as<std::decay_t<T>, std::string_view>;

   template <class T>
   concept array_char_t =
      requires { std::tuple_size<T>::value; } && std::same_as<T, std::array<char, std::tuple_size_v<T>>>;

   template <class T>
   concept str_t = (!std::same_as<std::nullptr_t, T> && std::constructible_from<std::string_view, std::decay_t<T>>) ||
                   array_char_t<T>;

   template <class T>
   concept is_static_string =
      requires { meta<std::decay_t<T>>::glaze_static_string == true; } || std::decay_t<T>::glaze_static_string == true;

   // this concept requires that T is a writeable string. It can be resized, appended to, or assigned to
   template <class T>
   concept string_t = str_t<T> && !string_view_t<T> &&
                      (has_assign<T> || (resizable<T> && has_data<T>) || has_append<T>) && !is_static_string<T>;

   // static string; very like `string_t`, but with a fixed max capacity
   template <class T>
   concept static_string_t = str_t<T> && !string_view_t<T> &&
                             (has_assign<T> || (resizable<T> && has_data<T>) || has_append<T>) && is_static_string<T>;

   template <class T>
   concept char_array_t = str_t<T> && std::is_array_v<std::remove_pointer_t<std::remove_reference_t<T>>>;

   template <class T>
   concept readable_map_t = !custom_read<T> && !meta_value_t<T> && !str_t<T> && range<T> && pair_t<range_value_t<T>> &&
                            map_subscriptable<std::decay_t<T>>;

   template <class T>
   concept writable_map_t = !custom_write<T> && !meta_value_t<T> && !str_t<T> && range<T> && pair_t<range_value_t<T>> &&
                            map_subscriptable<std::decay_t<T>>;

   template <class Map>
   concept heterogeneous_map = requires {
      typename Map::key_compare;
      requires(std::same_as<typename Map::key_compare, std::less<>> ||
               std::same_as<typename Map::key_compare, std::greater<>> ||
               requires { typename Map::key_compare::is_transparent; });
   };

   template <class T>
   concept array_t = (!meta_value_t<T> && !str_t<T> && !(readable_map_t<T> || writable_map_t<T>) && range<T>);

   template <class T>
   concept readable_array_t =
      (range<T> && !custom_read<T> && !meta_value_t<T> && !str_t<T> && !readable_map_t<T> && !filesystem_path<T>);

   template <class T>
   concept writable_array_t =
      (range<T> && !custom_write<T> && !meta_value_t<T> && !str_t<T> && !writable_map_t<T> && !filesystem_path<T>);

   template <class T>
   concept fixed_array_value_t =
      array_t<std::decay_t<decltype(std::declval<T>()[0])>> && !resizable<std::decay_t<decltype(std::declval<T>()[0])>>;

   template <class T>
   concept boolean_like = std::same_as<std::remove_cvref_t<T>, bool> || std::same_as<T, std::vector<bool>::reference> ||
                          std::same_as<T, std::vector<bool>::const_reference>;

   template <class T>
   concept is_no_reflect = requires(T t) { requires std::remove_cvref_t<T>::glaze_reflect == false; };

   /// \brief check if container has fixed size and its subsequent T::value_type
   template <class T>
   concept has_static_size =
      (is_span<T> && !is_dynamic_span<T>) || (
                                                requires(T container) {
                                                   {
                                                      std::bool_constant<(std::decay_t<T>{}.size(), true)>()
                                                   } -> std::same_as<std::true_type>;
                                                } && std::decay_t<T>{}.size() > 0 &&
                                                requires {
                                                   typename T::value_type;
                                                   requires std::is_trivially_copyable_v<typename T::value_type>;
                                                });
   static_assert(has_static_size<std::array<int, 2>>);
   static_assert(!has_static_size<std::array<std::string, 2>>);

   template <class T>
   constexpr bool is_std_array = false;
   template <class T, std::size_t N>
   constexpr bool is_std_array<std::array<T, N>> = true;

   template <class T>
   concept has_fixed_size_container = std::is_array_v<T> || is_std_array<T>;
   static_assert(has_fixed_size_container<std::array<std::string, 2>>);
   static_assert(has_fixed_size_container<int[54]>);

   template <class T>
   constexpr size_t get_size() noexcept
   {
      if constexpr (is_span<T>) {
         return T::extent;
      }
      else if constexpr (std::is_array_v<T>) {
         return std::extent_v<T>;
      }
      else if constexpr (is_std_array<T>) {
         return glz::tuple_size_v<T>;
      }
      else {
         return std::decay_t<T>{}.size();
      }
   }

   template <class T>
   concept is_reference_wrapper = is_specialization_v<T, std::reference_wrapper>;

   template <class T>
   concept tuple_t = requires(T t) {
      glz::tuple_size<T>::value;
      glz::get<0>(t);
   } && !meta_value_t<T> && !range<T>;

   template <class T>
   concept glaze_wrapper = requires { requires T::glaze_wrapper == true; };

   template <class T>
   concept always_null_t =
      std::same_as<T, std::nullptr_t> || std::convertible_to<T, std::monostate> || std::same_as<T, std::nullopt_t>;

   template <class T>
   concept always_skipped = is_includer<T> || std::same_as<T, hidden> || std::same_as<T, skip>;

   template <class T>
   concept nullable_t = !meta_value_t<T> && !str_t<T> && requires(T t) {
      bool(t);
      { *t };
   };

   template <class T>
   concept nullable_like = nullable_t<T> && (!is_expected<T> && !std::is_array_v<T>);

   // For optional like types that cannot overload `operator bool()`
   template <class T>
   concept nullable_value_t = !meta_value_t<T> && requires(T t) {
      t.value();
      { t.has_value() } -> std::convertible_to<bool>;
   };

   template <class T>
   concept nullable_wrapper = glaze_wrapper<T> && nullable_t<typename T::value_type>;

   template <class T>
   concept null_t = nullable_t<T> || nullable_value_t<T> || always_null_t<T> || nullable_wrapper<T>;

   template <class T>
   concept func_t = requires(T t) {
      typename T::result_type;
      std::function(t);
   } && !glaze_t<T>;

   template <class T>
   concept glaze_array_t = glaze_t<T> && is_specialization_v<meta_wrapper_t<T>, detail::Array>;

   template <class T>
   concept glaze_object_t = glaze_t<T> && (is_specialization_v<meta_wrapper_t<T>, detail::Object> ||
                                           (not std::is_enum_v<std::decay_t<T>> && meta_keys<T>));

   template <class T>
   concept glaze_enum_t = glaze_t<T> && is_specialization_v<meta_wrapper_t<T>, detail::Enum>;

   template <class T>
   concept is_named_enum = ((glaze_enum_t<T> || (meta_keys<T> && std::is_enum_v<T>)) && !custom_read<T>);

   template <class T>
   concept glaze_flags_t = glaze_t<T> && is_specialization_v<meta_wrapper_t<T>, detail::Flags>;

   template <class T>
   concept glaze_value_t =
      glaze_t<T> && !(glaze_array_t<T> || glaze_object_t<T> || glaze_enum_t<T> || meta_keys<T> || glaze_flags_t<T>);

   template <class T>
   concept reflectable = std::is_aggregate_v<std::remove_cvref_t<T>> && std::is_class_v<std::remove_cvref_t<T>> &&
                         !(is_no_reflect<T> || glaze_value_t<T> || glaze_object_t<T> || glaze_array_t<T> ||
                           glaze_flags_t<T> || range<T> || pair_t<T> || null_t<T> || meta_keys<T>);

   template <class T>
   concept is_memory_object = is_memory_type<T> && (glaze_object_t<memory_type<T>> || reflectable<memory_type<T>>);

   template <class T>
   concept glaze_const_value_t = glaze_value_t<T> && std::is_pointer_v<glz::meta_wrapper_t<T>> &&
                                 std::is_const_v<std::remove_pointer_t<glz::meta_wrapper_t<T>>>;

   template <class From, class To>
   concept non_narrowing_convertable = requires(From from, To to) {
#if __GNUC__
      // TODO: guard gcc against narrowing conversions when fixed
      to = from;
#else
      To{from};
#endif
   };

   template <is_variant T, size_t... I>
   constexpr auto make_variant_sv_id_map_impl(std::index_sequence<I...>, auto&& variant_ids)
   {
      return normal_map<sv, size_t, std::variant_size_v<T>>(std::array{pair<sv, size_t>{sv(variant_ids[I]), I}...});
   }

   template <is_variant T, size_t... I>
   constexpr auto make_variant_id_map_impl(std::index_sequence<I...>, auto&& variant_ids)
   {
      using id_type = std::decay_t<decltype(ids_v<T>[0])>;
      return normal_map<id_type, size_t, std::variant_size_v<T>>(std::array{pair{variant_ids[I], I}...});
   }

   template <is_variant T>
   constexpr auto make_variant_id_map()
   {
      constexpr auto indices = std::make_index_sequence<std::variant_size_v<T>>{};

      using id_type = std::decay_t<decltype(ids_v<T>[0])>;

      if constexpr (std::integral<id_type>) {
         return make_variant_id_map_impl<T>(indices, ids_v<T>);
      }
      else {
         return make_variant_sv_id_map_impl<T>(indices, ids_v<T>);
      }
   }

   /**
    * @brief Extracts the underlying member from a struct.
    *
    * Core Glaze function to extract the underlying member. Typically, the `value` parameter
    * is the struct containing the member field. The `element` parameter denotes a member object
    * pointer or an invocable function, which allows member extraction from the struct.
    *
    * @tparam Value   The type of the value, usually a struct containing the member.
    * @tparam Element The type of the element, either a member pointer, an invocable function, or reference/pointer.
    * @param value    The struct from which to extract the member.
    * @param element  The member pointer or invocable function used to extract the member.
    * @return         The extracted member.
    */
   template <class Value, class Element>
   GLZ_ALWAYS_INLINE decltype(auto) get_member(Value&& value, Element&& element)
   {
      using V = std::decay_t<decltype(element)>;
      if constexpr (std::is_member_object_pointer_v<V>) {
         return value.*element;
      }
      else if constexpr (std::is_member_function_pointer_v<V>) {
         return element;
      }
      else if constexpr (std::invocable<Element, Value> && not eigen_t<Element>) {
         // Eigen places a static_assert inside of the operator()(), so we must target
         // matrix types and reject them from the invocable check
         // Eigen ought to put the check in the `enable_if` for operator()()
         return std::invoke(std::forward<Element>(element), std::forward<Value>(value));
      }
      else if constexpr (std::is_pointer_v<V>) {
         if constexpr (std::invocable<decltype(*element), Value>) {
            return std::invoke(*element, std::forward<Value>(value));
         }
         else {
            return *element;
         }
      }
      else {
         return element;
      }
   }

   /**
    * @brief Alias for the expected return type of `get_member`.
    *
    * This template alias deduces the return type of the `get_member` function when called with
    * the specified `Value` and `Element` types.
    *
    * @tparam Value   The type of the value parameter, typically a struct containing the member.
    * @tparam Element The type of the element, either a member pointer, an invocable function, or reference/pointer.
    *
    * @see get_member
    */
   template <class Value, class Element>
   using member_t = decltype(get_member(std::declval<std::add_lvalue_reference_t<Value>>(), std::declval<Element>()));

   // member_ptr and lambda wrapper helper
   template <template <class> class Wrapper, class Wrapped>
   struct wrap
   {
      Wrapped wrapped;
      constexpr decltype(auto) operator()(auto&& value) const
      {
         return Wrapper<std::decay_t<decltype(get_member(value, wrapped))>>{get_member(value, wrapped)};
      }

      constexpr decltype(auto) unwrap(auto&& value) const { return get_member(value, wrapped); }
   };

   // Output variants in the following format  ["variant_type", variant_json_data] with
   // glz::detail:array_variant(&T::var);
   template <is_variant T>
   struct array_variant_wrapper final
   {
      T& value;
   };
   // TODO: Could do this if the compiler supports alias template deduction
   // template <class T>
   // using array_var = wrap<array_var_wrapper, T>;
   template <class T>
   struct array_variant : wrap<array_variant_wrapper, T>
   {};
   template <class T>
   array_variant(T) -> array_variant<T>; // Only needed on older compilers until we move to template alias deduction

   constexpr decltype(auto) conv_sv(auto&& value) noexcept
   {
      using V = std::decay_t<decltype(value)>;
      if constexpr (std::is_convertible_v<V, sv>) {
         return sv{value};
      }
      else {
         return value;
      }
   }

   constexpr auto array(auto&&... args) noexcept { return detail::Array{glz::tuple{conv_sv(args)...}}; }

   template <class... Args>
   constexpr auto object(Args&&... args) noexcept
   {
      return detail::Object{tuple{std::forward<Args>(args)...}};
   }

   constexpr auto enumerate(auto&&... args) noexcept { return detail::Enum{tuple{args...}}; }

   constexpr auto flags(auto&&... args) noexcept { return detail::Flags{tuple{args...}}; }
}

namespace glz
{
   // This wraps glz::expected error (unexpected) values in an object with an "error" key
   // This makes them discernable from the expected value
   template <class T>
   struct unexpected_wrapper final
   {
      T* unexpected;

      struct glaze
      {
         using V = unexpected_wrapper;
         static constexpr auto value = glz::object("unexpected", &V::unexpected);
      };
   };

   template <class T>
   unexpected_wrapper(T*) -> unexpected_wrapper<T>;

   template <auto Opts, class Value>
   [[nodiscard]] GLZ_ALWAYS_INLINE constexpr bool skip_member(const Value& value) noexcept
   {
      if constexpr (null_t<Value> && Opts.skip_null_members) {
         if constexpr (always_null_t<Value>)
            return true;
         else {
            return !bool(value);
         }
      }
      else {
         return false;
      }
   }
}

template <>
struct glz::meta<glz::error_code>
{
   static constexpr sv name = "glz::error_code";
   using enum glz::error_code;
   static constexpr std::array keys{"none",
                                    "version_mismatch",
                                    "invalid_header",
                                    "invalid_query",
                                    "invalid_body",
                                    "parse_error",
                                    "method_not_found",
                                    "timeout",
                                    "send_error",
                                    "connection_failure",
                                    "end_reached",
                                    "partial_read_complete",
                                    "no_read_input",
                                    "data_must_be_null_terminated",
                                    "parse_number_failure",
                                    "expected_brace",
                                    "expected_bracket",
                                    "expected_quote",
                                    "expected_comma",
                                    "expected_colon",
                                    "exceeded_static_array_size",
                                    "exceeded_max_recursive_depth",
                                    "unexpected_end",
                                    "expected_end_comment",
                                    "syntax_error",
                                    "unexpected_enum",
                                    "attempt_const_read",
                                    "attempt_member_func_read",
                                    "attempt_read_hidden",
                                    "invalid_nullable_read",
                                    "invalid_variant_object",
                                    "invalid_variant_array",
                                    "invalid_variant_string",
                                    "no_matching_variant_type",
                                    "expected_true_or_false",
                                    "constraint_violated",
                                    "key_not_found",
                                    "unknown_key",
                                    "missing_key",
                                    "invalid_flag_input",
                                    "invalid_escape",
                                    "u_requires_hex_digits",
                                    "unicode_escape_conversion_failure",
                                    "dump_int_error",
                                    "file_open_failure",
                                    "file_close_failure",
                                    "file_include_error",
                                    "file_extension_not_supported",
                                    "could_not_determine_extension",
                                    "get_nonexistent_json_ptr",
                                    "get_wrong_type",
                                    "seek_failure",
                                    "cannot_be_referenced",
                                    "invalid_get",
                                    "invalid_get_fn",
                                    "invalid_call",
                                    "invalid_partial_key",
                                    "name_mismatch",
                                    "array_element_not_found",
                                    "elements_not_convertible_to_design",
                                    "unknown_distribution",
                                    "invalid_distribution_elements",
                                    "hostname_failure",
                                    "includer_error",
                                    "feature_not_supported"};
   static constexpr std::array value{none, //
                                     version_mismatch, //
                                     invalid_header, //
                                     invalid_query, //
                                     invalid_body, //
                                     parse_error, //
                                     method_not_found, //
                                     timeout, //
                                     send_error, //
                                     connection_failure, //
                                     end_reached, // A non-error code for non-null terminated input buffers
                                     partial_read_complete,
                                     no_read_input, //
                                     data_must_be_null_terminated, //
                                     parse_number_failure, //
                                     expected_brace, //
                                     expected_bracket, //
                                     expected_quote, //
                                     expected_comma, //
                                     expected_colon, //
                                     exceeded_static_array_size, //
                                     exceeded_max_recursive_depth, //
                                     unexpected_end, //
                                     expected_end_comment, //
                                     syntax_error, //
                                     unexpected_enum, //
                                     attempt_const_read, //
                                     attempt_member_func_read, //
                                     attempt_read_hidden, //
                                     invalid_nullable_read, //
                                     invalid_variant_object, //
                                     invalid_variant_array, //
                                     invalid_variant_string, //
                                     no_matching_variant_type, //
                                     expected_true_or_false, //
                                     constraint_violated, //
                                     // Key errors
                                     key_not_found, //
                                     unknown_key, //
                                     missing_key, //
                                     // Other errors
                                     invalid_flag_input, //
                                     invalid_escape, //
                                     u_requires_hex_digits, //
                                     unicode_escape_conversion_failure, //
                                     dump_int_error, //
                                     // File errors
                                     file_open_failure, //
                                     file_close_failure, //
                                     file_include_error, //
                                     file_extension_not_supported, //
                                     could_not_determine_extension, //
                                     // JSON pointer access errors
                                     get_nonexistent_json_ptr, //
                                     get_wrong_type, //
                                     seek_failure, //
                                     // Other errors
                                     cannot_be_referenced, //
                                     invalid_get, //
                                     invalid_get_fn, //
                                     invalid_call, //
                                     invalid_partial_key, //
                                     name_mismatch, //
                                     array_element_not_found, //
                                     elements_not_convertible_to_design, //
                                     unknown_distribution, //
                                     invalid_distribution_elements, //
                                     hostname_failure, //
                                     includer_error, //
                                     feature_not_supported};
};
