/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*!
 * \file tvm/ffi/string.h
 * \brief Runtime Bytes and String types.
 */
#ifndef TVM_FFI_STRING_H_
#define TVM_FFI_STRING_H_

#include <tvm/ffi/base_details.h>
#include <tvm/ffi/error.h>
#include <tvm/ffi/memory.h>
#include <tvm/ffi/object.h>
#include <tvm/ffi/type_traits.h>

#include <cctype>
#include <cstddef>
#include <cstring>
#include <iomanip>
#include <sstream>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

// Note: We place string in tvm/ffi instead of tvm/ffi/container
// because string itself needs special handling and is an inherent
// core component for return string handling.
// The following dependency relation holds
// any -> string -> object

/// \cond Doxygen_Suppress
#ifdef _MSC_VER
#define TVM_FFI_SNPRINTF _snprintf_s
#pragma warning(push)
#pragma warning(disable : 4244)
#pragma warning(disable : 4127)
#pragma warning(disable : 4702)
#else
#define TVM_FFI_SNPRINTF snprintf
#endif
/// \endcond

namespace tvm {
namespace ffi {
namespace details {
/*!
 * \brief Base class for bytes and string objects.
 */
class BytesObjBase : public Object, public TVMFFIByteArray {};

/*!
 * \brief An object representing bytes.
 * \note We use a separate object for bytes to follow Python convention
 *       and indicate passing of raw bytes.
 *       Bytes can be converted from/to string.
 */
class BytesObj : public BytesObjBase {
 public:
  static constexpr const uint32_t _type_index = TypeIndex::kTVMFFIBytes;
  static const constexpr bool _type_final = true;
  TVM_FFI_DECLARE_OBJECT_INFO_STATIC(StaticTypeKey::kTVMFFIBytes, BytesObj, Object);
};

/*! \brief An object representing string. This is a POD type. */
class StringObj : public BytesObjBase {
 public:
  static constexpr const uint32_t _type_index = TypeIndex::kTVMFFIStr;
  static const constexpr bool _type_final = true;
  TVM_FFI_DECLARE_OBJECT_INFO_STATIC(StaticTypeKey::kTVMFFIStr, StringObj, Object);
};

// String moved from std::string
// without having to trigger a copy
template <typename Base>
class BytesObjStdImpl : public Base {
 public:
  explicit BytesObjStdImpl(std::string other) : data_{std::move(other)} {
    this->data = data_.data();
    this->size = data_.size();
  }

 private:
  std::string data_;
};

/*!
 * \brief Helper cell class that can be used to back small string
 * \note Do not use directly, use String or Bytes instead
 */
class BytesBaseCell {
 public:
  BytesBaseCell() {
    // initialize to none
    data_.type_index = TypeIndex::kTVMFFINone;
    data_.zero_padding = 0;
    data_.v_int64 = 0;
  }

  explicit BytesBaseCell(std::nullopt_t) {
    data_.type_index = TypeIndex::kTVMFFINone;
    data_.zero_padding = 0;
    data_.v_int64 = 0;
  }

  BytesBaseCell(const BytesBaseCell& other) : data_(other.data_) {  // NOLINT(*)
    if (data_.type_index >= TypeIndex::kTVMFFIStaticObjectBegin) {
      details::ObjectUnsafe::IncRefObjectHandle(data_.v_obj);
    }
  }

  BytesBaseCell(BytesBaseCell&& other) : data_(other.data_) {  // NOLINT(*)
    other.data_.type_index = TypeIndex::kTVMFFINone;
  }

  BytesBaseCell& operator=(const BytesBaseCell& other) {
    BytesBaseCell(other).swap(*this);  // NOLINT(*)
    return *this;
  }

  BytesBaseCell& operator=(BytesBaseCell&& other) noexcept {
    BytesBaseCell(std::move(other)).swap(*this);  // NOLINT(*)
    return *this;
  }

  ~BytesBaseCell() {
    if (data_.type_index >= TypeIndex::kTVMFFIStaticObjectBegin) {
      details::ObjectUnsafe::DecRefObjectHandle(data_.v_obj);
    }
  }

  /*!
   * \brief Check if the cell is null
   * \return true if the cell is null, false otherwise
   */
  bool operator==(std::nullopt_t) const { return data_.type_index == TypeIndex::kTVMFFINone; }

  /*!
   * \brief Check if the cell is not null
   * \return true if the cell is not null, false otherwise
   */
  bool operator!=(std::nullopt_t) const { return data_.type_index != TypeIndex::kTVMFFINone; }

  /*!
   * \brief Swap this String with another string
   * \param other The other string
   */
  void swap(BytesBaseCell& other) {  // NOLINT(*)
    std::swap(data_, other.data_);
  }

  const char* data() const noexcept {
    if (data_.type_index < TypeIndex::kTVMFFIStaticObjectBegin) {
      return data_.v_bytes;
    } else {
      // NOLINTNEXTLINE(clang-analyzer-security.ArrayBound)
      return TVMFFIBytesGetByteArrayPtr(data_.v_obj)->data;
    }
  }

  size_t size() const noexcept {
    if (data_.type_index < TypeIndex::kTVMFFIStaticObjectBegin) {
      return data_.small_str_len;
    } else {
      // NOLINTNEXTLINE(clang-analyzer-security.ArrayBound)
      return TVMFFIBytesGetByteArrayPtr(data_.v_obj)->size;
    }
  }

  template <typename LargeObj>
  void InitFromStd(std::string&& other, int32_t large_type_index) {
    // needs to be reset to none first for exception safety
    data_.type_index = TypeIndex::kTVMFFINone;
    data_.zero_padding = 0;
    TVM_FFI_CLEAR_PTR_PADDING_IN_FFI_ANY(&data_);
    ObjectPtr<LargeObj> ptr = make_object<BytesObjStdImpl<LargeObj>>(std::move(other));
    data_.v_obj = details::ObjectUnsafe::MoveObjectPtrToTVMFFIObjectPtr(std::move(ptr));
    data_.type_index = large_type_index;
  }

