/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed 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 "ecmascript/builtins/builtins_string.h"

#include <algorithm>

#include "ecmascript/base/number_helper.h"
#include "ecmascript/base/string_helper.h"
#include "ecmascript/builtins/builtins_json.h"
#include "ecmascript/builtins/builtins_regexp.h"
#include "ecmascript/builtins/builtins_symbol.h"
#include "ecmascript/ecma_runtime_call_info.h"
#include "ecmascript/ecma_string-inl.h"
#include "ecmascript/ecma_vm.h"
#include "ecmascript/global_env.h"
#include "ecmascript/internal_call_params.h"
#include "ecmascript/interpreter/fast_runtime_stub-inl.h"
#include "ecmascript/js_array.h"
#include "ecmascript/js_collator.h"
#include "ecmascript/js_hclass.h"
#include "ecmascript/js_invoker.h"
#include "ecmascript/js_locale.h"
#include "ecmascript/js_object-inl.h"
#include "ecmascript/js_primitive_ref.h"
#include "ecmascript/js_string_iterator.h"
#include "ecmascript/js_tagged_value-inl.h"
#include "ecmascript/mem/c_containers.h"
#include "ecmascript/object_factory.h"
#include "ecmascript/tagged_array-inl.h"
#include "ecmascript/tagged_array.h"
#include "unicode/normalizer2.h"
#include "unicode/normlzr.h"
#include "unicode/unistr.h"

namespace panda::ecmascript::builtins {
using ObjectFactory = ecmascript::ObjectFactory;
using JSArray = ecmascript::JSArray;

// 21.1.1.1 String(value)
JSTaggedValue BuiltinsString::StringConstructor(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, Constructor);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
    if (argv->GetArgsNumber() > 0) {
        JSHandle<JSTaggedValue> valTagNew = GetCallArg(argv, 0);
        if (newTarget->IsUndefined() && valTagNew->IsSymbol()) {
            return BuiltinsSymbol::SymbolDescriptiveString(thread, valTagNew.GetTaggedValue());
        }
        JSHandle<EcmaString> str = JSTaggedValue::ToString(thread, valTagNew);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        if (newTarget->IsUndefined()) {
            return str.GetTaggedValue();
        }
        JSHandle<JSTaggedValue> strTag(str);
        return JSPrimitiveRef::StringCreate(thread, strTag).GetTaggedValue();
    }
    JSHandle<EcmaString> val = factory->GetEmptyString();
    JSHandle<JSTaggedValue> valTag(val);
    if (newTarget->IsUndefined()) {
        return factory->GetEmptyString().GetTaggedValue();
    }
    return JSPrimitiveRef::StringCreate(thread, valTag).GetTaggedValue();
}

// 21.1.2.1
JSTaggedValue BuiltinsString::FromCharCode(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, FromCharCode);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    int32_t argLength = argv->GetArgsNumber();
    if (argLength == 0) {
        return factory->GetEmptyString().GetTaggedValue();
    }
    JSHandle<JSTaggedValue> codePointTag = BuiltinsString::GetCallArg(argv, 0);
    uint16_t codePointValue = JSTaggedValue::ToUint16(thread, codePointTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    JSHandle<EcmaString> strHandle = factory->NewFromUtf16Literal(&codePointValue, 1);
    if (argLength == 1) {
        return strHandle.GetTaggedValue();
    }
    std::u16string u16str = base::StringHelper::Utf16ToU16String(&codePointValue, 1);
    CVector<uint16_t> valueTable;
    valueTable.reserve(argLength - 1);
    for (int32_t i = 1; i < argLength; i++) {
        JSHandle<JSTaggedValue> nextCp = BuiltinsString::GetCallArg(argv, i);
        uint16_t nextCv = JSTaggedValue::ToUint16(thread, nextCp);
        valueTable.emplace_back(nextCv);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    }
    std::u16string nextU16str = base::StringHelper::Utf16ToU16String(valueTable.data(), argLength - 1);
    u16str = base::StringHelper::Append(u16str, nextU16str);
    const char16_t *constChar16tData = u16str.data();
    auto *char16tData = const_cast<char16_t *>(constChar16tData);
    auto *uint16tData = reinterpret_cast<uint16_t *>(char16tData);
    int32_t u16strSize = u16str.size();
    return factory->NewFromUtf16Literal(uint16tData, u16strSize).GetTaggedValue();
}

// 21.1.2.2
JSTaggedValue BuiltinsString::FromCodePoint(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, FromCodePoint);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    int32_t argLength = argv->GetArgsNumber();
    if (argLength == 0) {
        return factory->GetEmptyString().GetTaggedValue();
    }
    std::u16string u16str;
    int32_t u16strSize = argLength;
    for (int i = 0; i < argLength; i++) {
        JSHandle<JSTaggedValue> nextCpTag = BuiltinsString::GetCallArg(argv, i);
        JSTaggedNumber nextCpVal = JSTaggedValue::ToNumber(thread, nextCpTag);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        if (!nextCpVal.IsInteger()) {
            THROW_RANGE_ERROR_AND_RETURN(thread, "is not integer", JSTaggedValue::Exception());
        }
        int32_t cp = nextCpVal.ToInt32();
        if (cp < 0 || cp > ENCODE_MAX_UTF16) {
            THROW_RANGE_ERROR_AND_RETURN(thread, "CodePoint < 0 or CodePoint > 0x10FFFF", JSTaggedValue::Exception());
        }
        if (cp == 0) {
            CVector<uint16_t> data {0x00};
            return factory->NewFromUtf16Literal(data.data(), 1).GetTaggedValue();
        }
        if (cp > UINT16_MAX) {
            uint16_t cu1 = std::floor((cp - ENCODE_SECOND_FACTOR) / ENCODE_FIRST_FACTOR) + ENCODE_LEAD_LOW;
            uint16_t cu2 = ((cp - ENCODE_SECOND_FACTOR) % ENCODE_FIRST_FACTOR) + ENCODE_TRAIL_LOW;
            std::u16string nextU16str1 = base::StringHelper::Utf16ToU16String(&cu1, 1);
            std::u16string nextU16str2 = base::StringHelper::Utf16ToU16String(&cu2, 1);
            u16str = base::StringHelper::Append(u16str, nextU16str1);
            u16str = base::StringHelper::Append(u16str, nextU16str2);
            u16strSize++;
        } else {
            auto u16tCp = static_cast<uint16_t>(cp);
            std::u16string nextU16str = base::StringHelper::Utf16ToU16String(&u16tCp, 1);
            u16str = base::StringHelper::Append(u16str, nextU16str);
        }
    }
    const char16_t *constChar16tData = u16str.data();
    auto *char16tData = const_cast<char16_t *>(constChar16tData);
    auto *uint16tData = reinterpret_cast<uint16_t *>(char16tData);
    return factory->NewFromUtf16Literal(uint16tData, u16strSize).GetTaggedValue();
}

