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

#pragma once

#include "glaze/cbor/header.hpp"
#include "glaze/cbor/skip.hpp"
#include "glaze/core/chrono.hpp"
#include "glaze/core/opts.hpp"
#include "glaze/core/read.hpp"
#include "glaze/core/reflect.hpp"
#include "glaze/file/file_ops.hpp"
#include "glaze/util/dump.hpp"
#include "glaze/util/for_each.hpp"

// Recursion depth: a CBOR nesting level costs a single byte, so input alone can drive the reader
// arbitrarily deep and overflow the stack. Every reader that consumes an array or map head and then
// descends into the items takes one level with glz::depth_guard, and skip_value<CBOR> does the same,
// which bounds the descent at max_recursive_depth_limit and reports exceeded_max_recursive_depth
// instead of crashing.

namespace glz
{
   namespace cbor_detail
   {
      // Decode CBOR argument (variable-length unsigned integer)
      [[nodiscard]] GLZ_ALWAYS_INLINE uint64_t decode_arg(is_context auto& ctx, auto& it, auto end,
                                                          uint8_t additional_info) noexcept
      {
         using namespace cbor;

         if (additional_info < 24) {
            return additional_info;
         }

         switch (additional_info) {
         case info::uint8_follows: {
            if (it >= end) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return 0;
            }
            uint8_t val;
            std::memcpy(&val, it, 1);
            ++it;
            return val;
         }
         case info::uint16_follows: {
            if ((end - it) < 2) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return 0;
            }
            uint16_t val;
            std::memcpy(&val, it, 2);
            if constexpr (std::endian::native == std::endian::little) {
               val = std::byteswap(val);
            }
            it += 2;
            return val;
         }
         case info::uint32_follows: {
            if ((end - it) < 4) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return 0;
            }
            uint32_t val;
            std::memcpy(&val, it, 4);
            if constexpr (std::endian::native == std::endian::little) {
               val = std::byteswap(val);
            }
            it += 4;
            return val;
         }
         case info::uint64_follows: {
            if ((end - it) < 8) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return 0;
            }
            uint64_t val;
            std::memcpy(&val, it, 8);
            if constexpr (std::endian::native == std::endian::little) {
               val = std::byteswap(val);
            }
            it += 8;
            return val;
         }
         default:
            ctx.error = error_code::syntax_error;
            return 0;
         }
      }

      // Byteswap one typed-array element in place. An RFC 8746 tag names the producer's endianness,
      // so an element whose tag disagrees with this platform is swapped through its bit pattern:
      // V may be a floating point type, which has no byteswap of its own.
      //
      // Only the widths RFC 8746 defines below binary128 are handled. A one-byte element has no
      // endianness, and a wider one reaches here only if typed_array::matches accepted its tag, which
      // it does not do for binary128 -- so the widths left unhandled are the widths never passed in.
      template <class V>
      GLZ_ALWAYS_INLINE void byteswap_element(V& elem) noexcept
      {
         if constexpr (sizeof(V) == 2) {
            uint16_t bits;
            std::memcpy(&bits, &elem, sizeof(V));
            bits = std::byteswap(bits);
            std::memcpy(&elem, &bits, sizeof(V));
         }
         else if constexpr (sizeof(V) == 4) {
            uint32_t bits;
            std::memcpy(&bits, &elem, sizeof(V));
            bits = std::byteswap(bits);
            std::memcpy(&elem, &bits, sizeof(V));
         }
         else if constexpr (sizeof(V) == 8) {
            uint64_t bits;
            std::memcpy(&bits, &elem, sizeof(V));
            bits = std::byteswap(bits);
            std::memcpy(&elem, &bits, sizeof(V));
         }
      }
   }

   template <>
   struct parse<CBOR>
   {
      template <auto Opts, class T, is_context Ctx, class It0, class It1>
      GLZ_ALWAYS_INLINE static void op(T&& value, Ctx&& ctx, It0&& it, It1 end)
      {
         if constexpr (const_value_v<T>) {
            if constexpr (check_error_on_const_read(Opts)) {
               ctx.error = error_code::attempt_const_read;
            }
            else {
               skip_value<CBOR>::op<Opts>(std::forward<Ctx>(ctx), std::forward<It0>(it), end);
            }
         }
         else {
            using V = std::remove_cvref_t<T>;
            from<CBOR, V>::template op<Opts>(std::forward<T>(value), std::forward<Ctx>(ctx), std::forward<It0>(it),
                                             end);
         }
      }
   };

   // Null
   template <always_null_t T>
   struct from<CBOR, T>
   {
      template <auto Opts>
      GLZ_ALWAYS_INLINE static void op(auto&&, is_context auto& ctx, auto& it, auto end) noexcept
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);

         if (initial != initial_byte(major::simple, simple::null_value)) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }
         ++it;
      }
   };

   // Skip type
   template <>
   struct from<CBOR, skip>
   {
      template <auto Opts>
      GLZ_ALWAYS_INLINE static void op(auto&&, is_context auto&& ctx, auto&&... args) noexcept
      {
         skip_value<CBOR>::op<Opts>(ctx, args...);
      }
   };

   // Bitset - read from byte string
   template <is_bitset T>
   struct from<CBOR, T>
   {
      template <auto Opts>
      static void op(auto&& value, is_context auto&& ctx, auto&& it, auto end) noexcept
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);
         ++it;

         const uint8_t major_type = get_major_type(initial);
         const uint8_t additional_info = get_additional_info(initial);

         if (major_type != major::bstr) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         const uint64_t num_bytes = cbor_detail::decode_arg(ctx, it, end, additional_info);
         if (bool(ctx.error)) [[unlikely]]
            return;

         const auto expected_bytes = (value.size() + 7) / 8;
         if (num_bytes != expected_bytes) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         if (static_cast<uint64_t>(end - it) < num_bytes) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         // Unpack bytes into bits (LSB first within each byte)
         for (size_t byte_i = 0, bit_idx = 0; byte_i < num_bytes; ++byte_i, ++it) {
            uint8_t byte_val;
            std::memcpy(&byte_val, it, 1);
            for (size_t bit_i = 0; bit_i < 8 && bit_idx < value.size(); ++bit_i, ++bit_idx) {
               value[bit_idx] = (byte_val >> bit_i) & uint8_t(1);
            }
         }
      }
   };

   // Complex numbers - tag 43000 with 2-element array [real, imag]
   template <class T>
      requires complex_t<T>
   struct from<CBOR, T>
   {
      template <auto Opts>
      static void op(auto&& value, is_context auto&& ctx, auto&& it, auto end) noexcept
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);
         ++it;

         // Expect tag 43000 (complex number)
         if (get_major_type(initial) != major::tag) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         const uint64_t tag = cbor_detail::decode_arg(ctx, it, end, get_additional_info(initial));
         if (bool(ctx.error)) [[unlikely]]
            return;

         if (tag != semantic_tag::complex_number) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         // Read array header
         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         std::memcpy(&initial, it, 1);
         ++it;

         if (get_major_type(initial) != major::array) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         // Expect exactly 2 elements
         uint64_t count = cbor_detail::decode_arg(ctx, it, end, get_additional_info(initial));
         if (bool(ctx.error)) [[unlikely]]
            return;

         if (count != 2) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         // Read real and imaginary parts
         using V = typename std::remove_cvref_t<T>::value_type;
         V real_part{}, imag_part{};
         from<CBOR, V>::template op<Opts>(real_part, ctx, it, end);
         if (bool(ctx.error)) [[unlikely]]
            return;
         from<CBOR, V>::template op<Opts>(imag_part, ctx, it, end);
         if (bool(ctx.error)) [[unlikely]]
            return;

         value = std::remove_cvref_t<T>{real_part, imag_part};
      }
   };

   // Boolean
   template <boolean_like T>
   struct from<CBOR, T>
   {
      // A forwarding reference, not auto&: an element of std::vector<bool> is reached through a proxy
      // that its container hands back by value, so binding the target as an lvalue reference would
      // reject every bool that lives in one. The other formats' boolean readers take it the same way.
      template <auto Opts>
      GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto& ctx, auto& it, auto end) noexcept
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);
         ++it;

         if (initial == initial_byte(major::simple, simple::false_value)) {
            value = false;
         }
         else if (initial == initial_byte(major::simple, simple::true_value)) {
            value = true;
         }
         else [[unlikely]] {
            ctx.error = error_code::syntax_error;
         }
      }
   };

   // Unsigned integers
   template <class T>
      requires(std::unsigned_integral<T> && !std::same_as<T, bool>)
   struct from<CBOR, T>
   {
      template <auto Opts>
      GLZ_ALWAYS_INLINE static void op(auto& value, is_context auto& ctx, auto& it, auto end) noexcept
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);
         ++it;

         const uint8_t major_type = get_major_type(initial);
         const uint8_t additional_info = get_additional_info(initial);

         if (major_type != major::uint) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         uint64_t result = cbor_detail::decode_arg(ctx, it, end, additional_info);
         if (bool(ctx.error)) [[unlikely]]
            return;

         // Reject a value that does not fit in T, matching the signed reader below and the
         // JSON/MessagePack integer readers. Without this a uint16/uint32/uint64 argument is
         // silently truncated into a narrower target (e.g. 300 -> uint8_t 44) with success.
         if (result > static_cast<uint64_t>((std::numeric_limits<T>::max)())) [[unlikely]] {
            ctx.error = error_code::parse_number_failure;
            return;
         }

         value = static_cast<T>(result);
      }
   };

   // Signed integers
   template <class T>
      requires(std::signed_integral<T> && !std::same_as<T, bool>)
   struct from<CBOR, T>
   {
      template <auto Opts>
      GLZ_ALWAYS_INLINE static void op(auto& value, is_context auto& ctx, auto& it, auto end) noexcept
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);
         ++it;

         const uint8_t major_type = get_major_type(initial);
         const uint8_t additional_info = get_additional_info(initial);

         if (major_type == major::uint) {
            uint64_t n = cbor_detail::decode_arg(ctx, it, end, additional_info);
            if (bool(ctx.error)) [[unlikely]]
               return;

            // Range check: n must fit in T's positive range
            constexpr auto max_val = static_cast<uint64_t>((std::numeric_limits<T>::max)());
            if (n > max_val) [[unlikely]] {
               ctx.error = error_code::parse_number_failure;
               return;
            }
            value = static_cast<T>(n);
         }
         else if (major_type == major::nint) {
            uint64_t n = cbor_detail::decode_arg(ctx, it, end, additional_info);
            if (bool(ctx.error)) [[unlikely]]
               return;

            // CBOR negative value = -1 - n
            // For T's range [-2^(bits-1), 2^(bits-1)-1], max valid n = 2^(bits-1) - 1
            constexpr auto max_n = static_cast<uint64_t>((std::numeric_limits<T>::max)());

            if (n > max_n) [[unlikely]] {
               ctx.error = error_code::parse_number_failure;
               return;
            }

            // Safe computation using two's complement identity:
            // ~n = -1 - n (bitwise NOT)
            value = static_cast<T>(~n);
         }
         else [[unlikely]] {
            ctx.error = error_code::syntax_error;
         }
      }
   };

   // Floating-point
   template <std::floating_point T>
   struct from<CBOR, T>
   {
      template <auto Opts>
      GLZ_ALWAYS_INLINE static void op(auto& value, is_context auto& ctx, auto& it, auto end) noexcept
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);
         ++it;

         const uint8_t major_type = get_major_type(initial);
         const uint8_t additional_info = get_additional_info(initial);

         if (major_type != major::simple) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         switch (additional_info) {
         case simple::float16: {
            if ((end - it) < 2) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return;
            }
            uint16_t half;
            std::memcpy(&half, it, 2);
            if constexpr (std::endian::native == std::endian::little) {
               half = std::byteswap(half);
            }
            it += 2;
            value = static_cast<T>(decode_half(half));
            break;
         }
         case simple::float32: {
            if ((end - it) < 4) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return;
            }
            uint32_t bits;
            std::memcpy(&bits, it, 4);
            if constexpr (std::endian::native == std::endian::little) {
               bits = std::byteswap(bits);
            }
            float f;
            std::memcpy(&f, &bits, 4);
            it += 4;
            value = static_cast<T>(f);
            break;
         }
         case simple::float64: {
            if ((end - it) < 8) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return;
            }
            uint64_t bits;
            std::memcpy(&bits, it, 8);
            if constexpr (std::endian::native == std::endian::little) {
               bits = std::byteswap(bits);
            }
            double d;
            std::memcpy(&d, &bits, 8);
            it += 8;
            value = static_cast<T>(d);
            break;
         }
         default:
            ctx.error = error_code::syntax_error;
         }
      }
   };

   // Text strings (UTF-8)
   template <str_t T>
   struct from<CBOR, T> final
   {
      template <auto Opts>
      static void op(auto& value, is_context auto& ctx, auto& it, auto end)
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);
         ++it;

         const uint8_t major_type = get_major_type(initial);
         const uint8_t additional_info = get_additional_info(initial);

         if (major_type != major::tstr) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         if (additional_info == info::indefinite) {
            // Indefinite-length text string
            if constexpr (string_view_t<T>) {
               // Cannot read indefinite string into string_view
               ctx.error = error_code::syntax_error;
               return;
            }
            else {
               if constexpr (resizable<T>) {
                  value.clear();
               }
               size_t offset = 0; // fill position for fixed-size targets
               while (true) {
                  if (it >= end) [[unlikely]] {
                     ctx.error = error_code::unexpected_end;
                     return;
                  }

                  uint8_t chunk_initial;
                  std::memcpy(&chunk_initial, it, 1);

                  // Check for break code
                  if (chunk_initial == initial_byte(major::simple, simple::break_code)) {
                     ++it;
                     break;
                  }

                  const uint8_t chunk_major = get_major_type(chunk_initial);
                  const uint8_t chunk_info = get_additional_info(chunk_initial);

                  // Chunks must be text strings with definite length
                  if (chunk_major != major::tstr) [[unlikely]] {
                     ctx.error = error_code::syntax_error;
                     return;
                  }
                  if (chunk_info == info::indefinite) [[unlikely]] {
                     ctx.error = error_code::syntax_error;
                     return;
                  }

                  ++it;
                  uint64_t chunk_len = cbor_detail::decode_arg(ctx, it, end, chunk_info);
                  if (bool(ctx.error)) [[unlikely]]
                     return;

                  if (static_cast<uint64_t>(end - it) < chunk_len) [[unlikely]] {
                     ctx.error = error_code::unexpected_end;
                     return;
                  }

                  if constexpr (array_char_t<T>) {
                     // Fixed-size std::array<char, N>: accumulate with bounds checking.
                     if (offset + chunk_len > value.size()) [[unlikely]] {
                        ctx.error = error_code::syntax_error;
                        return;
                     }
                     std::memcpy(value.data() + offset, it, chunk_len);
                     offset += static_cast<size_t>(chunk_len);
                  }
                  else {
                     value.append(reinterpret_cast<const char*>(it), chunk_len);
                  }
                  it += chunk_len;
               }
               if constexpr (array_char_t<T>) {
                  // Zero-fill any unused tail of the fixed-size buffer.
                  if (offset < value.size()) {
                     std::memset(value.data() + offset, 0, value.size() - offset);
                  }
               }
            }
         }
         else {
            // Definite-length text string
            uint64_t length = cbor_detail::decode_arg(ctx, it, end, additional_info);
            if (bool(ctx.error)) [[unlikely]]
               return;

            if (static_cast<uint64_t>(end - it) < length) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return;
            }

            // Check user-configured string length limit
            if constexpr (check_max_string_length(Opts) > 0) {
               if (length > check_max_string_length(Opts)) [[unlikely]] {
                  ctx.error = error_code::invalid_length;
                  return;
               }
            }
            if constexpr (has_runtime_max_string_length<std::decay_t<decltype(ctx)>>) {
               if (ctx.max_string_length > 0 && length > ctx.max_string_length) [[unlikely]] {
                  ctx.error = error_code::invalid_length;
                  return;
               }
            }

            if constexpr (string_view_t<T>) {
               value = {reinterpret_cast<const char*>(it), static_cast<size_t>(length)};
            }
            else if constexpr (array_char_t<T>) {
               // Fixed-size std::array<char, N>: bounds-check, copy, zero-fill remainder.
               if (length > value.size()) [[unlikely]] {
                  ctx.error = error_code::syntax_error;
                  return;
               }
               std::memcpy(value.data(), it, length);
               if (length < value.size()) {
                  std::memset(value.data() + static_cast<size_t>(length), 0,
                              value.size() - static_cast<size_t>(length));
               }
            }
            else {
               value.assign(reinterpret_cast<const char*>(it), length);
            }
            it += length;
         }
      }
   };

   // Byte strings - any contiguous byte-like range (std::vector<std::byte>, std::vector<uint8_t>,
   // std::array<std::byte, N>, std::array<uint8_t, N>, ...). Handles both resizable and fixed-size
   // targets (the latter bounds-checked with the remainder zero-filled); std::byte, unsigned char,
   // and uint8_t ranges share one implementation (see glz::contiguous_byte_range / byte_like).
   template <class T>
      requires(contiguous_byte_range<std::remove_cvref_t<T>> && !str_t<T>)
   struct from<CBOR, T>
   {
      // The same bytes are also legitimately carried as a plain array of small unsigned integers
      // (major type 4). That is what a non-contiguous byte range such as std::list<uint8_t> writes,
      // and what CBOR producers that treat a byte sequence as an array of numbers emit, so read
      // either encoding into the same target. The initial byte has already been consumed.
      template <auto Opts>
      static void read_as_array(auto& value, is_context auto& ctx, auto& it, auto end, const uint8_t additional_info)
      {
         using namespace cbor;

         depth_guard guard{ctx};
         if (!guard) [[unlikely]] {
            return;
         }

         // Grow to i + 1 elements, or bounds-check a fixed-size target. Returns false on failure.
         const auto make_room = [&](const size_t i) {
            if constexpr (resizable<T>) {
               if (exceeds_capacity(value, i + 1, ctx)) [[unlikely]] {
                  return false;
               }
               value.resize(i + 1);
            }
            else {
               if (i >= value.size()) [[unlikely]] {
                  ctx.error = error_code::exceeded_static_array_size;
                  return false;
               }
            }
            return true;
         };

         size_t count = 0;
         if (additional_info == info::indefinite) {
            if constexpr (resizable<T>) {
               value.clear();
            }
            while (true) {
               if (it >= end) [[unlikely]] {
                  ctx.error = error_code::unexpected_end;
                  return;
               }

               uint8_t peek;
               std::memcpy(&peek, it, 1);
               if (peek == initial_byte(major::simple, simple::break_code)) {
                  ++it;
                  break;
               }

               if constexpr (check_max_array_size(Opts) > 0) {
                  if (count >= check_max_array_size(Opts)) [[unlikely]] {
                     ctx.error = error_code::invalid_length;
                     return;
                  }
               }
               if constexpr (has_runtime_max_array_size<std::decay_t<decltype(ctx)>>) {
                  if (ctx.max_array_size > 0 && count >= ctx.max_array_size) [[unlikely]] {
                     ctx.error = error_code::invalid_length;
                     return;
                  }
               }
               if (!make_room(count)) [[unlikely]] {
                  return;
               }
               // Reached through data() rather than a subscript: contiguous storage is what this
               // reader relies on, and a contiguous range need not also be indexable. Re-read on
               // every pass, since make_room grows a resizable target one element at a time and any
               // pointer taken before that is stale.
               parse<CBOR>::op<Opts>(value.data()[count], ctx, it, end);
               if (bool(ctx.error)) [[unlikely]]
                  return;
               ++count;
            }
         }
         else {
            const uint64_t n = cbor_detail::decode_arg(ctx, it, end, additional_info);
            if (bool(ctx.error)) [[unlikely]]
               return;

            // Every element occupies at least one byte, so a valid count cannot exceed the input.
            if (n > static_cast<uint64_t>(end - it)) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return;
            }
            if constexpr (check_max_array_size(Opts) > 0) {
               if (n > check_max_array_size(Opts)) [[unlikely]] {
                  ctx.error = error_code::invalid_length;
                  return;
               }
            }
            if constexpr (has_runtime_max_array_size<std::decay_t<decltype(ctx)>>) {
               if (ctx.max_array_size > 0 && n > ctx.max_array_size) [[unlikely]] {
                  ctx.error = error_code::invalid_length;
                  return;
               }
            }

            if constexpr (resizable<T>) {
               if (exceeds_capacity(value, static_cast<size_t>(n), ctx)) [[unlikely]] {
                  return;
               }
               value.resize(static_cast<size_t>(n));
            }
            else {
               if (n > value.size()) [[unlikely]] {
                  ctx.error = error_code::exceeded_static_array_size;
                  return;
               }
            }

            // As above, through data() rather than a subscript. The target reached its full size
            // before the loop, so one pointer serves the whole of it.
            auto* dest = value.data();
            for (; count < n; ++count) {
               parse<CBOR>::op<Opts>(dest[count], ctx, it, end);
               if (bool(ctx.error)) [[unlikely]]
                  return;
            }
         }

         if constexpr (!resizable<T>) {
            // Zero-fill any unused tail, as the byte string path does.
            if (count < value.size()) {
               std::memset(value.data() + count, 0, value.size() - count);
            }
         }
      }

      template <auto Opts>
      static void op(auto& value, is_context auto& ctx, auto& it, auto end)
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);
         ++it;

         const uint8_t major_type = get_major_type(initial);
         const uint8_t additional_info = get_additional_info(initial);

         if (major_type == major::array) {
            read_as_array<Opts>(value, ctx, it, end, additional_info);
            return;
         }

         if (major_type != major::bstr) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         if (additional_info == info::indefinite) {
            // Indefinite-length byte string
            if constexpr (resizable<T>) {
               value.clear();
            }
            size_t offset = 0; // fill position for fixed-size targets
            uint64_t total = 0; // bytes accumulated so far, across chunks
            while (true) {
               if (it >= end) [[unlikely]] {
                  ctx.error = error_code::unexpected_end;
                  return;
               }

               uint8_t chunk_initial;
               std::memcpy(&chunk_initial, it, 1);

               if (chunk_initial == initial_byte(major::simple, simple::break_code)) {
                  ++it;
                  break;
               }

               const uint8_t chunk_major = get_major_type(chunk_initial);
               const uint8_t chunk_info = get_additional_info(chunk_initial);

               if (chunk_major != major::bstr) [[unlikely]] {
                  ctx.error = error_code::syntax_error;
                  return;
               }
               if (chunk_info == info::indefinite) [[unlikely]] {
                  ctx.error = error_code::syntax_error;
                  return;
               }

               ++it;
               uint64_t chunk_len = cbor_detail::decode_arg(ctx, it, end, chunk_info);
               if (bool(ctx.error)) [[unlikely]]
                  return;

               if (static_cast<uint64_t>(end - it) < chunk_len) [[unlikely]] {
                  ctx.error = error_code::unexpected_end;
                  return;
               }

               // A break code rather than a length ends this byte string, so the caller's limit is
               // enforced against the running total instead of a single header.
               total += chunk_len;
               if constexpr (check_max_array_size(Opts) > 0) {
                  if (total > check_max_array_size(Opts)) [[unlikely]] {
                     ctx.error = error_code::invalid_length;
                     return;
                  }
               }
               if constexpr (has_runtime_max_array_size<std::decay_t<decltype(ctx)>>) {
                  if (ctx.max_array_size > 0 && total > ctx.max_array_size) [[unlikely]] {
                     ctx.error = error_code::invalid_length;
                     return;
                  }
               }

               if constexpr (resizable<T>) {
                  const size_t old_size = value.size();
                  if (exceeds_capacity(value, old_size + static_cast<size_t>(chunk_len), ctx)) [[unlikely]] {
                     return;
                  }
                  value.resize(old_size + static_cast<size_t>(chunk_len));
                  std::memcpy(value.data() + old_size, it, chunk_len);
               }
               else {
                  // Fixed-size std::array<std::byte, N>: accumulate with bounds checking.
                  if (offset + chunk_len > value.size()) [[unlikely]] {
                     ctx.error = error_code::syntax_error;
                     return;
                  }
                  std::memcpy(value.data() + offset, it, chunk_len);
                  offset += static_cast<size_t>(chunk_len);
               }
               it += chunk_len;
            }
            if constexpr (!resizable<T>) {
               // Zero-fill any unused tail of the fixed-size buffer.
               if (offset < value.size()) {
                  std::memset(value.data() + offset, 0, value.size() - offset);
               }
            }
         }
         else {
            uint64_t length = cbor_detail::decode_arg(ctx, it, end, additional_info);
            if (bool(ctx.error)) [[unlikely]]
               return;

            if (static_cast<uint64_t>(end - it) < length) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return;
            }

            // Check user-configured array size limit
            if constexpr (check_max_array_size(Opts) > 0) {
               if (length > check_max_array_size(Opts)) [[unlikely]] {
                  ctx.error = error_code::invalid_length;
                  return;
               }
            }
            if constexpr (has_runtime_max_array_size<std::decay_t<decltype(ctx)>>) {
               if (ctx.max_array_size > 0 && length > ctx.max_array_size) [[unlikely]] {
                  ctx.error = error_code::invalid_length;
                  return;
               }
            }

            if constexpr (resizable<T>) {
               if (exceeds_capacity(value, static_cast<size_t>(length), ctx)) [[unlikely]] {
                  return;
               }
               value.resize(static_cast<size_t>(length));
               std::memcpy(value.data(), it, length);
            }
            else {
               // Fixed-size std::array<std::byte, N>: bounds-check, copy, zero-fill remainder.
               if (length > value.size()) [[unlikely]] {
                  ctx.error = error_code::syntax_error;
                  return;
               }
               std::memcpy(value.data(), it, length);
               if (length < value.size()) {
                  std::memset(value.data() + static_cast<size_t>(length), 0,
                              value.size() - static_cast<size_t>(length));
               }
            }
            it += length;
         }
      }
   };

   // Arrays (std::vector, std::deque, etc.)
   // Note: eigen_t types have their own specialization in glaze/ext/eigen.hpp
   // Contiguous byte-like ranges are excluded here; they are read as CBOR byte strings above.
   template <readable_array_t T>
      requires(!eigen_t<T> && !contiguous_byte_range<std::remove_cvref_t<T>>)
   struct from<CBOR, T> final
   {
      // A sequence of bytes is also legitimately carried as a CBOR byte string (major type 2). That
      // is what a contiguous byte range such as std::vector<uint8_t> writes, so a byte-like range
      // reads either encoding. `append` takes one decoded byte and returns false once the target is
      // full; the initial byte has already been consumed.
      template <auto Opts>
      static void read_byte_string(is_context auto& ctx, auto& it, auto end, const uint8_t additional_info,
                                   auto&& append)
      {
         using namespace cbor;

         // An indefinite-length byte string is ended by a break code rather than a length, so the
         // caller's limit is enforced against the running total rather than a single header.
         uint64_t total = 0;

         // Copy `length` bytes out of the input, having checked they are all present.
         const auto take = [&](const uint64_t length) {
            if (static_cast<uint64_t>(end - it) < length) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return false;
            }
            total += length;
            if constexpr (check_max_array_size(Opts) > 0) {
               if (total > check_max_array_size(Opts)) [[unlikely]] {
                  ctx.error = error_code::invalid_length;
                  return false;
               }
            }
            if constexpr (has_runtime_max_array_size<std::decay_t<decltype(ctx)>>) {
               if (ctx.max_array_size > 0 && total > ctx.max_array_size) [[unlikely]] {
                  ctx.error = error_code::invalid_length;
                  return false;
               }
            }
            for (uint64_t i = 0; i < length; ++i, ++it) {
               uint8_t byte;
               std::memcpy(&byte, it, 1);
               if (!append(byte)) [[unlikely]] {
                  return false;
               }
            }
            return true;
         };

         if (additional_info == info::indefinite) {
            while (true) {
               if (it >= end) [[unlikely]] {
                  ctx.error = error_code::unexpected_end;
                  return;
               }

               uint8_t chunk_initial;
               std::memcpy(&chunk_initial, it, 1);
               if (chunk_initial == initial_byte(major::simple, simple::break_code)) {
                  ++it;
                  return;
               }

               // Chunks of an indefinite-length byte string are themselves definite byte strings.
               if (get_major_type(chunk_initial) != major::bstr ||
                   get_additional_info(chunk_initial) == info::indefinite) [[unlikely]] {
                  ctx.error = error_code::syntax_error;
                  return;
               }
               ++it;

               const uint64_t chunk_len = cbor_detail::decode_arg(ctx, it, end, get_additional_info(chunk_initial));
               if (bool(ctx.error)) [[unlikely]]
                  return;
               if (!take(chunk_len)) [[unlikely]]
                  return;
            }
         }

         const uint64_t length = cbor_detail::decode_arg(ctx, it, end, additional_info);
         if (bool(ctx.error)) [[unlikely]]
            return;

         (void)take(length);
      }

      template <auto Opts>
      static void op(auto& value, is_context auto& ctx, auto& it, auto end)
      {
         using namespace cbor;
         using V = range_value_t<std::remove_cvref_t<T>>;

         // Set-like containers (std::set, std::unordered_set, ...) can neither be resized nor
         // back-inserted into: each element is parsed into a temporary and then emplaced.
         constexpr bool set_like = !resizable<T> && !emplace_backable<T> && emplaceable<T>;
         // Everything else either has a fixed size the input must fit, or grows to match the input.
         // The definite- and indefinite-length branches below must agree on which is which, or the
         // same container would accept one framing of an array and reject the other.
         constexpr bool growable = resizable<T> || emplace_backable<T> || set_like;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);

         const uint8_t major_type = get_major_type(initial);
         const uint8_t additional_info = get_additional_info(initial);

         // A byte-like range also accepts a CBOR byte string, which is what the contiguous byte
         // ranges write for the same bytes.
         if constexpr (byte_like<V>) {
            if (major_type == major::bstr) {
               ++it; // consume the initial byte

               depth_guard guard{ctx};
               if (!guard) [[unlikely]] {
                  return;
               }

               if constexpr (growable) {
                  value.clear();
               }

               size_t filled = 0;
               // Only a target that cannot grow is filled in place; growing would invalidate this.
               [[maybe_unused]] decltype(value.begin()) dest{};
               if constexpr (not growable) {
                  dest = value.begin();
               }
               read_byte_string<Opts>(ctx, it, end, additional_info, [&](const uint8_t byte) {
                  if (exceeds_capacity(value, filled + 1, ctx)) [[unlikely]] {
                     return false;
                  }
                  if constexpr (set_like) {
                     value.emplace(static_cast<V>(byte));
                  }
                  else if constexpr (emplace_backable<T>) {
                     value.emplace_back(static_cast<V>(byte));
                  }
                  else if constexpr (resizable<T>) {
                     value.resize(filled + 1);
                     auto slot = value.begin();
                     std::advance(slot, filled);
                     *slot = static_cast<V>(byte);
                  }
                  else {
                     if (filled >= value.size()) [[unlikely]] {
                        ctx.error = error_code::exceeded_static_array_size;
                        return false;
                     }
                     *dest = static_cast<V>(byte);
                     ++dest;
                  }
                  ++filled;
                  return true;
               });

               if constexpr (!growable) {
                  // Zero-fill any unused tail, as the byte string reader does for std::array.
                  if (not bool(ctx.error)) {
                     for (; filled < value.size(); ++filled, ++dest) {
                        *dest = V{};
                     }
                  }
               }
               return;
            }
         }

         // Check for RFC 8746 typed array (tag + byte string)
         if constexpr (num_t<V> && !std::same_as<V, bool>) {
            if (major_type == major::tag) {
               ++it; // consume the tag initial byte

               // Decode the tag number
               const uint64_t tag_num = cbor_detail::decode_arg(ctx, it, end, additional_info);
               if (bool(ctx.error)) [[unlikely]]
                  return;

               // Verify the tag describes exactly this element type, not merely one of its width
               if (typed_array::matches<V>(typed_array::get_info(tag_num))) {
                  // Read the byte string
                  if (it >= end) [[unlikely]] {
                     ctx.error = error_code::unexpected_end;
                     return;
                  }

                  uint8_t bstr_initial;
                  std::memcpy(&bstr_initial, it, 1);
                  ++it;

                  if (get_major_type(bstr_initial) != major::bstr) [[unlikely]] {
                     ctx.error = error_code::syntax_error;
                     return;
                  }

                  const uint64_t byte_len = cbor_detail::decode_arg(ctx, it, end, get_additional_info(bstr_initial));
                  if (bool(ctx.error)) [[unlikely]]
                     return;

                  if (byte_len % sizeof(V) != 0) [[unlikely]] {
                     ctx.error = error_code::syntax_error;
                     return;
                  }

                  if (static_cast<uint64_t>(end - it) < byte_len) [[unlikely]] {
                     ctx.error = error_code::unexpected_end;
                     return;
                  }

                  const size_t count = byte_len / sizeof(V);

                  // Check user-configured array size limit
                  if constexpr (check_max_array_size(Opts) > 0) {
                     if (count > check_max_array_size(Opts)) [[unlikely]] {
                        ctx.error = error_code::invalid_length;
                        return;
                     }
                  }
                  if constexpr (has_runtime_max_array_size<std::decay_t<decltype(ctx)>>) {
                     if (ctx.max_array_size > 0 && count > ctx.max_array_size) [[unlikely]] {
                        ctx.error = error_code::invalid_length;
                        return;
                     }
                  }

                  if constexpr (set_like) {
                     value.clear();
                  }
                  else if constexpr (resizable<T>) {
                     if (exceeds_capacity(value, count, ctx)) [[unlikely]] {
                        return;
                     }
                     value.resize(count);
                     if constexpr (check_shrink_to_fit(Opts) && has_shrink_to_fit<T>) {
                        value.shrink_to_fit();
                     }
                  }
                  else if constexpr (emplace_backable<T>) {
                     // Append-only: the count is known, but the only way to reach it is one element at
                     // a time. Growing to count here rather than while filling leaves both fill paths
                     // below writing into storage that already exists.
                     if (exceeds_capacity(value, count, ctx)) [[unlikely]] {
                        return;
                     }
                     value.clear();
                     for (size_t i = 0; i < count; ++i) {
                        value.emplace_back();
                     }
                  }
                  else {
                     if (count != value.size()) [[unlikely]] {
                        ctx.error = error_code::exceeded_static_array_size;
                        return;
                     }
                  }

                  // Check if we need to byteswap
                  const bool need_swap = typed_array::needs_byteswap(tag_num);

                  if constexpr (contiguous<T>) {
                     if (need_swap && sizeof(V) > 1) {
                        // Need to byteswap each element. Written through data() rather than a
                        // subscript: contiguous storage is what this branch relies on, and a
                        // contiguous range need not also be indexable.
                        auto* dest = value.data();
                        for (size_t i = 0; i < count; ++i) {
                           V elem;
                           std::memcpy(&elem, it, sizeof(V));
                           cbor_detail::byteswap_element(elem);
                           dest[i] = elem;
                           it += sizeof(V);
                        }
                     }
                     else {
                        // Native endianness or single-byte: bulk read
                        if (byte_len > 0) {
                           std::memcpy(value.data(), it, byte_len);
                           it += byte_len;
                        }
                     }
                  }
                  else {
                     // std::list, std::set and friends have no contiguous storage to bulk read into,
                     // so each element is decoded on its own.
                     const bool swap_each = need_swap && sizeof(V) > 1;
                     const auto next_element = [&] {
                        V elem;
                        std::memcpy(&elem, it, sizeof(V));
                        if (swap_each) {
                           cbor_detail::byteswap_element(elem);
                        }
                        it += sizeof(V);
                        return elem;
                     };

                     if constexpr (set_like) {
                        for (size_t i = 0; i < count; ++i) {
                           value.emplace(next_element());
                        }
                     }
                     else {
                        auto dest = value.begin();
                        for (size_t i = 0; i < count; ++i, ++dest) {
                           *dest = next_element();
                        }
                     }
                  }
                  return; // Done with typed array
               }
               else {
                  // Not a matching typed array tag - error
                  ctx.error = error_code::syntax_error;
                  return;
               }
            }
         }

         // Check for complex array (tag 43001 with nested typed array)
         if constexpr (complex_t<V>) {
            if (major_type == major::tag) {
               ++it; // consume the tag initial byte

               // Decode the tag number
               const uint64_t tag_num = cbor_detail::decode_arg(ctx, it, end, additional_info);
               if (bool(ctx.error)) [[unlikely]]
                  return;

               // Check for tag 43001 (complex array)
               if (tag_num == semantic_tag::complex_array) {
                  using Scalar = typename V::value_type;

                  // Read nested typed array tag
                  if (it >= end) [[unlikely]] {
                     ctx.error = error_code::unexpected_end;
                     return;
                  }

                  uint8_t ta_initial;
                  std::memcpy(&ta_initial, it, 1);
                  ++it;

                  if (get_major_type(ta_initial) != major::tag) [[unlikely]] {
                     ctx.error = error_code::syntax_error;
                     return;
                  }

                  const uint64_t scalar_tag = cbor_detail::decode_arg(ctx, it, end, get_additional_info(ta_initial));
                  if (bool(ctx.error)) [[unlikely]]
                     return;

                  // Verify the tag describes exactly this scalar type, not merely one of its width
                  if (!typed_array::matches<Scalar>(typed_array::get_info(scalar_tag))) [[unlikely]] {
                     ctx.error = error_code::syntax_error;
                     return;
                  }

                  // Read the byte string
                  if (it >= end) [[unlikely]] {
                     ctx.error = error_code::unexpected_end;
                     return;
                  }

                  uint8_t bstr_initial;
                  std::memcpy(&bstr_initial, it, 1);
                  ++it;

                  if (get_major_type(bstr_initial) != major::bstr) [[unlikely]] {
                     ctx.error = error_code::syntax_error;
                     return;
                  }

                  const uint64_t byte_len = cbor_detail::decode_arg(ctx, it, end, get_additional_info(bstr_initial));
                  if (bool(ctx.error)) [[unlikely]]
                     return;

                  // Each complex has 2 scalars
                  constexpr size_t complex_byte_size = sizeof(V); // sizeof(complex<T>) = 2 * sizeof(T)
                  if (byte_len % complex_byte_size != 0) [[unlikely]] {
                     ctx.error = error_code::syntax_error;
                     return;
                  }

                  if (static_cast<uint64_t>(end - it) < byte_len) [[unlikely]] {
                     ctx.error = error_code::unexpected_end;
                     return;
                  }

                  const size_t count = byte_len / complex_byte_size;

                  // Check user-configured array size limit
                  if constexpr (check_max_array_size(Opts) > 0) {
                     if (count > check_max_array_size(Opts)) [[unlikely]] {
                        ctx.error = error_code::invalid_length;
                        return;
                     }
                  }
                  if constexpr (has_runtime_max_array_size<std::decay_t<decltype(ctx)>>) {
                     if (ctx.max_array_size > 0 && count > ctx.max_array_size) [[unlikely]] {
                        ctx.error = error_code::invalid_length;
                        return;
                     }
                  }

                  if constexpr (set_like) {
                     value.clear();
                  }
                  else if constexpr (resizable<T>) {
                     if (exceeds_capacity(value, count, ctx)) [[unlikely]] {
                        return;
                     }
                     value.resize(count);
                     if constexpr (check_shrink_to_fit(Opts) && has_shrink_to_fit<T>) {
                        value.shrink_to_fit();
                     }
                  }
                  else if constexpr (emplace_backable<T>) {
                     // Append-only, as above: grow to the known count so the fill paths below only
                     // have to write into elements that already exist.
                     if (exceeds_capacity(value, count, ctx)) [[unlikely]] {
                        return;
                     }
                     value.clear();
                     for (size_t i = 0; i < count; ++i) {
                        value.emplace_back();
                     }
                  }
                  else {
                     if (count != value.size()) [[unlikely]] {
                        ctx.error = error_code::exceeded_static_array_size;
                        return;
                     }
                  }

                  // Check if we need to byteswap
                  const bool need_swap = typed_array::needs_byteswap(scalar_tag);

                  if constexpr (contiguous<T>) {
                     if (need_swap && sizeof(Scalar) > 1) {
                        // Need to byteswap each scalar in the interleaved data
                        auto* dest = reinterpret_cast<Scalar*>(value.data());
                        const size_t num_scalars = count * 2; // 2 scalars per complex
                        for (size_t i = 0; i < num_scalars; ++i) {
                           Scalar elem;
                           std::memcpy(&elem, it, sizeof(Scalar));
                           cbor_detail::byteswap_element(elem);
                           dest[i] = elem;
                           it += sizeof(Scalar);
                        }
                     }
                     else {
                        // Native endianness or single-byte: bulk read
                        if (byte_len > 0) {
                           std::memcpy(value.data(), it, byte_len);
                           it += byte_len;
                        }
                     }
                  }
                  else {
                     // No contiguous storage to bulk read into, so the interleaved [real, imag] pairs
                     // are reassembled one element at a time.
                     const bool swap_each = need_swap && sizeof(Scalar) > 1;
                     const auto next_element = [&] {
                        Scalar parts[2];
                        std::memcpy(parts, it, sizeof(V));
                        if (swap_each) {
                           cbor_detail::byteswap_element(parts[0]);
                           cbor_detail::byteswap_element(parts[1]);
                        }
                        it += sizeof(V);
                        return V{parts[0], parts[1]};
                     };

                     if constexpr (set_like) {
                        for (size_t i = 0; i < count; ++i) {
                           value.emplace(next_element());
                        }
                     }
                     else {
                        auto dest = value.begin();
                        for (size_t i = 0; i < count; ++i, ++dest) {
                           *dest = next_element();
                        }
                     }
                  }
                  return; // Done with complex array
               }
               else {
                  // Not a complex array tag - error
                  ctx.error = error_code::syntax_error;
                  return;
               }
            }
         }

         // Regular CBOR array (major type 4)
         ++it; // consume the initial byte

         if (major_type != major::array) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         // The typed and complex array forms above are flat; only this branch descends into elements.
         depth_guard guard{ctx};
         if (!guard) [[unlikely]] {
            return;
         }

         if (additional_info == info::indefinite) {
            // Indefinite-length array. The resizable-only path below grows with resize() from index
            // zero, which drops any prior contents on its own, so it needs no clear() of its own.
            if constexpr (emplace_backable<T> || set_like) {
               value.clear();
            }
            // A target that cannot grow is filled in place, and it is walked with an iterator rather
            // than a subscript because a non-resizable range need not be indexable. The growing cases
            // leave this default constructed: insertion would invalidate it.
            [[maybe_unused]] decltype(value.begin()) dest{};
            if constexpr (not growable) {
               dest = value.begin();
            }
            size_t i = 0;
            while (true) {
               if (it >= end) [[unlikely]] {
                  ctx.error = error_code::unexpected_end;
                  return;
               }

               uint8_t peek;
               std::memcpy(&peek, it, 1);

               if (peek == initial_byte(major::simple, simple::break_code)) {
                  ++it;
                  break;
               }

               // A break code rather than a count ends this array, so the caller's element limit is
               // enforced as the array grows instead of up front.
               if constexpr (check_max_array_size(Opts) > 0) {
                  if (i >= check_max_array_size(Opts)) [[unlikely]] {
                     ctx.error = error_code::invalid_length;
                     return;
                  }
               }
               if constexpr (has_runtime_max_array_size<std::decay_t<decltype(ctx)>>) {
                  if (ctx.max_array_size > 0 && i >= ctx.max_array_size) [[unlikely]] {
                     ctx.error = error_code::invalid_length;
                     return;
                  }
               }

               if (exceeds_capacity(value, i + 1, ctx)) [[unlikely]] {
                  return;
               }

               if constexpr (emplace_backable<T>) {
                  parse<CBOR>::op<Opts>(value.emplace_back(), ctx, it, end);
               }
               else if constexpr (set_like) {
                  V element{};
                  parse<CBOR>::op<Opts>(element, ctx, it, end);
                  if (bool(ctx.error)) [[unlikely]]
                     return;
                  value.emplace(std::move(element));
               }
               else if constexpr (resizable<T>) {
                  // Resizable but append-only through resize: grow by one and re-derive the write
                  // position, since growing may invalidate any iterator held across it.
                  value.resize(i + 1);
                  auto slot = value.begin();
                  std::advance(slot, i);
                  parse<CBOR>::op<Opts>(*slot, ctx, it, end);
               }
               else {
                  if (i >= value.size()) [[unlikely]] {
                     ctx.error = error_code::exceeded_static_array_size;
                     return;
                  }
                  parse<CBOR>::op<Opts>(*dest, ctx, it, end);
                  ++dest;
               }
               ++i;

               if (bool(ctx.error)) [[unlikely]]
                  return;
            }
         }
         else {
            // Definite-length array
            uint64_t count = cbor_detail::decode_arg(ctx, it, end, additional_info);
            if (bool(ctx.error)) [[unlikely]]
               return;

            // Validate count against remaining buffer size (minimum 1 byte per element)
            if (count > static_cast<uint64_t>(end - it)) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return;
            }

            // Check user-configured array size limit
            if constexpr (check_max_array_size(Opts) > 0) {
               if (count > check_max_array_size(Opts)) [[unlikely]] {
                  ctx.error = error_code::invalid_length;
                  return;
               }
            }
            if constexpr (has_runtime_max_array_size<std::decay_t<decltype(ctx)>>) {
               if (ctx.max_array_size > 0 && count > ctx.max_array_size) [[unlikely]] {
                  ctx.error = error_code::invalid_length;
                  return;
               }
            }

            if constexpr (set_like) {
               value.clear();
               for (size_t i = 0; i < count; ++i) {
                  V element{};
                  parse<CBOR>::op<Opts>(element, ctx, it, end);
                  if (bool(ctx.error)) [[unlikely]]
                     return;
                  value.emplace(std::move(element));
               }
            }
            else if constexpr (emplace_backable<T> && !resizable<T>) {
               // Append-only: there is no way to size the target up front even though the count is known.
               if (exceeds_capacity(value, static_cast<size_t>(count), ctx)) [[unlikely]] {
                  return;
               }
               value.clear();
               for (size_t i = 0; i < count; ++i) {
                  parse<CBOR>::op<Opts>(value.emplace_back(), ctx, it, end);
                  if (bool(ctx.error)) [[unlikely]]
                     return;
               }
            }
            else {
               if constexpr (resizable<T>) {
                  if (exceeds_capacity(value, static_cast<size_t>(count), ctx)) [[unlikely]] {
                     return;
                  }
                  value.resize(static_cast<size_t>(count));

                  if constexpr (check_shrink_to_fit(Opts) && has_shrink_to_fit<T>) {
                     value.shrink_to_fit();
                  }
               }
               else {
                  if (count > value.size()) [[unlikely]] {
                     ctx.error = error_code::exceeded_static_array_size;
                     return;
                  }
               }

               // Iteration rather than subscripting: std::list resizes but does not index.
               auto dest = value.begin();
               for (size_t i = 0; i < count; ++i, ++dest) {
                  parse<CBOR>::op<Opts>(*dest, ctx, it, end);
                  if (bool(ctx.error)) [[unlikely]]
                     return;
               }
            }
         }
      }
   };

   // Maps (std::map, std::unordered_map, etc.)
   template <readable_map_t T>
   struct from<CBOR, T> final
   {
      template <auto Opts>
      static void op(auto& value, is_context auto& ctx, auto& it, auto end)
      {
         using namespace cbor;
         using Key = typename T::key_type;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);
         ++it;

         const uint8_t major_type = get_major_type(initial);
         const uint8_t additional_info = get_additional_info(initial);

         if (major_type != major::map) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         depth_guard guard{ctx};
         if (!guard) [[unlikely]] {
            return;
         }

         value.clear();

         if (additional_info == info::indefinite) {
            // Indefinite-length map
            while (true) {
               if (it >= end) [[unlikely]] {
                  ctx.error = error_code::unexpected_end;
                  return;
               }

               uint8_t peek;
               std::memcpy(&peek, it, 1);

               if (peek == initial_byte(major::simple, simple::break_code)) {
                  ++it;
                  break;
               }

               Key key{};
               parse<CBOR>::op<Opts>(key, ctx, it, end);
               if (bool(ctx.error)) [[unlikely]]
                  return;

               parse<CBOR>::op<Opts>(value[std::move(key)], ctx, it, end);
               if (bool(ctx.error)) [[unlikely]]
                  return;
            }
         }
         else {
            uint64_t count = cbor_detail::decode_arg(ctx, it, end, additional_info);
            if (bool(ctx.error)) [[unlikely]]
               return;

            // Validate count against remaining buffer size (minimum 2 bytes per key-value pair).
            // Tested as a division so count * 2 cannot overflow uint64_t for an attacker-supplied
            // count (decode_arg returns an unclamped 64-bit value); this is equivalent to
            // count * 2 > end - it.
            if (count > static_cast<uint64_t>(end - it) / 2) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return;
            }

            // Check user-configured map size limit
            if constexpr (check_max_map_size(Opts) > 0) {
               if (count > check_max_map_size(Opts)) [[unlikely]] {
                  ctx.error = error_code::invalid_length;
                  return;
               }
            }
            if constexpr (has_runtime_max_map_size<std::decay_t<decltype(ctx)>>) {
               if (ctx.max_map_size > 0 && count > ctx.max_map_size) [[unlikely]] {
                  ctx.error = error_code::invalid_length;
                  return;
               }
            }

            for (size_t i = 0; i < count; ++i) {
               Key key{};
               parse<CBOR>::op<Opts>(key, ctx, it, end);
               if (bool(ctx.error)) [[unlikely]]
                  return;

               parse<CBOR>::op<Opts>(value[std::move(key)], ctx, it, end);
               if (bool(ctx.error)) [[unlikely]]
                  return;
            }
         }
      }
   };

   // Pairs
   template <pair_t T>
   struct from<CBOR, T> final
   {
      template <auto Opts>
      GLZ_ALWAYS_INLINE static void op(T& value, is_context auto& ctx, auto& it, auto end)
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);
         ++it;

         const uint8_t major_type = get_major_type(initial);
         const uint8_t additional_info = get_additional_info(initial);

         if (major_type != major::map) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         depth_guard guard{ctx};
         if (!guard) [[unlikely]] {
            return;
         }

         uint64_t count = cbor_detail::decode_arg(ctx, it, end, additional_info);
         if (bool(ctx.error)) [[unlikely]]
            return;

         if (count != 1) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         parse<CBOR>::op<Opts>(value.first, ctx, it, end);
         if (bool(ctx.error)) [[unlikely]]
            return;
         parse<CBOR>::op<Opts>(value.second, ctx, it, end);
      }
   };

