/*
 * Copyright (c) 2026 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 <unordered_map>

#include "ani_utils.h"

#include "bridge/common/utils/utils.h"
#include "frameworks/base/error/error_code.h"

namespace OHOS::Ace::AniUtils {

static const std::unordered_map<int32_t, std::string> ERROR_CODE_TO_MSG {
    { ERROR_CODE_PERMISSION_DENIED, "Permission denied. " },
    { ERROR_CODE_PARAM_INVALID, "Parameter error. " },
    { ERROR_CODE_SYSTEMCAP_ERROR, "Capability not supported. " },
    { ERROR_CODE_INTERNAL_ERROR, "Internal error. " },
    { ERROR_CODE_URI_ERROR, "Uri error. " },
    { ERROR_CODE_PAGE_STACK_FULL, "Page stack error. " },
    { ERROR_CODE_URI_ERROR_LITE, "Uri error. " },
    { ERROR_CODE_DIALOG_CONTENT_ERROR, "Dialog content error. " },
    { ERROR_CODE_DIALOG_CONTENT_ALREADY_EXIST, "Dialog content already exist. " },
    { ERROR_CODE_DIALOG_CONTENT_NOT_FOUND, "Dialog content not found. " },
    { ERROR_CODE_OVERLAY_CANNOT_OPEN_DUE_TO_SYSTEM_WINDOW,
        "The overlay cannot be opened due to the system pop-up window." },
    { ERROR_CODE_TOAST_NOT_FOUND, "Toast not found. " }
};

ani_object CreateANIIntObject(ani_env* env, int32_t intValue)
{
    ani_class intCls {};
    env->FindClass("std.core.Int", &intCls);
    ani_method ctor {};
    env->Class_FindMethod(intCls, "<ctor>", "i:", &ctor);
    ani_object result {};
    ani_int aniInt = static_cast<ani_int>(intValue);
    ani_status status = env->Object_New(intCls, ctor, &result, aniInt);
    if (status != ANI_OK) {
        ani_ref undefinedRef;
        env->GetUndefined(&undefinedRef);
        return static_cast<ani_object>(undefinedRef);
    }
    return result;
}

ani_object CreateANILongObject(ani_env* env, int64_t longValue)
{
    ani_class longCls {};
    env->FindClass("std.core.Long", &longCls);
    ani_method ctor {};
    env->Class_FindMethod(longCls, "<ctor>", "l:", &ctor);
    ani_object result {};
    ani_long aniLong = static_cast<ani_long>(longValue);
    ani_status status = env->Object_New(longCls, ctor, &result, aniLong);
    if (status != ANI_OK) {
        ani_ref undefinedRef;
        env->GetUndefined(&undefinedRef);
        return static_cast<ani_object>(undefinedRef);
    }
    return result;
}

ani_object CreateANIDoubleObject(ani_env* env, double doubleValue)
{
    ani_class doubleCls {};
    env->FindClass("std.core.Double", &doubleCls);
    ani_method ctor {};
    env->Class_FindMethod(doubleCls, "<ctor>", "d:", &ctor);
    ani_object result {};
    ani_double aniDouble = static_cast<ani_double>(doubleValue);
    ani_status status = env->Object_New(doubleCls, ctor, &result, aniDouble);
    if (status != ANI_OK) {
        ani_ref undefinedRef;
        env->GetUndefined(&undefinedRef);
        return static_cast<ani_object>(undefinedRef);
    }
    return result;
}

bool IsUndefinedObject(ani_env* env, ani_ref objectRef)
{
    ani_boolean isUndefined;
    ani_status status = env->Reference_IsUndefined(objectRef, &isUndefined);
    if (status != ANI_OK) {
        return false;
    }
    return static_cast<bool>(isUndefined);
}

bool IsClassObject(ani_env* env, ani_object object, const char* classDescriptor)
{
    ani_class objectClass;
    ani_status status = env->FindClass(classDescriptor, &objectClass);
    if (status != ANI_OK) {
        return false;
    }

    ani_boolean isInstance;
    status = env->Object_InstanceOf(object, objectClass, &isInstance);
    if (status != ANI_OK) {
        return false;
    }
    return static_cast<bool>(isInstance);
}

bool IsArrayObject(ani_env* env, ani_object object)
{
    return IsClassObject(env, object, "std.core.Array");
}

bool IsArrayObject(ani_env* env, ani_ref ref)
{
    ani_object object = static_cast<ani_object>(ref);
    return IsArrayObject(env, object);
}

bool IsEnum(ani_env* env, ani_object object, const char* enumDescriptor)
{
    ani_enum valueEnum;
    ani_status status = env->FindEnum(enumDescriptor, &valueEnum);
    if (status != ANI_OK) {
        return false;
    }

    ani_boolean isEnum;
    status = env->Object_InstanceOf(object, valueEnum, &isEnum);
    if (status != ANI_OK) {
        return false;
    }
    return static_cast<bool>(isEnum);
}

bool GetBoolParam(ani_env* env, ani_ref ref, bool& result)
{
    if (IsUndefinedObject(env, ref)) {
        return false;
    }

    ani_object object = static_cast<ani_object>(ref);
    if (!IsClassObject(env, object, "std.core.Boolean")) {
        return false;
    }

    ani_boolean resultValue;
    ani_status status = env->Object_CallMethodByName_Boolean(object, "toBoolean", ":z", &resultValue);
    if (status != ANI_OK) {
        return false;
    }
    result = static_cast<bool>(resultValue);
    return true;
}

bool GetBoolParam(ani_env* env, ani_object object, const char* name, bool& result)
{
    ani_ref resultRef;
    ani_status status = env->Object_GetPropertyByName_Ref(object, name, &resultRef);
    if (status != ANI_OK) {
        return false;
    }
    return GetBoolParam(env, resultRef, result);
}

bool GetInt32Param(ani_env* env, ani_ref ref, int32_t& result)
{
    if (IsUndefinedObject(env, ref)) {
        return false;
    }

    ani_object object = static_cast<ani_object>(ref);
    if (!IsClassObject(env, object, "std.core.Int")) {
        return false;
    }

    ani_int resultValue;
    ani_status status = env->Object_CallMethodByName_Int(object, "toInt", ":i", &resultValue);
    if (status != ANI_OK) {
        return false;
    }
    result = static_cast<int32_t>(resultValue);
    return true;
}

bool GetInt32Param(ani_env* env, ani_object object, const char* name, int32_t& result)
{
    ani_ref resultRef;
    ani_status status = env->Object_GetPropertyByName_Ref(object, name, &resultRef);
    if (status != ANI_OK) {
        return false;
    }
    return GetInt32Param(env, resultRef, result);
}

bool GetInt64Param(ani_env* env, ani_ref ref, int64_t& result)
{
    if (IsUndefinedObject(env, ref)) {
        return false;
    }

    ani_object object = static_cast<ani_object>(ref);
    if (!IsClassObject(env, object, "std.core.Long")) {
        return false;
    }

    ani_long resultValue;
    ani_status status = env->Object_CallMethodByName_Long(object, "toLong", ":l", &resultValue);
    if (status != ANI_OK) {
        return false;
    }
    result = static_cast<int64_t>(resultValue);
    return true;
}

bool GetInt64Param(ani_env* env, ani_object object, const char* name, int64_t& result)
{
    ani_ref resultRef;
    ani_status status = env->Object_GetPropertyByName_Ref(object, name, &resultRef);
    if (status != ANI_OK) {
        return false;
    }
    return GetInt64Param(env, resultRef, result);
}

bool GetDoubleParam(ani_env* env, ani_ref ref, double& result)
{
    if (IsUndefinedObject(env, ref)) {
        return false;
    }

    ani_object object = static_cast<ani_object>(ref);
    if (!IsClassObject(env, object, "std.core.Double")) {
        return false;
    }

    ani_double resultValue;
    ani_status status = env->Object_CallMethodByName_Double(object, "toDouble", ":d", &resultValue);
    if (status != ANI_OK) {
        return false;
    }
    result = static_cast<double>(resultValue);
    return true;
}

bool GetDoubleParam(ani_env* env, ani_object object, const char* name, double& result)
{
    ani_ref resultRef;
    ani_status status = env->Object_GetPropertyByName_Ref(object, name, &resultRef);
    if (status != ANI_OK) {
        return false;
    }
    return GetDoubleParam(env, resultRef, result);
}

bool GetDoubleParamOpt(ani_env* env, ani_object object, const char* name, std::optional<double>& result)
{
    double resultValue;
    if (!GetDoubleParam(env, object, name, resultValue)) {
        return false;
    }
    result = std::make_optional<double>(resultValue);
    return true;
}

std::string ANIStringToStdString(ani_env* env, ani_string aniStr)
{
    ani_size strSize;
    env->String_GetUTF8Size(aniStr, &strSize);

    std::vector<char> buffer(strSize + 1);
    char* utf8Buffer = buffer.data();

    ani_size bytesWritten = 0;
    env->String_GetUTF8(aniStr, utf8Buffer, strSize + 1, &bytesWritten);

    utf8Buffer[bytesWritten] = '\0';
    std::string content = std::string(utf8Buffer);
    return content;
}

bool GetStringParam(ani_env* env, ani_ref ref, std::string& result)
{
    if (IsUndefinedObject(env, ref)) {
        return false;
    }

    ani_object object = static_cast<ani_object>(ref);
    if (!IsClassObject(env, object, "std.core.String")) {
        return false;
    }
    result = ANIStringToStdString(env, static_cast<ani_string>(object));
    return true;
}

bool GetStringParam(ani_env* env, ani_object object, const char* name, std::string& result)
{
    ani_ref resultRef;
    ani_status status = env->Object_GetPropertyByName_Ref(object, name, &resultRef);
    if (status != ANI_OK) {
        return false;
    }
    return GetStringParam(env, resultRef, result);
}

bool GetStringParamOpt(ani_env* env, ani_object object, const char* name, std::optional<std::string>& result)
{
    std::string resultStr;
    if (!GetStringParam(env, object, name, resultStr)) {
        return false;
    }
    result = std::make_optional<std::string>(resultStr);
    return true;
}

bool GetStringArrayParam(ani_env* env, ani_object object, const char* name, std::vector<std::string>& result)
{
    ani_ref resultRef;
    ani_status status = env->Object_GetPropertyByName_Ref(object, name, &resultRef);
    if (status != ANI_OK) {
        return false;
    }

    if (IsUndefinedObject(env, resultRef) || !IsArrayObject(env, resultRef)) {
        return false;
    }

    ani_size length;
    ani_array resultObj = static_cast<ani_array>(resultRef);
    status = env->Array_GetLength(resultObj, &length);
    if (status != ANI_OK) {
        return false;
    }

    std::vector<std::string> stringArray;
    for (int32_t i = 0; i < int32_t(length); i++) {
        ani_ref itemRef;
        status = env->Array_Get(resultObj, static_cast<ani_size>(i), &itemRef);
        if (status != ANI_OK) {
            continue;
        }
        stringArray.emplace_back(ANIStringToStdString(env, static_cast<ani_string>(itemRef)));
    }
    result = stringArray;
    return true;
}

bool GetEnumInt(ani_env* env, ani_object resultObj, const char* enumDescriptor, int32_t& result)
{
    if (!IsEnum(env, resultObj, enumDescriptor)) {
        return false;
    }

    ani_int resultInt;
    ani_status status = env->EnumItem_GetValue_Int(static_cast<ani_enum_item>(resultObj), &resultInt);
    if (status != ANI_OK) {
        return false;
    }
    result = static_cast<int32_t>(resultInt);
    return true;
}

bool GetEnumInt(ani_env* env, ani_object object, const char* name, const char* enumDescriptor, int32_t& result)
{
    ani_ref resultRef;
    ani_status status = env->Object_GetPropertyByName_Ref(object, name, &resultRef);
    if (status != ANI_OK) {
        return false;
    }

    if (IsUndefinedObject(env, resultRef)) {
        return false;
    }
    ani_object resultObj = static_cast<ani_object>(resultRef);
    return GetEnumInt(env, resultObj, enumDescriptor, result);
}

bool GetEnumIntOpt(ani_env* env, ani_object object, const char* name, const char* enumDescriptor,
    std::optional<int32_t>& result)
{
    int32_t resultInt;
    if (!GetEnumInt(env, object, name, enumDescriptor, resultInt)) {
        return false;
    }
    result = std::make_optional<int32_t>(resultInt);
    return true;
}

bool GetEnumString(ani_env* env, ani_object resultObj, const char* enumDescriptor, std::string& result)
{
    if (!IsEnum(env, resultObj, enumDescriptor)) {
        return false;
    }

    ani_string resultStr;
    ani_status status = env->EnumItem_GetValue_String(static_cast<ani_enum_item>(resultObj), &resultStr);
    if (status != ANI_OK) {
        return false;
    }
    result = ANIStringToStdString(env, resultStr);
    return true;
}

bool GetEnumString(ani_env* env, ani_object object, const char* name, const char* enumDescriptor, std::string& result)
{
    ani_ref resultRef;
    ani_status status = env->Object_GetPropertyByName_Ref(object, name, &resultRef);
    if (status != ANI_OK) {
        return false;
    }

    if (IsUndefinedObject(env, resultRef)) {
        return false;
    }
    ani_object resultObj = static_cast<ani_object>(resultRef);
    return GetEnumString(env, resultObj, enumDescriptor, result);
}

bool GetEnumStringOpt(ani_env* env, ani_object object, const char* name, const char* enumDescriptor,
    std::optional<std::string>& result)
{
    std::string resultStr;
    if (!GetEnumString(env, object, name, enumDescriptor, resultStr)) {
        return false;
    }
    result = std::make_optional<std::string>(resultStr);
    return true;
}

bool GetFunctionParam(ani_env* env, ani_ref ref, std::function<void()>& result)
{
    ani_ref globalRef;
    ani_status status = env->GlobalReference_Create(ref, &globalRef);
    if (status != ANI_OK) {
        return false;
    }

    ani_vm* vm = nullptr;
    status = env->GetVM(&vm);
    if (status != ANI_OK || vm == nullptr) {
        return false;
    }

    result = [vm, globalRef]() {
        if (!globalRef) {
            return;
        }

        ani_env* env = nullptr;
        ani_status status = vm->GetEnv(ANI_VERSION_1, &env);
        if (status != ANI_OK || env == nullptr) {
            return;
        }

        ani_fn_object func = static_cast<ani_fn_object>(globalRef);
        std::vector<ani_ref> args;
        ani_ref fnReturnVal {};
        status = env->FunctionalObject_Call(func, args.size(), args.data(), &fnReturnVal);
        env->GlobalReference_Delete(globalRef);
    };

    return true;
}

bool GetFunctionParam(ani_env* env, ani_object object, const char* name, std::function<void()>& result)
{
    ani_ref resultRef;
    ani_status status = env->Object_GetPropertyByName_Ref(object, name, &resultRef);
    if (status != ANI_OK) {
        return false;
    }

    if (IsUndefinedObject(env, resultRef)) {
        return false;
    }
    return GetFunctionParam(env, resultRef, result);
}

std::string ErrorToMessage(int32_t code)
{
    auto iter = ERROR_CODE_TO_MSG.find(code);
    return (iter != ERROR_CODE_TO_MSG.end()) ? iter->second : "";
}

std::string GetErrorMsg(int32_t errorCode)
{
    std::string strMsg;
    if (errorCode == ERROR_CODE_DIALOG_CONTENT_ERROR) {
        strMsg = ErrorToMessage(ERROR_CODE_DIALOG_CONTENT_ERROR) + "The ComponentContent is incorrect.";
    } else if (errorCode == ERROR_CODE_DIALOG_CONTENT_ALREADY_EXIST) {
        strMsg = ErrorToMessage(ERROR_CODE_DIALOG_CONTENT_ALREADY_EXIST) +
            "The ComponentContent has already been opened.";
    } else if (errorCode == ERROR_CODE_DIALOG_CONTENT_NOT_FOUND) {
        strMsg = ErrorToMessage(ERROR_CODE_DIALOG_CONTENT_NOT_FOUND) + "The ComponentContent cannot be found.";
    } else {
        strMsg = ErrorToMessage(ERROR_CODE_INTERNAL_ERROR) + "Build custom dialog failed.";
    }
    return strMsg;
}

ani_error GetErrorObject(ani_env *env, const std::string &errMsg, int32_t code)
{
    CHECK_NULL_RETURN(env, nullptr);
    ani_class errClass;
    ani_status status = env->FindClass("@ohos.base.BusinessError", &errClass);
    if (status != ANI_OK) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "find class failed. status: %{public}d", status);
        return nullptr;
    }
    ani_method ctor;
    status = env->Class_FindMethod(errClass, "<ctor>", ":", &ctor);
    if (status != ANI_OK) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "cannot find constructor for class. status: %{public}d", status);
        return nullptr;
    }
    ani_string errMessage;
    status = env->String_NewUTF8(errMsg.c_str(), errMsg.size(), &errMessage);
    if (status != ANI_OK) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "convert string to ani string failed. status: %{public}d", status);
        return nullptr;
    }
    ani_object errObj;
    status = env->Object_New(errClass, ctor, &errObj);
    if (status != ANI_OK) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "cannot create ani error object. status: %{public}d", status);
        return nullptr;
    }
    status = env->Object_SetPropertyByName_Int(errObj, "code", static_cast<ani_int>(code));
    if (status != ANI_OK) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "set error code failed. status: %{public}d", status);
        return nullptr;
    }
    status = env->Object_SetPropertyByName_Ref(errObj, "message", errMessage);
    if (status != ANI_OK) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "set error message failed. status: %{public}d", status);
        return nullptr;
    }
    return static_cast<ani_error>(errObj);
}

void AniThrow(ani_env *env, const std::string &errMsg, int32_t code)
{
    CHECK_NULL_VOID(env);
    auto errObj = GetErrorObject(env, errMsg, code);
    if (errObj == nullptr) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "get error object failed!");
        return;
    }
    if (ANI_OK != env->ThrowError(errObj)) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "throw ani error object failed!");
        return;
    }
}

} // namespace OHOS::Ace::AniUtils