  /*!
   * \brief Create a new empty space for a string
   * \param size The size of the string
   * \param small_type_index The type index for the small string
   * \param large_type_index The type index for the large string
   * \note always reserve one byte for \0 compactibility
   * \return A pointer to the empty space
   */
  template <typename LargeObj>
  char* InitSpaceForSize(size_t size, int32_t small_type_index, int32_t large_type_index) {
    size_t kMaxSmallBytesLen = sizeof(int64_t) - 1;
    // first zero the content, this is important for exception safety
    data_.type_index = small_type_index;
    data_.zero_padding = 0;
    if (size <= kMaxSmallBytesLen) {
      // set up the size accordingly
      data_.small_str_len = static_cast<uint32_t>(size);
      return data_.v_bytes;
    } else {
      // allocate from heap
      ObjectPtr<LargeObj> ptr = make_inplace_array_object<LargeObj, char>(size + 1);
      char* dest_data = reinterpret_cast<char*>(ptr.get()) + sizeof(LargeObj);
      ptr->data = dest_data;
      ptr->size = size;
      TVM_FFI_CLEAR_PTR_PADDING_IN_FFI_ANY(&data_);
      data_.v_obj = details::ObjectUnsafe::MoveObjectPtrToTVMFFIObjectPtr(std::move(ptr));
      // now reset the type index to str
      data_.type_index = large_type_index;
      return dest_data;
    }
  }

  void InitTypeIndex(int32_t type_index) { data_.type_index = type_index; }

  void MoveToAny(TVMFFIAny* result) {
    *result = data_;
    data_.type_index = TypeIndex::kTVMFFINone;
    data_.zero_padding = 0;
    data_.v_int64 = 0;
  }

  TVMFFIAny CopyToTVMFFIAny() const { return data_; }

  static BytesBaseCell CopyFromAnyView(const TVMFFIAny* src) {
    BytesBaseCell result(*src);
    if (result.data_.type_index >= TypeIndex::kTVMFFIStaticObjectBegin) {
      details::ObjectUnsafe::IncRefObjectHandle(result.data_.v_obj);
    }
    return result;
  }

  static BytesBaseCell MoveFromAny(TVMFFIAny* src) {
    BytesBaseCell result(*src);
    src->type_index = TypeIndex::kTVMFFINone;
    src->zero_padding = 0;
    src->v_int64 = 0;
    return result;
  }

 private:
  explicit BytesBaseCell(TVMFFIAny data) : data_(data) {}
  /*! \brief internal backing data */
  TVMFFIAny data_;
};
}  // namespace details

/*!
 * \brief Managed reference of byte array.
 */
class Bytes {
 public:
  /*! \brief default constructor */
  Bytes() { data_.InitTypeIndex(TypeIndex::kTVMFFISmallBytes); }
  /*!
   * \brief constructor from size
   *
   * \param data The data pointer.
   * \param size The size of the char array.
   */
  Bytes(const char* data, size_t size) { this->InitData(data, size); }
  /*!
   * \brief constructor from TVMFFIByteArray
   *
   * \param bytes a char array.
   */
  Bytes(TVMFFIByteArray bytes) {  // NOLINT(*)
    this->InitData(bytes.data, bytes.size);
  }
  /*!
   * \brief constructor from std::string
   *
   * \param other a char array.
   */
  Bytes(const std::string& other) {  // NOLINT(*)
    this->InitData(other.data(), other.size());
  }
  /*!
   * \brief constructor from std::string
   *
   * \param other a char array.
   */
  Bytes(std::string&& other) {  // NOLINT(*)
    data_.InitFromStd<details::BytesObj>(std::move(other), TypeIndex::kTVMFFIBytes);
  }
  /*!
   * \brief Swap this String with another string
   * \param other The other string
   */
  void swap(Bytes& other) {  // NOLINT(*)
    std::swap(data_, other.data_);
  }

  template <typename T>
  Bytes& operator=(T&& other) {
    // copy-and-swap idiom
    Bytes(std::forward<T>(other)).swap(*this);  // NOLINT(*)
    return *this;
  }
  /*!
   * \brief Return the length of the string
   *
   * \return size_t string length
   */
  size_t size() const { return data_.size(); }
  /*!
   * \brief Return the data pointer
   *
   * \return const char* data pointer
   */
  const char* data() const { return data_.data(); }
  /*!
   * \brief Convert String to an std::string object
   *
   * \return std::string
   */
  operator std::string() const {  // NOLINT(google-explicit-constructor)
    return std::string{data(), size()};
  }

  /*!
   * \brief Compare two char sequence
   *
   * \param lhs Pointers to the char array to compare
   * \param rhs Pointers to the char array to compare
   * \param lhs_count Length of the char array to compare
   * \param rhs_count Length of the char array to compare
   * \return int zero if both char sequences compare equal. negative if this
   * appear before other, positive otherwise.
   */
  static int memncmp(const char* lhs, const char* rhs, size_t lhs_count, size_t rhs_count) {
    if (lhs == rhs && lhs_count == rhs_count) return 0;

    for (size_t i = 0; i < lhs_count && i < rhs_count; ++i) {
      if (lhs[i] < rhs[i]) return -1;
      if (lhs[i] > rhs[i]) return 1;
    }
    if (lhs_count < rhs_count) {
      return -1;
    } else if (lhs_count > rhs_count) {
      return 1;
    } else {
      return 0;
    }
  }
  /*!
   * \brief Compare two char sequence for equality
   *
   * \param lhs Pointers to the char array to compare
   * \param rhs Pointers to the char array to compare
   * \param lhs_count Length of the char array to compare
   * \param rhs_count Length of the char array to compare
   *
   * \return true if the two char sequences are equal, false otherwise.
   */
  static bool memequal(const void* lhs, const void* rhs, size_t lhs_count, size_t rhs_count) {
    return lhs_count == rhs_count && (lhs == rhs || std::memcmp(lhs, rhs, lhs_count) == 0);
  }