// 21.1.2.4
JSTaggedValue BuiltinsString::Raw(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, Raw);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    // Let cooked be ToObject(template).
    JSHandle<JSObject> cooked = JSTaggedValue::ToObject(thread, BuiltinsString::GetCallArg(argv, 0));
    // ReturnIfAbrupt(cooked).
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    // Let raw be ToObject(Get(cooked, "raw")).
    JSHandle<JSTaggedValue> rawKey(factory->NewFromCanBeCompressString("raw"));
    JSHandle<JSTaggedValue> rawTag =
        JSObject::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(cooked), rawKey).GetValue();
    JSHandle<JSObject> rawObj = JSTaggedValue::ToObject(thread, rawTag);
    // ReturnIfAbrupt(rawObj).
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    JSHandle<JSTaggedValue> lengthKey = thread->GlobalConstants()->GetHandledLengthString();
    JSHandle<JSTaggedValue> rawLen =
        JSObject::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(rawObj), lengthKey).GetValue();
    // ReturnIfAbrupt(rawLen).
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    JSTaggedNumber lengthNumber = JSTaggedValue::ToLength(thread, rawLen);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int length = lengthNumber.ToUint32();
    if (length <= 0) {
        return factory->GetEmptyString().GetTaggedValue();
    }

    std::u16string u16str;
    int argc = static_cast<int>(argv->GetArgsNumber()) - 1;
    bool canBeCompress = true;
    for (int i = 0, argsI = 1; i < length; ++i, ++argsI) {
        // Let nextSeg be ToString(Get(raw, nextKey)).
        JSHandle<JSTaggedValue> elementString =
            JSObject::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(rawObj), i).GetValue();
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        EcmaString *nextSeg = *JSTaggedValue::ToString(thread, elementString);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        if (nextSeg->IsUtf16()) {
            u16str += base::StringHelper::Utf16ToU16String(nextSeg->GetDataUtf16(), nextSeg->GetLength());
            canBeCompress = false;
        } else {
            u16str += base::StringHelper::Utf8ToU16String(nextSeg->GetDataUtf8(), nextSeg->GetLength());
        }
        if (i + 1 == length) {
            break;
        }
        if (argsI <= argc) {
            EcmaString *nextSub = *JSTaggedValue::ToString(thread, GetCallArg(argv, argsI));
            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
            if (nextSub->IsUtf16()) {
                u16str += base::StringHelper::Utf16ToU16String(nextSub->GetDataUtf16(), nextSub->GetLength());
                canBeCompress = false;
            } else {
                u16str += base::StringHelper::Utf8ToU16String(nextSub->GetDataUtf8(), nextSub->GetLength());
            }
        }
    }
    // return the result string
    auto *uint16tData = reinterpret_cast<uint16_t *>(const_cast<char16_t *>(u16str.data()));
    return factory->NewFromUtf16LiteralUnCheck(uint16tData, u16str.size(), canBeCompress).GetTaggedValue();
}

// 21.1.3.1
JSTaggedValue BuiltinsString::CharAt(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, CharAt);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t thisLen = thisHandle->GetLength();
    JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 0);
    int32_t pos;
    if (posTag->IsUndefined()) {
        pos = 0;
    } else {
        JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        pos = posVal.ToInt32();
    }
    if (pos < 0 || pos >= thisLen) {
        return factory->GetEmptyString().GetTaggedValue();
    }
    uint16_t res = thisHandle->At<false>(pos);
    return factory->NewFromUtf16Literal(&res, 1).GetTaggedValue();
}

// 21.1.3.2
JSTaggedValue BuiltinsString::CharCodeAt(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, CharCodeAt);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t thisLen = thisHandle->GetLength();
    JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 0);
    int32_t pos;
    if (posTag->IsUndefined()) {
        pos = 0;
    } else {
        JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        pos = posVal.ToInt32();
    }
    if (pos < 0 || pos >= thisLen) {
        return GetTaggedDouble(base::NAN_VALUE);
    }
    uint16_t ret = thisHandle->At<false>(pos);
    return GetTaggedInt(ret);
}

// 21.1.3.3
JSTaggedValue BuiltinsString::CodePointAt(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, CodePointAt);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 0);

    JSTaggedNumber posVal = JSTaggedValue::ToNumber(thread, posTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t pos = base::NumberHelper::DoubleInRangeInt32(posVal.GetNumber());
    int32_t thisLen = thisHandle->GetLength();
    if (pos < 0 || pos >= thisLen) {
        return JSTaggedValue::Undefined();
    }
    uint16_t first = thisHandle->At<false>(pos);
    if (first < base::utf_helper::DECODE_LEAD_LOW || first > base::utf_helper::DECODE_LEAD_HIGH || pos + 1 == thisLen) {
        return GetTaggedInt(first);
    }
    uint16_t second = thisHandle->At<false>(pos + 1);
    if (second < base::utf_helper::DECODE_TRAIL_LOW || second > base::utf_helper::DECODE_TRAIL_HIGH) {
        return GetTaggedInt(first);
    }
    uint32_t res = base::utf_helper::UTF16Decode(first, second);
    return GetTaggedInt(res);
}

// 21.1.3.4
JSTaggedValue BuiltinsString::Concat(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, Concat);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t thisLen = thisHandle->GetLength();
    int32_t argLength = argv->GetArgsNumber();
    if (argLength == 0) {
        return thisHandle.GetTaggedValue();
    }
    std::u16string u16strThis;
    std::u16string u16strNext;
    bool canBeCompress = true;
    if (thisHandle->IsUtf16()) {
        u16strThis = base::StringHelper::Utf16ToU16String(thisHandle->GetDataUtf16(), thisLen);
        canBeCompress = false;
    } else {
        u16strThis = base::StringHelper::Utf8ToU16String(thisHandle->GetDataUtf8(), thisLen);
    }
    for (int i = 0; i < argLength; i++) {
        JSHandle<JSTaggedValue> nextTag = BuiltinsString::GetCallArg(argv, i);
        JSHandle<EcmaString> nextHandle = JSTaggedValue::ToString(thread, nextTag);
        int32_t nextLen = nextHandle->GetLength();
        if (nextHandle->IsUtf16()) {
            u16strNext = base::StringHelper::Utf16ToU16String(nextHandle->GetDataUtf16(), nextLen);
            canBeCompress = false;
        } else {
            u16strNext = base::StringHelper::Utf8ToU16String(nextHandle->GetDataUtf8(), nextLen);
        }
        u16strThis = base::StringHelper::Append(u16strThis, u16strNext);
    }
    const char16_t *constChar16tData = u16strThis.data();
    auto *char16tData = const_cast<char16_t *>(constChar16tData);
    auto *uint16tData = reinterpret_cast<uint16_t *>(char16tData);
    int32_t u16strSize = u16strThis.size();
    return factory->NewFromUtf16LiteralUnCheck(uint16tData, u16strSize, canBeCompress).GetTaggedValue();
}

// 21.1.3.5 String.prototype.constructor
// 21.1.3.6
JSTaggedValue BuiltinsString::EndsWith(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, EndsWith);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0);
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    bool isRegexp = JSObject::IsRegExp(thread, searchTag);
    if (isRegexp) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "is regexp", JSTaggedValue::Exception());
    }
    JSHandle<EcmaString> searchHandle = JSTaggedValue::ToString(thread, searchTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t thisLen = thisHandle->GetLength();
    int32_t searchLen = searchHandle->GetLength();
    int32_t pos;
    JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 1);
    if (posTag->IsUndefined()) {
        pos = thisLen;
    } else {
        JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        pos = posVal.ToInt32();
    }
    int32_t end = std::min(std::max(pos, 0), thisLen);
    int32_t start = end - searchLen;
    if (start < 0) {
        return BuiltinsString::GetTaggedBoolean(false);
    }
    std::u16string u16strThis;
    std::u16string u16strSearch;
    if (thisHandle->IsUtf16()) {
        u16strThis = base::StringHelper::Utf16ToU16String(thisHandle->GetDataUtf16(), thisLen);
    } else {
        const uint8_t *uint8This = thisHandle->GetDataUtf8();
        u16strThis = base::StringHelper::Utf8ToU16String(uint8This, thisLen);
    }
    if (searchHandle->IsUtf16()) {
        u16strSearch = base::StringHelper::Utf16ToU16String(searchHandle->GetDataUtf16(), searchLen);
    } else {
        const uint8_t *uint8Search = searchHandle->GetDataUtf8();
        u16strSearch = base::StringHelper::Utf8ToU16String(uint8Search, searchLen);
    }
    int32_t idx = base::StringHelper::Find(u16strThis, u16strSearch, start);
    if (idx == start) {
        return BuiltinsString::GetTaggedBoolean(true);
    }
    return BuiltinsString::GetTaggedBoolean(false);
}

