#pragma once

#include <charconv>
#include <cstdint>
#include <limits>
#include <optional>
#include <string>
#include <string_view>
#include <system_error>
#include <type_traits>

namespace smartcut::core {

namespace json_detail {

inline void append_replacement_character(std::string& output) {
  output.append("\xEF\xBF\xBD", 3);
}

inline bool valid_continuation(std::string_view value, std::size_t offset, std::size_t count) {
  if (offset + count > value.size()) return false;
  for (std::size_t index = 0; index < count; ++index) {
    if ((static_cast<unsigned char>(value[offset + index]) & 0xC0U) != 0x80U) return false;
  }
  return true;
}

inline std::optional<std::pair<std::size_t, std::uint32_t>> decode_utf8(
    std::string_view value, std::size_t offset) {
  if (offset >= value.size()) return std::nullopt;
  const auto byte = static_cast<unsigned char>(value[offset]);
  std::size_t length = 0;
  std::uint32_t code_point = 0;
  if (byte >= 0xC2U && byte <= 0xDFU) {
    length = 2;
    code_point = byte & 0x1FU;
  } else if (byte >= 0xE0U && byte <= 0xEFU) {
    length = 3;
    code_point = byte & 0x0FU;
  } else if (byte >= 0xF0U && byte <= 0xF4U) {
    length = 4;
    code_point = byte & 0x07U;
  } else {
    return std::nullopt;
  }
  if (!valid_continuation(value, offset + 1, length - 1)) return std::nullopt;
  for (std::size_t continuation = 1; continuation < length; ++continuation) {
    code_point = (code_point << 6U) |
                 (static_cast<unsigned char>(value[offset + continuation]) & 0x3FU);
  }
  const bool overlong = (length == 2 && code_point < 0x80U) ||
                        (length == 3 && code_point < 0x800U) ||
                        (length == 4 && code_point < 0x10000U);
  if (overlong || code_point > 0x10FFFFU ||
      (code_point >= 0xD800U && code_point <= 0xDFFFU)) return std::nullopt;
  return std::pair{length, code_point};
}

inline void append_utf8(std::string& output, std::uint32_t code_point) {
  if (code_point <= 0x7FU) {
    output.push_back(static_cast<char>(code_point));
  } else if (code_point <= 0x7FFU) {
    output.push_back(static_cast<char>(0xC0U | (code_point >> 6U)));
    output.push_back(static_cast<char>(0x80U | (code_point & 0x3FU)));
  } else if (code_point <= 0xFFFFU && !(code_point >= 0xD800U && code_point <= 0xDFFFU)) {
    output.push_back(static_cast<char>(0xE0U | (code_point >> 12U)));
    output.push_back(static_cast<char>(0x80U | ((code_point >> 6U) & 0x3FU)));
    output.push_back(static_cast<char>(0x80U | (code_point & 0x3FU)));
  } else if (code_point <= 0x10FFFFU) {
    output.push_back(static_cast<char>(0xF0U | (code_point >> 18U)));
    output.push_back(static_cast<char>(0x80U | ((code_point >> 12U) & 0x3FU)));
    output.push_back(static_cast<char>(0x80U | ((code_point >> 6U) & 0x3FU)));
    output.push_back(static_cast<char>(0x80U | (code_point & 0x3FU)));
  } else {
    append_replacement_character(output);
  }
}

inline int hex_digit(char value) {
  if (value >= '0' && value <= '9') return value - '0';
  if (value >= 'a' && value <= 'f') return value - 'a' + 10;
  if (value >= 'A' && value <= 'F') return value - 'A' + 10;
  return -1;
}

}  // namespace json_detail

// Produces a valid UTF-8 JSON string payload. Invalid UTF-8 input is replaced
// with U+FFFD rather than leaking malformed bytes into reports or journals.
inline std::string json_escape(std::string_view value) {
  constexpr char hex[] = "0123456789abcdef";
  std::string output;
  output.reserve(value.size() + 8);
  for (std::size_t index = 0; index < value.size();) {
    const auto byte = static_cast<unsigned char>(value[index]);
    if (byte < 0x80U) {
      switch (byte) {
        case '"': output += "\\\""; break;
        case '\\': output += "\\\\"; break;
        case '\b': output += "\\b"; break;
        case '\f': output += "\\f"; break;
        case '\n': output += "\\n"; break;
        case '\r': output += "\\r"; break;
        case '\t': output += "\\t"; break;
        default:
          if (byte < 0x20U) {
            output += "\\u00";
            output.push_back(hex[(byte >> 4U) & 0x0FU]);
            output.push_back(hex[byte & 0x0FU]);
          } else {
            output.push_back(static_cast<char>(byte));
          }
      }
      ++index;
      continue;
    }

    std::size_t length = 0;
    std::uint32_t code_point = 0;
    if (byte >= 0xC2U && byte <= 0xDFU) {
      length = 2;
      code_point = byte & 0x1FU;
    } else if (byte >= 0xE0U && byte <= 0xEFU) {
      length = 3;
      code_point = byte & 0x0FU;
    } else if (byte >= 0xF0U && byte <= 0xF4U) {
      length = 4;
      code_point = byte & 0x07U;
    }
    if (length == 0 || !json_detail::valid_continuation(value, index + 1, length - 1)) {
      json_detail::append_replacement_character(output);
      ++index;
      continue;
    }
    for (std::size_t continuation = 1; continuation < length; ++continuation) {
      code_point = (code_point << 6U) |
                   (static_cast<unsigned char>(value[index + continuation]) & 0x3FU);
    }
    const bool overlong = (length == 2 && code_point < 0x80U) ||
                          (length == 3 && code_point < 0x800U) ||
                          (length == 4 && code_point < 0x10000U);
    const bool invalid = overlong || code_point > 0x10FFFFU ||
                         (code_point >= 0xD800U && code_point <= 0xDFFFU);
    if (invalid) {
      json_detail::append_replacement_character(output);
      ++index;
      continue;
    }
    if (code_point == 0x2028U) output += "\\u2028";
    else if (code_point == 0x2029U) output += "\\u2029";
    else output.append(value.substr(index, length));
    index += length;
  }
  return output;
}

inline std::string json_quote(std::string_view value) {
  return '"' + json_escape(value) + '"';
}

inline std::optional<std::string> json_unescape(std::string_view value) {
  std::string output;
  output.reserve(value.size());
  for (std::size_t index = 0; index < value.size(); ++index) {
    const char character = value[index];
    if (character != '\\') {
      const auto byte = static_cast<unsigned char>(character);
      if (byte < 0x20U) return std::nullopt;
      if (byte < 0x80U) {
        output.push_back(character);
      } else {
        const auto decoded = json_detail::decode_utf8(value, index);
        if (!decoded) return std::nullopt;
        output.append(value.substr(index, decoded->first));
        index += decoded->first - 1;
      }
      continue;
    }
    if (++index >= value.size()) return std::nullopt;
    switch (value[index]) {
      case '"': output.push_back('"'); break;
      case '\\': output.push_back('\\'); break;
      case '/': output.push_back('/'); break;
      case 'b': output.push_back('\b'); break;
      case 'f': output.push_back('\f'); break;
      case 'n': output.push_back('\n'); break;
      case 'r': output.push_back('\r'); break;
      case 't': output.push_back('\t'); break;
      case 'u': {
        if (index + 4 >= value.size()) return std::nullopt;
        std::uint32_t code_point = 0;
        for (int digit = 0; digit < 4; ++digit) {
          const int decoded = json_detail::hex_digit(value[++index]);
          if (decoded < 0) return std::nullopt;
          code_point = (code_point << 4U) | static_cast<std::uint32_t>(decoded);
        }
        if (code_point >= 0xD800U && code_point <= 0xDBFFU) {
          if (index + 6 >= value.size() || value[index + 1] != '\\' || value[index + 2] != 'u')
            return std::nullopt;
          index += 2;
          std::uint32_t low = 0;
          for (int digit = 0; digit < 4; ++digit) {
            const int decoded = json_detail::hex_digit(value[++index]);
            if (decoded < 0) return std::nullopt;
            low = (low << 4U) | static_cast<std::uint32_t>(decoded);
          }
          if (low < 0xDC00U || low > 0xDFFFU) return std::nullopt;
          code_point = 0x10000U + ((code_point - 0xD800U) << 10U) + (low - 0xDC00U);
        } else if (code_point >= 0xDC00U && code_point <= 0xDFFFU) {
          return std::nullopt;
        }
        json_detail::append_utf8(output, code_point);
        break;
      }
      default: return std::nullopt;
    }
  }
  return output;
}

// Extracts one primitive field from the compact line-oriented JSON records
// written by SmartCut64. Quoted values are decoded and escaped quotes are
// handled correctly; malformed records return nullopt.
inline std::optional<std::string> json_field(std::string_view object, std::string_view key) {
  const auto whitespace = [](char value) {
    return value == ' ' || value == '\t' || value == '\r' || value == '\n';
  };
  const auto skip_whitespace = [&](std::size_t& cursor) {
    while (cursor < object.size() && whitespace(object[cursor])) ++cursor;
  };
  const auto quoted = [&](std::size_t& cursor) -> std::optional<std::string> {
    if (cursor >= object.size() || object[cursor] != '"') return std::nullopt;
    const auto begin = ++cursor;
    bool escaped = false;
    for (; cursor < object.size(); ++cursor) {
      if (!escaped && object[cursor] == '"') {
        const auto decoded = json_unescape(object.substr(begin, cursor - begin));
        ++cursor;
        return decoded;
      }
      if (!escaped && object[cursor] == '\\') escaped = true;
      else escaped = false;
    }
    return std::nullopt;
  };

  std::size_t cursor = 0;
  skip_whitespace(cursor);
  if (cursor >= object.size() || object[cursor++] != '{') return std::nullopt;
  std::optional<std::string> result;
  while (true) {
    skip_whitespace(cursor);
    if (cursor >= object.size()) return std::nullopt;
    if (object[cursor] == '}') {
      ++cursor;
      break;
    }
    const auto member_key = quoted(cursor);
    if (!member_key) return std::nullopt;
    skip_whitespace(cursor);
    if (cursor >= object.size() || object[cursor++] != ':') return std::nullopt;
    skip_whitespace(cursor);
    if (cursor >= object.size()) return std::nullopt;

    std::optional<std::string> member_value;
    if (object[cursor] == '"') {
      member_value = quoted(cursor);
      if (!member_value) return std::nullopt;
    } else {
      const auto begin = cursor;
      while (cursor < object.size() && object[cursor] != ',' && object[cursor] != '}') {
        if (object[cursor] == '"' || object[cursor] == '{' || object[cursor] == '[') return std::nullopt;
        ++cursor;
      }
      auto end = cursor;
      while (end > begin && whitespace(object[end - 1])) --end;
      if (end == begin) return std::nullopt;
      member_value = std::string(object.substr(begin, end - begin));
    }
    if (*member_key == key) {
      if (result) return std::nullopt;
      result = std::move(*member_value);
    }

    skip_whitespace(cursor);
    if (cursor >= object.size()) return std::nullopt;
    if (object[cursor] == '}') {
      ++cursor;
      break;
    }
    if (object[cursor++] != ',') return std::nullopt;
    skip_whitespace(cursor);
    if (cursor >= object.size() || object[cursor] == '}') return std::nullopt;
  }
  skip_whitespace(cursor);
  return cursor == object.size() ? result : std::nullopt;
}

template <typename Value>
std::optional<Value> parse_unsigned(std::string_view text) {
  static_assert(std::is_integral_v<Value> && std::is_unsigned_v<Value>);
  if (text.empty() || text.front() == '-' || text.front() == '+') return std::nullopt;
  std::uint64_t parsed = 0;
  const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), parsed, 10);
  if (error != std::errc{} || end != text.data() + text.size() ||
      parsed > static_cast<std::uint64_t>(std::numeric_limits<Value>::max())) return std::nullopt;
  return static_cast<Value>(parsed);
}

template <typename Value>
std::optional<Value> parse_signed(std::string_view text) {
  static_assert(std::is_integral_v<Value> && std::is_signed_v<Value>);
  if (text.empty() || text.front() == '+') return std::nullopt;
  std::int64_t parsed = 0;
  const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), parsed, 10);
  if (error != std::errc{} || end != text.data() + text.size() ||
      parsed < static_cast<std::int64_t>(std::numeric_limits<Value>::min()) ||
      parsed > static_cast<std::int64_t>(std::numeric_limits<Value>::max())) return std::nullopt;
  return static_cast<Value>(parsed);
}

}  // namespace smartcut::core