#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4702) // unreachable code from if constexpr
#endif
   // Glaze objects (structs with reflection)
   template <class T>
      requires((glaze_object_t<T> || reflectable<T>) && !custom_read<T>)
   struct from<CBOR, T> final
   {
      template <auto Opts>
      static void op(auto& value, is_context auto& ctx, auto& it, auto end)
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);
         ++it;

         const uint8_t major_type = get_major_type(initial);
         const uint8_t additional_info = get_additional_info(initial);

         if (major_type != major::map) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         depth_guard guard{ctx};
         if (!guard) [[unlikely]] {
            return;
         }

         static constexpr auto N = reflect<T>::size;
         if constexpr (N == 0) {
            (void)value;
         }

         uint64_t n_keys;
         if (additional_info == info::indefinite) {
            // Handle indefinite map by counting as we go
            n_keys = (std::numeric_limits<uint64_t>::max)();
         }
         else {
            n_keys = cbor_detail::decode_arg(ctx, it, end, additional_info);
            if (bool(ctx.error)) [[unlikely]]
               return;
         }

         for (uint64_t key_idx = 0; key_idx < n_keys; ++key_idx) {
            if (it >= end) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return;
            }

            // Check for break in indefinite map
            if (additional_info == info::indefinite) {
               uint8_t peek;
               std::memcpy(&peek, it, 1);
               if (peek == initial_byte(major::simple, simple::break_code)) {
                  ++it;
                  break;
               }
            }

            // Read key
            uint8_t key_initial;
            std::memcpy(&key_initial, it, 1);
            ++it;

            const uint8_t key_major = get_major_type(key_initial);
            const uint8_t key_info = get_additional_info(key_initial);

            if (key_major != major::tstr) [[unlikely]] {
               ctx.error = error_code::syntax_error;
               return;
            }

            uint64_t key_len = cbor_detail::decode_arg(ctx, it, end, key_info);
            if (bool(ctx.error)) [[unlikely]]
               return;

            if (static_cast<uint64_t>(end - it) < key_len) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return;
            }

            if constexpr (N > 0) {
               static constexpr auto HashInfo = hash_info<T>;

               // decode_hash_with_size pre-screens the key length against [min_length, max_length],
               // so no call-site length filter is needed here (the buffer bound above still applies).
               const auto index = decode_hash_with_size<CBOR, T, HashInfo, HashInfo.type>::op(it, end, key_len);

               if (index < N) [[likely]] {
                  const sv key{reinterpret_cast<const char*>(it), static_cast<size_t>(key_len)};
                  it += key_len;

                  visit<N>(
                     [&]<size_t I>() {
                        static constexpr auto TargetKey = get<I>(reflect<T>::keys);
                        static constexpr auto Length = TargetKey.size();
                        if ((Length == key_len) && compare<Length>(TargetKey.data(), key.data())) [[likely]] {
                           if constexpr (reflectable<T>) {
                              parse<CBOR>::op<Opts>(get_member(value, get<I>(to_tie(value))), ctx, it, end);
                           }
                           else {
                              parse<CBOR>::op<Opts>(get_member(value, get<I>(reflect<T>::values)), ctx, it, end);
                           }
                        }
                        else {
                           if constexpr (Opts.error_on_unknown_keys) {
                              ctx.error = error_code::unknown_key;
                              return;
                           }
                           else {
                              skip_value<CBOR>::op<Opts>(ctx, it, end);
                              if (bool(ctx.error)) [[unlikely]]
                                 return;
                           }
                        }
                     },
                     index);

                  if (bool(ctx.error)) [[unlikely]]
                     return;
               }
               else [[unlikely]] {
                  if constexpr (Opts.error_on_unknown_keys) {
                     ctx.error = error_code::unknown_key;
                     return;
                  }
                  else {
                     it += key_len;
                     skip_value<CBOR>::op<Opts>(ctx, it, end);
                     if (bool(ctx.error)) [[unlikely]]
                        return;
                  }
               }
            }
            else if constexpr (Opts.error_on_unknown_keys) {
               ctx.error = error_code::unknown_key;
               return;
            }
            else {
               it += key_len;
               skip_value<CBOR>::op<Opts>(ctx, it, end);
               if (bool(ctx.error)) [[unlikely]]
                  return;
            }
         }
      }
   };
