/*
 * 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.
 */
#include <gtest/gtest.h>
#include <tvm/ffi/any.h>
#include <tvm/ffi/string.h>

namespace {

using namespace tvm::ffi;

TEST(String, MoveFromStd) {
  using namespace std;
  string source = "this is a string";
  string expect = source;
  String s(std::move(source));
  string copy = string(s);
  EXPECT_EQ(copy, expect);
  EXPECT_EQ(source.size(), 0);  // NOLINT(bugprone-use-after-move)
}

TEST(String, CopyFromStd) {
  using namespace std;
  string source = "this is a string";
  string expect = source;  // NOLINT(performance-unnecessary-copy-initialization)
  String s{source};
  string copy = string(s);
  EXPECT_EQ(copy, expect);
  EXPECT_EQ(source.size(), expect.size());
}

TEST(String, Assignment) {
  using namespace std;
  String s{string{"hello"}};
  s = string{"world"};
  EXPECT_EQ(s == "world", true);
  string s2{"world2"};
  s = std::move(s2);
  EXPECT_EQ(s == "world2", true);

  Any r;
  r = String("hello");
  EXPECT_EQ(r != nullptr, true);
}

TEST(String, empty) {
  using namespace std;
  String s{"hello"};
  EXPECT_EQ(s.empty(), false);
  s = std::string("");
  EXPECT_EQ(s.empty(), true);
}

TEST(String, Comparisons) {
  using namespace std;
  string source = "a string";
  string mismatch = "a string but longer";
  String s{"a string"};
  String m{mismatch};

  EXPECT_EQ("a str" >= s, false);
  EXPECT_EQ(s == source, true);
  EXPECT_EQ(s == mismatch, false);
  EXPECT_EQ(s == source.data(), true);
  EXPECT_EQ(s == mismatch.data(), false);

  EXPECT_EQ(s < m, source < mismatch);
  EXPECT_EQ(s > m, source > mismatch);
  EXPECT_EQ(s <= m, source <= mismatch);
  EXPECT_EQ(s >= m, source >= mismatch);
  EXPECT_EQ(s == m, source == mismatch);
  EXPECT_EQ(s != m, source != mismatch);

  EXPECT_EQ(m < s, mismatch < source);
  EXPECT_EQ(m > s, mismatch > source);
  EXPECT_EQ(m <= s, mismatch <= source);
  EXPECT_EQ(m >= s, mismatch >= source);
  EXPECT_EQ(m == s, mismatch == source);
  EXPECT_EQ(m != s, mismatch != source);
}

TEST(String, Compare) {
  // string compare const char*
  String s{"hello"};
  EXPECT_EQ(s.compare("hello"), 0);
  EXPECT_EQ(s.compare(String("hello")), 0);

  EXPECT_EQ(s.compare("hallo"), 1);
  EXPECT_EQ(s.compare(String("hallo")), 1);
  EXPECT_EQ(s.compare("hfllo"), -1);
  EXPECT_EQ(s.compare(String("hfllo")), -1);
  // s is longer
  EXPECT_EQ(s.compare("hell"), 1);
  EXPECT_EQ(s.compare(String("hell")), 1);
  // s is shorter
  EXPECT_EQ(s.compare("hello world"), -1);
  EXPECT_EQ(s.compare(String("helloworld")), -1);
}

// Check '\0' handling
TEST(String, NullByteHandling) {
  using namespace std;
  // Ensure string still compares equal if it contains '\0'.
  string v1 = "hello world";
  size_t v1_size = v1.size();
  v1[5] = '\0';
  EXPECT_EQ(v1[5], '\0');
  EXPECT_EQ(v1.size(), v1_size);
  String str_v1{v1};
  EXPECT_EQ(str_v1.compare(v1), 0);
  EXPECT_EQ(str_v1.size(), v1_size);

  // Ensure bytes after '\0' are taken into account for mismatches.
  string v2 = "aaa one";
  string v3 = "aaa two";
  v2[3] = '\0';
  v3[3] = '\0';
  String str_v2{v2};
  String str_v3{v3};
  EXPECT_EQ(str_v2.compare(str_v3), -1);
  EXPECT_EQ(str_v2.size(), 7);
  // strcmp won't be able to detect the mismatch
  EXPECT_EQ(strcmp(v2.data(), v3.data()), 0);
  // string::compare can handle \0 since it knows size
  EXPECT_LT(v2.compare(v3), 0);

  // If there is mismatch before '\0', should still handle it.
  string v4 = "acc one";
  string v5 = "abb two";
  v4[3] = '\0';
  v5[3] = '\0';
  String str_v4{v4};
  String str_v5{v5};
  EXPECT_GT(str_v4.compare(str_v5), 0);
  EXPECT_EQ(str_v4.size(), 7);
  // strcmp is able to detect the mismatch
  EXPECT_GT(strcmp(v4.data(), v5.data()), 0);
  // string::compare can handle \0 since it knows size
  EXPECT_GT(v4.compare(v5), 0);
}

TEST(String, CompareSameMemoryRegionDifferentSize) {
  using namespace std;
  string source = "a string";
  String str_source{source};
  char* memory = const_cast<char*>(str_source.data());
  EXPECT_EQ(str_source.compare(memory), 0);
  // This changes the string size
  memory[2] = '\0';
  // memory is logically shorter now
  EXPECT_GT(str_source.compare(memory), 0);
}

TEST(String, compare) {
  using namespace std;
  constexpr auto mismatch1_cstr = "a string but longer";
  string source = "a string";
  string mismatch1 = mismatch1_cstr;
  string mismatch2 = "a strin";
  string mismatch3 = "a b";
  string mismatch4 = "a t";
  String str_source{source};
  String str_mismatch1{mismatch1_cstr};
  String str_mismatch2{mismatch2};
  String str_mismatch3{mismatch3};
  String str_mismatch4{mismatch4};

  // compare with string
  EXPECT_EQ(str_source.compare(source), 0);
  EXPECT_TRUE(str_source == source);
  EXPECT_TRUE(source == str_source);
  EXPECT_TRUE(str_source <= source);
  EXPECT_TRUE(source <= str_source);
  EXPECT_TRUE(str_source >= source);
  EXPECT_TRUE(source >= str_source);
  EXPECT_LT(str_source.compare(mismatch1), 0);
  EXPECT_TRUE(str_source < mismatch1);
  EXPECT_TRUE(mismatch1 != str_source);
  EXPECT_GT(str_source.compare(mismatch2), 0);
  EXPECT_TRUE(str_source > mismatch2);
  EXPECT_TRUE(mismatch2 < str_source);
  EXPECT_GT(str_source.compare(mismatch3), 0);
  EXPECT_TRUE(str_source > mismatch3);
  EXPECT_LT(str_source.compare(mismatch4), 0);
  EXPECT_TRUE(str_source < mismatch4);
  EXPECT_TRUE(mismatch4 > str_source);

  // compare with char*
  EXPECT_EQ(str_source.compare(source.data()), 0);
  EXPECT_TRUE(str_source == source.data());
  EXPECT_TRUE(source.data() == str_source);
  EXPECT_TRUE(str_source <= source.data());
  EXPECT_TRUE(source <= str_source.data());
  EXPECT_TRUE(str_source >= source.data());
  EXPECT_TRUE(source >= str_source.data());
  EXPECT_LT(str_source.compare(mismatch1.data()), 0);
  EXPECT_TRUE(str_source < mismatch1.data());
  EXPECT_TRUE(str_source != mismatch1.data());
  EXPECT_TRUE(mismatch1.data() != str_source);
  EXPECT_GT(str_source.compare(mismatch2.data()), 0);
  EXPECT_TRUE(str_source > mismatch2.data());
  EXPECT_TRUE(mismatch2.data() < str_source);
  EXPECT_GT(str_source.compare(mismatch3.data()), 0);
  EXPECT_TRUE(str_source > mismatch3.data());
  EXPECT_LT(str_source.compare(mismatch4.data()), 0);
  EXPECT_TRUE(str_source < mismatch4.data());
  EXPECT_TRUE(mismatch4.data() > str_source);

  // compare with String
  EXPECT_LT(str_source.compare(str_mismatch1), 0);
  EXPECT_TRUE(str_source < str_mismatch1);
  EXPECT_GT(str_source.compare(str_mismatch2), 0);
  EXPECT_TRUE(str_source > str_mismatch2);
  EXPECT_GT(str_source.compare(str_mismatch3), 0);
  EXPECT_TRUE(str_source > str_mismatch3);
  EXPECT_LT(str_source.compare(str_mismatch4), 0);
  EXPECT_TRUE(str_source < str_mismatch4);
}

TEST(String, CString) {
  using namespace std;
  string source = "this is a string";
  string mismatch = "mismatch";
  String s{source};

  EXPECT_EQ(std::strcmp(s.c_str(), source.data()), 0);
  EXPECT_NE(std::strcmp(s.c_str(), mismatch.data()), 0);
}

TEST(String, hash) {
  using namespace std;
  string source = "this is a string";
  String s{source};
  std::hash<String>()(s);

  std::unordered_map<String, std::string> map;
  String k1{string{"k1"}};
  string v1{"v1"};
  String k2{string{"k2"}};
  string v2{"v2"};
  map[k1] = v1;
  map[k2] = v2;

  EXPECT_EQ(map[k1], v1);
  EXPECT_EQ(map[k2], v2);
}

TEST(String, Cast) {
  using namespace std;
  string source = "this is a string";
  String s{source};
  Any r = s;
  String s2 = r.cast<String>();
}

TEST(String, Concat) {
  String s1("hello");
  String s2("world");
  std::string s3("world");
  String res1 = s1 + s2;
  String res2 = s1 + s3;
  String res3 = s3 + s1;
  String res4 = s1 + "world";
  String res5 = "world" + s1;

  EXPECT_EQ(res1.compare("helloworld"), 0);
  EXPECT_EQ(res2.compare("helloworld"), 0);
  EXPECT_EQ(res3.compare("worldhello"), 0);
  EXPECT_EQ(res4.compare("helloworld"), 0);
  EXPECT_EQ(res5.compare("worldhello"), 0);

  String storage_scope;
  String res = "The input storage scope \"" + storage_scope + "\" is invalid.";
  EXPECT_EQ(res.compare("The input storage scope \"\" is invalid."), 0);
}

TEST(String, Any) {
  // test anyview promotion to any
  AnyView view = "hello";
  EXPECT_EQ(view.type_index(), TypeIndex::kTVMFFIRawStr);

  Any b = view;
  EXPECT_EQ(b.type_index(), TypeIndex::kTVMFFISmallStr);
  // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
  EXPECT_EQ(b.as<String>().value(), "hello");
  EXPECT_TRUE(b.as<String>().has_value());
  // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
  EXPECT_EQ(b.try_cast<std::string>().value(), "hello");

  std::string s_world = "world";
  view = s_world;
  // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
  EXPECT_EQ(view.try_cast<std::string>().value(), "world");

  String s{"hello"};
  Any a = s;
  EXPECT_EQ(a.type_index(), TypeIndex::kTVMFFISmallStr);
  // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
  EXPECT_EQ(a.as<String>().value(), "hello");
  // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
  EXPECT_EQ(a.try_cast<std::string>().value(), "hello");

  Any c = "long string very long";
  EXPECT_EQ(c.type_index(), TypeIndex::kTVMFFIStr);
  // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
  EXPECT_EQ(c.as<String>().value(), "long string very long");
  // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
  EXPECT_EQ(c.try_cast<std::string>().value(), "long string very long");
}

TEST(String, Bytes) {
  Bytes b0;
  EXPECT_EQ(b0.size(), 0);
  EXPECT_EQ(b0.operator std::string(), "");

  // explicitly test zero element
  std::string s = {'\0', 'a', 'b', 'c'};
  Bytes b = s;
  EXPECT_EQ(b.size(), 4);
  EXPECT_EQ(b.operator std::string(), s);

  TVMFFIByteArray arr{s.data(), static_cast<size_t>(s.size())};
  Bytes b2 = arr;
  EXPECT_EQ(b2.size(), 4);
  EXPECT_EQ(b2.operator std::string(), s);
}

TEST(String, BytesAny) {
  std::string s = {'\0', 'a', 'b', 'c'};
  TVMFFIByteArray arr{s.data(), static_cast<size_t>(s.size())};

  AnyView view = &arr;
  EXPECT_EQ(view.type_index(), TypeIndex::kTVMFFIByteArrayPtr);
  // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
  EXPECT_EQ(view.try_cast<Bytes>().value().operator std::string(), s);

  Any b = view;
  EXPECT_EQ(b.type_index(), TypeIndex::kTVMFFISmallBytes);

  // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
  EXPECT_EQ(b.try_cast<Bytes>().value().operator std::string(), s);
  EXPECT_EQ(b.cast<std::string>(), s);

  std::string s2 = "hello long long long string";
  s2[0] = '\0';
  Any b2 = Bytes(s2);
  EXPECT_EQ(b2.type_index(), TypeIndex::kTVMFFIBytes);
  EXPECT_EQ(b2.try_cast<std::string>().value(),  // NOLINT(bugprone-unchecked-optional-access)
            s2);
  EXPECT_EQ(b2.cast<std::string>(), s2);
}

TEST(String, StdString) {
  std::string s1 = "test_string";
  AnyView view1 = s1;
  EXPECT_EQ(view1.type_index(), TypeIndex::kTVMFFIRawStr);
  EXPECT_EQ(view1.try_cast<std::string>().value(),  // NOLINT(bugprone-unchecked-optional-access)
            s1);

  TVMFFIByteArray arr1{s1.data(), static_cast<size_t>(s1.size())};
  AnyView view2 = &arr1;
  EXPECT_EQ(view2.type_index(), TypeIndex::kTVMFFIByteArrayPtr);
  EXPECT_EQ(view2.try_cast<std::string>().value(),  // NOLINT(bugprone-unchecked-optional-access)
            s1);

  Bytes bytes1 = s1;
  AnyView view3 = bytes1;
  EXPECT_EQ(view3.type_index(), TypeIndex::kTVMFFIBytes);
  EXPECT_EQ(view3.try_cast<std::string>().value(),  // NOLINT(bugprone-unchecked-optional-access)
            s1);

  String string1 = s1;
  AnyView view4 = string1;
  EXPECT_EQ(view4.type_index(), TypeIndex::kTVMFFIStr);
  EXPECT_EQ(view4.try_cast<std::string>().value(),  // NOLINT(bugprone-unchecked-optional-access)
            s1);

  // Test with Any
  Any any1 = s1;
  EXPECT_EQ(any1.type_index(), TypeIndex::kTVMFFIStr);
  EXPECT_EQ(any1.try_cast<std::string>().value(),  // NOLINT(bugprone-unchecked-optional-access)
            s1);

  Any any2 = &arr1;
  EXPECT_EQ(any2.type_index(), TypeIndex::kTVMFFIBytes);
  EXPECT_EQ(any2.try_cast<std::string>().value(),  // NOLINT(bugprone-unchecked-optional-access)
            s1);

  Any any3 = bytes1;
  EXPECT_EQ(any3.type_index(), TypeIndex::kTVMFFIBytes);
  EXPECT_EQ(any3.try_cast<std::string>().value(),  // NOLINT(bugprone-unchecked-optional-access)
            s1);

  Any any4 = string1;
  EXPECT_EQ(any4.type_index(), TypeIndex::kTVMFFIStr);
  EXPECT_EQ(any4.try_cast<std::string>().value(),  // NOLINT(bugprone-unchecked-optional-access)
            s1);
}

TEST(String, CAPIAccessor) {
  using namespace std;
  String s{"hello"};
  TVMFFIByteArray arr{s.data(), s.size()};
  EXPECT_EQ(arr.size, 5);
  EXPECT_EQ(std::string(arr.data, arr.size), "hello");
}

TEST(String, BytesHash) {
  std::vector<int64_t> data1(10);
  std::vector<int64_t> data2(11);
  for (size_t i = 0; i < data1.size(); ++i) {
    data1[i] = static_cast<int64_t>(i);
  }
  char* data1_ptr = reinterpret_cast<char*>(data1.data());
  char* data2_ptr = reinterpret_cast<char*>(data2.data()) + 1;
  std::memcpy(data2_ptr, data1.data(), data1.size() * sizeof(int64_t));
  // has of aligned and unaligned data should be the same
  uint64_t hash1 = details::StableHashBytes(data1_ptr, data1.size() * sizeof(int64_t));
  uint64_t hash2 = details::StableHashBytes(data2_ptr, data1.size() * sizeof(int64_t));
  EXPECT_EQ(hash1, hash2);
}

TEST(String, StdHash) {
  String s1 = "a";
  String s2(std::string("a"));
  EXPECT_EQ(std::hash<String>()(s1), std::hash<String>()(s2));

  Bytes s3("a", 1);
  Bytes s4(std::string("a"));
  EXPECT_EQ(std::hash<Bytes>()(s3), std::hash<Bytes>()(s4));
}

TEST(String, Find) {
  String s{"hello world"};
  EXPECT_EQ(s.find("world"), 6);
  EXPECT_EQ(s.find("hello"), 0);
  EXPECT_EQ(s.find("o"), 4);
  EXPECT_EQ(s.find("o", 5), 7);
  EXPECT_EQ(s.find("notfound"), String::npos);
  EXPECT_EQ(s.find(""), 0);
  EXPECT_EQ(s.find("", 5), 5);
  EXPECT_EQ(s.find("", 11), 11);
  EXPECT_EQ(s.find("", 20), String::npos);

  String pattern{"world"};
  EXPECT_EQ(s.find(pattern), 6);

  String empty{""};
  EXPECT_EQ(empty.find("x"), String::npos);
  EXPECT_EQ(empty.find(""), 0);
}

TEST(String, Substr) {
  String s{"hello world"};
  EXPECT_EQ(s.substr(0, 5), "hello");
  EXPECT_EQ(s.substr(6, 5), "world");
  EXPECT_EQ(s.substr(6), "world");
  EXPECT_EQ(s.substr(0), "hello world");
  EXPECT_EQ(s.substr(11), "");
  EXPECT_EQ(s.substr(0, 0), "");

  EXPECT_THROW(s.substr(12), std::out_of_range);
  EXPECT_THROW(s.substr(100), std::out_of_range);

  String empty{""};
  EXPECT_EQ(empty.substr(0), "");
  EXPECT_THROW(empty.substr(1), std::out_of_range);
}

TEST(String, StartsWith) {
  String s{"hello world"};
  EXPECT_TRUE(s.starts_with("hello"));
  EXPECT_TRUE(s.starts_with("h"));
  EXPECT_TRUE(s.starts_with(""));
  EXPECT_TRUE(s.starts_with(String{"hello"}));
  EXPECT_TRUE(s.starts_with(std::string_view{"hello"}));
  EXPECT_FALSE(s.starts_with("world"));
  EXPECT_FALSE(s.starts_with("Hello"));
  EXPECT_FALSE(s.starts_with("hello world extra"));
  EXPECT_FALSE(s.starts_with(std::string_view{"world"}));

  String empty{""};
  EXPECT_TRUE(empty.starts_with(""));
  EXPECT_TRUE(empty.starts_with(std::string_view{""}));
  EXPECT_FALSE(empty.starts_with("x"));

  String single{"x"};
  EXPECT_TRUE(single.starts_with("x"));
  EXPECT_TRUE(single.starts_with(""));
  EXPECT_FALSE(single.starts_with("xy"));
}

TEST(String, EndsWith) {
  String s{"hello world"};
  EXPECT_TRUE(s.ends_with("world"));
  EXPECT_TRUE(s.ends_with("d"));
  EXPECT_TRUE(s.ends_with(""));
  EXPECT_TRUE(s.ends_with(String{"world"}));
  EXPECT_TRUE(s.ends_with(std::string_view{"world"}));
  EXPECT_FALSE(s.ends_with("hello"));
  EXPECT_FALSE(s.ends_with("World"));
  EXPECT_FALSE(s.ends_with("extra hello world"));
  EXPECT_FALSE(s.ends_with(std::string_view{"hello"}));

  String empty{""};
  EXPECT_TRUE(empty.ends_with(""));
  EXPECT_TRUE(empty.ends_with(std::string_view{""}));
  EXPECT_FALSE(empty.ends_with("x"));

  String single{"x"};
  EXPECT_TRUE(single.ends_with("x"));
  EXPECT_TRUE(single.ends_with(""));
  EXPECT_FALSE(single.ends_with("yx"));
}

TEST(String, Split) {
  String s{"a,b,c"};
  auto parts = s.Split(',');
  ASSERT_EQ(parts.size(), 3);
  EXPECT_EQ(parts[0], "a");
  EXPECT_EQ(parts[1], "b");
  EXPECT_EQ(parts[2], "c");

  // No delimiter present
  String s2{"hello"};
  auto parts2 = s2.Split(',');
  ASSERT_EQ(parts2.size(), 1);
  EXPECT_EQ(parts2[0], "hello");

  // Empty string
  String s3{""};
  auto parts3 = s3.Split(',');
  ASSERT_EQ(parts3.size(), 1);
  EXPECT_EQ(parts3[0], "");

  // Delimiter at boundaries
  String s4{",a,b,"};
  auto parts4 = s4.Split(',');
  ASSERT_EQ(parts4.size(), 4);
  EXPECT_EQ(parts4[0], "");
  EXPECT_EQ(parts4[1], "a");
  EXPECT_EQ(parts4[2], "b");
  EXPECT_EQ(parts4[3], "");

  // Consecutive delimiters
  String s5{"a,,b"};
  auto parts5 = s5.Split(',');
  ASSERT_EQ(parts5.size(), 3);
  EXPECT_EQ(parts5[0], "a");
  EXPECT_EQ(parts5[1], "");
  EXPECT_EQ(parts5[2], "b");
}

TEST(String, EscapeStringJSON) {
  // Basic escaping
  String s1{"hello"};
  EXPECT_EQ(EscapeStringJSON(s1), "\"hello\"");

  // Special characters
  String s2{"line1\nline2\ttab"};
  EXPECT_EQ(EscapeStringJSON(s2), "\"line1\\nline2\\ttab\"");

  // Backslash and quote
  String s3{"a\\b\"c"};
  EXPECT_EQ(EscapeStringJSON(s3), "\"a\\\\b\\\"c\"");

  // Control characters
  String s4{std::string("a\x01\x1f z", 5)};
  EXPECT_EQ(EscapeStringJSON(s4), "\"a\\u0001\\u001f z\"");
}

TEST(String, EscapedStringPyBasic) {
  // Plain ASCII
  String s1{"hello world"};
  EXPECT_EQ(EscapedStringPy(s1), "\"hello world\"");

  // C escape sequences
  String s2{"a\nb\tc\r"};
  EXPECT_EQ(EscapedStringPy(s2), "\"a\\nb\\tc\\r\"");

  // Backslash and quote
  String s3{"a\\b\"c"};
  EXPECT_EQ(EscapedStringPy(s3), "\"a\\\\b\\\"c\"");
}

TEST(String, EscapedStringPyControlChars) {
  // Control characters -> \xNN
  String s1{std::string("\x01\x02\x7f", 3)};
  String result = EscapedStringPy(s1);
  EXPECT_EQ(result, "\"\\x01\\x02\\x7f\"");
}

TEST(String, EscapedStringPyANSI) {
  // ANSI escape: ESC[31m (red)
  String s1{std::string("\x1b[31mred\x1b[0m", 12)};
  String result = EscapedStringPy(s1);
  EXPECT_EQ(result, "\"\\x1b[31mred\\x1b[0m\"");

  // ANSI erase line: ESC[K
  String s2{std::string("\x1b[K", 3)};
  EXPECT_EQ(EscapedStringPy(s2), "\"\\x1b[K\"");
}

TEST(String, EscapedStringPyUTF8) {
  // 2-byte: U+00E9 (é) = C3 A9
  String s1{std::string("\xc3\xa9", 2)};
  EXPECT_EQ(EscapedStringPy(s1), "\"\\u00e9\"");

  // 3-byte: U+4E16 (世) = E4 B8 96
  String s2{std::string("\xe4\xb8\x96", 3)};
  EXPECT_EQ(EscapedStringPy(s2), "\"\\u4e16\"");

  // 4-byte: U+1F600 (😀) = F0 9F 98 80
  String s3{std::string("\xf0\x9f\x98\x80", 4)};
  EXPECT_EQ(EscapedStringPy(s3), "\"\\U0001f600\"");
}

TEST(String, EscapedStringPyMalformedUTF8) {
  // Lone continuation byte -> \xNN fallback
  String s1{std::string("\x80", 1)};
  EXPECT_EQ(EscapedStringPy(s1), "\"\\x80\"");

  // 2-byte leader followed by non-continuation -> fallback for leader
  String s2{std::string("\xc3\x20", 2)};
  String result2 = EscapedStringPy(s2);
  EXPECT_EQ(result2, "\"\\xc3 \"");

  // 3-byte leader with bad continuation -> fallback for leader
  String s3{std::string("\xe4\xb8\x20", 3)};
  String result3 = EscapedStringPy(s3);
  EXPECT_EQ(result3, "\"\\xe4\\xb8 \"");

  // Truncated 2-byte at end of string
  String s4{std::string("\xc3", 1)};
  EXPECT_EQ(EscapedStringPy(s4), "\"\\xc3\"");
}

}  // namespace
