/*
 * 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 "napi_utils.h"

#include <cstdint>
#include <mutex>
#include <string>

#include "base/request/request/common/include/constant.h"
#include "js_native_api.h"
#include "js_native_api_types.h"
#include "napi/native_common.h"

namespace OHOS::Request {
napi_value CreateBusinessError(napi_env env, int32_t errorCode, const std::string &errorMessage)
{
    napi_value error = nullptr;
    napi_value msg = nullptr;
    PRELOAD_NAPI_CALL(env, napi_create_string_utf8(env, errorMessage.c_str(), errorMessage.length(), &msg),
        "napi_create_string_utf8 failed");
    PRELOAD_NAPI_CALL(env, napi_create_error(env, nullptr, msg, &error), "napi_create_error failed");
    napi_value code = nullptr;
    PRELOAD_NAPI_CALL(env, napi_create_uint32(env, static_cast<uint32_t>(errorCode), &code),
        "napi_create_uint32 failed");
    napi_set_named_property(env, error, "code", code);
    return error;
}

void ThrowError(napi_env env, int32_t code, const std::string &msg)
{
    napi_value error = CreateBusinessError(env, code, msg);
    napi_throw(env, error);
}

napi_valuetype GetValueType(napi_env env, napi_value value)
{
    if (value == nullptr) {
        return napi_undefined;
    }
    napi_valuetype valueType = napi_undefined;
    PRELOAD_NAPI_CALL_BASE(env, napi_typeof(env, value, &valueType), E_OTHER, "napi_typeof failed", napi_undefined);
    return valueType;
}

size_t GetStringLength(napi_env env, napi_value value)
{
    size_t length;
    PRELOAD_NAPI_CALL_BASE(env, napi_get_value_string_utf8(env, value, nullptr, 0, &length), E_OTHER,
        "napi_get_value_string_utf8 failed", 0);
    return length;
}

std::string GetValueString(napi_env env, napi_value value, size_t length)
{
    std::vector<char> chars(length + 1);
    PRELOAD_NAPI_CALL(env, napi_get_value_string_utf8(env, value, chars.data(), chars.size(), &length),
        "napi_get_value_string_utf8 failed");
    return std::string(chars.data());
}

int64_t GetValueNum(napi_env env, napi_value value)
{
    int64_t ret;
    PRELOAD_NAPI_CALL_BASE(env, napi_get_value_int64(env, value, &ret), E_OTHER, "napi_get_value_int64 failed", 0);
    return ret;
}

std::vector<std::string> GetPropertyNames(napi_env env, napi_value object)
{
    std::vector<std::string> ret;
    napi_value names = nullptr;
    PRELOAD_NAPI_CALL_BASE(env, napi_get_property_names(env, object, &names), E_OTHER,
        "napi_get_property_names failed", ret);
    uint32_t length = 0;
    PRELOAD_NAPI_CALL_BASE(env, napi_get_array_length(env, names, &length), E_OTHER, "napi_get_array_length failed",
        ret);
    for (uint32_t index = 0; index < length; ++index) {
        napi_value name = nullptr;
        if (napi_get_element(env, names, index, &name) != napi_ok) {
            continue;
        }
        if (GetValueType(env, name) != napi_string) {
            continue;
        }
        size_t propertyLength = GetStringLength(env, name);
        ret.emplace_back(GetValueString(env, name, propertyLength));
    }
    return ret;
}

bool HasNamedProperty(napi_env env, napi_value object, const std::string &propertyName)
{
    bool hasProperty = false;
    PRELOAD_NAPI_CALL_BASE(env, napi_has_named_property(env, object, propertyName.c_str(), &hasProperty),
        E_OTHER, "napi_has_named_property failed", false);
    return hasProperty;
}

napi_value GetNamedProperty(napi_env env, napi_value object, const std::string &propertyName)
{
    napi_value value = nullptr;
    bool hasProperty = false;
    PRELOAD_NAPI_CALL(env, napi_has_named_property(env, object, propertyName.c_str(), &hasProperty),
        "napi_has_named_property failed");
    if (!hasProperty) {
        return value;
    }
    PRELOAD_NAPI_CALL(env, napi_get_named_property(env, object, propertyName.c_str(), &value),
        "napi_get_named_property failed");
    return value;
}

std::string GetStringValueWithDefault(napi_env env, napi_value value)
{
    if (GetValueType(env, value) != napi_string) {
        return "";
    }
    auto length = GetStringLength(env, value);
    return GetValueString(env, value, length);
}

std::string GetPropertyValue(napi_env env, napi_value object, const std::string &propertyName)
{
    if (!HasNamedProperty(env, object, propertyName)) {
        return "";
    }
    napi_value value = GetNamedProperty(env, object, propertyName);
    return GetStringValueWithDefault(env, value);
}

napi_value Convert2JSValue(napi_env env, const std::string &str)
{
    napi_value value = nullptr;
    if (napi_create_string_utf8(env, str.c_str(), strlen(str.c_str()), &value) != napi_ok) {
        return nullptr;
    }
    return value;
}

napi_value Convert2JSValue(napi_env env, uint32_t code)
{
    napi_value value = nullptr;
    if (napi_create_uint32(env, code, &value) != napi_ok) {
        return nullptr;
    }
    return value;
}

void SetStringPropertyUtf8(napi_env env, napi_value object, const std::string &name, const std::string &value)
{
    napi_value jsValue = Convert2JSValue(env, value);
    if (GetValueType(env, jsValue) != napi_string) {
        return;
    }
    napi_status status = napi_set_named_property(env, object, name.c_str(), jsValue);
    if (status != napi_ok) {
        napi_throw_error(env, nullptr, "preload failed to set named property");
    }
}

void SetUint32Property(napi_env env, napi_value object, const std::string &name, uint32_t value)
{
    napi_value jsValue = Convert2JSValue(env, value);
    if (GetValueType(env, jsValue) != napi_number) {
        return;
    }
    napi_status status = napi_set_named_property(env, object, name.c_str(), jsValue);
    if (status != napi_ok) {
        napi_throw_error(env, nullptr, "preload failed to set named property");
    }
}

} // namespace OHOS::Request