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

#pragma once

#include "glaze/core/streaming_state.hpp"
#include "glaze/util/parse.hpp"

namespace glz
{
   template <>
   struct skip_value<JSON>
   {
      template <auto Opts>
         requires(not Opts.comments)
      GLZ_ALWAYS_INLINE static void op(is_context auto&& ctx, auto&& it, auto end) noexcept;

      template <auto Opts>
         requires(bool(Opts.comments))
      GLZ_ALWAYS_INLINE static void op(is_context auto&& ctx, auto&& it, auto end) noexcept;
   };

   template <auto Opts>
   void skip_object(is_context auto&& ctx, auto&& it, auto end) noexcept
   {
      if constexpr (!check_validate_skipped(Opts)) {
         ++it;
         if constexpr (not Opts.null_terminated) {
            if (it == end) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return;
            }
         }
         skip_until_closed<Opts, '{', '}'>(ctx, it, end);
      }
      else {
         // one nesting level (see enter_depth): counted in both modes so the limit binds,
         // released at each syntactic close below
         if (enter_depth(ctx)) [[unlikely]] {
            return;
         }
         ++it;
         if constexpr (not Opts.null_terminated) {
            if (it == end) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return;
            }
         }
         if (skip_ws<Opts>(ctx, it, end)) {
            return;
         }
         if (*it == '}') {
            --ctx.depth;
            ++it;
            if constexpr (not Opts.null_terminated) {
               if (it == end) {
                  ctx.error = error_code::end_reached;
                  return;
               }
            }
            return;
         }
         while (true) {
            if (*it != '"') {
               ctx.error = error_code::syntax_error;
               return;
            }
            skip_string<Opts>(ctx, it, end);
            if (bool(ctx.error)) [[unlikely]]
               return;
            if (skip_ws<Opts>(ctx, it, end)) {
               return;
            }
            if (match_invalid_end<':', Opts>(ctx, it, end)) {
               return;
            }
            if (skip_ws<Opts>(ctx, it, end)) {
               return;
            }
            skip_value<JSON>::op<Opts>(ctx, it, end);
            if (bool(ctx.error)) [[unlikely]]
               return;
            if (skip_ws<Opts>(ctx, it, end)) {
               return;
            }
            if (*it != ',') break;
            ++it;
            if constexpr (not Opts.null_terminated) {
               if (it == end) [[unlikely]] {
                  ctx.error = error_code::unexpected_end;
                  return;
               }
            }
            if (skip_ws<Opts>(ctx, it, end)) {
               return;
            }
         }
         match<'}'>(ctx, it);
         --ctx.depth;
         if constexpr (not Opts.null_terminated) {
            if (it == end) {
               ctx.error = error_code::end_reached;
               return;
            }
         }
      }
   }

   template <auto Opts>
      requires(Opts.format == JSON || Opts.format == NDJSON)
   void skip_array(is_context auto&& ctx, auto&& it, auto end) noexcept
   {
      if constexpr (!check_validate_skipped(Opts)) {
         ++it;
         if constexpr (not Opts.null_terminated) {
            if (it == end) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return;
            }
         }
         skip_until_closed<Opts, '[', ']'>(ctx, it, end);
      }
      else {
         // one nesting level (see enter_depth): counted in both modes so the limit binds,
         // released at each syntactic close below
         if (enter_depth(ctx)) [[unlikely]] {
            return;
         }
         ++it;
         if constexpr (not Opts.null_terminated) {
            if (it == end) [[unlikely]] {
               ctx.error = error_code::unexpected_end;
               return;
            }
         }
         if (skip_ws<Opts>(ctx, it, end)) {
            return;
         }
         if (*it == ']') {
            --ctx.depth;
            ++it;
            if constexpr (not Opts.null_terminated) {
               if (it == end) {
                  ctx.error = error_code::end_reached;
                  return;
               }
            }
            return;
         }
         while (true) {
            skip_value<JSON>::op<Opts>(ctx, it, end);
            if (bool(ctx.error)) [[unlikely]]
               return;
            if (skip_ws<Opts>(ctx, it, end)) {
               return;
            }
            if (*it != ',') break;
            ++it;
            if constexpr (not Opts.null_terminated) {
               if (it == end) [[unlikely]] {
                  ctx.error = error_code::unexpected_end;
                  return;
               }
            }
            if (skip_ws<Opts>(ctx, it, end)) {
               return;
            }
         }
         match<']'>(ctx, it);
         --ctx.depth;
         if constexpr (not Opts.null_terminated) {
            if (it == end) {
               ctx.error = error_code::end_reached;
               return;
            }
         }
      }
   }

   // Streaming-aware value skip. The fixed-position skip routines error with
   // unexpected_end when a value extends past the current buffer, so when a
   // streaming context is active we instead scan character by character,
   // refilling the input buffer whenever it runs dry. String, escape, and
   // nesting state is tracked across refills, so skipped values may be
   // arbitrarily larger than the buffer (e.g. unknown keys holding large arrays).
   // Skipped bytes are consumed and not validated, matching the non-validating
   // skip path. After this returns, the caller's `end` may be stale; callers
   // resynchronize iterators at their streaming refill checkpoints.
   template <auto Opts>
      requires(not Opts.comments)
   void skip_value_streaming(is_context auto&& ctx, auto&& it, auto end) noexcept
   {
      const auto refill = [&]() -> bool {
         const size_t consumed = static_cast<size_t>(it - ctx.stream.data());
         const char* new_it;
         const char* new_end;
         ctx.stream.consume_and_refill(consumed, new_it, new_end);
         it = new_it;
         end = new_end;
         return it < end;
      };

      // Skip leading whitespace, refilling as needed
      while (true) {
         while (it < end && whitespace_table[uint8_t(*it)]) {
            ++it;
         }
         if (it < end) break;
         if (!refill()) {
            ctx.error = error_code::unexpected_end;
            return;
         }
      }

      const char open = *it;
      if (open == '{' || open == '[') {
         ++it;
         size_t depth = 1;
         bool in_string = false;
         bool escaped = false;
         while (depth) {
            if (it == end) {
               if (!refill()) {
                  ctx.error = error_code::unexpected_end;
                  return;
               }
               continue;
            }
            const char c = *it;
            ++it;
            if (in_string) {
               if (escaped) {
                  escaped = false;
               }
               else if (c == '\\') {
                  escaped = true;
               }
               else if (c == '"') {
                  in_string = false;
               }
            }
            else {
               switch (c) {
               case '"':
                  in_string = true;
                  break;
               case '{':
               case '[':
                  ++depth;
                  break;
               case '}':
               case ']':
                  --depth;
                  break;
               }
            }
         }
      }
      else if (open == '"') {
         ++it;
         bool escaped = false;
         while (true) {
            if (it == end) {
               if (!refill()) {
                  ctx.error = error_code::unexpected_end;
                  return;
               }
               continue;
            }
            const char c = *it;
            ++it;
            if (escaped) {
               escaped = false;
            }
            else if (c == '\\') {
               escaped = true;
            }
            else if (c == '"') {
               break;
            }
         }
      }
      else {
         // Numbers and literals (true/false/null): scan until a delimiter.
         // End of stream also terminates the value.
         while (true) {
            if (it == end) {
               if (!refill()) {
                  break;
               }
               continue;
            }
            const char c = *it;
            if (whitespace_table[uint8_t(c)] || c == ',' || c == '}' || c == ']') {
               break;
            }
            ++it;
         }
      }
   }

   // parse_value is used for JSON pointer reading
   // we want the JSON pointer access to not care about trailing whitespace
   // so we use validate_skipped for precise validation and value skipping
   // expects opening whitespace to be handled
   template <auto Opts>
   GLZ_ALWAYS_INLINE auto parse_value(is_context auto&& ctx, auto&& it, auto end) noexcept
   {
      auto start = it;
      struct opts_validate_skipped : std::decay_t<decltype(Opts)>
      {
         bool validate_skipped = true;
      };
      skip_value<JSON>::op<opts_validate_skipped{{Opts}}>(ctx, it, end);
      return std::span{start, size_t(it - start)};
   }

   template <auto Opts>
      requires(not Opts.comments)
   GLZ_ALWAYS_INLINE void skip_value<JSON>::op(is_context auto&& ctx, auto&& it, auto end) noexcept
   {
      using namespace glz::detail;

      // Validating skips (parse_value/JSON pointer) require the full value in a
      // contiguous buffer, so streaming refill is not applied there.
      if constexpr (has_streaming_state<decltype(ctx)> && not check_validate_skipped(Opts)) {
         if (ctx.stream.enabled()) {
            skip_value_streaming<Opts>(ctx, it, end);
            return;
         }
      }

      if constexpr (not check_validate_skipped(Opts)) {
         if constexpr (not check_ws_handled(Opts)) {
            if (skip_ws<Opts>(ctx, it, end)) {
               return;
            }
         }
         while (true) {
            switch (*it) {
            case '{':
               ++it;
               if constexpr (not Opts.null_terminated) {
                  if (it == end) [[unlikely]] {
                     ctx.error = error_code::unexpected_end;
                     return;
                  }
               }
               skip_until_closed<Opts, '{', '}'>(ctx, it, end);
               if (bool(ctx.error)) [[unlikely]]
                  return;
               break;
            case '[':
               ++it;
               if constexpr (not Opts.null_terminated) {
                  if (it == end) [[unlikely]] {
                     ctx.error = error_code::unexpected_end;
                     return;
                  }
               }
               skip_until_closed<Opts, '[', ']'>(ctx, it, end);
               if (bool(ctx.error)) [[unlikely]]
                  return;
               break;
            case '"':
               skip_string<Opts>(ctx, it, end);
               if (bool(ctx.error)) [[unlikely]]
                  return;
               break;
            case ',':
            case '}':
            case ']':
               break;
            case '\0':
               ctx.error = error_code::unexpected_end;
               return;
            default: {
               ++it;
               if constexpr (not Opts.null_terminated) {
                  if (it == end) [[unlikely]] {
                     ctx.error = error_code::unexpected_end;
                     return;
                  }
               }
               continue;
            }
            }

            break;
         }
      }
      else {
         if constexpr (not check_ws_handled(Opts)) {
            if (skip_ws<Opts>(ctx, it, end)) {
               return;
            }
         }
         switch (*it) {
         case '{': {
            skip_object<Opts>(ctx, it, end);
            break;
         }
         case '[': {
            skip_array<Opts>(ctx, it, end);
            break;
         }
         case '"': {
            skip_string<Opts>(ctx, it, end);
            break;
         }
         case 'n': {
            ++it;
            match<"ull", Opts>(ctx, it, end);
            break;
         }
         case 'f': {
            ++it;
            match<"alse", Opts>(ctx, it, end);
            break;
         }
         case 't': {
            ++it;
            match<"rue", Opts>(ctx, it, end);
            break;
         }
         case '\0': {
            ctx.error = error_code::unexpected_end;
            break;
         }
         default: {
            skip_number<Opts>(ctx, it, end);
         }
         }
      }
   }

   template <auto Opts>
      requires(bool(Opts.comments))
   GLZ_ALWAYS_INLINE void skip_value<JSON>::op(is_context auto&& ctx, auto&& it, auto end) noexcept
   {
      using namespace glz::detail;

      if constexpr (not check_ws_handled(Opts)) {
         if (skip_ws<Opts>(ctx, it, end)) {
            return;
         }
      }
      switch (*it) {
      case '{': {
         skip_object<Opts>(ctx, it, end);
         break;
      }
      case '[': {
         skip_array<Opts>(ctx, it, end);
         break;
      }
      case '"': {
         skip_string<Opts>(ctx, it, end);
         break;
      }
      case '/': {
         skip_comment(ctx, it, end);
         if (bool(ctx.error)) [[unlikely]]
            return;
         break;
      }
      case 'n': {
         ++it;
         match<"ull", Opts>(ctx, it, end);
         break;
      }
      case 'f': {
         ++it;
         match<"alse", Opts>(ctx, it, end);
         break;
      }
      case 't': {
         ++it;
         match<"rue", Opts>(ctx, it, end);
         break;
      }
      case '\0': {
         ctx.error = error_code::unexpected_end;
         break;
      }
      default: {
         skip_number<Opts>(ctx, it, end);
      }
      }
   }
}
