/*
 * Copyright (c) 2024 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 "ext_napi_utils.h"
#include <memory>
#include <cstddef>

#include "napi/native_api.h"
#include "napi/native_node_api.h"
#include "securec.h"
#include "frameworks/base/json/json_util.h"
#include "frameworks/core/common/card_scope.h"
#include "frameworks/core/common/container.h"
#include "core/pipeline/pipeline_base.h"

namespace OHOS::Ace {
namespace {
constexpr uint32_t COLOR_ALPHA_OFFSET = 24;
constexpr uint32_t COLOR_ALPHA_VALUE = 0xFF000000;
constexpr uint32_t ERROR_COLOR_ID = -1;

enum class ResourceType : uint32_t {
    COLOR = 10001,
    FLOAT,
    STRING,
    PLURAL,
    BOOLEAN,
    INTARRAY,
    INTEGER,
    PATTERN,
    STRARRAY,
    MEDIA = 20000,
    RAWFILE = 30000
};
} // namespace
NapiAsyncEvent::NapiAsyncEvent(napi_env env, napi_value callback)
{
    env_ = env;
    napi_create_reference(env_, callback, 1, &ref_);
}

NapiAsyncEvent::~NapiAsyncEvent()
{
    napi_delete_reference(env_, ref_);
}

napi_value NapiAsyncEvent::Call(int32_t argc, napi_value* argv)
{
    napi_value result = nullptr;
    napi_handle_scope scope;
    napi_open_handle_scope(env_, &scope);
    if (scope == nullptr) {
        napi_close_handle_scope(env_, scope);
        return result;
    }
    napi_value callback = nullptr;
    napi_get_reference_value(env_, ref_, &callback);
    napi_value undefined = nullptr;
    napi_get_undefined(env_, &undefined);
    napi_call_function(env_, undefined, callback, argc, argv, &result);
    napi_close_handle_scope(env_, scope);
    return result;
}

napi_env NapiAsyncEvent::GetEnv()
{
    return env_;
}

napi_value ExtNapiUtils::CreateInt32(napi_env env, int32_t code)
{
    napi_value value = nullptr;
    if (napi_create_int32(env, code, &value) != napi_ok) {
        return nullptr;
    }
    return value;
}

int32_t ExtNapiUtils::GetCInt32(napi_env env, napi_value value)
{
    int32_t num = 0;
    napi_get_value_int32(env, value, &num);
    return num;
}

int64_t ExtNapiUtils::GetCInt64(napi_env env, napi_value value)
{
    int64_t num = 0;
    napi_get_value_int64(env, value, &num);
    return num;
}

double ExtNapiUtils::GetDouble(napi_env env, napi_value value)
{
    double numberValue = 0;
    napi_status ret = napi_get_value_double(env, value, &numberValue);
    if (ret == napi_ok) {
        return numberValue;
    }
    return 0;
}

napi_value ExtNapiUtils::CreateNull(napi_env env)
{
    napi_value jsNull = nullptr;
    NAPI_CALL(env, napi_get_null(env, &jsNull));
    return jsNull;
}

napi_value ExtNapiUtils::CreateObject(napi_env env)
{
    napi_value object = nullptr;
    NAPI_CALL(env, napi_create_object(env, &object));
    return object;
}

napi_value ExtNapiUtils::CreateDouble(napi_env env, double value)
{
    napi_value jsValue = nullptr;
    NAPI_CALL(env, napi_create_double(env, value, &jsValue));
    return jsValue;
}

napi_value ExtNapiUtils::CreateFunction(napi_env env,
                                        const char* utf8name, size_t length,
                                        napi_callback cb,
                                        void* data)
{
    napi_value jsfuncValue = nullptr;
    napi_create_function(env, utf8name, length, cb, data, &jsfuncValue);
    return jsfuncValue;
}

bool ExtNapiUtils::GetBool(napi_env env, napi_value value)
{
    bool boolValue = false;
    napi_status ret = napi_get_value_bool(env, value, &boolValue);
    if (ret == napi_ok) {
        return boolValue;
    }
    return false;
}

napi_valuetype ExtNapiUtils::GetValueType(napi_env env, napi_value value)
{
    if (value == nullptr) {
        return napi_undefined;
    }

    napi_valuetype valueType = napi_undefined;
    NAPI_CALL_BASE(env, napi_typeof(env, value, &valueType), napi_undefined);
    return valueType;
}

std::string ExtNapiUtils::GetStringFromValueUtf8(napi_env env, napi_value value)
{
    if (GetValueType(env, value) != napi_string) {
        return {};
    }

    std::string result;
    size_t stringLength = 0;
    NAPI_CALL_BASE(env, napi_get_value_string_utf8(env, value, nullptr, 0, &stringLength), result);
    if (stringLength == 0) {
        return result;
    }

    std::unique_ptr<char[]> str = std::make_unique<char[]>(stringLength + 1);
    if (memset_s(str.get(), stringLength + 1, 0, stringLength + 1) != EOK) {
        return result;
    }
    size_t length = 0;
    NAPI_CALL_BASE(env, napi_get_value_string_utf8(env, value, str.get(), stringLength + 1, &length), result);
    if (length > 0) {
        result.append(str.get(), length);
    }
    return result;
}

bool ExtNapiUtils::CheckTypeForNapiValue(napi_env env, napi_value param, napi_valuetype expectType)
{
    napi_valuetype valueType = napi_undefined;

    if (napi_typeof(env, param, &valueType) != napi_ok) {
        return false;
    }

    return valueType == expectType;
}

bool ExtNapiUtils::ParseLengthMetrics(napi_env env, napi_value param, CalcDimension& result)
{
    if (CheckTypeForNapiValue(env, param, napi_object)) {
        napi_value jsValue = GetNamedProperty(env, param, "value");
        napi_value jsUnit = GetNamedProperty(env, param, "unit");
        double value = 0;
        int32_t unit = static_cast<int32_t>(DimensionUnit::VP);
        if (CheckTypeForNapiValue(env, jsValue, napi_number) && CheckTypeForNapiValue(env, jsUnit, napi_number) &&
            napi_get_value_double(env, jsValue, &value) == napi_ok &&
            napi_get_value_int32(env, jsUnit, &unit) == napi_ok && GreatOrEqual(value, 0.0f)) {
            result = CalcDimension(value, static_cast<DimensionUnit>(unit));
            return true;
        }
    }
    return false;
}

napi_value ExtNapiUtils::GetNamedProperty(napi_env env, napi_value object, const std::string& propertyName)
{
    if (GetValueType(env, object) != napi_object) {
        return CreateUndefined(env);
    }

    napi_value value = nullptr;
    NAPI_CALL(env, napi_get_named_property(env, object, propertyName.c_str(), &value));
    return value;
}

bool ExtNapiUtils::IsArray(napi_env env, napi_value value)
{
    bool isArray = false;
    napi_status ret = napi_is_array(env, value, &isArray);
    if (ret == napi_ok) {
        return isArray;
    }
    return false;
}

napi_value ExtNapiUtils::CreateUndefined(napi_env env)
{
    napi_value undefined = nullptr;
    NAPI_CALL(env, napi_get_undefined(env, &undefined));
    return undefined;
}

uint32_t ColorAlphaAdapt(uint32_t origin)
{
    uint32_t result = origin;
    if ((origin >> COLOR_ALPHA_OFFSET) == 0) {
        result = origin | COLOR_ALPHA_VALUE;
    }
    return result;
}

RefPtr<ThemeConstants> ExtNapiUtils::GetThemeConstants(napi_env env, napi_value value)
{
    napi_value jsBundleName = ExtNapiUtils::GetNamedProperty(env, value, "bundleName");
    napi_value jsModuleName = ExtNapiUtils::GetNamedProperty(env, value, "moduleName");
    std::string bundleName = ExtNapiUtils::GetStringFromValueUtf8(env, jsBundleName);
    std::string moduleName = ExtNapiUtils::GetStringFromValueUtf8(env, jsModuleName);

    auto cardId = CardScope::CurrentId();
    if (cardId != INVALID_CARD_ID) {
        auto container = Container::Current();
        CHECK_NULL_RETURN(container, nullptr);
        auto weak = container->GetCardPipeline(cardId);
        auto cardPipelineContext = weak.Upgrade();
        CHECK_NULL_RETURN(cardPipelineContext, nullptr);
        auto cardThemeManager = cardPipelineContext->GetThemeManager();
        CHECK_NULL_RETURN(cardThemeManager, nullptr);
        return cardThemeManager->GetThemeConstants(bundleName, moduleName);
    }

    auto container = Container::CurrentSafely();
    CHECK_NULL_RETURN(container, nullptr);
    auto pipelineContext = container->GetPipelineContext();
    CHECK_NULL_RETURN(pipelineContext, nullptr);
    auto themeManager = pipelineContext->GetThemeManager();
    CHECK_NULL_RETURN(themeManager, nullptr);
    return themeManager->GetThemeConstants(bundleName, moduleName);
}

bool ExtNapiUtils::ParseColor(napi_env env, napi_value value, Color& result)
{
    napi_valuetype valueType = ExtNapiUtils::GetValueType(env, value);
    if (valueType != napi_number && valueType != napi_string && valueType != napi_object) {
        return false;
    }
    if (valueType == napi_number) {
        int32_t colorId = ExtNapiUtils::GetCInt32(env, value);
        result = Color(ColorAlphaAdapt(static_cast<uint32_t>(colorId)));
        return true;
    }
    if (valueType == napi_string) {
        std::string colorString = ExtNapiUtils::GetStringFromValueUtf8(env, value);
        return Color::ParseColorString(colorString, result);
    }
    return ParseColorFromResource(env, value, result);
}

bool ExtNapiUtils::ParseColorFromResource(napi_env env, napi_value value, Color& colorResult)
{
    auto themeConstants = GetThemeConstants(env, value);
    CHECK_NULL_RETURN(themeConstants, false);

    napi_value jsColorId = ExtNapiUtils::GetNamedProperty(env, value, "id");
    napi_value jsParams = ExtNapiUtils::GetNamedProperty(env, value, "params");
    uint32_t colorId = static_cast<uint32_t>(ExtNapiUtils::GetCInt32(env, jsColorId));
    if (!ExtNapiUtils::IsArray(env, jsParams)) {
        return false;
    }
    if (colorId == ERROR_COLOR_ID) {
        uint32_t length;
        napi_status status = napi_get_array_length(env, jsParams, &length);
        if (status != napi_ok || length == 0) {
            return false;
        }
        napi_value elementValue;
        status = napi_get_element(env, jsParams, 0, &elementValue);
        if (status != napi_ok) {
            return false;
        }
        std::string stringValue = ExtNapiUtils::GetStringFromValueUtf8(env, elementValue);
        colorResult = themeConstants->GetColorByName(stringValue);
        return true;
    }
    napi_value jsType = GetNamedProperty(env, value, "type");
    napi_valuetype valueType = GetValueType(env, jsType);
    if (valueType != napi_null && valueType == napi_number &&
        static_cast<uint32_t>(valueType) == static_cast<uint32_t>(ResourceType::STRING)) {
        auto value = themeConstants->GetString(ExtNapiUtils::GetCInt32(env, jsType));
        return Color::ParseColorString(value, colorResult);
    }
    if (valueType != napi_null && valueType == napi_number &&
        static_cast<uint32_t>(valueType) == static_cast<uint32_t>(ResourceType::INTEGER)) {
        auto value = themeConstants->GetInt(ExtNapiUtils::GetCInt32(env, jsType));
        colorResult = Color(ColorAlphaAdapt(value));
        return true;
    }
    colorResult = themeConstants->GetColor(colorId);
    return true;
}

void ExtNapiUtils::SetNamedProperty(napi_env env, napi_value object, const std::string& propertyName, napi_value value)
{
    if (GetValueType(env, object) != napi_object) {
        return;
    }

    napi_set_named_property(env, object, propertyName.c_str(), value);
}

std::unique_ptr<JsonValue> ExtNapiUtils::PutJsonValue(napi_env env, napi_value value, std::string& key)
{
    auto result = JsonUtil::Create(true);
    napi_valuetype valueType = ExtNapiUtils::GetValueType(env, value);
    switch (valueType) {
        case napi_boolean: {
            bool boolValue = ExtNapiUtils::GetBool(env, value);
            result->Put(key.c_str(), boolValue);
            break;
        }
        case napi_number: {
            int32_t intValue = ExtNapiUtils::GetCInt32(env, value);
            result->Put(key.c_str(), intValue);
            break;
        }
        case napi_string: {
            std::string stringValue = ExtNapiUtils::GetStringFromValueUtf8(env, value);
            result->Put(key.c_str(), stringValue.c_str());
            break;
        }
        default:
            break;
    }
    return result;
}

bool ExtNapiUtils::ParseColorMetrics(napi_env env, napi_value param, Color& result)
{
    if (CheckTypeForNapiValue(env, param, napi_object)) {
        napi_value jsToNumeric = GetNamedProperty(env, param, "toNumeric");
        napi_value jsColor;
        uint32_t colorVal = 0;
        if (CheckTypeForNapiValue(env, jsToNumeric, napi_function) &&
            napi_call_function(env, param, jsToNumeric, 0, nullptr, &jsColor) == napi_ok &&
            CheckTypeForNapiValue(env, jsColor, napi_number) &&
            napi_get_value_uint32(env, jsColor, &colorVal) == napi_ok) {
            result.SetValue(colorVal);
            return true;
        }
    }
    return false;
}
} // namespace OHOS::Ace