// 21.1.3.7
JSTaggedValue BuiltinsString::Includes(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, Includes);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0);
    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    bool isRegexp = JSObject::IsRegExp(thread, searchTag);
    if (isRegexp) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "is regexp", JSTaggedValue::Exception());
    }
    JSHandle<EcmaString> searchHandle = JSTaggedValue::ToString(thread, searchTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t thisLen = thisHandle->GetLength();
    int32_t searchLen = searchHandle->GetLength();
    int32_t pos = 0;
    JSHandle<JSTaggedValue> posTag = BuiltinsBase::GetCallArg(argv, 1);
    if (argv->GetArgsNumber() == 1) {
        pos = 0;
    } else {
        JSTaggedNumber posVal = JSTaggedValue::ToNumber(thread, posTag);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        pos = base::NumberHelper::DoubleInRangeInt32(posVal.GetNumber());
    }
    int32_t start = std::min(std::max(pos, 0), thisLen);
    std::u16string u16strThis;
    std::u16string u16strSearch;
    if (thisHandle->IsUtf16()) {
        u16strThis = base::StringHelper::Utf16ToU16String(thisHandle->GetDataUtf16(), thisLen);
    } else {
        const uint8_t *uint8This = thisHandle->GetDataUtf8();
        u16strThis = base::StringHelper::Utf8ToU16String(uint8This, thisLen);
    }
    if (searchHandle->IsUtf16()) {
        u16strSearch = base::StringHelper::Utf16ToU16String(searchHandle->GetDataUtf16(), searchLen);
    } else {
        const uint8_t *uint8Search = searchHandle->GetDataUtf8();
        u16strSearch = base::StringHelper::Utf8ToU16String(uint8Search, searchLen);
    }
    int32_t idx = base::StringHelper::Find(u16strThis, u16strSearch, start);
    if (idx < 0 || idx > thisLen) {
        return BuiltinsString::GetTaggedBoolean(false);
    }
    return BuiltinsString::GetTaggedBoolean(true);
}

// 21.1.3.8
JSTaggedValue BuiltinsString::IndexOf(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, IndexOf);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0);
    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t thisLen = thisHandle->GetLength();
    JSHandle<EcmaString> searchHandle = JSTaggedValue::ToString(thread, searchTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t searchLen = searchHandle->GetLength();
    int32_t pos;
    if (argv->GetArgsNumber() == 1) {
        pos = 0;
    } else {
        JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 1);
        JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        pos = posVal.ToInt32();
    }
    pos = std::min(std::max(pos, 0), thisLen);
    if (thisHandle->IsUtf8() && searchHandle->IsUtf8()) {
        std::string thisString = base::StringHelper::Utf8ToString(thisHandle->GetDataUtf8(), thisLen);
        std::string searchString = base::StringHelper::Utf8ToString(searchHandle->GetDataUtf8(), searchLen);
        int32_t res = base::StringHelper::Find(thisString, searchString, pos);
        if (res >= 0 && res < thisLen) {
            return GetTaggedInt(res);
        }
        return GetTaggedInt(-1);
    }
    std::u16string u16strThis;
    std::u16string u16strSearch;
    if (thisHandle->IsUtf16()) {
        u16strThis = base::StringHelper::Utf16ToU16String(thisHandle->GetDataUtf16(), thisLen);
    } else {
        const uint8_t *uint8This = thisHandle->GetDataUtf8();
        u16strThis = base::StringHelper::Utf8ToU16String(uint8This, thisLen);
    }
    if (searchHandle->IsUtf16()) {
        u16strSearch = base::StringHelper::Utf16ToU16String(searchHandle->GetDataUtf16(), searchLen);
    } else {
        const uint8_t *uint8Search = searchHandle->GetDataUtf8();
        u16strSearch = base::StringHelper::Utf8ToU16String(uint8Search, searchLen);
    }
    int32_t res = base::StringHelper::Find(u16strThis, u16strSearch, pos);
    if (res >= 0 && res < thisLen) {
        return GetTaggedInt(res);
    }
    return GetTaggedInt(-1);
}

// 21.1.3.9
JSTaggedValue BuiltinsString::LastIndexOf(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, LastIndexOf);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0);
    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t thisLen = thisHandle->GetLength();
    JSHandle<EcmaString> searchHandle = JSTaggedValue::ToString(thread, searchTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t searchLen = searchHandle->GetLength();
    int32_t pos;
    if (argv->GetArgsNumber() == 1) {
        pos = thisLen;
    } else {
        JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 1);
        JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        if (std::isnan(JSTaggedValue::ToNumber(thread, posTag).GetNumber())) {
            pos = thisLen;
        } else {
            pos = posVal.ToInt32();
        }
    }
    pos = std::min(std::max(pos, 0), thisLen);
    std::u16string u16strThis;
    std::u16string u16strSearch;
    if (thisHandle->IsUtf16()) {
        u16strThis = base::StringHelper::Utf16ToU16String(thisHandle->GetDataUtf16(), thisLen);
    } else {
        const uint8_t *uint8This = thisHandle->GetDataUtf8();
        u16strThis = base::StringHelper::Utf8ToU16String(uint8This, thisLen);
    }
    if (searchHandle->IsUtf16()) {
        u16strSearch = base::StringHelper::Utf16ToU16String(searchHandle->GetDataUtf16(), searchLen);
    } else {
        const uint8_t *uint8Search = searchHandle->GetDataUtf8();
        u16strSearch = base::StringHelper::Utf8ToU16String(uint8Search, searchLen);
    }
    int32_t res = base::StringHelper::RFind(u16strThis, u16strSearch, pos);
    if (res >= 0 && res < thisLen) {
        return GetTaggedInt(res);
    }
    res = -1;
    return GetTaggedInt(res);
}

// 21.1.3.10
JSTaggedValue BuiltinsString::LocaleCompare(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, LocaleCompare);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> that_tag = BuiltinsString::GetCallArg(argv, 0);
    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    JSHandle<EcmaString> thatHandle = JSTaggedValue::ToString(thread, that_tag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    JSHandle<JSTaggedValue> locales = GetCallArg(argv, 1);
    JSHandle<JSTaggedValue> options = GetCallArg(argv, 2); // 2: the second argument
    bool cacheable = (locales->IsUndefined() || locales->IsString()) && options->IsUndefined();
    if (cacheable) {
        auto collator = JSCollator::GetCachedIcuCollator(thread, locales);
        if (collator != nullptr) {
            JSTaggedValue result = JSCollator::CompareStrings(collator, thisHandle, thatHandle);
            return result;
        }
    }
    EcmaVM *ecmaVm = thread->GetEcmaVM();
    ObjectFactory *factory = ecmaVm->GetFactory();
    JSHandle<JSTaggedValue> ctor = ecmaVm->GetGlobalEnv()->GetCollatorFunction();
    JSHandle<JSCollator> collator =
        JSHandle<JSCollator>::Cast(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(ctor), ctor));
    JSHandle<JSCollator> initCollator =
        JSCollator::InitializeCollator(thread, collator, locales, options, cacheable);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    icu::Collator *icuCollator = nullptr;
    if (cacheable) {
        icuCollator = JSCollator::GetCachedIcuCollator(thread, locales);
        ASSERT(icuCollator != nullptr);
    } else {
        icuCollator = initCollator->GetIcuCollator();
    }
    JSTaggedValue result = JSCollator::CompareStrings(icuCollator, thisHandle, thatHandle);
    return result;
}

// 21.1.3.11
JSTaggedValue BuiltinsString::Match(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, Match);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    const GlobalEnvConstants *globalConst = thread->GlobalConstants();
    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<JSTaggedValue> regexp = BuiltinsString::GetCallArg(argv, 0);
    JSHandle<JSTaggedValue> matchTag = thread->GetEcmaVM()->GetGlobalEnv()->GetMatchSymbol();
    if (!regexp->IsUndefined() && !regexp->IsNull()) {
        if (regexp->IsECMAObject()) {
            JSHandle<JSTaggedValue> matcher = JSObject::GetMethod(thread, regexp, matchTag);
            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
            if (!matcher->IsUndefined()) {
                ASSERT(matcher->IsJSFunction());
                InternalCallParams *arguments = thread->GetInternalCallParams();
                arguments->MakeArgv(thisTag);
                return JSFunction::Call(thread, matcher, regexp, 1, arguments->GetArgv());
            }
        }
    }
    JSHandle<EcmaString> thisVal = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    JSHandle<JSTaggedValue> undifinedHandle = globalConst->GetHandledUndefined();
    JSHandle<JSTaggedValue> rx(thread, BuiltinsRegExp::RegExpCreate(thread, regexp, undifinedHandle));
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    InternalCallParams *arguments = thread->GetInternalCallParams();
    arguments->MakeArgv(thisVal.GetTaggedValue());
    return JSFunction::Invoke(thread, rx, matchTag, 1, arguments->GetArgv());
}