 private:
  template <typename, typename>
  friend struct TypeTraits;
  template <typename, typename>
  friend class Optional;
  // internal backing cell
  details::BytesBaseCell data_;
  // create a new String from TVMFFIAny, must keep private
  explicit Bytes(details::BytesBaseCell data) : data_(std::move(data)) {}
  char* InitSpaceForSize(size_t size) {
    return data_.InitSpaceForSize<details::BytesObj>(size, TypeIndex::kTVMFFISmallBytes,
                                                     TypeIndex::kTVMFFIBytes);
  }
  void InitData(const char* data, size_t size) {
    char* dest_data = InitSpaceForSize(size);
    if (size > 0) {
      std::memcpy(dest_data, data, size);
    }
    // mainly to be compat with string
    dest_data[size] = '\0';
  }
};

/*!
 * \brief String container class.
 */
class String {
 public:
  /*!
   * \brief avoid misuse of nullptr
   */
  String(std::nullptr_t) = delete;  // NOLINT(*)
  /*!
   * \brief constructor
   */
  String() { data_.InitTypeIndex(TypeIndex::kTVMFFISmallStr); }
  // constructors from Any
  /*!
   * \brief Copy constructor
   * \param other The other string
   */
  String(const String& other) = default;  // NOLINT(*)
  /*!
   * \brief Move constructor
   * \param other The other string
   */
  String(String&& other) = default;  // NOLINT(*)
  /*!
   * \brief Copy assignment operator
   * \param other The other string
   */
  String& operator=(const String& other) = default;  // NOLINT(*)
  /*!
   * \brief Move assignment operator
   * \param other The other string
   */
  String& operator=(String&& other) = default;  // NOLINT(*)

  /*!
   * \brief Swap this String with another string
   * \param other The other string
   */
  void swap(String& other) noexcept {  // NOLINT(*)
    std::swap(data_, other.data_);
  }

  /*!
   * \brief Copy assignment operator
   * \param other The other string
   */
  String& operator=(const std::string& other) {
    String(other).swap(*this);  // NOLINT(*)
    return *this;
  }
  /*!
   * \brief Move assignment operator
   * \param other The other string
   */
  String& operator=(std::string&& other) {
    String(std::move(other)).swap(*this);  // NOLINT(*)
    return *this;
  }

  /*!
   * \brief Copy assignment operator
   * \param other The other string
   */
  String& operator=(const char* other) {
    String(other).swap(*this);  // NOLINT(*)
    return *this;
  }

  /*!
   * \brief constructor from raw string
   *
   * \param data The data pointer.
   * \param size The size of the char array.
   */
  String(const char* data, size_t size) { this->InitData(data, size); }

  /*!
   * \brief constructor from raw string
   *
   * \param other a char array.
   * \note This constructor is marked as explicit to avoid implicit conversion
   *       of nullptr value here to string, which then was used in comparison
   */
  String(const char* other) {  // NOLINT(*)
    this->InitData(other, std::char_traits<char>::length(other));
  }
  /*!
   * \brief Construct a new string object
   * \param other The std::string object to be copied
   */
  String(const std::string& other) {  // NOLINT(*)
    this->InitData(other.data(), other.size());
  }

  /*!
   * \brief Construct a new string object
   * \param other The std::string object to be moved
   */
  String(std::string&& other) {  // NOLINT(*)
    // exception safety, first set to none so if exception is thrown
    // destructor works correctly
    data_.InitFromStd<details::StringObj>(std::move(other), TypeIndex::kTVMFFIStr);
  }

  /*!
   * \brief constructor from TVMFFIByteArray
   *
   * \param other a TVMFFIByteArray.
   */
  explicit String(TVMFFIByteArray other) { this->InitData(other.data, other.size); }

  /*!
   * \brief Return the data pointer
   *
   * \return const char* data pointer
   */
  const char* data() const noexcept { return data_.data(); }

  /*!
   * \brief Returns a pointer to the char array in the string.
   *
   * \return const char*
   */
  const char* c_str() const noexcept { return data(); }

  /*!
   * \brief Return the length of the string
   *
   * \return size_t string length
   */
  size_t size() const noexcept { return data_.size(); }

  /*!
   * \brief Compares this String object to other
   *
   * \param other The String to compare with.
   *
   * \return zero if both char sequences compare equal. negative if this appear
   * before other, positive otherwise.
   */
  int compare(const String& other) const {
    return Bytes::memncmp(data(), other.data(), size(), other.size());
  }

  /*!
   * \brief Compares this String object to other
   *
   * \param other The string to compare with.
   *
   * \return zero if both char sequences compare equal. negative if this appear
   * before other, positive otherwise.
   */
  int compare(const std::string& other) const {
    return Bytes::memncmp(data(), other.data(), size(), other.size());
  }

  /*!
   * \brief Compares this to other
   *
   * \param other The character array to compare with.
   *
   * \return zero if both char sequences compare equal. negative if this appear
   * before other, positive otherwise.
   */
  int compare(const char* other) const {
    const char* this_data = data();
    size_t this_size = size();
    for (size_t i = 0; i < this_size; ++i) {
      // other is shorter than this
      if (other[i] == '\0') return 1;
      if (this_data[i] < other[i]) return -1;
      if (this_data[i] > other[i]) return 1;
    }
    // other equals this
    if (other[this_size] == '\0') return 0;
    // other longer than this
    return -1;
  }

