/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.
 * 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 "clipboard_jni.h"

#include <cstddef>
#include <regex>

#include "clipboard_proxy.h"
#include "errors.h"
#include "inner_api/plugin_utils_inner.h"
#include "log.h"
#include "pasteboard_error.h"
#include "plugin_utils.h"
#include "want_params_wrapper.h"

using namespace OHOS::MiscServices;
namespace OHOS::Plugin {
namespace {
static const char CLIPBOARD_PLUGIN_CLASS_NAME[] = "ohos/ace/plugin/clipboard/ClipboardAosp";
static const char PASTE_DATA_DTO_CLASS_NAME[] = "ohos/ace/plugin/clipboard/PasteDataDTO";
static const char PASTE_DATA_RECORD_DTO_CLASS_NAME[] = "ohos/ace/plugin/clipboard/DataRecordDTO";
static const char PASTE_DATA_PROPERTY_DTO_CLASS_NAME[] = "ohos/ace/plugin/clipboard/PropertyDTO";

static const JNINativeMethod METHODS[] = {
    { .name = "nativeInit", .signature = "()V", .fnPtr = reinterpret_cast<void*>(ClipboardJni::NativeInit) },
    { .name = "onPasteboardChanged",
        .signature = "()V",
        .fnPtr = reinterpret_cast<void*>(ClipboardJni::OnPasteboardChanged) },
};

static const char METHOD_SET_DATA[] = "setData";
static const char METHOD_GET_DATA[] = "getData";
static const char METHOD_HAS_DATA[] = "hasData";
static const char METHOD_CLEAR[] = "clear";
static const char METHOD_HAS_DATA_TYPE[] = "hasDataType";
static const char METHOD_SUBSCRIBE_PASTEBOARD_CHANGE[] = "subscribePasteboardChange";
static const char METHOD_UNSUBSCRIBE_PASTEBOARD_CHANGE[] = "unsubscribePasteboardChange";
static const char METHOD_DETECT_PATTERNS[] = "detectPatterns";

static const char METHOD_SET_PROPERTY_DTO[] = "setPropertyDTO";
static const char METHOD_SET_RECORDS[] = "setRecords";
static const char METHOD_GET_PROPERTY_DTO[] = "getPropertyDTO";
static const char METHOD_GET_RECORDS[] = "getRecords";

static const char METHOD_SET_MIMETYPE[] = "setMimeType";
static const char METHOD_SET_HTMLTEXT[] = "setHtmlText";
static const char METHOD_SET_JSON_WANT[] = "setJsonWant";
static const char METHOD_SET_PLAINTEXT[] = "setPlainText";
static const char METHOD_SET_URI[] = "setUri";
static const char METHOD_GET_MIMETYPE[] = "getMimeType";
static const char METHOD_GET_HTMLTEXT[] = "getHtmlText";
static const char METHOD_GET_JSON_WANT[] = "getJsonWant";
static const char METHOD_GET_PLAINTEXT[] = "getPlainText";
static const char METHOD_GET_URI[] = "getUri";

static const char METHOD_SET_MIMETYPES[] = "setMimeTypes";
static const char METHOD_SET_TAG[] = "setTag";
static const char METHOD_SET_TIMESTAMP[] = "setTimestamp";
static const char METHOD_SET_JSON_ADDITIONS[] = "setJsonAdditions";
static const char METHOD_GET_MIMETYPES[] = "getMimeTypes";
static const char METHOD_GET_TAG[] = "getTag";
static const char METHOD_GET_TIMESTAMP[] = "getTimestamp";
static const char METHOD_GET_JSON_ADDITIONS[] = "getJsonAdditions";

static const char SIGNATURE_SET_DATA[] = "(Lohos/ace/plugin/clipboard/PasteDataDTO;)I";
static const char SIGNATURE_GET_DATA[] = "()Lohos/ace/plugin/clipboard/PasteDataDTO;";
static const char SIGNATURE_HAS_DATA[] = "()Z";
static const char SIGNATURE_HAS_DATA_TYPE[] = "(Ljava/lang/String;)Z";
static const char SIGNATURE_CLEAR[] = "()Z";
static const char SIGNATURE_SUBSCRIBE_PASTEBOARD_CHANGE[] = "()Z";
static const char SIGNATURE_UNSUBSCRIBE_PASTEBOARD_CHANGE[] = "()Z";
static const char SIGNATURE_DETECT_PATTERNS[] = "(I)I";

static const char SIGNATURE_SET_PROPERTY_DTO[] = "(Lohos/ace/plugin/clipboard/PropertyDTO;)V";
static const char SIGNATURE_SET_RECORDS[] = "([Lohos/ace/plugin/clipboard/DataRecordDTO;)V";
static const char SIGNATURE_GET_PROPERTY_DTO[] = "()Lohos/ace/plugin/clipboard/PropertyDTO;";
static const char SIGNATURE_GET_RECORDS[] = "()[Lohos/ace/plugin/clipboard/DataRecordDTO;";

static const char SIGNATURE_SET_MIMETYPE[] = "(Ljava/lang/String;)V";
static const char SIGNATURE_SET_HTMLTEXT[] = "(Ljava/lang/String;)V";
static const char SIGNATURE_SET_JSON_WANT[] = "(Ljava/lang/String;)V";
static const char SIGNATURE_SET_PLAINTEXT[] = "(Ljava/lang/String;)V";
static const char SIGNATURE_SET_URI[] = "(Ljava/lang/String;)V";
static const char SIGNATURE_GET_MIMETYPE[] = "()Ljava/lang/String;";
static const char SIGNATURE_GET_HTMLTEXT[] = "()Ljava/lang/String;";
static const char SIGNATURE_GET_JSON_WANT[] = "()Ljava/lang/String;";
static const char SIGNATURE_GET_PLAINTEXT[] = "()Ljava/lang/String;";
static const char SIGNATURE_GET_URI[] = "()Ljava/lang/String;";

static const char SIGNATURE_SET_MIMETYPES[] = "([Ljava/lang/String;)V";
static const char SIGNATURE_SET_TAG[] = "(Ljava/lang/String;)V";
static const char SIGNATURE_SET_TIMESTAMP[] = "(J)V";
static const char SIGNATURE_SET_JSON_ADDITIONS[] = "(Ljava/lang/String;)V";
static const char SIGNATURE_GET_MIMETYPES[] = "()[Ljava/lang/String;";
static const char SIGNATURE_GET_TAG[] = "()Ljava/lang/String;";
static const char SIGNATURE_GET_TIMESTAMP[] = "()J";
static const char SIGNATURE_GET_JSON_ADDITIONS[] = "()Ljava/lang/String;";

struct {
    jmethodID setData;
    jmethodID getData;
    jmethodID hasData;
    jmethodID clear;
    jmethodID subscribePasteboardChange;
    jmethodID unsubscribePasteboardChange;
    jmethodID detectPatterns;
    jmethodID hasDataType;
    jmethodID getMimeTypes;
    jobject globalRef;
} g_clipboard;

struct {
    jmethodID constructor;
    jmethodID getPropertyDTO;
    jmethodID getRecords;
    jmethodID setPropertyDTO;
    jmethodID setRecords;
    jclass clazz;
} g_pasteDataDTO;

struct PropertyDTOCache {
    jmethodID constructor;
    jmethodID setMimeTypes;
    jmethodID setTag;
    jmethodID setTimestamp;
    jmethodID setJsonAdditions;
    jmethodID getMimeTypes;
    jmethodID getTag;
    jmethodID getTimestamp;
    jmethodID getJsonAdditions;
    jclass clazz;
} g_propertyDTO;

struct DataRecordDTOCache {
    jmethodID constructor;
    jmethodID setMimeType;
    jmethodID setHtmlText;
    jmethodID setJsonWant;
    jmethodID setPlainText;
    jmethodID setUri;
    jmethodID getMimeType;
    jmethodID getHtmlText;
    jmethodID getJsonWant;
    jmethodID getPlainText;
    jmethodID getUri;
    jclass clazz;
} g_dataRecordDTO;

static bool SetJavaStringField(JNIEnv* env, jobject jObj, jmethodID setter, const std::string& value)
{
    CHECK_NULL_RETURN(env, false);
    CHECK_NULL_RETURN(jObj, false);
    CHECK_NULL_RETURN(setter, false);

    jstring jStr = env->NewStringUTF(value.c_str());
    CHECK_NULL_RETURN(jStr, false);
    env->CallVoidMethod(jObj, setter, jStr);
    bool hasException = env->ExceptionCheck();
    if (hasException) {
        LOGE("SetJavaStringField: call setter failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
    env->DeleteLocalRef(jStr);
    return !hasException;
}

std::shared_ptr<std::string> GetPlainTextFromRecord(const std::shared_ptr<OHOS::MiscServices::PasteDataRecord>& record)
{
    CHECK_NULL_RETURN(record, nullptr);
    auto plainText = record->GetPlainText();
    if (plainText) {
        return plainText;
    }
    auto type = MiscServices::CommonUtils::Convert(record->GetUDType(), record->GetMimeType());
    if (type == UDMF::HTML) {
        std::string res;
        auto entry = record->GetEntryByMimeType(MIMETYPE_TEXT_HTML);
        if (entry == nullptr) {
            LOGE("GetPlainTextFromRecord: get entry failed");
            return nullptr;
        }
        auto value = entry->GetValue();
        if (!std::holds_alternative<std::shared_ptr<Object>>(value)) {
            LOGE("GetPlainTextFromRecord: value error, no Object");
            return nullptr;
        }
        auto object = std::get<std::shared_ptr<Object>>(value);
        if (object == nullptr) {
            LOGE("GetPlainTextFromRecord: can not find object in value");
            return nullptr;
        }
        if (!object->GetValue(UDMF::PLAIN_CONTENT, res)) {
            LOGE("GetPlainTextFromRecord: get PLAIN_CONTENT failed");
            return nullptr;
        }
        return std::make_shared<std::string>(res);
    }
    return nullptr;
}

bool SetWantToJavaDataRecordDTO(
    JNIEnv* env, jobject jRecord, const std::shared_ptr<OHOS::MiscServices::PasteDataRecord> record)
{
    CHECK_NULL_RETURN(env, false);
    CHECK_NULL_RETURN(jRecord, false);
    CHECK_NULL_RETURN(record, false);
    CHECK_NULL_RETURN(g_dataRecordDTO.setJsonWant, false);
    std::shared_ptr<OHOS::AAFwk::Want> wantPtr = record->GetWant();
    CHECK_NULL_RETURN(wantPtr, true);

    std::string jsonWantStr = wantPtr->ToJson();
    nlohmann::json jsonObject = nlohmann::json::parse(jsonWantStr.c_str(), nullptr, false);
    if (jsonObject.is_discarded()) {
        LOGE("jsonObject is discarded. value = %{public}s", jsonWantStr.c_str());
        return false;
    }
    if (!wantPtr->GetBundleName().empty()) {
        jsonObject["bundleName"] = wantPtr->GetBundleName();
    }
    if (!wantPtr->GetModuleName().empty()) {
        jsonObject["moduleName"] = wantPtr->GetModuleName();
    }
    if (!wantPtr->GetAbilityName().empty()) {
        jsonObject["abilityName"] = wantPtr->GetAbilityName();
    }
    if (!wantPtr->GetType().empty()) {
        jsonObject["type"] = wantPtr->GetType();
    }

    std::string jsonWant = jsonObject.dump();
    jstring jJsonWant = env->NewStringUTF(jsonWant.c_str());
    CHECK_NULL_RETURN(jJsonWant, false);

    env->CallVoidMethod(jRecord, g_dataRecordDTO.setJsonWant, jJsonWant);
    env->DeleteLocalRef(jJsonWant);
    if (env->ExceptionCheck()) {
        LOGE("SetWantToJavaDataRecordDTO: call setJsonWant failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        return false;
    }
    return true;
}

jobject ToJavaDataRecordDTO(JNIEnv* env, const std::shared_ptr<OHOS::MiscServices::PasteDataRecord>& record)
{
    CHECK_NULL_RETURN(env, nullptr);
    CHECK_NULL_RETURN(record, nullptr);
    CHECK_NULL_RETURN(g_dataRecordDTO.constructor, nullptr);
    CHECK_NULL_RETURN(g_dataRecordDTO.clazz, nullptr);

    jobject jRecord = env->NewObject(g_dataRecordDTO.clazz, g_dataRecordDTO.constructor);
    if (!jRecord || env->ExceptionCheck()) {
        LOGE("ToJavaDataRecordDTO: create DataRecordDTO failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        if (jRecord) {
            env->DeleteLocalRef(jRecord);
        }
        return nullptr;
    }

    std::string mimeType = record->GetMimeType();
    if (!SetJavaStringField(env, jRecord, g_dataRecordDTO.setMimeType, mimeType)) {
        return nullptr;
    }
    std::shared_ptr<std::string> htmlTextPtr = record->GetHtmlText();
    if (htmlTextPtr != nullptr) {
        if (!SetJavaStringField(env, jRecord, g_dataRecordDTO.setHtmlText, *htmlTextPtr)) {
            return nullptr;
        }
    }
    if (!SetWantToJavaDataRecordDTO(env, jRecord, record)) {
        return nullptr;
    }
    std::shared_ptr<std::string> plainTextPtr = GetPlainTextFromRecord(record);
    if (plainTextPtr != nullptr) {
        if (!SetJavaStringField(env, jRecord, g_dataRecordDTO.setPlainText, *plainTextPtr)) {
            return nullptr;
        }
    }
    std::shared_ptr<OHOS::Uri> uri = record->GetUri();
    if (uri != nullptr) {
        std::string uriText = uri->ToString();
        if (!SetJavaStringField(env, jRecord, g_dataRecordDTO.setUri, uriText)) {
            return nullptr;
        }
    }

    return jRecord;
}

jobject ToJavaDataRecordDTOArray(
    JNIEnv* env, const std::vector<std::shared_ptr<OHOS::MiscServices::PasteDataRecord>>& records)
{
    CHECK_NULL_RETURN(env, nullptr);
    if (records.empty()) {
        LOGE("ToJavaDataRecordDTOArray: empty records");
        return nullptr;
    }

    jsize arrayLength = static_cast<jsize>(records.size());
    if (!g_pasteDataDTO.clazz) {
        LOGE("ToJavaDataRecordDTOArray: null pasteDataRecordData class");
        return nullptr;
    }
    jobjectArray jRecordArray = env->NewObjectArray(arrayLength, g_dataRecordDTO.clazz, nullptr);
    if (!jRecordArray) {
        LOGE("ToJavaDataRecordDTOArray: create DataRecordDTO array failed");
        return nullptr;
    }

    for (jsize i = 0; i < arrayLength; ++i) {
        const auto& record = records[i];
        jobject jRecord = ToJavaDataRecordDTO(env, record);
        if (jRecord) {
            env->SetObjectArrayElement(jRecordArray, i, jRecord);
            env->DeleteLocalRef(jRecord);
        } else {
            LOGE("ToJavaDataRecordDTOArray: convert single record failed at index %d", i);
            env->DeleteLocalRef(jRecordArray);
            return nullptr;
        }
    }
    return jRecordArray;
}

AAFwk::WantParams JsonToWantParams(const std::string& jsonParams)
{
    auto jsonObject = nlohmann::json::parse(jsonParams.c_str(), nullptr, false);
    AAFwk::WantParams wantParams;
    if (jsonObject.is_discarded()) {
        LOGE("JsonToWantParams: jsonObject is discarded. value = %{public}s", jsonParams.c_str());
        return wantParams;
    }
    if (jsonObject.contains("params")) {
        AAFwk::WantParamWrapper::ParseWantParams(jsonObject["params"], wantParams);
    }
    return wantParams;
}

std::string WantParamsToJson(AAFwk::WantParams Params)
{
    AAFwk::WantParamWrapper wrapper(Params);
    std::string jsonParams = "{\"" + AAFwk::JSON_WANTPARAMS_PARAM + "\":" + wrapper.ToString() + "}";
    return jsonParams;
}

jobject ToJavaPropertyDTO(JNIEnv* env, const OHOS::MiscServices::PasteDataProperty& property)
{
    CHECK_NULL_RETURN(env, nullptr);
    CHECK_NULL_RETURN(g_propertyDTO.constructor, nullptr);
    CHECK_NULL_RETURN(g_propertyDTO.clazz, nullptr);

    jobject jProperty = env->NewObject(g_propertyDTO.clazz, g_propertyDTO.constructor);
    if (!jProperty || env->ExceptionCheck()) {
        LOGE("ToJavaPropertyDTO: create PropertyDTO failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        if (jProperty) {
            env->DeleteLocalRef(jProperty);
        }
        return nullptr;
    }
    std::string tag = property.tag;
    SetJavaStringField(env, jProperty, g_propertyDTO.setTag, tag);

    CHECK_NULL_RETURN(g_propertyDTO.clazz, nullptr);
    CHECK_NULL_RETURN(g_propertyDTO.setMimeTypes, nullptr);
    const std::vector<std::string>& mimeTypes = property.mimeTypes;
    if (!mimeTypes.empty()) {
        jclass stringClazz = env->FindClass("java/lang/String");
        jobjectArray jMimeTypes = env->NewObjectArray(mimeTypes.size(), stringClazz, nullptr);
        for (size_t i = 0; i < mimeTypes.size(); ++i) {
            jstring jStr = env->NewStringUTF(mimeTypes[i].c_str());
            env->SetObjectArrayElement(jMimeTypes, i, jStr);
            env->DeleteLocalRef(jStr);
        }
        env->CallVoidMethod(jProperty, g_propertyDTO.setMimeTypes, jMimeTypes);
        if (env->ExceptionCheck()) {
            LOGE("ToJavaPropertyDTO: setMimeTypes failed");
            env->ExceptionDescribe();
            env->ExceptionClear();
        }
        env->DeleteLocalRef(jMimeTypes);
        env->DeleteLocalRef(stringClazz);
    }

    AAFwk::WantParams additions = property.additions;
    std::string jsonAdditions = WantParamsToJson(additions);
    SetJavaStringField(env, jProperty, g_propertyDTO.setJsonAdditions, jsonAdditions);
    return jProperty;
}

jobject ToJavaPasteDataDTO(JNIEnv* env, const OHOS::MiscServices::PasteData& pasteData)
{
    CHECK_NULL_RETURN(env, nullptr);
    CHECK_NULL_RETURN(g_pasteDataDTO.clazz, nullptr);
    jobject jPasteData = env->NewObject(g_pasteDataDTO.clazz, g_pasteDataDTO.constructor);
    if (!jPasteData || env->ExceptionCheck()) {
        LOGE("ToJavaPasteDataDTO: create PasteDataDTO failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        if (jPasteData) {
            env->DeleteLocalRef(jPasteData);
        }
        return nullptr;
    }
    OHOS::MiscServices::PasteDataProperty property = pasteData.GetProperty();
    std::vector<std::shared_ptr<OHOS::MiscServices::PasteDataRecord>> pasteDataRecords = pasteData.AllRecords();
    jobject jProperty = ToJavaPropertyDTO(env, property);
    jobject jRecords = ToJavaDataRecordDTOArray(env, pasteDataRecords);

    CHECK_NULL_RETURN(jProperty, nullptr);
    if (!g_pasteDataDTO.setPropertyDTO) {
        env->DeleteLocalRef(jProperty);
        env->DeleteLocalRef(jRecords);
        return nullptr;
    }
    env->CallVoidMethod(jPasteData, g_pasteDataDTO.setPropertyDTO, jProperty);
    env->DeleteLocalRef(jProperty);

    CHECK_NULL_RETURN(jRecords, nullptr);
    if (!g_pasteDataDTO.setRecords) {
        env->DeleteLocalRef(jRecords);
        return nullptr;
    }
    env->CallVoidMethod(jPasteData, g_pasteDataDTO.setRecords, jRecords);
    env->DeleteLocalRef(jRecords);
    return jPasteData;
}

bool GetJsonWantFromDataRecordDTO(JNIEnv* env, const jobject& jrecord,
    OHOS::MiscServices::PasteDataRecord::Builder& builder, std::vector<std::string> mimeTypes)
{
    CHECK_NULL_RETURN(env, false);
    CHECK_NULL_RETURN(jrecord, false);
    CHECK_NULL_RETURN(g_dataRecordDTO.clazz, false);
    CHECK_NULL_RETURN(g_dataRecordDTO.getJsonWant, false);
    jstring jJsonwant = static_cast<jstring>(env->CallObjectMethod(jrecord, g_dataRecordDTO.getJsonWant));
    if (env->ExceptionCheck()) {
        LOGE("GetWantFromRecordDTO: getUri failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        return false;
    }
    if (jJsonwant) {
        const char* jsonWantStr = env->GetStringUTFChars(jJsonwant, nullptr);
        if (jsonWantStr) {
            auto jsonObject = nlohmann::json::parse(jsonWantStr, nullptr, false);
            if (jsonObject.is_discarded()) {
                LOGE("jsonObject is discarded. value = %{public}s", jsonWantStr);
                std::shared_ptr<OHOS::AAFwk::Want> want = nullptr;
                builder.SetWant(want);
                env->ReleaseStringUTFChars(jJsonwant, jsonWantStr);
                env->DeleteLocalRef(jJsonwant);
                return false;
            }
            std::shared_ptr<OHOS::AAFwk::Want> want = std::make_shared<OHOS::AAFwk::Want>();
            if (jsonObject.contains("bundleName") && jsonObject["bundleName"].is_string()) {
                want->SetBundleName(jsonObject["bundleName"].get<std::string>());
            }
            if (jsonObject.contains("abilityName") && jsonObject["abilityName"].is_string()) {
                want->SetAbilityName(jsonObject["abilityName"].get<std::string>());
            }
            if (jsonObject.contains("moduleName") && jsonObject["moduleName"].is_string()) {
                want->SetModuleName(jsonObject["moduleName"].get<std::string>());
            }
            if (jsonObject.contains("type") && jsonObject["type"].is_string()) {
                want->SetType(jsonObject["type"].get<std::string>());
            }
            if (jsonObject.contains("params")) {
                std::string wantParamsJson = "{\"params\":" + jsonObject["params"].dump() + "}";
                want->ParseJson(wantParamsJson);
            }
            builder.SetWant(want);
            mimeTypes.emplace_back(MIMETYPE_TEXT_WANT);
            env->ReleaseStringUTFChars(jJsonwant, jsonWantStr);
        }
        env->DeleteLocalRef(jJsonwant);
    }
    return true;
}

bool GetUriFromDataRecordDTO(JNIEnv* env, const jobject& jrecord, OHOS::MiscServices::PasteDataRecord::Builder& builder,
    std::vector<std::string>& mimeTypes)
{
    CHECK_NULL_RETURN(env, false);
    CHECK_NULL_RETURN(jrecord, false);
    CHECK_NULL_RETURN(g_dataRecordDTO.clazz, false);
    CHECK_NULL_RETURN(g_dataRecordDTO.getUri, false);

    jstring jUri = static_cast<jstring>(env->CallObjectMethod(jrecord, g_dataRecordDTO.getUri));
    if (env->ExceptionCheck()) {
        LOGE("GetUriFromRecordDTO: getUri failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        return false;
    }
    if (jUri) {
        const char* uriStr = env->GetStringUTFChars(jUri, nullptr);
        if (uriStr) {
            std::shared_ptr<OHOS::Uri> uri = std::make_shared<OHOS::Uri>(uriStr);
            builder.SetUri(uri);
            mimeTypes.emplace_back(MIMETYPE_TEXT_URI);
            env->ReleaseStringUTFChars(jUri, uriStr);
        }
        env->DeleteLocalRef(jUri);
    }
    return true;
}

bool GetPlainTextFromDataRecordDTO(JNIEnv* env, const jobject& jrecord,
    OHOS::MiscServices::PasteDataRecord::Builder& builder, std::vector<std::string>& mimeTypes)
{
    CHECK_NULL_RETURN(env, false);
    CHECK_NULL_RETURN(jrecord, false);
    CHECK_NULL_RETURN(g_dataRecordDTO.clazz, false);
    CHECK_NULL_RETURN(g_dataRecordDTO.getPlainText, false);

    jstring jPlainText = static_cast<jstring>(env->CallObjectMethod(jrecord, g_dataRecordDTO.getPlainText));
    if (env->ExceptionCheck()) {
        LOGE("GetPlainTextFromRecordDTO: getPlainText failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        return false;
    }
    if (jPlainText) {
        const char* plainStr = env->GetStringUTFChars(jPlainText, nullptr);
        if (plainStr) {
            builder.SetPlainText(std::make_shared<std::string>(plainStr));
            mimeTypes.emplace_back(MIMETYPE_TEXT_PLAIN);
            env->ReleaseStringUTFChars(jPlainText, plainStr);
        }
        env->DeleteLocalRef(jPlainText);
    }
    return true;
}

bool GetHtmlTextFromDataRecordDTO(JNIEnv* env, const jobject& jrecord,
    OHOS::MiscServices::PasteDataRecord::Builder& builder, std::vector<std::string>& mimeTypes)
{
    CHECK_NULL_RETURN(env, false);
    CHECK_NULL_RETURN(jrecord, false);
    CHECK_NULL_RETURN(g_dataRecordDTO.clazz, false);
    CHECK_NULL_RETURN(g_dataRecordDTO.getHtmlText, false);

    jstring jHtmlText = static_cast<jstring>(env->CallObjectMethod(jrecord, g_dataRecordDTO.getHtmlText));
    if (env->ExceptionCheck()) {
        LOGE("GetHtmlTextFromRecordDTO: getHtmlText failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        return false;
    }
    if (jHtmlText) {
        const char* htmlStr = env->GetStringUTFChars(jHtmlText, nullptr);
        if (htmlStr) {
            builder.SetHtmlText(std::make_shared<std::string>(htmlStr));
            mimeTypes.emplace_back(MIMETYPE_TEXT_HTML);
            env->ReleaseStringUTFChars(jHtmlText, htmlStr);
        }
        env->DeleteLocalRef(jHtmlText);
    }
    return true;
}

bool ToDataRecord(
    JNIEnv* env, const jobject& jrecord, OHOS::MiscServices::PasteData& pasteData, const std::string& mimeType)
{
    OHOS::MiscServices::PasteDataRecord::Builder builder("");
    std::vector<std::string> mimeTypes;
    if (!GetHtmlTextFromDataRecordDTO(env, jrecord, builder, mimeTypes)) {
        return false;
    }
    if (!GetPlainTextFromDataRecordDTO(env, jrecord, builder, mimeTypes)) {
        return false;
    }
    if (!GetUriFromDataRecordDTO(env, jrecord, builder, mimeTypes)) {
        return false;
    }
    if (!GetJsonWantFromDataRecordDTO(env, jrecord, builder, mimeTypes)) {
        return false;
    }
    auto it = std::find(mimeTypes.begin(), mimeTypes.end(), mimeType);
    if (it != mimeTypes.end()) {
        builder.SetMimeType(mimeType);
    }
    std::shared_ptr<OHOS::MiscServices::PasteDataRecord> record = builder.Build();
    if (!record) {
        LOGE("ToDataRecord: Builder.Build() failed");
        return false;
    }
    pasteData.AddRecord(record);
    return true;
}

bool ToDataRecordArray(JNIEnv* env, const jobjectArray& jrecords, OHOS::MiscServices::PasteData& pasteData)
{
    CHECK_NULL_RETURN(env, false);
    CHECK_NULL_RETURN(jrecords, false);

    jsize length = env->GetArrayLength(jrecords);
    OHOS::MiscServices::PasteDataProperty property = pasteData.GetProperty();
    std::vector<std::string> mimeTypes = property.mimeTypes;
    int mimeTypeSize = mimeTypes.size();
    property.mimeTypes = {};
    pasteData.SetProperty(property);
    for (jsize i = length - 1; i >= 0; --i) {
        jobject jrecord = env->GetObjectArrayElement(jrecords, i);
        if (!jrecord) {
            LOGE("ToDataRecordArray: null record at index %d", i);
            return false;
        }
        std::string mimeType = "";
        if (i < mimeTypeSize) {
            mimeType = mimeTypes[i];
        }
        if (!ToDataRecord(env, jrecord, pasteData, mimeType)) {
            LOGE("ToDataRecordArray: convert record failed at index %d", i);
            env->DeleteLocalRef(jrecord);
            return false;
        }
        env->DeleteLocalRef(jrecord);
    }
    return true;
}

bool GetTagFromPropertyDTO(JNIEnv* env, const jobject& jproperty, std::string& tag)
{
    CHECK_NULL_RETURN(env, false);
    CHECK_NULL_RETURN(g_propertyDTO.clazz, false);
    CHECK_NULL_RETURN(g_propertyDTO.getTag, false);

    jstring jTag = static_cast<jstring>(env->CallObjectMethod(jproperty, g_propertyDTO.getTag));
    if (!jTag || env->ExceptionCheck()) {
        LOGE("GetTagFromPropertyDTO: getTag failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        if (jTag) {
            env->DeleteLocalRef(jTag);
        }
        return false;
    }
    const char* tagStr = env->GetStringUTFChars(jTag, nullptr);
    if (tagStr) {
        tag = tagStr;
        env->ReleaseStringUTFChars(jTag, tagStr);
    }
    env->DeleteLocalRef(jTag);

    return true;
}

bool GetMimeTypesFromPropertyDTO(JNIEnv* env, const jobject& jproperty, std::vector<std::string>& mimeTypes)
{
    CHECK_NULL_RETURN(env, false);
    CHECK_NULL_RETURN(g_propertyDTO.clazz, false);
    CHECK_NULL_RETURN(g_propertyDTO.getMimeTypes, false);

    jobjectArray jMimeTypes = static_cast<jobjectArray>(env->CallObjectMethod(jproperty, g_propertyDTO.getMimeTypes));
    if (!jMimeTypes || env->ExceptionCheck()) {
        LOGE("GetMimeTypesFromPropertyDTO: getMimeTypes failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        if (jMimeTypes) {
            env->DeleteLocalRef(jMimeTypes);
        }
        return false;
    }
    jsize mimeLength = env->GetArrayLength(jMimeTypes);
    for (jsize i = 0; i < mimeLength; ++i) {
        jstring jMime = static_cast<jstring>(env->GetObjectArrayElement(jMimeTypes, i));
        if (jMime) {
            const char* mimeStr = env->GetStringUTFChars(jMime, nullptr);
            if (mimeStr) {
                mimeTypes.emplace_back(mimeStr);
                env->ReleaseStringUTFChars(jMime, mimeStr);
            }
            env->DeleteLocalRef(jMime);
        }
    }
    return true;
}

bool GetTimeStampFromPropertyDTO(JNIEnv* env, const jobject& jproperty, jlong& timestamp)
{
    CHECK_NULL_RETURN(env, false);
    CHECK_NULL_RETURN(g_propertyDTO.clazz, false);
    CHECK_NULL_RETURN(g_propertyDTO.getTimestamp, false);

    timestamp = static_cast<jlong>(env->CallLongMethod(jproperty, g_propertyDTO.getTimestamp));
    if (env->ExceptionCheck()) {
        LOGE("GetTimeStampFromPropertyDTO: getTimestamp failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        return false;
    }
    return true;
}

bool GetAdditionsFromPropertyDTO(JNIEnv* env, const jobject& jproperty, AAFwk::WantParams& additions)
{
    CHECK_NULL_RETURN(env, false);
    CHECK_NULL_RETURN(g_propertyDTO.clazz, false);
    CHECK_NULL_RETURN(g_propertyDTO.getJsonAdditions, false);

    jstring jAdditions = static_cast<jstring>(env->CallObjectMethod(jproperty, g_propertyDTO.getJsonAdditions));
    if (env->ExceptionCheck()) {
        LOGE("GetAdditionsFromPropertyDTO: getJsonAdditions failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        return false;
    }
    CHECK_NULL_RETURN(jAdditions, true);
    const char* jsonAdditions = env->GetStringUTFChars(jAdditions, nullptr);
    if (jsonAdditions) {
        additions = JsonToWantParams(jsonAdditions);
        env->ReleaseStringUTFChars(jAdditions, jsonAdditions);
    }
    env->DeleteLocalRef(jAdditions);
    return true;
}

bool GetPropertyFromPropertyDTO(JNIEnv* env, const jobject& jproperty, OHOS::MiscServices::PasteData& pasteData)
{
    std::string tag = "";
    if (!GetTagFromPropertyDTO(env, jproperty, tag)) {
        return false;
    }
    std::vector<std::string> mimeTypes;
    if (!GetMimeTypesFromPropertyDTO(env, jproperty, mimeTypes)) {
        return false;
    }
    jlong timestamp = 0;
    if (!GetTimeStampFromPropertyDTO(env, jproperty, timestamp)) {
        return false;
    }
    AAFwk::WantParams additions;
    if (!GetAdditionsFromPropertyDTO(env, jproperty, additions)) {
        return false;
    }
    OHOS::MiscServices::PasteDataProperty property;
    property.tag = tag;
    property.mimeTypes = mimeTypes;
    property.timestamp = timestamp;
    property.additions = additions;
    pasteData.SetProperty(property);
    return true;
}

bool ToPasteData(JNIEnv* env, const jobject& jPasteData, OHOS::MiscServices::PasteData& pasteData)
{
    CHECK_NULL_RETURN(env, false);
    CHECK_NULL_RETURN(g_pasteDataDTO.clazz, false);
    CHECK_NULL_RETURN(g_pasteDataDTO.getPropertyDTO, false);
    CHECK_NULL_RETURN(g_pasteDataDTO.getRecords, false);

    jobject jproperty = env->CallObjectMethod(jPasteData, g_pasteDataDTO.getPropertyDTO);
    if (!jproperty || env->ExceptionCheck()) {
        LOGE("ToPasteData: getPropertyDTO failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        if (jproperty) {
            env->DeleteLocalRef(jproperty);
        }
        return false;
    }
    if (!GetPropertyFromPropertyDTO(env, jproperty, pasteData)) {
        LOGE("ToPasteData: convert PropertyDTO failed");
        env->DeleteLocalRef(jproperty);
        return false;
    }
    env->DeleteLocalRef(jproperty);

    jobjectArray jrecords = static_cast<jobjectArray>(env->CallObjectMethod(jPasteData, g_pasteDataDTO.getRecords));
    if (!jrecords || env->ExceptionCheck()) {
        LOGE("ToPasteData: getRecords failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        if (jrecords) {
            env->DeleteLocalRef(jrecords);
        }
        return false;
    }
    if (!ToDataRecordArray(env, jrecords, pasteData)) {
        LOGE("ToPasteData: convert DataRecordArray failed");
        env->DeleteLocalRef(jrecords);
        return false;
    }
    env->DeleteLocalRef(jrecords);
    return true;
}
} // namespace

bool ClipboardJni::Register(void* env)
{
    auto* jniEnv = static_cast<JNIEnv*>(env);
    CHECK_NULL_RETURN(jniEnv, false);

    jclass clazz = jniEnv->FindClass(CLIPBOARD_PLUGIN_CLASS_NAME);
    CHECK_NULL_RETURN(clazz, false);

    bool ret = jniEnv->RegisterNatives(clazz, METHODS, sizeof(METHODS) / sizeof(METHODS[0])) == 0;
    jniEnv->DeleteLocalRef(clazz);
    CHECK_NULL_RETURN(ret, false);

    jclass pasteDataClass = jniEnv->FindClass(PASTE_DATA_DTO_CLASS_NAME);
    g_pasteDataDTO.clazz = static_cast<jclass>(jniEnv->NewGlobalRef(pasteDataClass));
    jniEnv->DeleteLocalRef(pasteDataClass);
    CHECK_NULL_RETURN(g_pasteDataDTO.clazz, false);

    jclass dataRecordClass = jniEnv->FindClass(PASTE_DATA_RECORD_DTO_CLASS_NAME);
    g_dataRecordDTO.clazz = static_cast<jclass>(jniEnv->NewGlobalRef(dataRecordClass));
    jniEnv->DeleteLocalRef(dataRecordClass);
    CHECK_NULL_RETURN(g_dataRecordDTO.clazz, false);

    jclass propertyClass = jniEnv->FindClass(PASTE_DATA_PROPERTY_DTO_CLASS_NAME);
    g_propertyDTO.clazz = static_cast<jclass>(jniEnv->NewGlobalRef(propertyClass));
    jniEnv->DeleteLocalRef(propertyClass);
    CHECK_NULL_RETURN(g_propertyDTO.clazz, false);
    return true;
}

inline bool CheckNullAndReleaseClazz(const void* ptr, JNIEnv* env, jclass clazz, const char* ptrName)
{
    if (ptr == nullptr) {
        LOGW("%s is null, release clazz and return on line", ptrName);
        if (env != nullptr && clazz != nullptr) {
            env->DeleteLocalRef(clazz);
        }
        return true;
    }
    return false;
}

inline bool CheckBatchMethodIDs(
    JNIEnv* env, jclass clazz, std::initializer_list<std::pair<void*, const char*>> methodList)
{
    for (auto& [methodPtr, methodName] : methodList) {
        if (CheckNullAndReleaseClazz(methodPtr, env, clazz, methodName)) {
            return true;
        }
    }
    return false;
}

void ClipboardJni::NativeInit(JNIEnv* env, jobject object)
{
    CHECK_NULL_VOID(env);
    CHECK_NULL_VOID(object);
    g_clipboard.globalRef = env->NewGlobalRef(object);
    CHECK_NULL_VOID(g_clipboard.globalRef);
    jclass clazz = env->GetObjectClass(object);
    CHECK_NULL_VOID(clazz);

    g_clipboard.setData = env->GetMethodID(clazz, METHOD_SET_DATA, SIGNATURE_SET_DATA);
    g_clipboard.getData = env->GetMethodID(clazz, METHOD_GET_DATA, SIGNATURE_GET_DATA);
    g_clipboard.hasData = env->GetMethodID(clazz, METHOD_HAS_DATA, SIGNATURE_HAS_DATA);
    g_clipboard.clear = env->GetMethodID(clazz, METHOD_CLEAR, SIGNATURE_CLEAR);
    g_clipboard.subscribePasteboardChange =
        env->GetMethodID(clazz, METHOD_SUBSCRIBE_PASTEBOARD_CHANGE, SIGNATURE_SUBSCRIBE_PASTEBOARD_CHANGE);
    g_clipboard.unsubscribePasteboardChange =
        env->GetMethodID(clazz, METHOD_UNSUBSCRIBE_PASTEBOARD_CHANGE, SIGNATURE_UNSUBSCRIBE_PASTEBOARD_CHANGE);
    g_clipboard.detectPatterns = env->GetMethodID(clazz, METHOD_DETECT_PATTERNS, SIGNATURE_DETECT_PATTERNS);
    g_clipboard.hasDataType = env->GetMethodID(clazz, METHOD_HAS_DATA_TYPE, SIGNATURE_HAS_DATA_TYPE);
    g_clipboard.getMimeTypes = env->GetMethodID(clazz, METHOD_GET_MIMETYPES, SIGNATURE_GET_MIMETYPES);

    if (CheckBatchMethodIDs(env, clazz, {
        { g_clipboard.setData, "g_clipboard.setData" },
        { g_clipboard.getData, "g_clipboard.getData" },
        { g_clipboard.hasData, "g_clipboard.hasData" },
        { g_clipboard.clear, "g_clipboard.clear" },
        { g_clipboard.subscribePasteboardChange, "g_clipboard.subscribePasteboardChange" },
        { g_clipboard.unsubscribePasteboardChange, "g_clipboard.unsubscribePasteboardChange" },
        { g_clipboard.detectPatterns, "g_clipboard.detectPatterns" },
        { g_clipboard.hasDataType, "g_clipboard.hasDataType" },
        { g_clipboard.getMimeTypes, "g_clipboard.getMimeTypes" }
        })) {
        return;
    }

    env->DeleteLocalRef(clazz);

    PasteDataDTOInit(env);
    DataRecordDTOInit(env);
    PropertyDTOInit(env);
}

void ClipboardJni::PasteDataDTOInit(JNIEnv* env)
{
    CHECK_NULL_VOID(env);

    g_pasteDataDTO.constructor = env->GetMethodID(g_pasteDataDTO.clazz, "<init>", "()V");
    CHECK_NULL_VOID(g_pasteDataDTO.constructor);

    g_pasteDataDTO.setPropertyDTO =
        env->GetMethodID(g_pasteDataDTO.clazz, METHOD_SET_PROPERTY_DTO, SIGNATURE_SET_PROPERTY_DTO);
    CHECK_NULL_VOID(g_pasteDataDTO.setPropertyDTO);

    g_pasteDataDTO.setRecords = env->GetMethodID(g_pasteDataDTO.clazz, METHOD_SET_RECORDS, SIGNATURE_SET_RECORDS);
    CHECK_NULL_VOID(g_pasteDataDTO.setRecords);

    g_pasteDataDTO.getPropertyDTO =
        env->GetMethodID(g_pasteDataDTO.clazz, METHOD_GET_PROPERTY_DTO, SIGNATURE_GET_PROPERTY_DTO);
    CHECK_NULL_VOID(g_pasteDataDTO.getPropertyDTO);

    g_pasteDataDTO.getRecords = env->GetMethodID(g_pasteDataDTO.clazz, METHOD_GET_RECORDS, SIGNATURE_GET_RECORDS);
    CHECK_NULL_VOID(g_pasteDataDTO.getRecords);
}

void ClipboardJni::DataRecordDTOInit(JNIEnv* env)
{
    CHECK_NULL_VOID(env);

    g_dataRecordDTO.constructor = env->GetMethodID(g_dataRecordDTO.clazz, "<init>", "()V");
    CHECK_NULL_VOID(g_dataRecordDTO.constructor);

    g_dataRecordDTO.setMimeType = env->GetMethodID(g_dataRecordDTO.clazz, METHOD_SET_MIMETYPE, SIGNATURE_SET_MIMETYPE);
    CHECK_NULL_VOID(g_dataRecordDTO.setMimeType);

    g_dataRecordDTO.setHtmlText = env->GetMethodID(g_dataRecordDTO.clazz, METHOD_SET_HTMLTEXT, SIGNATURE_SET_HTMLTEXT);
    CHECK_NULL_VOID(g_dataRecordDTO.setHtmlText);

    g_dataRecordDTO.setJsonWant =
        env->GetMethodID(g_dataRecordDTO.clazz, METHOD_SET_JSON_WANT, SIGNATURE_SET_JSON_WANT);
    CHECK_NULL_VOID(g_dataRecordDTO.setJsonWant);

    g_dataRecordDTO.setPlainText =
        env->GetMethodID(g_dataRecordDTO.clazz, METHOD_SET_PLAINTEXT, SIGNATURE_SET_PLAINTEXT);
    CHECK_NULL_VOID(g_dataRecordDTO.setPlainText);

    g_dataRecordDTO.setUri = env->GetMethodID(g_dataRecordDTO.clazz, METHOD_SET_URI, SIGNATURE_SET_URI);
    CHECK_NULL_VOID(g_dataRecordDTO.setUri);

    g_dataRecordDTO.getMimeType = env->GetMethodID(g_dataRecordDTO.clazz, METHOD_GET_MIMETYPE, SIGNATURE_GET_MIMETYPE);
    CHECK_NULL_VOID(g_dataRecordDTO.getMimeType);

    g_dataRecordDTO.getHtmlText = env->GetMethodID(g_dataRecordDTO.clazz, METHOD_GET_HTMLTEXT, SIGNATURE_GET_HTMLTEXT);
    CHECK_NULL_VOID(g_dataRecordDTO.getHtmlText);

    g_dataRecordDTO.getJsonWant =
        env->GetMethodID(g_dataRecordDTO.clazz, METHOD_GET_JSON_WANT, SIGNATURE_GET_JSON_WANT);
    CHECK_NULL_VOID(g_dataRecordDTO.getJsonWant);

    g_dataRecordDTO.getPlainText =
        env->GetMethodID(g_dataRecordDTO.clazz, METHOD_GET_PLAINTEXT, SIGNATURE_GET_PLAINTEXT);
    CHECK_NULL_VOID(g_dataRecordDTO.getPlainText);

    g_dataRecordDTO.getUri = env->GetMethodID(g_dataRecordDTO.clazz, METHOD_GET_URI, SIGNATURE_GET_URI);
    CHECK_NULL_VOID(g_dataRecordDTO.getUri);
}

void ClipboardJni::PropertyDTOInit(JNIEnv* env)
{
    CHECK_NULL_VOID(env);

    g_propertyDTO.constructor = env->GetMethodID(g_propertyDTO.clazz, "<init>", "()V");
    CHECK_NULL_VOID(g_propertyDTO.constructor);

    g_propertyDTO.setMimeTypes = env->GetMethodID(g_propertyDTO.clazz, METHOD_SET_MIMETYPES, SIGNATURE_SET_MIMETYPES);
    CHECK_NULL_VOID(g_propertyDTO.setMimeTypes);

    g_propertyDTO.setTag = env->GetMethodID(g_propertyDTO.clazz, METHOD_SET_TAG, SIGNATURE_SET_TAG);
    CHECK_NULL_VOID(g_propertyDTO.setTag);

    g_propertyDTO.setTimestamp = env->GetMethodID(g_propertyDTO.clazz, METHOD_SET_TIMESTAMP, SIGNATURE_SET_TIMESTAMP);
    CHECK_NULL_VOID(g_propertyDTO.setTimestamp);

    g_propertyDTO.setJsonAdditions =
        env->GetMethodID(g_propertyDTO.clazz, METHOD_SET_JSON_ADDITIONS, SIGNATURE_SET_JSON_ADDITIONS);
    CHECK_NULL_VOID(g_propertyDTO.setJsonAdditions);

    g_propertyDTO.getMimeTypes = env->GetMethodID(g_propertyDTO.clazz, METHOD_GET_MIMETYPES, SIGNATURE_GET_MIMETYPES);
    CHECK_NULL_VOID(g_propertyDTO.getMimeTypes);

    g_propertyDTO.getTag = env->GetMethodID(g_propertyDTO.clazz, METHOD_GET_TAG, SIGNATURE_GET_TAG);
    CHECK_NULL_VOID(g_propertyDTO.getTag);

    g_propertyDTO.getTimestamp = env->GetMethodID(g_propertyDTO.clazz, METHOD_GET_TIMESTAMP, SIGNATURE_GET_TIMESTAMP);
    CHECK_NULL_VOID(g_propertyDTO.getTimestamp);

    g_propertyDTO.getJsonAdditions =
        env->GetMethodID(g_propertyDTO.clazz, METHOD_GET_JSON_ADDITIONS, SIGNATURE_GET_JSON_ADDITIONS);
    CHECK_NULL_VOID(g_propertyDTO.getJsonAdditions);
}

int32_t ClipboardJni::SetData(const OHOS::MiscServices::PasteData& pasteData)
{
    auto env = ARKUI_X_Plugin_GetJniEnv();
    if (!env) {
        LOGE("SetData: null env");
        return static_cast<int32_t>(PasteboardError::OTHER_ERROR);
    }
    if (!g_clipboard.globalRef) {
        LOGE("SetData: null g_clipboard.globalRef");
        return static_cast<int32_t>(PasteboardError::OTHER_ERROR);
    }
    if (!g_clipboard.setData) {
        LOGE("SetData: null setData method");
        return static_cast<int32_t>(PasteboardError::OTHER_ERROR);
    }

    jobject jPasteData = ToJavaPasteDataDTO(env, pasteData);
    if (jPasteData == nullptr) {
        LOGE("SetData: jPasteData is null");
        return static_cast<int32_t>(PasteboardError::OTHER_ERROR);
    }

    auto setDataRet = env->CallIntMethod(g_clipboard.globalRef, g_clipboard.setData, jPasteData);
    env->DeleteLocalRef(jPasteData);
    if (env->ExceptionCheck()) {
        LOGE("SetData: call setData has exception");
        env->ExceptionDescribe();
        env->ExceptionClear();
        return static_cast<int32_t>(PasteboardError::OTHER_ERROR);
    }
    int32_t ret = static_cast<int32_t>(setDataRet);
    return ret;
}

int32_t ClipboardJni::GetData(OHOS::MiscServices::PasteData& pasteData)
{
    auto env = ARKUI_X_Plugin_GetJniEnv();
    if (!env) {
        LOGE("GetData: null env");
        return static_cast<int32_t>(PasteboardError::OTHER_ERROR);
    }
    if (!g_clipboard.globalRef) {
        LOGE("GetData: null g_clipboard.globalRef");
        return static_cast<int32_t>(PasteboardError::OTHER_ERROR);
    }
    if (!g_clipboard.getData) {
        LOGE("GetData: null getData method");
        return static_cast<int32_t>(PasteboardError::OTHER_ERROR);
    }
    jobject jPasteData = env->CallObjectMethod(g_clipboard.globalRef, g_clipboard.getData);
    if (env->ExceptionCheck()) {
        LOGE("GetData: Call getData method failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        return static_cast<int32_t>(PasteboardError::OTHER_ERROR);
    }
    if (!jPasteData) {
        return static_cast<int32_t>(PasteboardError::E_OK);
    }
    if (!ToPasteData(env, jPasteData, pasteData)) {
        LOGE("GetData: Convert jPasteData to PasteData failed");
        pasteData = OHOS::MiscServices::PasteData();
        env->DeleteLocalRef(jPasteData);
        return static_cast<int32_t>(PasteboardError::OTHER_ERROR);
    }
    env->DeleteLocalRef(jPasteData);
    return static_cast<int32_t>(PasteboardError::E_OK);
}

bool ClipboardJni::HasData()
{
    return HasPasteData();
}

bool ClipboardJni::HasPasteData()
{
    auto env = ARKUI_X_Plugin_GetJniEnv();
    if (!env) {
        LOGE("Clipborad JNI: null env");
        return false;
    }
    if (!g_clipboard.globalRef) {
        LOGE("Clipborad JNI: null g_clipboard.globalRef");
        return false;
    }
    if (!g_clipboard.hasData) {
        LOGE("Clipborad JNI: null HasPasteData method");
        return false;
    }

    auto ret = env->CallBooleanMethod(g_clipboard.globalRef, g_clipboard.hasData);
    if (env->ExceptionCheck()) {
        LOGE("Clipborad JNI: call HasPasteData has exception");
        env->ExceptionDescribe();
        env->ExceptionClear();
        return false;
    }
    return ret;
}

bool ClipboardJni::Clear()
{
    auto env = ARKUI_X_Plugin_GetJniEnv();
    if (!env) {
        LOGE("Clipborad JNI: null env");
        return false;
    }
    if (!g_clipboard.globalRef) {
        LOGE("Clipborad JNI: null g_clipboard.globalRef");
        return false;
    }
    if (!g_clipboard.clear) {
        LOGE("Clipborad JNI: null Clear method");
        return false;
    }

    auto ret = env->CallBooleanMethod(g_clipboard.globalRef, g_clipboard.clear);
    if (env->ExceptionCheck()) {
        LOGE("Clipborad JNI: call Clear has exception");
        env->ExceptionDescribe();
        env->ExceptionClear();
        return false;
    }
    return ret;
}

bool ClipboardJni::Subscribe()
{
    auto env = ARKUI_X_Plugin_GetJniEnv();
    if (!env) {
        LOGE("Clipborad JNI: null env");
        return false;
    }
    if (!g_clipboard.globalRef) {
        LOGE("Clipborad JNI: null g_clipboard.globalRef");
        return false;
    }
    if (!g_clipboard.subscribePasteboardChange) {
        LOGE("Clipborad JNI: null subscribePasteboardChange method");
        return false;
    }

    auto ret = env->CallBooleanMethod(g_clipboard.globalRef, g_clipboard.subscribePasteboardChange);
    if (env->ExceptionCheck()) {
        LOGE("Clipborad JNI: call subscribePasteboardChange has exception");
        env->ExceptionDescribe();
        env->ExceptionClear();
        return false;
    }
    return ret;
}

bool ClipboardJni::Unsubscribe()
{
    auto env = ARKUI_X_Plugin_GetJniEnv();
    if (!env) {
        LOGE("Clipborad JNI: null env");
        return false;
    }
    if (!g_clipboard.globalRef) {
        LOGE("Clipborad JNI: null g_clipboard.globalRef");
        return false;
    }
    if (!g_clipboard.unsubscribePasteboardChange) {
        LOGE("Clipborad JNI: null unsubscribePasteboardChange method");
        return false;
    }

    auto ret = env->CallBooleanMethod(g_clipboard.globalRef, g_clipboard.unsubscribePasteboardChange);
    if (env->ExceptionCheck()) {
        LOGE("Clipborad JNI: call unsubscribePasteboardChange has exception");
        env->ExceptionDescribe();
        env->ExceptionClear();
        return false;
    }
    return ret;
}

void ClipboardJni::OnPasteboardChanged()
{
    auto clipboardProxy = ClipboardProxy::GetInstance();
    if (clipboardProxy == nullptr) {
        LOGE("Clipborad JNI: clipboardProxy is null");
        return;
    }
    clipboardProxy->NotifyObservers();
}

int ClipboardJni::DetectPatterns(
    const std::vector<MiscServices::Pattern>& patternsToCheck, std::vector<MiscServices::Pattern>& funcResult)
{
    auto env = ARKUI_X_Plugin_GetJniEnv();
    if (!env) {
        LOGE("Clipborad JNI: null env");
        return static_cast<int32_t>(PasteboardError::INVALID_OPERATION_ERROR);
    }
    if (!g_clipboard.globalRef) {
        LOGE("Clipborad JNI: null g_clipboard.globalRef");
        return static_cast<int32_t>(PasteboardError::INVALID_OPERATION_ERROR);
    }
    if (!g_clipboard.detectPatterns) {
        LOGE("Clipborad JNI: null detectPatterns method");
        return static_cast<int32_t>(PasteboardError::INVALID_OPERATION_ERROR);
    }

    int patterns = 0;
    for (auto pattern : patternsToCheck) {
        if (pattern == Pattern::URL) {
            patterns |= 1 << static_cast<uint32_t>(Pattern::URL);
        } else if (pattern == Pattern::EMAIL_ADDRESS) {
            patterns |= 1 << static_cast<uint32_t>(Pattern::EMAIL_ADDRESS);
        } else {
            patterns |= 1 << static_cast<uint32_t>(Pattern::NUMBER);
        }
    }
    auto ret = env->CallIntMethod(g_clipboard.globalRef, g_clipboard.detectPatterns, patterns);
    if (env->ExceptionCheck()) {
        LOGE("Clipborad JNI: call DetectPatterns has exception");
        env->ExceptionDescribe();
        env->ExceptionClear();
        return static_cast<int32_t>(PasteboardError::INVALID_OPERATION_ERROR);
    }
    if (ret & (1 << static_cast<uint32_t>(Pattern::URL))) {
        funcResult.push_back(Pattern::URL);
    }
    if (ret & (1 << static_cast<uint32_t>(Pattern::EMAIL_ADDRESS))) {
        funcResult.push_back(Pattern::EMAIL_ADDRESS);
    }
    if (ret & (1 << static_cast<uint32_t>(Pattern::NUMBER))) {
        funcResult.push_back(Pattern::NUMBER);
    }
    return ERR_OK;
}

bool ClipboardJni::HasDataType(const std::string& mimeType)
{
    auto env = ARKUI_X_Plugin_GetJniEnv();
    if (!env) {
        LOGE("Clipborad JNI: null env");
        return false;
    }
    if (!g_clipboard.globalRef) {
        LOGE("Clipborad JNI: null g_clipboard.globalRef");
        return false;
    }
    if (!g_clipboard.hasDataType) {
        LOGE("Clipborad JNI: null hasDataType method");
        return false;
    }

    jstring jMimeType = env->NewStringUTF(mimeType.c_str());
    if (!jMimeType) {
        LOGE("Clipborad JNI: failed to create jstring for mimeType");
        return false;
    }

    auto ret = env->CallBooleanMethod(g_clipboard.globalRef, g_clipboard.hasDataType, jMimeType);
    env->DeleteLocalRef(jMimeType);

    if (env->ExceptionCheck()) {
        LOGE("Clipborad JNI: call hasDataType has exception");
        env->ExceptionDescribe();
        env->ExceptionClear();
        return false;
    }
    return ret;
}

bool ClipboardJni::GetMimeTypes(std::vector<std::string>& funcResult)
{
    auto env = ARKUI_X_Plugin_GetJniEnv();
    if (!env) {
        LOGE("Clipborad JNI: null env");
        return false;
    }
    if (!g_clipboard.globalRef) {
        LOGE("Clipborad JNI: null g_clipboard.globalRef");
        return false;
    }
    if (!g_clipboard.getMimeTypes) {
        LOGE("Clipborad JNI: null getMimeTypes method");
        return false;
    }

    jobjectArray jMimeTypes =
        static_cast<jobjectArray>(env->CallObjectMethod(g_clipboard.globalRef, g_clipboard.getMimeTypes));
    if (!jMimeTypes || env->ExceptionCheck()) {
        LOGE("Clipborad: getMimeTypes failed");
        env->ExceptionDescribe();
        env->ExceptionClear();
        if (jMimeTypes) {
            env->DeleteLocalRef(jMimeTypes);
        }
        return false;
    }
    std::set<std::string> mimeTypes;
    jsize mimeLength = env->GetArrayLength(jMimeTypes);
    for (jsize i = 0; i < mimeLength; ++i) {
        jstring jMime = static_cast<jstring>(env->GetObjectArrayElement(jMimeTypes, i));
        if (jMime) {
            const char* mimeStr = env->GetStringUTFChars(jMime, nullptr);
            if (mimeStr) {
                mimeTypes.insert(mimeStr);
                env->ReleaseStringUTFChars(jMime, mimeStr);
            }
            env->DeleteLocalRef(jMime);
        }
    }
    funcResult = std::vector<std::string>(mimeTypes.begin(), mimeTypes.end());
    return true;
}
} // namespace OHOS::Plugin