// 21.1.3.12
JSTaggedValue BuiltinsString::Normalize(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, Normalize);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
    JSHandle<EcmaString> formValue;
    if (argv->GetArgsNumber() == 0) {
        formValue = factory->NewFromString("NFC");
    } else {
        JSHandle<JSTaggedValue> formTag = BuiltinsString::GetCallArg(argv, 0);
        if (formTag->IsUndefined()) {
            formValue = factory->NewFromString("NFC");
        } else {
            formValue = JSTaggedValue::ToString(thread, formTag);
            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
        }
    }
    JSHandle<EcmaString> nfc = factory->NewFromString("NFC");
    JSHandle<EcmaString> nfd = factory->NewFromString("NFD");
    JSHandle<EcmaString> nfkc = factory->NewFromString("NFKC");
    JSHandle<EcmaString> nfkd = factory->NewFromString("NFKD");
    if (formValue->Compare(*nfc) != 0 && formValue->Compare(*nfd) != 0 && formValue->Compare(*nfkc) != 0 &&
        formValue->Compare(*nfkd) != 0) {
        THROW_RANGE_ERROR_AND_RETURN(thread, "compare not equal", JSTaggedValue::Exception());
    }
    std::u16string u16strThis;
    if (thisHandle->IsUtf16()) {
        u16strThis = base::StringHelper::Utf16ToU16String(thisHandle->GetDataUtf16(), thisHandle->GetLength());
    } else {
        const uint8_t *uint8This = thisHandle->GetDataUtf8();
        u16strThis = base::StringHelper::Utf8ToU16String(uint8This, thisHandle->GetLength());
    }
    const char16_t *constChar16tData = u16strThis.data();
    icu::UnicodeString src(constChar16tData);
    icu::UnicodeString res;
    UErrorCode errorCode = U_ZERO_ERROR;
    UNormalizationMode uForm;
    int32_t option = 0;
    if (formValue->Compare(*nfc) == 0) {
        uForm = UNORM_NFC;
    } else if (formValue->Compare(*nfd) == 0) {
        uForm = UNORM_NFD;
    } else if (formValue->Compare(*nfkc) == 0) {
        uForm = UNORM_NFKC;
    } else if (formValue->Compare(*nfkd) == 0) {
        uForm = UNORM_NFKD;
    } else {
        UNREACHABLE();
    }

    icu::Normalizer::normalize(src, uForm, option, res, errorCode);
    JSHandle<EcmaString> str = JSLocale::IcuToString(thread, res);
    return JSTaggedValue(*str);
}

// 21.1.3.13
JSTaggedValue BuiltinsString::Repeat(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, Repeat);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t thisLen = thisHandle->GetLength();
    JSHandle<JSTaggedValue> countTag = BuiltinsString::GetCallArg(argv, 0);
    JSTaggedNumber num = JSTaggedValue::ToInteger(thread, countTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    double d = num.GetNumber();
    if (d < 0) {
        THROW_RANGE_ERROR_AND_RETURN(thread, "less than 0", JSTaggedValue::Exception());
    }
    if (d == base::POSITIVE_INFINITY) {
        THROW_RANGE_ERROR_AND_RETURN(thread, "is infinity", JSTaggedValue::Exception());
    }
    int32_t count = base::NumberHelper::DoubleInRangeInt32(d);
    std::u16string u16strThis;
    bool canBeCompress = true;
    if (thisHandle->IsUtf16()) {
        u16strThis = base::StringHelper::Utf16ToU16String(thisHandle->GetDataUtf16(), thisLen);
        canBeCompress = false;
    } else {
        const uint8_t *uint8This = thisHandle->GetDataUtf8();
        u16strThis = base::StringHelper::Utf8ToU16String(uint8This, thisLen);
    }
    if (thisLen == 0) {
        return thisHandle.GetTaggedValue();
    }

    EcmaString *res = base::StringHelper::Repeat(thread, u16strThis, count, canBeCompress);
    return JSTaggedValue(res);
}

// 21.1.3.14
JSTaggedValue BuiltinsString::Replace(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, Replace);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, BuiltinsString::GetThis(argv));
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    auto ecmaVm = thread->GetEcmaVM();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
    const GlobalEnvConstants *globalConst = thread->GlobalConstants();
    JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0);
    JSHandle<JSTaggedValue> replaceTag = BuiltinsString::GetCallArg(argv, 1);

    ObjectFactory *factory = ecmaVm->GetFactory();

    // If searchValue is neither undefined nor null, then
    if (searchTag->IsECMAObject()) {
        JSHandle<JSTaggedValue> replaceKey = env->GetReplaceSymbol();
        // Let replacer be GetMethod(searchValue, @@replace).
        JSHandle<JSTaggedValue> replaceMethod = JSObject::GetMethod(thread, searchTag, replaceKey);
        // ReturnIfAbrupt(replacer).
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        // If replacer is not undefined, then
        if (!replaceMethod->IsUndefined()) {
            // Return Call(replacer, searchValue, «O, replaceValue»).
            InternalCallParams *arguments = thread->GetInternalCallParams();
            arguments->MakeArgv(thisTag, replaceTag);
            return JSFunction::Call(thread, replaceMethod, searchTag, 2, arguments->GetArgv());  // 2: two args
        }
    }

    // Let string be ToString(O).
    JSHandle<EcmaString> thisString = JSTaggedValue::ToString(thread, thisTag);
    // ReturnIfAbrupt(string).
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    // Let searchString be ToString(searchValue).
    JSHandle<EcmaString> searchString = JSTaggedValue::ToString(thread, searchTag);
    // ReturnIfAbrupt(searchString).
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    // Let functionalReplace be IsCallable(replaceValue).
    if (!replaceTag->IsCallable()) {
        // If functionalReplace is false, then
        // Let replaceValue be ToString(replaceValue).
        // ReturnIfAbrupt(replaceValue)
        replaceTag = JSHandle<JSTaggedValue>(JSTaggedValue::ToString(thread, replaceTag));
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    }
    // Search string for the first occurrence of searchString and let pos be the index within string of the first code
    // unit of the matched substring and let matched be searchString. If no occurrences of searchString were found,
    // return string.
    int32_t pos = thisString->IndexOf(*searchString);
    if (pos == -1) {
        return thisString.GetTaggedValue();
    }

    JSMutableHandle<JSTaggedValue> replHandle(thread, factory->GetEmptyString().GetTaggedValue());
    // If functionalReplace is true, then
    if (replaceTag->IsCallable()) {
        // Let replValue be Call(replaceValue, undefined,«matched, pos, and string»).
        InternalCallParams *arguments = thread->GetInternalCallParams();
        arguments->MakeArgv(JSHandle<JSTaggedValue>(searchString),
            JSHandle<JSTaggedValue>(thread, JSTaggedValue(pos)), JSHandle<JSTaggedValue>(thisString));
        JSTaggedValue replStrDeocodeValue =
            JSFunction::Call(thread, replaceTag,
                globalConst->GetHandledUndefined(), 3, arguments->GetArgv());  // 3: «matched, pos, and string»
        replHandle.Update(replStrDeocodeValue);
    } else {
        // Let captures be an empty List.
        JSHandle<TaggedArray> capturesList = factory->NewTaggedArray(0);
        ASSERT_PRINT(replaceTag->IsString(), "replace must be string");
        JSHandle<EcmaString> replacement(thread, replaceTag->GetTaggedObject());
        // Let replStr be GetSubstitution(matched, string, pos, captures, replaceValue)
        replHandle.Update(GetSubstitution(thread, searchString, thisString, pos, capturesList, replacement));
    }
    JSHandle<EcmaString> realReplaceStr = JSTaggedValue::ToString(thread, replHandle);
    // Let tailPos be pos + the number of code units in matched.
    int32_t tailPos = pos + searchString->GetLength();
    // Let newString be the String formed by concatenating the first pos code units of string, replStr, and the trailing
    // substring of string starting at index tailPos. If pos is 0, the first element of the concatenation will be the
    // empty String.
    // Return newString.
    JSHandle<EcmaString> prefixString(thread, EcmaString::FastSubString(thisString, 0, pos, ecmaVm));
    JSHandle<EcmaString> suffixString(
        thread, EcmaString::FastSubString(thisString, tailPos, thisString->GetLength() - tailPos, ecmaVm));
    std::u16string stringBuilder;
    bool canBeCompress = true;
    if (prefixString->IsUtf16()) {
        const uint16_t *data = prefixString->GetDataUtf16();
        stringBuilder += base::StringHelper::Utf16ToU16String(data, prefixString->GetLength());
        canBeCompress = false;
    } else {
        const uint8_t *data = prefixString->GetDataUtf8();
        stringBuilder += base::StringHelper::Utf8ToU16String(data, prefixString->GetLength());
    }

    if (realReplaceStr->IsUtf16()) {
        const uint16_t *data = realReplaceStr->GetDataUtf16();
        stringBuilder += base::StringHelper::Utf16ToU16String(data, realReplaceStr->GetLength());
        canBeCompress = false;
    } else {
        const uint8_t *data = realReplaceStr->GetDataUtf8();
        stringBuilder += base::StringHelper::Utf8ToU16String(data, realReplaceStr->GetLength());
    }

    if (suffixString->IsUtf16()) {
        const uint16_t *data = suffixString->GetDataUtf16();
        stringBuilder += base::StringHelper::Utf16ToU16String(data, suffixString->GetLength());
        canBeCompress = false;
    } else {
        const uint8_t *data = suffixString->GetDataUtf8();
        stringBuilder += base::StringHelper::Utf8ToU16String(data, suffixString->GetLength());
    }

    auto *char16tData = const_cast<char16_t *>(stringBuilder.c_str());
    auto *uint16tData = reinterpret_cast<uint16_t *>(char16tData);
    return factory->NewFromUtf16LiteralUnCheck(uint16tData, stringBuilder.size(), canBeCompress).GetTaggedValue();
}