  /*!
   * \brief Compares this to other
   *
   * \param other The TVMFFIByteArray to compare with.
   *
   * \return zero if both char sequences compare equal. negative if this appear
   * before other, positive otherwise.
   */
  int compare(const TVMFFIByteArray& other) const {
    return Bytes::memncmp(data(), other.data, size(), other.size);
  }

  /*!
   * \brief Return the length of the string
   *
   * \return size_t string length
   */
  size_t length() const { return size(); }

  /*!
   * \brief Retun if the string is empty
   *
   * \return true if empty, false otherwise.
   */
  bool empty() const { return size() == 0; }

  /*!
   * \brief Read an element.
   * \param pos The position at which to read the character.
   *
   * \return The char at position
   */
  char at(size_t pos) const {
    if (pos < size()) {
      return data()[pos];
    } else {
      throw std::out_of_range("tvm::String index out of bounds");
    }
  }

  /*! \brief Value returned by find() when no match is found */
  static constexpr size_t npos = static_cast<size_t>(-1);

  /*!
   * \brief Find the first occurrence of a substring
   * \param str The substring to search for
   * \param pos The position at which to start the search
   * \return The position of the first character of the first match, or npos if not found
   */
  size_t find(const String& str, size_t pos = 0) const { return find(str.data(), pos, str.size()); }

  /*!
   * \brief Find the first occurrence of a substring
   * \param str The substring to search for
   * \param pos The position at which to start the search
   * \return The position of the first character of the first match, or npos if not found
   */
  size_t find(const char* str, size_t pos = 0) const { return find(str, pos, std::strlen(str)); }

  /*!
   * \brief Find the first occurrence of a substring
   * \param str The substring to search for
   * \param pos The position at which to start the search
   * \param count The length of the substring
   * \return The position of the first character of the first match, or npos if not found
   */
  size_t find(const char* str, size_t pos, size_t count) const {
    return std::string_view(data(), size()).find(std::string_view(str, count), pos);
  }

  /*!
   * \brief Returns a substring [pos, pos+count)
   * \param pos The position of the first character to include
   * \param count The length of the substring (default: until end of string)
   * \return A string containing the substring
   */
  String substr(size_t pos = 0, size_t count = npos) const {
    if (pos > size()) {
      throw std::out_of_range("tvm::String substr index out of bounds");
    }
    size_t rcount = std::min(count, size() - pos);
    return String(data() + pos, rcount);
  }

  /*!
   * \brief Check if the string starts with a prefix
   * \param prefix The prefix to check for
   * \return true if the string starts with prefix, false otherwise
   */
  bool starts_with(const String& prefix) const { return starts_with(prefix.data(), prefix.size()); }

  /*!
   * \brief Check if the string starts with a prefix
   * \param prefix The prefix to check for
   * \return true if the string starts with prefix, false otherwise
   */
  bool starts_with(std::string_view prefix) const {
    return starts_with(prefix.data(), prefix.size());
  }

  /*!
   * \brief Check if the string starts with a prefix
   * \param prefix The prefix to check for
   * \return true if the string starts with prefix, false otherwise
   */
  bool starts_with(const char* prefix) const { return starts_with(prefix, std::strlen(prefix)); }

  /*!
   * \brief Check if the string starts with a prefix
   * \param prefix The prefix to check for
   * \param count The length of the prefix
   * \return true if the string starts with prefix, false otherwise
   */
  bool starts_with(const char* prefix, size_t count) const {
    if (count > size()) {
      return false;
    }
    return std::memcmp(data(), prefix, count) == 0;
  }

  /*!
   * \brief Check if the string ends with a suffix
   * \param suffix The suffix to check for
   * \return true if the string ends with suffix, false otherwise
   */
  bool ends_with(const String& suffix) const { return ends_with(suffix.data(), suffix.size()); }

  /*!
   * \brief Check if the string ends with a suffix
   * \param suffix The suffix to check for
   * \return true if the string ends with suffix, false otherwise
   */
  bool ends_with(std::string_view suffix) const { return ends_with(suffix.data(), suffix.size()); }

  /*!
   * \brief Check if the string ends with a suffix
   * \param suffix The suffix to check for
   * \return true if the string ends with suffix, false otherwise
   */
  bool ends_with(const char* suffix) const { return ends_with(suffix, std::strlen(suffix)); }

  /*!
   * \brief Check if the string ends with a suffix
   * \param suffix The suffix to check for
   * \param count The length of the suffix
   * \return true if the string ends with suffix, false otherwise
   */
  bool ends_with(const char* suffix, size_t count) const {
    if (count > size()) {
      return false;
    }
    return std::memcmp(data() + size() - count, suffix, count) == 0;
  }

  /*!
   * \brief Convert String to an std::string object
   *
   * \return std::string
   */
  operator std::string() const {  // NOLINT(google-explicit-constructor)
    return std::string{data(), size()};
  }

  /*!
   * \brief Split the string by a delimiter character.
   * \param delim The delimiter character.
   * \return A vector of string_views pointing into this string's data.
   * \note The returned string_views are only valid while this String is alive.
   */
  std::vector<std::string_view> Split(char delim) const {
    std::vector<std::string_view> ret;
    const char* start = data();
    const char* end = start + size();
    for (const char* p = start; p < end; ++p) {
      if (*p == delim) {
        ret.emplace_back(start, static_cast<size_t>(p - start));
        start = p + 1;
      }
    }
    ret.emplace_back(start, static_cast<size_t>(end - start));
    return ret;
  }

