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

#pragma once

#include "glaze/beve/header.hpp"
#include "glaze/beve/key_traits.hpp"
#include "glaze/core/opts.hpp"
#include "glaze/core/reflect.hpp"
#include "glaze/util/expected.hpp"
#include "glaze/util/for_each.hpp"
#include "glaze/util/variant.hpp"

namespace glz
{
   // Calculate the number of bytes needed to store a compressed integer
   [[nodiscard]] GLZ_ALWAYS_INLINE constexpr size_t compressed_int_size(uint64_t i) noexcept
   {
      if (i < 64) return 1;
      if (i < 16384) return 2;
      if (i < 1073741824) return 4;
      return 8;
   }

   // Compile-time version for known values
   template <uint64_t i>
   [[nodiscard]] consteval size_t compressed_int_size() noexcept
   {
      if constexpr (i < 64)
         return 1;
      else if constexpr (i < 16384)
         return 2;
      else if constexpr (i < 1073741824)
         return 4;
      else
         return 8;
   }

   // Forward declaration for the size calculation template
   template <uint32_t Format, class T>
   struct calculate_size;

   // Primary template for BEVE size calculation dispatch
   // The offset parameter tracks the absolute byte offset from the message start,
   // used for computing exact alignment padding for aligned typed arrays.
   template <>
   struct calculate_size<BEVE, void>
   {
      template <auto Opts, class T>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(T&& value, size_t offset = 0)
      {
         return calculate_size<BEVE, std::remove_cvref_t<T>>::template op<Opts>(std::forward<T>(value), offset);
      }

      template <auto Opts, class T>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t no_header(T&& value, size_t offset = 0)
      {
         return calculate_size<BEVE, std::remove_cvref_t<T>>::template no_header<Opts>(std::forward<T>(value), offset);
      }
   };