JSTaggedValue BuiltinsString::GetSubstitution(JSThread *thread, const JSHandle<EcmaString> &matched,
                                              const JSHandle<EcmaString> &srcString, int position,
                                              const JSHandle<TaggedArray> &captureList,
                                              const JSHandle<EcmaString> &replacement)
{
    BUILTINS_API_TRACE(thread, String, GetSubstitution);
    auto ecmaVm = thread->GetEcmaVM();
    ObjectFactory *factory = ecmaVm->GetFactory();
    JSHandle<EcmaString> dollarString = factory->NewFromCanBeCompressString("$");
    int32_t replaceLength = replacement->GetLength();
    int32_t tailPos = position + matched->GetLength();

    int32_t nextDollarIndex = replacement->IndexOf(*dollarString, 0);
    if (nextDollarIndex < 0) {
        return replacement.GetTaggedValue();
    }

    std::u16string stringBuilder;
    bool canBeCompress = true;
    if (nextDollarIndex > 0) {
        if (replacement->IsUtf16()) {
            const uint16_t *data = replacement->GetDataUtf16();
            stringBuilder += base::StringHelper::Utf16ToU16String(data, nextDollarIndex);
            canBeCompress = false;
        } else {
            const uint8_t *data = replacement->GetDataUtf8();
            stringBuilder += base::StringHelper::Utf8ToU16String(data, nextDollarIndex);
        }
    }

    while (true) {
        int peekIndex = nextDollarIndex + 1;
        if (peekIndex >= replaceLength) {
            stringBuilder += '$';
            auto *char16tData = const_cast<char16_t *>(stringBuilder.c_str());
            auto *uint16tData = reinterpret_cast<uint16_t *>(char16tData);
            return factory->NewFromUtf16LiteralUnCheck(uint16tData, stringBuilder.length(), canBeCompress)
                .GetTaggedValue();
        }
        int continueFromIndex = -1;
        uint16_t peek = replacement->At(peekIndex);
        switch (peek) {
            case '$':  // $$
                stringBuilder += '$';
                continueFromIndex = peekIndex + 1;
                break;
            case '&':  // $& - match
                if (matched->IsUtf16()) {
                    const uint16_t *data = matched->GetDataUtf16();
                    stringBuilder += base::StringHelper::Utf16ToU16String(data, matched->GetLength());
                    canBeCompress = false;
                } else {
                    const uint8_t *data = matched->GetDataUtf8();
                    stringBuilder += base::StringHelper::Utf8ToU16String(data, matched->GetLength());
                }
                continueFromIndex = peekIndex + 1;
                break;
            case '`':  // $` - prefix
                if (position > 0) {
                    EcmaString *prefix = EcmaString::FastSubString(srcString, 0, position, ecmaVm);
                    if (prefix->IsUtf16()) {
                        const uint16_t *data = prefix->GetDataUtf16();
                        stringBuilder += base::StringHelper::Utf16ToU16String(data, prefix->GetLength());
                        canBeCompress = false;
                    } else {
                        const uint8_t *data = prefix->GetDataUtf8();
                        stringBuilder += base::StringHelper::Utf8ToU16String(data, prefix->GetLength());
                    }
                }
                continueFromIndex = peekIndex + 1;
                break;
            case '\'': {
                // $' - suffix
                int32_t srcLength = srcString->GetLength();
                if (tailPos < srcLength) {
                    EcmaString *sufffix = EcmaString::FastSubString(srcString, tailPos, srcLength - tailPos, ecmaVm);
                    if (sufffix->IsUtf16()) {
                        const uint16_t *data = sufffix->GetDataUtf16();
                        stringBuilder += base::StringHelper::Utf16ToU16String(data, sufffix->GetLength());
                        canBeCompress = false;
                    } else {
                        const uint8_t *data = sufffix->GetDataUtf8();
                        stringBuilder += base::StringHelper::Utf8ToU16String(data, sufffix->GetLength());
                    }
                }
                continueFromIndex = peekIndex + 1;
                break;
            }
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9': {
                int capturesLength = captureList->GetLength();
                // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99
                int32_t scaledIndex = (peek - '0');
                int32_t advance = 1;
                if (peekIndex + 1 < replaceLength) {
                    uint16_t nextPeek = replacement->At(peekIndex + 1);
                    if (nextPeek >= '0' && nextPeek <= '9') {
                        constexpr int32_t TEN_BASE = 10;
                        int32_t newScaledIndex = scaledIndex * TEN_BASE + (nextPeek - '0');
                        if (newScaledIndex <= capturesLength) {
                            scaledIndex = newScaledIndex;
                            advance = 2;  // 2: 2 means from index needs to add two.
                        }
                    }
                }

                if (scaledIndex == 0 || scaledIndex > capturesLength) {
                    stringBuilder += '$';
                    continueFromIndex = peekIndex;
                    break;
                }

                JSTaggedValue capturesVal(captureList->Get(scaledIndex - 1));
                if (!capturesVal.IsUndefined()) {
                    EcmaString *captureString = EcmaString::Cast(capturesVal.GetTaggedObject());
                    if (captureString->IsUtf16()) {
                        const uint16_t *data = captureString->GetDataUtf16();
                        stringBuilder += base::StringHelper::Utf16ToU16String(data, captureString->GetLength());
                        canBeCompress = false;
                    } else {
                        const uint8_t *data = captureString->GetDataUtf8();
                        stringBuilder += base::StringHelper::Utf8ToU16String(data, captureString->GetLength());
                    }
                }
                continueFromIndex = peekIndex + advance;
                break;
            }
            default:
                stringBuilder += '$';
                continueFromIndex = peekIndex;
                break;
        }
        // Go the the next $ in the replacement.
        nextDollarIndex = replacement->IndexOf(*dollarString, continueFromIndex);
        if (nextDollarIndex < 0) {
            if (continueFromIndex < replaceLength) {
                EcmaString *nextAppend = EcmaString::FastSubString(replacement, continueFromIndex,
                                                                   replaceLength - continueFromIndex, ecmaVm);
                if (nextAppend->IsUtf16()) {
                    const uint16_t *data = nextAppend->GetDataUtf16();
                    stringBuilder += base::StringHelper::Utf16ToU16String(data, nextAppend->GetLength());
                    canBeCompress = false;
                } else {
                    const uint8_t *data = nextAppend->GetDataUtf8();
                    stringBuilder += base::StringHelper::Utf8ToU16String(data, nextAppend->GetLength());
                }
            }
            auto *char16tData = const_cast<char16_t *>(stringBuilder.c_str());
            auto *uint16tData = reinterpret_cast<uint16_t *>(char16tData);
            return factory->NewFromUtf16LiteralUnCheck(uint16tData, stringBuilder.length(), canBeCompress)
                .GetTaggedValue();
        }
        // Append substring between the previous and the next $ character.
        if (nextDollarIndex > continueFromIndex) {
            EcmaString *nextAppend =
                EcmaString::FastSubString(replacement, continueFromIndex, nextDollarIndex - continueFromIndex, ecmaVm);
            if (nextAppend->IsUtf16()) {
                const uint16_t *data = nextAppend->GetDataUtf16();
                stringBuilder += base::StringHelper::Utf16ToU16String(data, nextAppend->GetLength());
                canBeCompress = false;
            } else {
                const uint8_t *data = nextAppend->GetDataUtf8();
                stringBuilder += base::StringHelper::Utf8ToU16String(data, nextAppend->GetLength());
            }
        }
    }
    UNREACHABLE();
}