 private:
  template <typename, typename>
  friend struct TypeTraits;
  template <typename, typename>
  friend class Optional;
  // internal backing cell
  details::BytesBaseCell data_;
  // create a new String from TVMFFIAny, must keep private
  explicit String(details::BytesBaseCell data) : data_(std::move(data)) {}
  /*!
   * \brief Create a new empty space for a string
   * \param size The size of the string
   * \return A pointer to the empty space
   */
  char* InitSpaceForSize(size_t size) {
    return data_.InitSpaceForSize<details::StringObj>(size, TypeIndex::kTVMFFISmallStr,
                                                      TypeIndex::kTVMFFIStr);
  }
  void InitData(const char* data, size_t size) {
    char* dest_data = InitSpaceForSize(size);
    if (size > 0) {
      std::memcpy(dest_data, data, size);
    }
    dest_data[size] = '\0';
  }
  /*!
   * \brief Concatenate two char sequences
   *
   * \param lhs Pointers to the lhs char array
   * \param lhs_size The size of the lhs char array
   * \param rhs Pointers to the rhs char array
   * \param rhs_size The size of the rhs char array
   *
   * \return The concatenated char sequence
   */
  static String Concat(const char* lhs, size_t lhs_size, const char* rhs, size_t rhs_size) {
    String ret;
    // disable stringop-overflow and restrict warnings
    // gcc may produce false positive when we enable dest_data returned from small string path
    // Because compiler is not able to detect the condition that the path is only triggered via
    // size < kMaxSmallStrLen and can report it as a overflow case.
#if (__GNUC__) && !(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-overflow"
#pragma GCC diagnostic ignored "-Warray-bounds"
#pragma GCC diagnostic ignored "-Wrestrict"
#endif
    char* dest_data = ret.InitSpaceForSize(lhs_size + rhs_size);
    std::memcpy(dest_data, lhs, lhs_size);
    std::memcpy(dest_data + lhs_size, rhs, rhs_size);
    // NOLINTNEXTLINE(clang-analyzer-security.ArrayBound)
    dest_data[lhs_size + rhs_size] = '\0';
#if (__GNUC__) && !(__clang__)
#pragma GCC diagnostic pop
#endif
    return ret;
  }
  // Overload + operator
  friend String operator+(const String& lhs, const String& rhs);
  friend String operator+(const String& lhs, const std::string& rhs);
  friend String operator+(const std::string& lhs, const String& rhs);
  friend String operator+(const String& lhs, const char* rhs);
  friend String operator+(const char* lhs, const String& rhs);
};

/*!
 * \brief Return a JSON-escaped version of the string (RFC 8259).
 *
 * Uses ``\\uXXXX`` for control characters, escapes ``\\/``, ``\\b``, ``\\f`` per the JSON spec.
 * Non-ASCII bytes are passed through as-is (valid UTF-8 is preserved).
 *
 * \param value The input string
 * \return The escaped string, quoted with double quotes
 */
inline String EscapeStringJSON(const String& value) {
  std::ostringstream oss;
  oss << '"';
  const char* data = value.data();
  const size_t size = value.size();
  for (size_t i = 0; i < size; ++i) {
    switch (data[i]) {
/// \cond Doxygen_Suppress
#define TVM_FFI_ESCAPE_CHAR(pattern, val) \
  case pattern:                           \
    oss << (val);                         \
    break
      TVM_FFI_ESCAPE_CHAR('\"', "\\\"");
      TVM_FFI_ESCAPE_CHAR('\\', "\\\\");
      TVM_FFI_ESCAPE_CHAR('/', "\\/");
      TVM_FFI_ESCAPE_CHAR('\b', "\\b");
      TVM_FFI_ESCAPE_CHAR('\f', "\\f");
      TVM_FFI_ESCAPE_CHAR('\n', "\\n");
      TVM_FFI_ESCAPE_CHAR('\r', "\\r");
      TVM_FFI_ESCAPE_CHAR('\t', "\\t");
#undef TVM_FFI_ESCAPE_CHAR
        /// \endcond
      default: {
        uint8_t u8_val = static_cast<uint8_t>(data[i]);
        // this is a control character, print as \uXXXX
        if (u8_val < 0x20 || u8_val == 0x7f) {
          char buffer[8];
          int size = TVM_FFI_SNPRINTF(buffer, sizeof(buffer), "\\u%04x",
                                      static_cast<int32_t>(data[i]) & 0xff);
          oss.write(buffer, size);
        } else {
          oss << data[i];
        }
        break;
      }
    }
  }
  oss << '"';
  return String(oss.str());
}

/*!
 * \brief Escape a string for JSON output.
 * \deprecated Use EscapeStringJSON instead.
 * \param value The input string
 * \return The escaped string, quoted with double quotes
 */
[[deprecated("Use EscapeStringJSON instead")]] inline String EscapeString(const String& value) {
  return EscapeStringJSON(value);
}

/*!
 * \brief Return a Python-style escaped string representation.
 *
 * Handles ANSI escape sequences, UTF-8 multibyte characters, and standard
 * C escape sequences (\\n, \\t, \\r, \\\\, \\"). Uses \\xNN for control
 * characters and \\uXXXX / \\UXXXXXXXX for non-ASCII codepoints.
 *
 * \param value The input string to escape.
 * \return The escaped string, quoted with double quotes.
 */
inline String EscapedStringPy(const String& value) {
  const char* data = value.data();
  const size_t length = value.size();
  std::ostringstream oss;
  oss << '"';
  for (size_t i = 0; i < length;) {
    unsigned char c = static_cast<unsigned char>(data[i]);
    unsigned char d = (i + 1 < length) ? static_cast<unsigned char>(data[i + 1]) : 0;
    // Detect ANSI escape sequences
    if (c == '\x1b' && d == '[') {
      size_t j = i + 2;
      while (j < length && (std::isdigit(static_cast<unsigned char>(data[j])) || data[j] == ';')) {
        ++j;
      }
      if (j < length && (data[j] == 'm' || data[j] == 'K')) {
        oss << "\\x1b[";
        for (i += 2; i <= j; ++i) {
          oss << data[i];
        }
        continue;
      }
    }
    // Handle ASCII C escape sequences
    switch (c) {
      case '\n':
        oss << "\\n";
        ++i;
        continue;
      case '\t':
        oss << "\\t";
        ++i;
        continue;
      case '\r':
        oss << "\\r";
        ++i;
        continue;
      case '\\':
        oss << "\\\\";
        ++i;
        continue;
      case '\"':
        oss << "\\\"";
        ++i;
        continue;
      default:
        break;
    }
    // Handle ASCII
    if ((c & 0x80) == 0) {
      if (c < 0x20 || c == 0x7f) {
        // Escape control characters as \xNN
        char buf[5];
        TVM_FFI_SNPRINTF(buf, sizeof(buf), "\\x%02x", static_cast<unsigned>(c));
        oss << buf;
      } else {
        oss << static_cast<char>(c);
      }
      ++i;
      continue;
    }
    if ((c & 0xE0) == 0xC0 && i + 1 < length && (d & 0xC0) == 0x80) {
      int32_t codepoint = ((c & 0x1F) << 6) | (d & 0x3F);
      oss << "\\u" << std::hex << std::setw(4) << std::setfill('0') << codepoint;
      i += 2;
    } else if ((c & 0xF0) == 0xE0 && i + 2 < length) {
      unsigned char e = static_cast<unsigned char>(data[i + 2]);
      if ((d & 0xC0) == 0x80 && (e & 0xC0) == 0x80) {
        int32_t codepoint = ((c & 0x0F) << 12) | ((d & 0x3F) << 6) | (e & 0x3F);
        oss << "\\u" << std::hex << std::setw(4) << std::setfill('0') << codepoint;
        i += 3;
      } else {
        oss << "\\x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(c);
        ++i;
      }
    } else if ((c & 0xF8) == 0xF0 && i + 3 < length) {
      unsigned char e = static_cast<unsigned char>(data[i + 2]);
      unsigned char f = static_cast<unsigned char>(data[i + 3]);
      if ((d & 0xC0) == 0x80 && (e & 0xC0) == 0x80 && (f & 0xC0) == 0x80) {
        int32_t codepoint =
            ((c & 0x07) << 18) | ((d & 0x3F) << 12) | ((e & 0x3F) << 6) | (f & 0x3F);
        oss << "\\U" << std::hex << std::setw(8) << std::setfill('0') << codepoint;
        i += 4;
      } else {
        oss << "\\x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(c);
        ++i;
      }
    } else {
      oss << "\\x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(c);
      ++i;
    }
    oss.unsetf(std::ios::adjustfield | std::ios::basefield | std::ios::floatfield);
    oss.fill(' ');
  }
  oss << '"';
  return String(oss.str());
}

/*! \brief Convert TVMFFIByteArray to std::string_view */
TVM_FFI_INLINE std::string_view ToStringView(TVMFFIByteArray str) {
  return std::string_view(str.data, str.size);
}
/// \cond Doxygen_Suppress

template <>
inline constexpr bool use_default_type_traits_v<Bytes> = false;

// specialize to enable implicit conversion from TVMFFIByteArray*
template <>
struct TypeTraits<Bytes> : public TypeTraitsBase {
  // bytes can be union type of small bytes and object, so keep it as any
  static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIAny;