   template <class T>
      requires(glaze_value_t<T> && !custom_write<T>)
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&& value, size_t offset = 0)
      {
         using V = std::remove_cvref_t<decltype(get_member(std::declval<decltype(value)>(), meta_wrapper_v<T>))>;
         return calculate_size<BEVE, V>::template op<Opts>(get_member(value, meta_wrapper_v<T>), offset);
      }

      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t no_header(auto&& value, size_t offset = 0)
      {
         using V = std::remove_cvref_t<decltype(get_member(std::declval<decltype(value)>(), meta_wrapper_v<T>))>;
         return calculate_size<BEVE, V>::template no_header<Opts>(get_member(value, meta_wrapper_v<T>), offset);
      }
   };

   template <always_null_t T>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&&, size_t = 0) noexcept
      {
         return 1; // null tag
      }
   };

   template <is_bitset T>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] static size_t op(auto&& value, size_t = 0) noexcept
      {
         const auto num_bytes = (value.size() + 7) / 8;
         return 1 + compressed_int_size(value.size()) + num_bytes; // tag + size + data
      }
   };

   template <glaze_flags_t T>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] static size_t op(auto&&, size_t = 0) noexcept
      {
         static constexpr auto data_size = byte_length<T>();
         return data_size; // flags are written directly without header
      }
   };

   template <is_member_function_pointer T>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&&, size_t = 0) noexcept
      {
         return 0; // member function pointers are not serialized
      }
   };

   template <is_includer T>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&&, size_t = 0) noexcept
      {
         return 1 + 1; // tag + compressed_int(0) for empty string
      }
   };

   template <boolean_like T>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&&, size_t = 0) noexcept
      {
         return 1; // bool tag contains the value
      }
   };

   template <func_t T>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&& value, size_t offset = 0)
      {
         return calculate_size<BEVE, void>::template op<Opts>(name_v<std::decay_t<decltype(value)>>, offset);
      }
   };

   template <class T>
   struct calculate_size<BEVE, basic_raw_json<T>>
   {
      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&& value, size_t offset = 0)
      {
         return calculate_size<BEVE, void>::template op<Opts>(value.str, offset);
      }
   };

   template <class T>
   struct calculate_size<BEVE, basic_text<T>>
   {
      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&& value, size_t offset = 0)
      {
         return calculate_size<BEVE, void>::template op<Opts>(value.str, offset);
      }
   };

   // BEVE Version 2 variant size, mirroring the writer byte-for-byte (see to<BEVE, is_variant>).
   // The tagging representation is fixed per variant, so the shape sized here is too: the merged
   // { tag : id, ...members } object for internal tagging, the two member { tag : id, content : value }
   // object for adjacent tagging (the two element array [id, value] positionally), and the bare value
   // otherwise. No type-tag extension bytes are counted.
   template <is_variant T>
   struct calculate_size<BEVE, T>
   {
      static constexpr auto tagging = variant_tagging_v<T>;

      // Sizes a headerless string key, matching to<BEVE, is_variant>::write_key.
      template <const sv& Key, auto Opts>
      [[nodiscard]] static size_t key_size()
      {
         return calculate_size<BEVE, std::remove_cvref_t<decltype(tag_v<T>)>>::template no_header_cx<Key.size(),
                                                                                                     Opts>();
      }

      template <auto Opts>
      [[nodiscard]] static size_t op(auto&& value, size_t offset = 0)
      {
         constexpr bool tagged = check_write_type_info(Opts) && tagging != variant_tagging_kind::none;

         if constexpr (tagged && check_structs_as_arrays(Opts)) {
            // Positional projection of adjacent tagging: [id, value]. Mirrors the writer's guard, or
            // beve_size_untagged would hand back a byte count for a value that cannot be written.
            static_assert(
               tagging == variant_tagging_kind::adjacent || detail::beve_positional_tagging_needs_content<T>::value,
               "Positional writes (structs_as_arrays) have no keys, so there is nothing for "
               "internal tagging to merge a discriminator into. Declare `content` beside "
               "`tag` in glz::meta to select adjacent tagging, which projects positionally "
               "to the two element array [id, value].");
            if (value.index() >= ids_v<T>.size()) {
               return size_t(0); // writer errors and emits nothing; mirror it
            }
            size_t result = 1; // generic array tag
            result += compressed_int_size(2); // element count
            result += calculate_size<BEVE, void>::template op<Opts>(ids_v<T>[value.index()], offset + result);
            result += std::visit(
               [&](auto&& v) { return calculate_size<BEVE, void>::template op<Opts>(v, offset + result); }, value);
            return result;
         }
         else if constexpr (tagged && tagging == variant_tagging_kind::adjacent) {
            if (value.index() >= ids_v<T>.size()) {
               return size_t(0); // writer errors and emits nothing; mirror it
            }
            size_t result = 1; // object tag (string keys)
            result += compressed_int_size(2); // member count
            result += key_size<tag_v<T>, Opts>();
            result += calculate_size<BEVE, void>::template op<Opts>(ids_v<T>[value.index()], offset + result);
            result += key_size<content_v<T>, Opts>();
            result += std::visit(
               [&](auto&& v) { return calculate_size<BEVE, void>::template op<Opts>(v, offset + result); }, value);
            return result;
         }
         else {
            return std::visit(
               [&](auto&& v) -> size_t {
                  using V = std::decay_t<decltype(v)>;
                  using X = variant_alternative_object_t<V>;

                  if constexpr (tagged && variant_unit_alternative<V>) {
                     // Discriminator-only object: tag byte + member count + key + id.
                     if (value.index() >= ids_v<T>.size()) {
                        return size_t(0); // writer errors and emits nothing; mirror it
                     }
                     size_t result = 1;
                     result += compressed_int_size(1);
                     result += key_size<tag_v<T>, Opts>();
                     result += calculate_size<BEVE, void>::template op<Opts>(ids_v<T>[value.index()], offset + result);
                     return result;
                  }
                  // `not custom_write<V>`: mirrors the writer, whose merged branch excludes custom
                  // alternatives because they have no reflected members to count.
                  else if constexpr (tagged && (glaze_object_t<X> || reflectable<X>) && (not custom_write<V>) &&
                                     (not alternative_declares_key<V>(tag_v<T>))) {
                     if constexpr (is_memory_object<V>) {
                        // A null memory_object makes the writer error (invalid_variant_object) and
                        // emit nothing; mirror that so beve_size never under-counts what is written.
                        if (!v) {
                           return size_t(0);
                        }
                     }

                     if (value.index() >= ids_v<T>.size()) {
                        return size_t(0); // writer errors and emits nothing; mirror it
                     }

                     size_t result = 1; // object tag (string keys)

                     const size_t body_count = [&]() -> size_t {
                        if constexpr (is_memory_object<V>) {
                           return calculate_size<BEVE, X>::template written_member_count<Opts>(*v);
                        }
                        else {
                           return calculate_size<BEVE, X>::template written_member_count<Opts>(v);
                        }
                     }();
                     result += compressed_int_size(body_count + 1); // merged member count

                     // discriminator: headerless string key + id VALUE (string or integral)
                     result += key_size<tag_v<T>, Opts>();
                     result += calculate_size<BEVE, void>::template op<Opts>(ids_v<T>[value.index()], offset + result);

                     // alternative body, header suppressed (members spliced into the merged object)
                     if constexpr (is_memory_object<V>) {
                        if (v) {
                           result += calculate_size<BEVE, X>::template op<opening_handled<Opts>()>(*v, offset + result);
                        }
                     }
                     else {
                        result += calculate_size<BEVE, X>::template op<opening_handled<Opts>()>(v, offset + result);
                     }
                     return result;
                  }
                  else {
                     return calculate_size<BEVE, void>::template op<Opts>(v, offset);
                  }
               },
               value);
         }
      }
   };

   template <class T>
      requires num_t<T> || char_t<T> || glaze_enum_t<T>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&&, size_t = 0) noexcept
      {
         return 1 + sizeof(T); // tag + value
      }

      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t no_header(auto&&, size_t = 0) noexcept
      {
         return sizeof(T); // value only
      }
   };

   template <class T>
      requires(std::is_enum_v<T> && !glaze_enum_t<T>)
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&&, size_t = 0) noexcept
      {
         using V = std::underlying_type_t<std::decay_t<T>>;
         return 1 + sizeof(V); // tag + value
      }

      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t no_header(auto&&, size_t = 0) noexcept
      {
         using V = std::underlying_type_t<std::decay_t<T>>;
         return sizeof(V); // value only
      }
   };

   // std::chrono::duration / steady-or-high-res time_point encode as their bare `rep`
   // (tag + rep value), matching the generic to<BEVE, ...> writer.
   template <class T>
      requires(is_duration<T> || is_count_time_point<T>)
   struct calculate_size<BEVE, T>
   {
      using Rep = typename std::remove_cvref_t<T>::rep;

      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&&, size_t = 0) noexcept
      {
         return 1 + sizeof(Rep); // tag + value
      }

      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t no_header(auto&&, size_t = 0) noexcept
      {
         return sizeof(Rep); // value only
      }
   };

   template <complex_t T>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&&, size_t = 0) noexcept
      {
         using V = typename T::value_type;
         return 2 + 2 * sizeof(V); // extension tag + complex header + real + imag
      }

      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t no_header(auto&&, size_t = 0) noexcept
      {
         using V = typename T::value_type;
         return 2 * sizeof(V); // real + imag
      }
   };

   template <str_t T>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&& value, size_t = 0)
      {
         const sv str = [&]() -> const sv {
            if constexpr (!char_array_t<T> && std::is_pointer_v<std::decay_t<T>>) {
               return value ? value : "";
            }
            else {
               return sv{value};
            }
         }();

         const auto n = str.size();
         return 1 + compressed_int_size(n) + n; // tag + length + data
      }

      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t no_header(auto&& value, size_t = 0)
      {
         const auto n = value.size();
         return compressed_int_size(n) + n; // length + data
      }

      template <uint64_t N, auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static constexpr size_t no_header_cx() noexcept
      {
         return compressed_int_size<N>() + N; // length + data
      }
   };

   template <writable_array_t T>
   struct calculate_size<BEVE, T>
   {
      static constexpr bool map_like_array = pair_t<range_value_t<T>>;

      template <auto Opts>
         requires(map_like_array ? check_concatenate(Opts) == false : true)
      [[nodiscard]] static size_t op(auto&& value, size_t offset = 0)
      {
         using V = range_value_t<std::decay_t<T>>;

         size_t result = 1; // tag byte
         result += compressed_int_size(value.size()); // element count

         if constexpr (boolean_like<V>) {
            const auto num_bytes = (value.size() + 7) / 8;
            result += num_bytes;
         }
         else if constexpr (beve_num_array_t<V>) {
            // A chrono element packs as its rep; sizeof(V) == sizeof(rep) so the byte counts
            // below match the numeric typed array the writer emits.
            if constexpr (check_aligned_arrays(Opts) && sizeof(V) > 1) {
               result += 1; // extra numeric header byte
               result += 1; // padding length byte
               // Compute exact padding from absolute offset
               constexpr size_t alignment = sizeof(V);
               const size_t abs_offset = offset + result;
               const size_t padding = (alignment - (abs_offset % alignment)) % alignment;
               result += padding;
            }
            result += value.size() * sizeof(V);
         }
         else if constexpr (str_t<V>) {
            for (auto& x : value) {
               result += compressed_int_size(x.size()) + x.size();
            }
         }
         else if constexpr (complex_t<V>) {
            // extension tag + complex header + count + data
            using X = typename V::value_type;
            result += 1; // complex_header byte
            result += value.size() * 2 * sizeof(X);
         }
         else {
            // generic array - need to sum element sizes
            for (auto&& x : value) {
               result += calculate_size<BEVE, void>::template op<Opts>(x, offset + result);
            }
         }

         return result;
      }

      template <auto Opts>
         requires(map_like_array && check_concatenate(Opts) == true)
      [[nodiscard]] static size_t op(auto&& value, size_t offset = 0)
      {
         size_t result = 1; // tag byte
         result += compressed_int_size(value.size()); // element count

         for (auto&& [k, v] : value) {
            result += calculate_size<BEVE, void>::template no_header<Opts>(k, offset + result);
            result += calculate_size<BEVE, void>::template op<Opts>(v, offset + result);
         }

         return result;
      }
   };

   template <pair_t T>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&& value, size_t offset = 0)
      {
         size_t result = 1; // tag byte
         result += compressed_int_size<1>(); // count = 1

         const auto& [k, v] = value;
         result += calculate_size<BEVE, void>::template no_header<Opts>(k, offset + result);
         result += calculate_size<BEVE, void>::template op<Opts>(v, offset + result);

         return result;
      }
   };

   template <writable_map_t T>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] static size_t op(auto&& value, size_t offset = 0)
      {
         size_t result = 1; // tag byte
         result += compressed_int_size(value.size()); // element count

         for (auto&& [k, v] : value) {
            result += calculate_size<BEVE, void>::template no_header<Opts>(k, offset + result);
            result += calculate_size<BEVE, void>::template op<Opts>(v, offset + result);
         }

         return result;
      }
   };

   template <nullable_t T>
      requires(std::is_array_v<T>)
   struct calculate_size<BEVE, T>
   {
      template <auto Opts, class V, size_t N>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(const V (&value)[N], size_t offset = 0)
      {
         return calculate_size<BEVE, void>::template op<Opts>(std::span{value, N}, offset);
      }
   };

   template <nullable_like T>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&& value, size_t offset = 0)
      {
         if (value) {
            return calculate_size<BEVE, void>::template op<Opts>(*value, offset);
         }
         else {
            return 1; // null tag
         }
      }
   };

   template <class T>
      requires(nullable_value_t<T> && not nullable_like<T> && not is_expected<T>)
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] GLZ_ALWAYS_INLINE static size_t op(auto&& value, size_t offset = 0)
      {
         if (value.has_value()) {
            return calculate_size<BEVE, void>::template op<Opts>(value.value(), offset);
         }
         else {
            return 1; // null tag
         }
      }
   };

   template <class T>
      requires is_specialization_v<T, glz::obj> || is_specialization_v<T, glz::obj_copy>
   struct calculate_size<BEVE, T>
   {
      template <auto Options>
      [[nodiscard]] static size_t op(auto&& value, size_t offset = 0)
      {
         using V = std::decay_t<decltype(value.value)>;
         static constexpr auto N = glz::tuple_size_v<V> / 2;

         size_t result = 0;

         if constexpr (!check_opening_handled(Options)) {
            result += 1; // object tag
            result += compressed_int_size<N>(); // member count
         }

         for_each<N>([&]<size_t I>() {
            constexpr auto Opts = opening_handled_off<Options>();
            result += calculate_size<BEVE, void>::template no_header<Opts>(get<2 * I>(value.value), offset + result);
            result += calculate_size<BEVE, void>::template op<Opts>(get<2 * I + 1>(value.value), offset + result);
         });

         return result;
      }
   };

   template <class T>
      requires is_specialization_v<T, glz::merge>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts, class Value, size_t I>
      static consteval bool should_skip_field()
      {
         using V = field_t<Value, I>;

         if constexpr (always_skipped<V>) {
            return true;
         }
         else if constexpr (is_any_function_ptr<V>) {
            return !check_write_function_pointers(Opts);
         }
         else {
            return false;
         }
      }

      template <auto Opts, class Value>
      static consteval size_t count_fields_for_type()
      {
         constexpr auto N = reflect<Value>::size;
         return []<size_t... I>(std::index_sequence<I...>) {
            return (size_t{} + ... + (should_skip_field<Opts, Value, I>() ? size_t{} : size_t{1}));
         }(std::make_index_sequence<N>{});
      }

      template <auto Opts>
      static consteval size_t merge_element_count()
      {
         size_t count{};
         using Tuple = std::decay_t<decltype(std::declval<T>().value)>;
         for_each<glz::tuple_size_v<Tuple>>([&]<auto I>() constexpr {
            using Value = std::decay_t<glz::tuple_element_t<I, Tuple>>;
            if constexpr (is_specialization_v<Value, glz::obj> || is_specialization_v<Value, glz::obj_copy>) {
               count += glz::tuple_size_v<decltype(std::declval<Value>().value)> / 2;
            }
            else {
               count += count_fields_for_type<Opts, Value>();
            }
         });
         return count;
      }

      template <auto Opts>
      [[nodiscard]] static size_t op(auto&& value, size_t offset = 0)
      {
         using V = std::decay_t<decltype(value.value)>;
         static constexpr auto N = glz::tuple_size_v<V>;

         size_t result = 1; // object tag
         result += compressed_int_size<merge_element_count<Opts>()>(); // member count

         [&]<size_t... I>(std::index_sequence<I...>) {
            ((result += calculate_size<BEVE, void>::template op<opening_handled<Opts>()>(glz::get<I>(value.value),
                                                                                         offset + result)),
             ...);
         }(std::make_index_sequence<N>{});

         return result;
      }
   };

   namespace detail
   {
      // Instantiated only to fail, so the diagnostic names the type that needs the specialization.
      template <class T>
      struct beve_size_needs_calculate_size_specialization : std::false_type
      {};
   }

   // A custom-serialized type is written by code Glaze cannot inspect, so its encoded length cannot
   // be derived from reflection the way every other size specialization does. Without this
   // specialization the reflected one above is excluded by `not custom_write<T>` and nothing else
   // matches, leaving glz::beve_size to fail on an incomplete type; say what is actually missing.
   template <class T>
      requires((glaze_object_t<T> || reflectable<T>) && custom_write<T>)
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] static size_t op(auto&&, size_t = 0)
      {
         static_assert(detail::beve_size_needs_calculate_size_specialization<T>::value,
                       "glz::beve_size cannot size a custom-serialized type: only your to<BEVE, T> "
                       "knows how many bytes it writes. Specialize glz::calculate_size<glz::BEVE, T> "
                       "beside to<BEVE, T> with an `op<Opts>(value, offset)` returning that byte "
                       "count, or size the value by writing it (write_beve(value)->size()). The type "
                       "is the template argument of beve_size_needs_calculate_size_specialization in "
                       "the instantiation backtrace.");
         return 0;
      }
   };

   template <class T>
      requires((glaze_object_t<T> || reflectable<T>) && not custom_write<T>)
   struct calculate_size<BEVE, T>
   {
      static constexpr auto N = reflect<T>::size;

      template <auto Opts, size_t I>
      static consteval bool should_skip_field()
      {
         using V = field_t<T, I>;

         if constexpr (always_skipped<V>) {
            return true;
         }
         else if constexpr (is_any_function_ptr<V>) {
            return !check_write_function_pointers(Opts);
         }
         else {
            return false;
         }
      }

      template <auto Opts>
      static consteval size_t count_to_write()
      {
         return []<size_t... I>(std::index_sequence<I...>) {
            return (size_t{} + ... + (should_skip_field<Opts, I>() ? size_t{} : size_t{1}));
         }(std::make_index_sequence<N>{});
      }

      // Number of members this object will size (and write) under Opts for the given value, matching
      // the object writer. Used by the merged-object variant sizer so it agrees with the writer's
      // discriminator + body member count.
      template <auto Options>
      [[nodiscard]] static size_t written_member_count(auto&& value)
      {
         if constexpr (!maybe_skipped<Options, T>) {
            (void)value;
            return count_to_write<Options>();
         }
         else {
            [[maybe_unused]] decltype(auto) t = [&]() -> decltype(auto) {
               if constexpr (reflectable<T>) {
                  return to_tie(value);
               }
               else {
                  return nullptr;
               }
            }();

            size_t member_count = 0;
            for_each<N>([&]<size_t I>() {
               if constexpr (should_skip_field<Options, I>()) {
                  return;
               }
               else {
                  using val_t = field_t<T, I>;

                  if constexpr (null_t<val_t> && Options.skip_null_members) {
                     if constexpr (always_null_t<val_t>) {
                        return;
                     }
                     else {
                        const auto is_null = [&]() {
                           decltype(auto) element = [&]() -> decltype(auto) {
                              if constexpr (reflectable<T>) {
                                 return get<I>(t);
                              }
                              else {
                                 return get<I>(reflect<T>::values);
                              }
                           };

                           if constexpr (nullable_wrapper<val_t>) {
                              return !bool(element()(value).val);
                           }
                           else if constexpr (nullable_value_t<val_t>) {
                              return !get_member(value, element()).has_value();
                           }
                           else {
                              return !bool(get_member(value, element()));
                           }
                        }();
                        if (!is_null) {
                           ++member_count;
                        }
                     }
                  }
                  else if constexpr (check_skip_default_members(Options) && has_skippable_default<val_t>) {
                     decltype(auto) member = [&]() -> decltype(auto) {
                        if constexpr (reflectable<T>) {
                           return get_member(value, get<I>(t));
                        }
                        else {
                           return get_member(value, get<I>(reflect<T>::values));
                        }
                     }();
                     if (!is_default_value(member)) {
                        ++member_count;
                     }
                  }
                  else {
                     ++member_count;
                  }
               }
            });
            return member_count;
         }
      }

      template <auto Opts>
         requires(check_structs_as_arrays(Opts) == true)
      [[nodiscard]] static size_t op(auto&& value, size_t offset = 0)
      {
         size_t result = 1; // generic_array tag
         result += compressed_int_size<count_to_write<Opts>()>(); // element count

         [[maybe_unused]] decltype(auto) t = [&]() -> decltype(auto) {
            if constexpr (reflectable<T>) {
               return to_tie(value);
            }
            else {
               return nullptr;
            }
         }();

         for_each<N>([&]<size_t I>() {
            if constexpr (should_skip_field<Opts, I>()) {
               return;
            }
            else {
               if constexpr (reflectable<T>) {
                  result +=
                     calculate_size<BEVE, void>::template op<Opts>(get_member(value, get<I>(t)), offset + result);
               }
               else {
                  result += calculate_size<BEVE, void>::template op<Opts>(get_member(value, get<I>(reflect<T>::values)),
                                                                          offset + result);
               }
            }
         });

         return result;
      }

      template <auto Options>
         requires(check_structs_as_arrays(Options) == false)
      [[nodiscard]] static size_t op(auto&& value, size_t offset = 0)
      {
         constexpr auto Opts = opening_handled_off<Options>();

         [[maybe_unused]] decltype(auto) t = [&]() -> decltype(auto) {
            if constexpr (reflectable<T>) {
               return to_tie(value);
            }
            else {
               return nullptr;
            }
         }();

         size_t result = 0;

         if constexpr (maybe_skipped<Options, T>) {
            // Dynamic path: count members at runtime to handle skip_null_members
            const size_t member_count = written_member_count<Options>(value);

            // Write header with dynamic count
            if constexpr (!check_opening_handled(Options)) {
               result += 1; // object tag
               result += compressed_int_size(member_count); // member count
            }

            // Second pass: calculate member sizes
            for_each<N>([&]<size_t I>() {
               if constexpr (should_skip_field<Options, I>()) {
                  return;
               }
               else {
                  using val_t = field_t<T, I>;

                  if constexpr (null_t<val_t> && Options.skip_null_members) {
                     if constexpr (always_null_t<val_t>) {
                        return;
                     }
                     else {
                        const auto is_null = [&]() {
                           decltype(auto) element = [&]() -> decltype(auto) {
                              if constexpr (reflectable<T>) {
                                 return get<I>(t);
                              }
                              else {
                                 return get<I>(reflect<T>::values);
                              }
                           };

                           if constexpr (nullable_wrapper<val_t>) {
                              return !bool(element()(value).val);
                           }
                           else if constexpr (nullable_value_t<val_t>) {
                              return !get_member(value, element()).has_value();
                           }
                           else {
                              return !bool(get_member(value, element()));
                           }
                        }();
                        if (is_null) {
                           return;
                        }
                     }
                  }

                  if constexpr (check_skip_default_members(Options) && has_skippable_default<val_t>) {
                     decltype(auto) member_val = [&]() -> decltype(auto) {
                        if constexpr (reflectable<T>) {
                           return get_member(value, get<I>(t));
                        }
                        else {
                           return get_member(value, get<I>(reflect<T>::values));
                        }
                     }();
                     if (is_default_value(member_val)) {
                        return;
                     }
                  }

                  static constexpr sv key = reflect<T>::keys[I];
                  result += calculate_size<BEVE, std::remove_cvref_t<decltype(key)>>::template no_header_cx<key.size(),
                                                                                                            Opts>();

                  decltype(auto) member = [&]() -> decltype(auto) {
                     if constexpr (reflectable<T>) {
                        return get<I>(t);
                     }
                     else {
                        return get<I>(reflect<T>::values);
                     }
                  }();

                  result += calculate_size<BEVE, void>::template op<Opts>(get_member(value, member), offset + result);
               }
            });
         }
         else {
            // Static path: use compile-time count for better performance
            if constexpr (!check_opening_handled(Options)) {
               result += 1; // object tag
               result += compressed_int_size<count_to_write<Options>()>(); // member count
            }

            for_each<N>([&]<size_t I>() {
               if constexpr (should_skip_field<Options, I>()) {
                  return;
               }
               else {
                  static constexpr sv key = reflect<T>::keys[I];
                  result += calculate_size<BEVE, std::remove_cvref_t<decltype(key)>>::template no_header_cx<key.size(),
                                                                                                            Opts>();

                  decltype(auto) member = [&]() -> decltype(auto) {
                     if constexpr (reflectable<T>) {
                        return get<I>(t);
                     }
                     else {
                        return get<I>(reflect<T>::values);
                     }
                  }();

                  result += calculate_size<BEVE, void>::template op<Opts>(get_member(value, member), offset + result);
               }
            });
         }

         return result;
      }
   };

   template <class T>
      requires glaze_array_t<T>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] static size_t op(auto&& value, size_t offset = 0)
      {
         static constexpr auto N = reflect<T>::size;

         size_t result = 1; // generic_array tag
         result += compressed_int_size<N>(); // element count

         for_each<N>([&]<size_t I>() {
            result += calculate_size<BEVE, void>::template op<Opts>(get_member(value, get<I>(reflect<T>::values)),
                                                                    offset + result);
         });

         return result;
      }
   };

   template <class T>
      requires(tuple_t<T> || is_std_tuple<T>)
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] static size_t op(auto&& value, size_t offset = 0)
      {
         static constexpr auto N = glz::tuple_size_v<T>;

         size_t result = 1; // generic_array tag
         result += compressed_int_size<N>(); // element count

         if constexpr (is_std_tuple<T>) {
            [&]<size_t... I>(std::index_sequence<I...>) {
               ((result += calculate_size<BEVE, void>::template op<Opts>(std::get<I>(value), offset + result)), ...);
            }(std::make_index_sequence<N>{});
         }
         else {
            [&]<size_t... I>(std::index_sequence<I...>) {
               ((result += calculate_size<BEVE, void>::template op<Opts>(glz::get<I>(value), offset + result)), ...);
            }(std::make_index_sequence<N>{});
         }

         return result;
      }
   };

   // Filesystem path support
   template <filesystem_path T>
   struct calculate_size<BEVE, T>
   {
      template <auto Opts>
      [[nodiscard]] static size_t op(auto&& value, size_t offset = 0)
      {
         return calculate_size<BEVE, decltype(value.string())>::template op<Opts>(value.string(), offset);
      }
   };

   // ============ Public API ============

   // Calculate the size in bytes needed to serialize a value to BEVE format
   template <auto Opts = opts{}, class T>
   [[nodiscard]] size_t beve_size(T&& value)
   {
      return calculate_size<BEVE, std::remove_cvref_t<T>>::template op<set_beve<Opts>()>(std::forward<T>(value));
   }

   // Calculate size for untagged BEVE (structs_as_arrays = true)
   template <auto Opts = opts{}, class T>
   [[nodiscard]] size_t beve_size_untagged(T&& value)
   {
      return calculate_size<BEVE, std::remove_cvref_t<T>>::template op<
         opt_true<set_beve<Opts>(), structs_as_arrays_opt_tag{}>>(std::forward<T>(value));
   }
}