// 21.1.3.15
JSTaggedValue BuiltinsString::Search(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, Search);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    const GlobalEnvConstants *globalConst = thread->GlobalConstants();
    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<JSTaggedValue> regexp = BuiltinsString::GetCallArg(argv, 0);
    JSHandle<JSTaggedValue> searchTag = thread->GetEcmaVM()->GetGlobalEnv()->GetSearchSymbol();
    if (!regexp->IsUndefined() && !regexp->IsNull()) {
        if (regexp->IsECMAObject()) {
            JSHandle<JSTaggedValue> searcher = JSObject::GetMethod(thread, regexp, searchTag);
            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
            if (!searcher->IsUndefined()) {
                ASSERT(searcher->IsJSFunction());
                InternalCallParams *arguments = thread->GetInternalCallParams();
                arguments->MakeArgv(thisTag);
                return JSFunction::Call(thread, searcher, regexp, 1, arguments->GetArgv());
            }
        }
    }
    JSHandle<EcmaString> thisVal = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    JSHandle<JSTaggedValue> undifinedHandle = globalConst->GetHandledUndefined();
    JSHandle<JSTaggedValue> rx(thread, BuiltinsRegExp::RegExpCreate(thread, regexp, undifinedHandle));
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    InternalCallParams *arguments = thread->GetInternalCallParams();
    arguments->MakeArgv(thisVal.GetTaggedValue());
    return JSFunction::Invoke(thread, rx, searchTag, 1, arguments->GetArgv());
}

// 21.1.3.16
JSTaggedValue BuiltinsString::Slice(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, Slice);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);

    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t thisLen = thisHandle->GetLength();
    JSHandle<JSTaggedValue> startTag = BuiltinsString::GetCallArg(argv, 0);
    JSTaggedNumber startVal = JSTaggedValue::ToInteger(thread, startTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t start = ConvertDoubleToInt(startVal.GetNumber());
    int32_t end;
    JSHandle<JSTaggedValue> endTag = BuiltinsString::GetCallArg(argv, 1);
    if (endTag->IsUndefined()) {
        end = thisLen;
    } else {
        JSTaggedNumber endVal = JSTaggedValue::ToInteger(thread, endTag);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        end = ConvertDoubleToInt(endVal.GetNumber());
    }
    int32_t from;
    int32_t to;
    if (start < 0) {
        from = std::max(start + thisLen, 0);
    } else {
        from = std::min(start, thisLen);
    }
    if (end < 0) {
        to = std::max(end + thisLen, 0);
    } else {
        to = std::min(end, thisLen);
    }
    int32_t len = std::max(to - from, 0);
    return JSTaggedValue(EcmaString::FastSubString(thisHandle, from, len, thread->GetEcmaVM()));
}

// 21.1.3.17
JSTaggedValue BuiltinsString::Split(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, Split);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    auto ecmaVm = thread->GetEcmaVM();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();

    // Let O be RequireObjectCoercible(this value).
    JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv));
    JSHandle<JSObject> thisObj(thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    JSHandle<JSTaggedValue> seperatorTag = BuiltinsString::GetCallArg(argv, 0);
    JSHandle<JSTaggedValue> limitTag = BuiltinsString::GetCallArg(argv, 1);
    // If separator is neither undefined nor null, then
    if (seperatorTag->IsECMAObject()) {
        JSHandle<JSTaggedValue> splitKey = env->GetSplitSymbol();
        // Let splitter be GetMethod(separator, @@split).
        JSHandle<JSTaggedValue> splitter = JSObject::GetMethod(thread, seperatorTag, splitKey);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        if (!splitter->IsUndefined()) {
            // Return Call(splitter, separator, «‍O, limit»).
            InternalCallParams *arguments = thread->GetInternalCallParams();
            arguments->MakeArgv(thisTag, limitTag);
            return JSFunction::Call(thread, splitter, seperatorTag, 2, arguments->GetArgv());  // 2: two args
        }
    }
    // Let S be ToString(O).
    JSHandle<EcmaString> thisString = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    // Let A be ArrayCreate(0).
    JSHandle<JSObject> resultArray(JSArray::ArrayCreate(thread, JSTaggedNumber(0)));
    uint32_t arrayLength = 0;
    // If limit is undefined, let lim = 2^53–1; else let lim = ToLength(limit).
    uint32_t lim;
    if (limitTag->IsUndefined()) {
        lim = UINT32_MAX - 1;
    } else {
        lim = JSTaggedValue::ToInteger(thread, limitTag).ToUint32();
    }
    // ReturnIfAbrupt(lim).
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    // If lim = 0, return A.
    if (lim == 0) {
        return resultArray.GetTaggedValue();
    }
    // Let s be the number of elements in S.
    int32_t thisLength = thisString->GetLength();
    JSHandle<EcmaString> seperatorString = JSTaggedValue::ToString(thread, seperatorTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    if (seperatorTag->IsUndefined()) {
        // Perform CreateDataProperty(A, "0", S).
        JSObject::CreateDataProperty(thread, resultArray, 0, JSHandle<JSTaggedValue>(thisString));
        ASSERT_PRINT(!thread->HasPendingException(), "CreateDataProperty(A, \"0\", S) can't throw exception");
        return resultArray.GetTaggedValue();
    }
    // If S.length = 0, then
    if (thisLength == 0) {
        if (SplitMatch(thisString, 0, seperatorString) != -1) {
            return resultArray.GetTaggedValue();
        }
        JSObject::CreateDataProperty(thread, resultArray, 0, JSHandle<JSTaggedValue>(thisString));
        ASSERT_PRINT(!thread->HasPendingException(), "CreateDataProperty(A, \"0\", S) can't throw exception");
        return resultArray.GetTaggedValue();
    }

    // Let q = p.
    // Repeat, while q ≠ s
    int32_t p = 0;
    int32_t q = p;
    while (q != thisLength) {
        int32_t matchedIndex = SplitMatch(thisString, q, seperatorString);
        if (matchedIndex == -1) {
            q = q + 1;
        } else {
            if (matchedIndex == p) {
                q = q + 1;
            } else {
                EcmaString *elementString = EcmaString::FastSubString(thisString, p, q - p, ecmaVm);
                JSHandle<JSTaggedValue> elementTag(thread, elementString);
                JSObject::CreateDataProperty(thread, resultArray, arrayLength, elementTag);
                ASSERT_PRINT(!thread->HasPendingException(), "CreateDataProperty can't throw exception");
                ++arrayLength;
                if (arrayLength == lim) {
                    return resultArray.GetTaggedValue();
                }
                p = matchedIndex;
                q = p;
            }
        }
    }
    EcmaString *elementString = EcmaString::FastSubString(thisString, p, thisLength - p, ecmaVm);
    JSHandle<JSTaggedValue> elementTag(thread, elementString);
    JSObject::CreateDataProperty(thread, resultArray, arrayLength, elementTag);
    ASSERT_PRINT(!thread->HasPendingException(), "CreateDataProperty can't throw exception");
    return resultArray.GetTaggedValue();
}

int32_t BuiltinsString::SplitMatch(const JSHandle<EcmaString> &str, int32_t q, const JSHandle<EcmaString> &reg)
{
    int32_t s = str->GetLength();
    int32_t r = reg->GetLength();
    if (q + r > s) {
        return -1;
    }
    int32_t i = 0;
    for (i = 0; i < r; i++) {
        if (str->At<false>(q + i) != reg->At<false>(i)) {
            return -1;
        }
    }
    return q + r;
}

// 21.1.3.18
JSTaggedValue BuiltinsString::StartsWith(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, StartsWith);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0);

    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    bool isRegexp = JSObject::IsRegExp(thread, searchTag);
    if (isRegexp) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "is regexp", JSTaggedValue::Exception());
    }

    JSHandle<EcmaString> searchHandle = JSTaggedValue::ToString(thread, searchTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t thisLen = thisHandle->GetLength();
    int32_t searchLen = searchHandle->GetLength();
    int32_t pos;
    JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 1);
    if (posTag->IsUndefined()) {
        pos = 0;
    } else {
        JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        pos = posVal.ToInt32();
    }
    pos = std::min(std::max(pos, 0), thisLen);
    if (pos + searchLen > thisLen) {
        return BuiltinsString::GetTaggedBoolean(false);
    }
    std::u16string u16strThis;
    std::u16string u16strSearch;
    if (thisHandle->IsUtf16()) {
        u16strThis = base::StringHelper::Utf16ToU16String(thisHandle->GetDataUtf16(), thisLen);
    } else {
        const uint8_t *uint8This = thisHandle->GetDataUtf8();
        u16strThis = base::StringHelper::Utf8ToU16String(uint8This, thisLen);
    }
    if (searchHandle->IsUtf16()) {
        u16strSearch = base::StringHelper::Utf16ToU16String(searchHandle->GetDataUtf16(), searchLen);
    } else {
        const uint8_t *uint8Search = searchHandle->GetDataUtf8();
        u16strSearch = base::StringHelper::Utf8ToU16String(uint8Search, searchLen);
    }
    int32_t idx = base::StringHelper::Find(u16strThis, u16strSearch, pos);
    if (idx == pos) {
        return BuiltinsString::GetTaggedBoolean(true);
    }
    return BuiltinsString::GetTaggedBoolean(false);
}