#if defined(_MSC_VER)
#pragma warning(pop)
#endif

   // Tuples
   template <class T>
      requires(tuple_t<T> || is_std_tuple<T>)
   struct from<CBOR, T> final
   {
      template <auto Opts>
      static void op(auto& value, is_context auto& ctx, auto& it, auto end)
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);
         ++it;

         const uint8_t major_type = get_major_type(initial);
         const uint8_t additional_info = get_additional_info(initial);

         if (major_type != major::array) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         depth_guard guard{ctx};
         if (!guard) [[unlikely]] {
            return;
         }

         using V = std::decay_t<T>;
         static constexpr auto N = glz::tuple_size_v<V>;

         uint64_t count = cbor_detail::decode_arg(ctx, it, end, additional_info);
         if (bool(ctx.error)) [[unlikely]]
            return;

         if (count != N) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         if constexpr (is_std_tuple<T>) {
            for_each<N>([&]<size_t I>() { parse<CBOR>::op<Opts>(std::get<I>(value), ctx, it, end); });
         }
         else {
            for_each<N>([&]<size_t I>() { parse<CBOR>::op<Opts>(glz::get<I>(value), ctx, it, end); });
         }
      }
   };

   // Glaze arrays
   template <class T>
      requires glaze_array_t<T>
   struct from<CBOR, T> final
   {
      template <auto Opts>
      static void op(auto& value, is_context auto& ctx, auto& it, auto end)
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);
         ++it;

         if (initial != (major::array << 5 | reflect<T>::size)) [[unlikely]] {
            // Allow longer forms too
            const uint8_t major_type = get_major_type(initial);
            const uint8_t additional_info = get_additional_info(initial);

            if (major_type != major::array) [[unlikely]] {
               ctx.error = error_code::syntax_error;
               return;
            }

            uint64_t count = cbor_detail::decode_arg(ctx, it, end, additional_info);
            if (bool(ctx.error)) [[unlikely]]
               return;

            if (count != reflect<T>::size) [[unlikely]] {
               ctx.error = error_code::syntax_error;
               return;
            }
         }

         depth_guard guard{ctx};
         if (!guard) [[unlikely]] {
            return;
         }

         for_each<reflect<T>::size>(
            [&]<size_t I>() { parse<CBOR>::op<Opts>(get_member(value, get<I>(reflect<T>::values)), ctx, it, end); });
      }
   };

   // Expected types
   template <is_expected T>
   struct from<CBOR, T> final
   {
      template <auto Opts>
      static void op(auto&& value, is_context auto&& ctx, auto&& it, auto end)
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         auto parse_val = [&] {
            if constexpr (not std::is_void_v<typename std::decay_t<T>::value_type>) {
               if (value) {
                  parse<CBOR>::op<Opts>(*value, ctx, it, end);
               }
               else {
                  value.emplace();
                  parse<CBOR>::op<Opts>(*value, ctx, it, end);
               }
            }
            else {
               value.emplace();
            }
         };

         uint8_t peek;
         std::memcpy(&peek, it, 1);
         const uint8_t major_type = get_major_type(peek);

         if (major_type == major::map) {
            auto start = it;
            ++it;

            const uint8_t additional_info = get_additional_info(peek);
            const uint64_t n_pairs = cbor_detail::decode_arg(ctx, it, end, additional_info);
            if (bool(ctx.error)) [[unlikely]] {
               return;
            }

            if (n_pairs == 0) {
               // empty map
               if constexpr (std::is_void_v<typename std::decay_t<T>::value_type>) {
                  value.emplace();
               }
               else {
                  // rewind and parse as value (the value type might be an empty map)
                  it = start;
                  parse_val();
               }
            }
            else if (n_pairs == 1) {
               // could be unexpected wrapper or a single-field object value
               // peek at the key
               if (it >= end) [[unlikely]] {
                  ctx.error = error_code::unexpected_end;
                  return;
               }

               uint8_t key_initial;
               std::memcpy(&key_initial, it, 1);
               const uint8_t key_major = get_major_type(key_initial);

               if (key_major == major::tstr) {
                  ++it;

                  const uint8_t key_info = get_additional_info(key_initial);
                  const uint64_t key_len = cbor_detail::decode_arg(ctx, it, end, key_info);
                  if (bool(ctx.error)) [[unlikely]] {
                     return;
                  }

                  static constexpr sv unexpected_key = "unexpected";
                  if (key_len == unexpected_key.size() && uint64_t(end - it) >= key_len) {
                     if (std::memcmp(it, unexpected_key.data(), key_len) == 0) {
                        // this is an unexpected wrapper
                        it += key_len;

                        using error_type = typename std::decay_t<T>::error_type;
                        if (!value) {
                           parse<CBOR>::op<Opts>(value.error(), ctx, it, end);
                        }
                        else {
                           std::decay_t<error_type> error{};
                           parse<CBOR>::op<Opts>(error, ctx, it, end);
                           if (bool(ctx.error)) [[unlikely]] {
                              return;
                           }
                           value = glz::unexpected(std::move(error));
                        }
                        return;
                     }
                  }
                  // not an unexpected wrapper, rewind and parse as value
                  it = start;
                  parse_val();
               }
               else {
                  // key is not a string, rewind and parse as value
                  it = start;
                  parse_val();
               }
            }
            else {
               // multiple pairs, must be a value map
               it = start;
               parse_val();
            }
         }
         else {
            // not a map, parse as value directly
            parse_val();
         }
      }
   };

   // Nullable types
   template <nullable_like T>
   struct from<CBOR, T> final
   {
      template <auto Opts>
      GLZ_ALWAYS_INLINE static void op(auto& value, is_context auto& ctx, auto& it, auto end)
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t peek;
         std::memcpy(&peek, it, 1);

         if (peek == initial_byte(major::simple, simple::null_value)) {
            ++it;
            if constexpr (is_specialization_v<T, std::optional>) {
               value = std::nullopt;
            }
            else if constexpr (is_specialization_v<T, std::unique_ptr>) {
               value = nullptr;
            }
            else if constexpr (is_specialization_v<T, std::shared_ptr>) {
               value = nullptr;
            }
         }
         else {
            if (!value) {
               if constexpr (is_specialization_v<T, std::optional>) {
                  value = std::make_optional<typename T::value_type>();
               }
               else if constexpr (is_specialization_v<T, std::unique_ptr>) {
                  value = std::make_unique<typename T::element_type>();
               }
               else if constexpr (is_specialization_v<T, std::shared_ptr>) {
                  value = std::make_shared<typename T::element_type>();
               }
               else if constexpr (constructible<T>) {
                  value = meta_construct_v<T>();
               }
               else if constexpr (std::is_pointer_v<T> && can_allocate_raw_pointer<Opts, std::decay_t<decltype(ctx)>>) {
                  if (!try_allocate_raw_pointer<Opts>(value, ctx)) {
                     return;
                  }
               }
               else {
                  ctx.error = error_code::invalid_nullable_read;
                  return;
               }
            }
            parse<CBOR>::op<Opts>(*value, ctx, it, end);
         }
      }
   };

   // C-style arrays
   template <nullable_t T>
      requires(std::is_array_v<T>)
   struct from<CBOR, T> final
   {
      template <auto Opts, class V, size_t N>
      GLZ_ALWAYS_INLINE static void op(V (&value)[N], is_context auto& ctx, auto& it, auto end) noexcept
      {
         parse<CBOR>::op<Opts>(std::span{value, N}, ctx, it, end);
      }
   };

   // Variants
   template <is_variant T>
      requires(not custom_read<T>)
   struct from<CBOR, T>
   {
      template <auto Opts>
      GLZ_ALWAYS_INLINE static void op(auto& value, is_context auto& ctx, auto& it, auto end) noexcept
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);
         ++it;

         const uint8_t major_type = get_major_type(initial);
         const uint8_t additional_info = get_additional_info(initial);

         // Expect array of [index, value]
         if (major_type != major::array) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         // This reader consumes the [index, value] array itself, so the level is its to count.
         depth_guard guard{ctx};
         if (!guard) [[unlikely]] {
            return;
         }

         uint64_t count = cbor_detail::decode_arg(ctx, it, end, additional_info);
         if (bool(ctx.error)) [[unlikely]]
            return;

         if (count != 2) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         // Read index
         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t idx_initial;
         std::memcpy(&idx_initial, it, 1);
         ++it;

         const uint8_t idx_major = get_major_type(idx_initial);
         const uint8_t idx_info = get_additional_info(idx_initial);

         if (idx_major != major::uint) [[unlikely]] {
            ctx.error = error_code::syntax_error;
            return;
         }

         uint64_t type_index = cbor_detail::decode_arg(ctx, it, end, idx_info);
         if (bool(ctx.error)) [[unlikely]]
            return;

         if (type_index >= std::variant_size_v<T>) [[unlikely]] {
            ctx.error = error_code::no_matching_variant_type;
            return;
         }

         if (value.index() != type_index) {
            emplace_runtime_variant(value, type_index);
         }

         std::visit([&](auto& v) { parse<CBOR>::op<Opts>(v, ctx, it, end); }, value);
      }
   };

   // Glaze value wrapper
   template <class T>
      requires(glaze_value_t<T> && !custom_read<T>)
   struct from<CBOR, T>
   {
      template <auto Opts, class Value, is_context Ctx, class It0, class It1>
      GLZ_ALWAYS_INLINE static void op(Value&& value, Ctx&& ctx, It0&& it, It1 end)
      {
         using V = std::decay_t<decltype(get_member(std::declval<Value>(), meta_wrapper_v<T>))>;
         from<CBOR, V>::template op<Opts>(get_member(std::forward<Value>(value), meta_wrapper_v<T>),
                                          std::forward<Ctx>(ctx), std::forward<It0>(it), end);
      }
   };

   // Enums, reflected or plain: both are read as the ordinal, so one reader covers them
   template <class T>
      requires(std::is_enum_v<T>)
   struct from<CBOR, T>
   {
      template <auto Opts>
      GLZ_ALWAYS_INLINE static void op(auto& value, is_context auto& ctx, auto& it, auto end) noexcept
      {
         // Read the ordinal through the range-checked integer reader so an out-of-range wire
         // value is rejected instead of being silently truncated into the underlying type.
         // bool is a legal fixed underlying type, and the writer emits such an enum as a CBOR
         // integer, so route it through the uint8_t reader rather than the boolean reader.
         using underlying = std::underlying_type_t<std::decay_t<T>>;
         using U = std::conditional_t<std::same_as<underlying, bool>, uint8_t, underlying>;
         U u{};
         from<CBOR, U>::template op<Opts>(u, ctx, it, end);
         if (bool(ctx.error)) [[unlikely]]
            return;
         if constexpr (std::same_as<underlying, bool>) {
            // The uint8_t bound is wider than bool's domain, so anything above 1 has to be
            // rejected here: casting it into a bool-backed enum would be undefined behavior.
            if (u > 1) [[unlikely]] {
               ctx.error = error_code::parse_number_failure;
               return;
            }
         }
         value = static_cast<std::decay_t<T>>(u);
      }
   };

   // Member function pointers (no-op)
   template <is_member_function_pointer T>
   struct from<CBOR, T>
   {
      template <auto Opts>
      GLZ_ALWAYS_INLINE static void op(auto&&, is_context auto&&, auto&&, auto&&) noexcept
      {}
   };

   // Hidden type
   template <>
   struct from<CBOR, hidden>
   {
      template <auto Opts>
      GLZ_ALWAYS_INLINE static void op(auto&&, is_context auto&& ctx, auto&&...) noexcept
      {
         ctx.error = error_code::attempt_read_hidden;
      }
   };

   // Nullable value types
   template <class T>
      requires(nullable_value_t<T> && !nullable_like<T> && !is_expected<T> && !custom_read<T>)
   struct from<CBOR, T> final
   {
      template <auto Opts>
      GLZ_ALWAYS_INLINE static void op(auto& value, is_context auto& ctx, auto& it, auto end)
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t peek;
         std::memcpy(&peek, it, 1);

         if (peek == initial_byte(major::simple, simple::null_value)) {
            ++it;
            if constexpr (requires { value.reset(); }) {
               value.reset();
            }
         }
         else {
            if (!value.has_value()) {
               if constexpr (constructible<T>) {
                  value = meta_construct_v<T>();
               }
               else if constexpr (requires { value.emplace(); }) {
                  value.emplace();
               }
               else {
                  ctx.error = error_code::invalid_nullable_read;
                  return;
               }
            }
            parse<CBOR>::op<Opts>(value.value(), ctx, it, end);
         }
      }
   };

   // Filesystem paths
   template <filesystem_path T>
   struct from<CBOR, T>
   {
      template <auto Opts>
      static void op(auto& value, is_context auto& ctx, auto&... args)
      {
         std::string buffer{};
         parse<CBOR>::op<Opts>(buffer, ctx, args...);
         if (bool(ctx.error)) [[unlikely]] {
            return;
         }
         value = buffer;
      }
   };

   // std::chrono::duration - parsed generically (from the bare rep count) by the
   // from<uint32_t Format, is_duration T> specialization in core/chrono.hpp.

   namespace cbor_detail
   {
      // Decode a CBOR tag-1 payload (RFC 8949 §3.4.2) into a system_clock::time_point.
      // Accepts unsigned/negative integer seconds or float16/32/64 seconds (NaN/Inf rejected).
      // Nested tags (e.g. tag 4 decimal fractions) are forbidden by §3.4.2 and rejected here.
      // Values that would overflow system_clock::duration are rejected rather than wrapping.
      //
      // The `Duration` template parameter names the wire-level precision the caller cares
      // about. For the float path, casting fsec -> Duration -> sys_dur when Duration is
      // coarser than sys_dur keeps the integer count under 2^53 (critical on platforms
      // where sys_dur is nanoseconds and the raw count for a modern epoch exceeds that).
      template <auto Opts, class Duration = std::chrono::system_clock::duration>
      inline void decode_tag1_payload(is_context auto& ctx, auto& it, auto end,
                                      std::chrono::system_clock::time_point& tp) noexcept
      {
         using namespace cbor;
         using namespace std::chrono;
         using sys_dur = system_clock::duration;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);
         const uint8_t mt = get_major_type(initial);
         const uint8_t ai = get_additional_info(initial);

         // duration_cast<sys_dur>(seconds{secs}) overflows if secs exceeds the seconds-
         // range sys_dur can represent. Compute the bounds from sys_dur::max/min.
         constexpr int64_t max_seconds = duration_cast<seconds>((sys_dur::max)()).count();
         constexpr int64_t min_seconds = duration_cast<seconds>((sys_dur::min)()).count();

         if (mt == major::uint || mt == major::nint) {
            int64_t secs{};
            from<CBOR, int64_t>::template op<Opts>(secs, ctx, it, end);
            if (bool(ctx.error)) [[unlikely]]
               return;
            if (secs > max_seconds || secs < min_seconds) [[unlikely]] {
               ctx.error = error_code::parse_error;
               return;
            }
            tp = system_clock::time_point{duration_cast<sys_dur>(seconds{secs})};
         }
         else if (mt == major::simple && (ai == simple::float16 || ai == simple::float32 || ai == simple::float64)) {
            double d{};
            from<CBOR, double>::template op<Opts>(d, ctx, it, end);
            if (bool(ctx.error)) [[unlikely]]
               return;
            if (!std::isfinite(d)) [[unlikely]] {
               ctx.error = error_code::parse_error;
               return;
            }
            // Guard against a finite-but-out-of-range double silently wrapping inside
            // duration_cast (the cast multiplies by sys_dur::period::den and casts to int64).
            if (!(d >= static_cast<double>(min_seconds) && d <= static_cast<double>(max_seconds))) [[unlikely]] {
               ctx.error = error_code::parse_error;
               return;
            }
            using fsec = duration<double>;
            if constexpr (std::ratio_greater_v<typename Duration::period, typename sys_dur::period>) {
               // Duration is coarser than sys_dur: fsec -> Duration -> sys_dur routes the
               // lossy-in-double step through the smaller integer count, then scales up
               // with exact integer math. This avoids precision loss when sys_dur is ns.
               const auto dur = duration_cast<Duration>(fsec{d});
               tp = system_clock::time_point{duration_cast<sys_dur>(dur)};
            }
            else {
               tp = system_clock::time_point{duration_cast<sys_dur>(fsec{d})};
            }
         }
         else [[unlikely]] {
            // Per RFC 8949 §3.4.2, other content types (including nested tags) are invalid.
            ctx.error = error_code::syntax_error;
         }
      }
   }

   // system_clock::time_point - decoder accepts:
   //   - tag 0 + tstr (RFC 8949 §3.4.1 canonical; what glaze writes)
   //   - tag 1 + int/float seconds (RFC 8949 §3.4.2; converted from epoch)
   //   - bare tstr (no tag) - lenient for producers that omit tag 0
   // Bare numbers and any other shape are rejected: the unit would be ambiguous.
   // Note: this does NOT cross-read with epoch_time<Duration>, which writes tag 1
   // directly; decode into the type that matches the wire form you expect.
   template <is_system_time_point T>
   struct from<CBOR, T>
   {
      template <auto Opts>
      static void op(auto& value, is_context auto& ctx, auto& it, auto end) noexcept
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);

         if (get_major_type(initial) == major::tag) {
            ++it;
            const uint64_t tag = cbor_detail::decode_arg(ctx, it, end, get_additional_info(initial));
            if (bool(ctx.error)) [[unlikely]]
               return;
            if (it >= end) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return;
            }

            if (tag == semantic_tag::datetime_string) {
               // RFC 8949 §3.4.1: tag 0 content MUST be a text string.
               uint8_t content;
               std::memcpy(&content, it, 1);
               if (get_major_type(content) != major::tstr) [[unlikely]] {
                  ctx.error = error_code::syntax_error;
                  return;
               }
               std::string_view str;
               from<CBOR, std::string_view>::template op<Opts>(str, ctx, it, end);
               if (bool(ctx.error)) [[unlikely]]
                  return;
               chrono_detail::parse_iso8601(str, value, ctx.error);
            }
            else if (tag == semantic_tag::datetime_epoch) {
               using Duration = typename std::remove_cvref_t<T>::duration;
               std::chrono::system_clock::time_point tp{};
               cbor_detail::decode_tag1_payload<Opts, Duration>(ctx, it, end, tp);
               if (bool(ctx.error)) [[unlikely]]
                  return;
               value = std::chrono::time_point_cast<Duration>(tp);
            }
            else [[unlikely]] {
               ctx.error = error_code::syntax_error;
            }
         }
         else if (get_major_type(initial) == major::tstr) {
            // Bare RFC 3339 string (no tag). Lenient for interop with producers that omit tag 0.
            std::string_view str;
            from<CBOR, std::string_view>::template op<Opts>(str, ctx, it, end);
            if (bool(ctx.error)) [[unlikely]]
               return;
            chrono_detail::parse_iso8601(str, value, ctx.error);
         }
         else [[unlikely]] {
            // A bare number has no defined unit for a time_point; require a tag.
            ctx.error = error_code::syntax_error;
         }
      }
   };

   // steady_clock / high_resolution_clock time_points: parsed generically (from the bare
   // count) by the from<uint32_t Format, is_count_time_point T> specialization in core/chrono.hpp.

   // epoch_time wrapper - decoder accepts:
   //   - tag 1 + integer seconds (RFC 8949 §3.4.2; what glaze writes for Period >= 1s)
   //   - tag 1 + float16/32/64 seconds (§3.4.2; what glaze writes for sub-second Periods)
   //   - bare integer or float (no tag) - lenient for producers that omit tag 1; same semantics
   // Nested tags (e.g. tag 4 decimal fraction) are rejected per §3.4.2.
   // Note: this does NOT cross-read with is_system_time_point, which writes tag 0 strings.
   template <class Duration>
   struct from<CBOR, epoch_time<Duration>>
   {
      template <auto Opts>
      static void op(auto& wrapper, is_context auto& ctx, auto& it, auto end) noexcept
      {
         using namespace cbor;

         if (it >= end) [[unlikely]] {
            ctx.error = error_code::unexpected_end;
            return;
         }

         uint8_t initial;
         std::memcpy(&initial, it, 1);

         if (get_major_type(initial) == major::tag) {
            ++it;
            const uint64_t tag = cbor_detail::decode_arg(ctx, it, end, get_additional_info(initial));
            if (bool(ctx.error)) [[unlikely]]
               return;
            if (tag != semantic_tag::datetime_epoch) [[unlikely]] {
               ctx.error = error_code::syntax_error;
               return;
            }
         }

         std::chrono::system_clock::time_point tp{};
         cbor_detail::decode_tag1_payload<Opts, Duration>(ctx, it, end, tp);
         if (bool(ctx.error)) [[unlikely]]
            return;
         wrapper.value = tp;
      }
   };

   // ===== High-level read APIs =====

   template <read_supported<CBOR> T, class Buffer>
   [[nodiscard]] inline error_ctx read_cbor(T&& value, Buffer&& buffer)
   {
      return read<opts{.format = CBOR}>(value, std::forward<Buffer>(buffer));
   }

   template <read_supported<CBOR> T, class Buffer>
   [[nodiscard]] inline expected<T, error_ctx> read_cbor(Buffer&& buffer)
   {
      T value{};
      const auto pe = read<opts{.format = CBOR}>(value, std::forward<Buffer>(buffer));
      if (pe) [[unlikely]] {
         return unexpected(pe);
      }
      return value;
   }

   template <auto Opts = opts{}, read_supported<CBOR> T>
   [[nodiscard]] inline error_ctx read_file_cbor(T& value, const sv file_name, auto&& buffer)
   {
      context ctx{};
      ctx.current_file = file_name;

      const auto file_error = file_to_buffer(buffer, ctx.current_file);

      if (bool(file_error)) [[unlikely]] {
         return error_ctx{file_error};
      }

      return read<set_cbor<Opts>()>(value, buffer, ctx);
   }
}