  TVM_FFI_INLINE static void CopyToAnyView(const Bytes& src, TVMFFIAny* result) {
    *result = src.data_.CopyToTVMFFIAny();
  }

  TVM_FFI_INLINE static void MoveToAny(Bytes src, TVMFFIAny* result) {
    src.data_.MoveToAny(result);
  }

  TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) {
    return src->type_index == TypeIndex::kTVMFFISmallBytes ||
           src->type_index == TypeIndex::kTVMFFIBytes;
  }

  TVM_FFI_INLINE static Bytes CopyFromAnyViewAfterCheck(const TVMFFIAny* src) {
    return Bytes(details::BytesBaseCell::CopyFromAnyView(src));
  }

  TVM_FFI_INLINE static Bytes MoveFromAnyAfterCheck(TVMFFIAny* src) {
    return Bytes(details::BytesBaseCell::MoveFromAny(src));
  }

  TVM_FFI_INLINE static std::optional<Bytes> TryCastFromAnyView(const TVMFFIAny* src) {
    if (src->type_index == TypeIndex::kTVMFFIByteArrayPtr) {
      return Bytes(*static_cast<TVMFFIByteArray*>(src->v_ptr));
    }
    if (src->type_index == TypeIndex::kTVMFFISmallBytes ||
        src->type_index == TypeIndex::kTVMFFIBytes) {
      return Bytes(details::BytesBaseCell::CopyFromAnyView(src));
    }
    return std::nullopt;
  }

  TVM_FFI_INLINE static std::string TypeStr() { return "bytes"; }
  TVM_FFI_INLINE static std::string TypeSchema() {
    return R"({"type":")" + std::string(StaticTypeKey::kTVMFFIBytes) + R"("})";
  }
};

template <>
inline constexpr bool use_default_type_traits_v<String> = false;

// specialize to enable implicit conversion from const char*
template <>
struct TypeTraits<String> : public TypeTraitsBase {
  // string can be union type of small string and object, so keep it as any
  static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIAny;

  TVM_FFI_INLINE static void CopyToAnyView(const String& src, TVMFFIAny* result) {
    *result = src.data_.CopyToTVMFFIAny();
  }

  TVM_FFI_INLINE static void MoveToAny(String src, TVMFFIAny* result) {
    src.data_.MoveToAny(result);
  }

  TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) {
    return src->type_index == TypeIndex::kTVMFFISmallStr ||
           src->type_index == TypeIndex::kTVMFFIStr;
  }