// 21.1.3.19
JSTaggedValue BuiltinsString::Substring(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, Substring);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);

    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t thisLen = thisHandle->GetLength();
    JSHandle<JSTaggedValue> startTag = BuiltinsString::GetCallArg(argv, 0);
    JSTaggedNumber startVal = JSTaggedValue::ToInteger(thread, startTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t start = ConvertDoubleToInt(startVal.GetNumber());
    int32_t end;
    JSHandle<JSTaggedValue> endTag = BuiltinsString::GetCallArg(argv, 1);
    if (endTag->IsUndefined()) {
        end = thisLen;
    } else {
        JSTaggedNumber endVal = JSTaggedValue::ToInteger(thread, endTag);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        end = ConvertDoubleToInt(endVal.GetNumber());
    }
    start = std::min(std::max(start, 0), thisLen);
    end = std::min(std::max(end, 0), thisLen);
    int32_t from = std::min(start, end);
    int32_t to = std::max(start, end);
    int32_t len = to - from;
    return JSTaggedValue(EcmaString::FastSubString(thisHandle, from, len, thread->GetEcmaVM()));
}

// 21.1.3.20
JSTaggedValue BuiltinsString::ToLocaleLowerCase(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, ToLocaleLowerCase);
    JSThread *thread = argv->GetThread();
    EcmaVM *ecmaVm = thread->GetEcmaVM();
    ObjectFactory *factory = ecmaVm->GetFactory();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);

    // Let O be RequireObjectCoercible(this value).
    JSHandle<JSTaggedValue> obj(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));

    // Let S be ? ToString(O).
    JSHandle<EcmaString> string = JSTaggedValue::ToString(thread, obj);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // Let requestedLocales be ? CanonicalizeLocaleList(locales).
    JSHandle<JSTaggedValue> locales = GetCallArg(argv, 0);
    JSHandle<TaggedArray> requestedLocales = JSLocale::CanonicalizeLocaleList(thread, locales);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // If requestedLocales is not an empty List, then Let requestedLocale be requestedLocales[0].
    // Else, Let requestedLocale be DefaultLocale().
    JSHandle<EcmaString> requestedLocale = JSLocale::DefaultLocale(thread);
    if (requestedLocales->GetLength() != 0) {
        requestedLocale = JSHandle<EcmaString>(thread, requestedLocales->Get(0));
    }

    // Let noExtensionsLocale be the String value that is requestedLocale with all Unicode locale extension sequences
    // removed.
    JSLocale::ParsedLocale noExtensionsLocale = JSLocale::HandleLocale(requestedLocale);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // Let availableLocales be a List with language tags that includes the languages for which the Unicode Character
    // Database contains language sensitive case mappings. Implementations may add additional language tags
    // if they support case mapping for additional locales.
    JSHandle<TaggedArray> availableLocales = JSLocale::GetAvailableLocales(thread, nullptr, nullptr);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // Let locale be BestAvailableLocale(availableLocales, noExtensionsLocale).
    std::string locale = JSLocale::BestAvailableLocale(thread, availableLocales, noExtensionsLocale.base);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // If locale is undefined, let locale be "und".
    if (locale.empty()) {
        locale = "und";
    }

    // Let uString be a List containing in order the code points of S as defined in ES2020, 6.1.4,
    // starting at the first element of S.
    // Transform those elements in uString to the to the Unicode Default Case Conversion algorithm
    icu::Locale icuLocale = icu::Locale::createFromName(locale.c_str());
    std::u16string utf16String;
    if (string->IsUtf16()) {
        utf16String = base::StringHelper::Utf16ToU16String(string->GetDataUtf16(), string->GetUtf16Length());
    } else {
        const uint8_t *uint8This = string->GetDataUtf8();
        utf16String = base::StringHelper::Utf8ToU16String(uint8This, string->GetLength());
    }
    icu::UnicodeString uString(utf16String.data());
    icu::UnicodeString res = uString.toLower(icuLocale);
    std::string CSLower;
    res.toUTF8String(CSLower);
    JSHandle<EcmaString> result = factory->NewFromStdString(CSLower);
    return result.GetTaggedValue();
}

// 21.1.3.21
JSTaggedValue BuiltinsString::ToLocaleUpperCase(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, ToLocaleLowerCase);
    JSThread *thread = argv->GetThread();
    EcmaVM *ecmaVm = thread->GetEcmaVM();
    ObjectFactory *factory = ecmaVm->GetFactory();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);

    // Let O be RequireObjectCoercible(this value).
    JSHandle<JSTaggedValue> obj(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));

    // Let S be ? ToString(O).
    JSHandle<EcmaString> string = JSTaggedValue::ToString(thread, obj);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // Let requestedLocales be ? CanonicalizeLocaleList(locales).
    JSHandle<JSTaggedValue> locales = GetCallArg(argv, 0);
    JSHandle<TaggedArray> requestedLocales = JSLocale::CanonicalizeLocaleList(thread, locales);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // If requestedLocales is not an empty List, then Let requestedLocale be requestedLocales[0].
    // Else, Let requestedLocale be DefaultLocale().
    JSHandle<EcmaString> requestedLocale = JSLocale::DefaultLocale(thread);
    if (requestedLocales->GetLength() != 0) {
        requestedLocale = JSHandle<EcmaString>(thread, requestedLocales->Get(0));
    }

    // Let noExtensionsLocale be the String value that is requestedLocale with all Unicode locale extension sequences
    // removed.
    JSLocale::ParsedLocale noExtensionsLocale = JSLocale::HandleLocale(requestedLocale);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // Let availableLocales be a List with language tags that includes the languages for which the Unicode Character
    // Database contains language sensitive case mappings. Implementations may add additional language tags
    // if they support case mapping for additional locales.
    JSHandle<TaggedArray> availableLocales = JSLocale::GetAvailableLocales(thread, nullptr, nullptr);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // Let locale be BestAvailableLocale(availableLocales, noExtensionsLocale).
    std::string locale = JSLocale::BestAvailableLocale(thread, availableLocales, noExtensionsLocale.base);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // If locale is undefined, let locale be "und".
    if (locale.empty()) {
        locale = "und";
    }

    // Let uString be a List containing in order the code points of S as defined in ES2020, 6.1.4,
    // starting at the first element of S.
    // Transform those elements in uString to the to the Unicode Default Case Conversion algorithm
    icu::Locale icuLocale = icu::Locale::createFromName(locale.c_str());
    std::u16string utf16String;
    if (string->IsUtf16()) {
        utf16String = base::StringHelper::Utf16ToU16String(string->GetDataUtf16(), string->GetUtf16Length());
    } else {
        const uint8_t *uint8This = string->GetDataUtf8();
        utf16String = base::StringHelper::Utf8ToU16String(uint8This, string->GetLength());
    }
    icu::UnicodeString uString(utf16String.data());
    icu::UnicodeString res = uString.toUpper(icuLocale);
    std::string CSUpper;
    res.toUTF8String(CSUpper);
    JSHandle<EcmaString> result = factory->NewFromStdString(CSUpper);
    return result.GetTaggedValue();
}