  TVM_FFI_INLINE static String CopyFromAnyViewAfterCheck(const TVMFFIAny* src) {
    return String(details::BytesBaseCell::CopyFromAnyView(src));
  }

  TVM_FFI_INLINE static String MoveFromAnyAfterCheck(TVMFFIAny* src) {
    return String(details::BytesBaseCell::MoveFromAny(src));
  }

  TVM_FFI_INLINE static std::optional<String> TryCastFromAnyView(const TVMFFIAny* src) {
    if (src->type_index == TypeIndex::kTVMFFIRawStr) {
      return String(src->v_c_str);
    }
    if (src->type_index == TypeIndex::kTVMFFISmallStr || src->type_index == TypeIndex::kTVMFFIStr) {
      return String(details::BytesBaseCell::CopyFromAnyView(src));
    }
    return std::nullopt;
  }

  TVM_FFI_INLINE static std::string TypeStr() { return "str"; }
  TVM_FFI_INLINE static std::string TypeSchema() {
    return R"({"type":")" + std::string(StaticTypeKey::kTVMFFIStr) + R"("})";
  }
};

// const char*, requirement: not nullable, do not retain ownership
template <int N>
struct TypeTraits<char[N]> : public TypeTraitsBase {
  // NOTE: only enable implicit conversion into AnyView
  static constexpr bool storage_enabled = false;

  TVM_FFI_INLINE static void CopyToAnyView(const char src[N], TVMFFIAny* result) {
    result->type_index = TypeIndex::kTVMFFIRawStr;
    result->zero_padding = 0;
    result->v_c_str = src;
  }

  TVM_FFI_INLINE static void MoveToAny(const char src[N], TVMFFIAny* result) {
    // when we need to move to any, convert to owned object first
    TypeTraits<String>::MoveToAny(String(src), result);
  }
};

template <>
struct TypeTraits<const char*> : public TypeTraitsBase {
  static constexpr bool storage_enabled = false;

  TVM_FFI_INLINE static void CopyToAnyView(const char* src, TVMFFIAny* result) {
    TVM_FFI_ICHECK_NOTNULL(src);
    result->type_index = TypeIndex::kTVMFFIRawStr;
    result->zero_padding = 0;
    result->v_c_str = src;
  }

  TVM_FFI_INLINE static void MoveToAny(const char* src, TVMFFIAny* result) {
    // when we need to move to any, convert to owned object first
    TypeTraits<String>::MoveToAny(String(src), result);
  }
  // Do not allow const char* in a container, so we do not need CheckAnyStrict
  TVM_FFI_INLINE static std::optional<const char*> TryCastFromAnyView(const TVMFFIAny* src) {
    if (src->type_index == TypeIndex::kTVMFFIRawStr) {
      return static_cast<const char*>(src->v_c_str);
    }
    return std::nullopt;
  }

  TVM_FFI_INLINE static std::string TypeStr() { return "const char*"; }
  TVM_FFI_INLINE static std::string TypeSchema() { return R"({"type":"const char*"})"; }
};

// TVMFFIByteArray, requirement: not nullable, do not retain ownership
template <>
struct TypeTraits<TVMFFIByteArray*> : public TypeTraitsBase {
  static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIByteArrayPtr;
  static constexpr bool storage_enabled = false;

  TVM_FFI_INLINE static void CopyToAnyView(TVMFFIByteArray* src, TVMFFIAny* result) {
    TVM_FFI_ICHECK_NOTNULL(src);
    result->type_index = TypeIndex::kTVMFFIByteArrayPtr;
    result->zero_padding = 0;
    TVM_FFI_CLEAR_PTR_PADDING_IN_FFI_ANY(result);
    result->v_ptr = src;
  }

  TVM_FFI_INLINE static void MoveToAny(TVMFFIByteArray* src, TVMFFIAny* result) {
    TypeTraits<Bytes>::MoveToAny(Bytes(*src), result);
  }

  TVM_FFI_INLINE static std::optional<TVMFFIByteArray*> TryCastFromAnyView(const TVMFFIAny* src) {
    if (src->type_index == TypeIndex::kTVMFFIByteArrayPtr) {
      return static_cast<TVMFFIByteArray*>(src->v_ptr);
    }
    return std::nullopt;
  }

  TVM_FFI_INLINE static std::string TypeStr() { return StaticTypeKey::kTVMFFIByteArrayPtr; }
  TVM_FFI_INLINE static std::string TypeSchema() {
    return R"({"type":")" + std::string(StaticTypeKey::kTVMFFIByteArrayPtr) + R"("})";
  }
};

template <>
inline constexpr bool use_default_type_traits_v<std::string> = false;

template <>
struct TypeTraits<std::string>
    : public FallbackOnlyTraitsBase<std::string, const char*, TVMFFIByteArray*, Bytes, String> {
  TVM_FFI_INLINE static void CopyToAnyView(const std::string& src, TVMFFIAny* result) {
    result->type_index = TypeIndex::kTVMFFIRawStr;
    result->zero_padding = 0;
    result->v_c_str = src.c_str();
  }

  TVM_FFI_INLINE static void MoveToAny(std::string src, TVMFFIAny* result) {
    // when we need to move to any, convert to owned object first
    TypeTraits<String>::MoveToAny(String(std::move(src)), result);
  }

  TVM_FFI_INLINE static std::string TypeStr() { return "std::string"; }
  TVM_FFI_INLINE static std::string TypeSchema() { return R"({"type":"std::string"})"; }

  TVM_FFI_INLINE static std::string ConvertFallbackValue(const char* src) {
    return std::string(src);
  }

  TVM_FFI_INLINE static std::string ConvertFallbackValue(TVMFFIByteArray* src) {
    return std::string(src->data, src->size);
  }

  // NOLINTNEXTLINE(performance-unnecessary-value-param)
  TVM_FFI_INLINE static std::string ConvertFallbackValue(Bytes src) {
    return src.operator std::string();
  }

  // NOLINTNEXTLINE(performance-unnecessary-value-param)
  TVM_FFI_INLINE static std::string ConvertFallbackValue(String src) {
    return src.operator std::string();
  }
};

inline String operator+(const String& lhs, const String& rhs) {
  size_t lhs_size = lhs.size();
  size_t rhs_size = rhs.size();
  return String::Concat(lhs.data(), lhs_size, rhs.data(), rhs_size);
}

inline String operator+(const String& lhs, const std::string& rhs) {
  size_t lhs_size = lhs.size();
  size_t rhs_size = rhs.size();
  return String::Concat(lhs.data(), lhs_size, rhs.data(), rhs_size);
}

inline String operator+(const std::string& lhs, const String& rhs) {
  size_t lhs_size = lhs.size();
  size_t rhs_size = rhs.size();
  return String::Concat(lhs.data(), lhs_size, rhs.data(), rhs_size);
}

inline String operator+(const char* lhs, const String& rhs) {
  size_t lhs_size = std::strlen(lhs);
  size_t rhs_size = rhs.size();
  return String::Concat(lhs, lhs_size, rhs.data(), rhs_size);
}

inline String operator+(const String& lhs, const char* rhs) {
  size_t lhs_size = lhs.size();
  size_t rhs_size = std::strlen(rhs);
  return String::Concat(lhs.data(), lhs_size, rhs, rhs_size);
}

// Overload < operator
inline bool operator<(std::nullptr_t, const String& rhs) = delete;
inline bool operator<(const String& lhs, std::nullptr_t) = delete;

inline bool operator<(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) < 0; }

inline bool operator<(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) > 0; }

inline bool operator<(const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; }

inline bool operator<(const String& lhs, const char* rhs) { return lhs.compare(rhs) < 0; }

inline bool operator<(const char* lhs, const String& rhs) { return rhs.compare(lhs) > 0; }

// Overload > operator
inline bool operator>(std::nullptr_t, const String& rhs) = delete;
inline bool operator>(const String& lhs, std::nullptr_t) = delete;

inline bool operator>(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) > 0; }

inline bool operator>(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) < 0; }

inline bool operator>(const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; }

inline bool operator>(const String& lhs, const char* rhs) { return lhs.compare(rhs) > 0; }

inline bool operator>(const char* lhs, const String& rhs) { return rhs.compare(lhs) < 0; }

// Overload <= operator
inline bool operator<=(std::nullptr_t, const String& rhs) = delete;
inline bool operator<=(const String& lhs, std::nullptr_t) = delete;

inline bool operator<=(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) <= 0; }

inline bool operator<=(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) >= 0; }

inline bool operator<=(const String& lhs, const String& rhs) { return lhs.compare(rhs) <= 0; }

inline bool operator<=(const String& lhs, const char* rhs) { return lhs.compare(rhs) <= 0; }

inline bool operator<=(const char* lhs, const String& rhs) { return rhs.compare(lhs) >= 0; }

// Overload >= operator
inline bool operator>=(std::nullptr_t, const String& rhs) = delete;
inline bool operator>=(const String& lhs, std::nullptr_t) = delete;

inline bool operator>=(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) >= 0; }

inline bool operator>=(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) <= 0; }

inline bool operator>=(const String& lhs, const String& rhs) { return lhs.compare(rhs) >= 0; }

inline bool operator>=(const String& lhs, const char* rhs) { return lhs.compare(rhs) >= 0; }

inline bool operator>=(const char* lhs, const String& rhs) { return rhs.compare(lhs) <= 0; }

// delete Overload == operator for nullptr
inline bool operator==(const String& lhs, std::nullptr_t) = delete;
inline bool operator==(std::nullptr_t, const String& rhs) = delete;

inline bool operator==(const String& lhs, const std::string& rhs) {
  return Bytes::memequal(lhs.data(), rhs.data(), lhs.size(), rhs.size());
}

inline bool operator==(const std::string& lhs, const String& rhs) {
  return Bytes::memequal(lhs.data(), rhs.data(), lhs.size(), rhs.size());
}

inline bool operator==(const String& lhs, const String& rhs) {
  return Bytes::memequal(lhs.data(), rhs.data(), lhs.size(), rhs.size());
}

inline bool operator==(const String& lhs, const char* rhs) { return lhs.compare(rhs) == 0; }

inline bool operator==(const char* lhs, const String& rhs) { return rhs.compare(lhs) == 0; }

// Overload != operator
inline bool operator!=(const String& lhs, std::nullptr_t) = delete;
inline bool operator!=(std::nullptr_t, const String& rhs) = delete;

inline bool operator!=(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) != 0; }

inline bool operator!=(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) != 0; }

inline bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; }

inline bool operator!=(const String& lhs, const char* rhs) { return lhs.compare(rhs) != 0; }

inline bool operator!=(const char* lhs, const String& rhs) { return rhs.compare(lhs) != 0; }

inline std::ostream& operator<<(std::ostream& out, const String& input) {
  out.write(input.data(), static_cast<std::streamsize>(input.size()));
  return out;
}
/// \endcond
}  // namespace ffi
}  // namespace tvm

/// \cond Doxygen_Suppress
namespace std {

template <>
struct hash<::tvm::ffi::Bytes> {
  std::size_t operator()(const ::tvm::ffi::Bytes& bytes) const {
    return std::hash<std::string_view>()(std::string_view(bytes.data(), bytes.size()));
  }
};

template <>
struct hash<::tvm::ffi::String> {
  std::size_t operator()(const ::tvm::ffi::String& str) const {
    return std::hash<std::string_view>()(std::string_view(str.data(), str.size()));
  }
};
}  // namespace std
/// \endcond
#endif  // TVM_FFI_STRING_H_