// 21.1.3.22
JSTaggedValue BuiltinsString::ToLowerCase(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, ToLowerCase);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t thisLen = thisHandle->GetLength();
    std::u16string u16strThis;
    if (thisHandle->IsUtf16()) {
        u16strThis = base::StringHelper::Utf16ToU16String(thisHandle->GetDataUtf16(), thisLen);
    } else {
        const uint8_t *uint8This = thisHandle->GetDataUtf8();
        u16strThis = base::StringHelper::Utf8ToU16String(uint8This, thisLen);
    }
    return JSTaggedValue(base::StringHelper::ToLower(thread, u16strThis));
}

// 21.1.3.23
JSTaggedValue BuiltinsString::ToString(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    return ThisStringValue(argv->GetThread(), GetThis(argv).GetTaggedValue());
}

// 21.1.3.24
JSTaggedValue BuiltinsString::ToUpperCase(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, ToUpperCase);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);

    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t thisLen = thisHandle->GetLength();
    std::u16string u16strThis;
    if (thisHandle->IsUtf16()) {
        u16strThis = base::StringHelper::Utf16ToU16String(thisHandle->GetDataUtf16(), thisLen);
    } else {
        const uint8_t *uint8This = thisHandle->GetDataUtf8();
        u16strThis = base::StringHelper::Utf8ToU16String(uint8This, thisLen);
    }
    return JSTaggedValue(base::StringHelper::ToUpper(thread, u16strThis));
}

// 21.1.3.25
JSTaggedValue BuiltinsString::Trim(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, Trim);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t thisLen = thisHandle->GetLength();
    if (UNLIKELY(thisLen == 0)) {
        return thread->GlobalConstants()->GetEmptyString();
    }

    if (thisHandle->IsUtf8()) {
        Span<const uint8_t> data(reinterpret_cast<const uint8_t *>(thisHandle->GetData()), thisLen);
        uint32_t start = base::StringHelper::GetStart(data, thisLen);
        uint32_t end = base::StringHelper::GetEnd(data, start, thisLen);
        EcmaString *res = EcmaString::FastSubUtf8String(thread->GetEcmaVM(), thisHandle, start, end + 1 - start);
        return JSTaggedValue(res);
    }

    Span<const uint16_t> data(thisHandle->GetData(), thisLen);
    uint32_t start = base::StringHelper::GetStart(data, thisLen);
    uint32_t end = base::StringHelper::GetEnd(data, start, thisLen);
    EcmaString *res = EcmaString::FastSubUtf16String(thread->GetEcmaVM(), thisHandle, start, end + 1 - start);
    return JSTaggedValue(res);
}

// 21.1.3.26
JSTaggedValue BuiltinsString::ValueOf(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    return ThisStringValue(argv->GetThread(), GetThis(argv).GetTaggedValue());
}

// 21.1.3.27
JSTaggedValue BuiltinsString::GetStringIterator(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, GetStringIterator);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    // 1. Let O be RequireObjectCoercible(this value).
    JSHandle<JSTaggedValue> current(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    // Let S be ToString(O).

    JSHandle<EcmaString> string = JSTaggedValue::ToString(thread, current);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(argv->GetThread());
    // Return CreateStringIterator(S).
    return JSStringIterator::CreateStringIterator(thread, string).GetTaggedValue();
}

//  B.2.3.1
JSTaggedValue BuiltinsString::SubStr(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), String, SubStr);
    JSThread *thread = argv->GetThread();

    [[maybe_unused]] EcmaHandleScope handleScope(thread);

    // 1. Let O be RequireObjectCoercible(this value).
    // 2. Let S be ToString(O).

    JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
    JSHandle<EcmaString> thisString = JSTaggedValue::ToString(thread, thisTag);

    // 3. ReturnIfAbrupt(S).
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    JSHandle<JSTaggedValue> intStart = GetCallArg(argv, 0);
    // 4. Let intStart be ToInteger(start).
    JSTaggedNumber numStart = JSTaggedValue::ToInteger(thread, intStart);
    // 5. ReturnIfAbrupt(intStart).
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    int32_t start = numStart.ToInt32();
    JSHandle<JSTaggedValue> lengthTag = GetCallArg(argv, 1);
    // 6. If length is undefined, let end be +ï‚¥; otherwise let end be ToInteger(length).
    int32_t end;
    if (lengthTag->IsUndefined()) {
        end = INT_MAX;
    } else {
        JSTaggedNumber lengthNumber = JSTaggedValue::ToInteger(thread, lengthTag);
        // 7. ReturnIfAbrupt(end).
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        end = lengthNumber.ToInt32();
    }
    // 8. Let size be the number of code units in S.
    int32_t size = thisString->GetLength();
    // 9. If intStart < 0, let intStart be max(size + intStart,0).
    if (start < 0) {
        start = std::max(size + start, 0);
    }
    // 10. Let resultLength be min(max(end,0), size – intStart).
    int32_t resultLength = std::min(std::max(end, 0), size - start);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    // 11. If resultLength ï‚£ 0, return the empty String "".
    if (resultLength <= 0) {
        return factory->GetEmptyString().GetTaggedValue();
    }
    return JSTaggedValue(EcmaString::FastSubString(thisString, start, resultLength, thread->GetEcmaVM()));
}

JSTaggedValue BuiltinsString::GetLength(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> thisHandle = GetThis(argv);

    JSHandle<EcmaString> thisString = JSTaggedValue::ToString(thread, thisHandle);
    return GetTaggedInt(thisString->GetLength());
}

// 21.1.3
JSTaggedValue BuiltinsString::ThisStringValue(JSThread *thread, JSTaggedValue value)
{
    if (value.IsString()) {
        return value;
    }
    if (value.IsECMAObject()) {
        auto jshClass = value.GetTaggedObject()->GetClass();
        if (jshClass->GetObjectType() == JSType::JS_PRIMITIVE_REF) {
            JSTaggedValue primitive = JSPrimitiveRef::Cast(value.GetTaggedObject())->GetValue();
            if (primitive.IsString()) {
                return primitive;
            }
        }
    }
    THROW_TYPE_ERROR_AND_RETURN(thread, "can not convert to String", JSTaggedValue::Exception());
}

int32_t BuiltinsString::ConvertDoubleToInt(double d)
{
    if (std::isnan(d) || d == -base::POSITIVE_INFINITY) {
        return 0;
    }
    if (d >= static_cast<double>(INT_MAX)) {
        return INT_MAX;
    }
    if (d <= static_cast<double>(INT_MIN)) {
        return INT_MIN;
    }
    return base::NumberHelper::DoubleToInt(d, base::INT32_BITS);
}
}  // namespace panda::ecmascript::builtins