/*
 * Copyright (C) 2021-2022 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.
 */
#define MLOG_TAG "AlbumNapi"

#include "album_napi.h"

#include "media_file_asset_columns.h"
#include "media_file_utils.h"
#include "media_library_napi.h"
#include "medialibrary_client_errno.h"
#include "medialibrary_tracer.h"
#include "userfile_client.h"
#include "media_file_uri.h"
#include "data_secondary_directory_uri.h"
#include "media_upgrade.h"

using OHOS::HiviewDFX::HiLog;
using OHOS::HiviewDFX::HiLogLabel;

namespace OHOS {
namespace Media {
using namespace std;
using namespace OHOS::DataShare;
thread_local napi_ref AlbumNapi::sConstructor_ = nullptr;
thread_local AlbumAsset *AlbumNapi::sAlbumData_ = nullptr;
using CompleteCallback = napi_async_complete_callback;

thread_local napi_ref AlbumNapi::userFileMgrConstructor_ = nullptr;
thread_local napi_ref AlbumNapi::photoAccessHelperConstructor_ = nullptr;

AlbumNapi::AlbumNapi()
    : env_(nullptr) {}

AlbumNapi::~AlbumNapi() = default;

void AlbumNapi::AlbumNapiDestructor(napi_env env, void *nativeObject, void *finalize_hint)
{
    AlbumNapi *album = reinterpret_cast<AlbumNapi*>(nativeObject);
    if (album != nullptr) {
        delete album;
        album = nullptr;
    }
}

napi_value AlbumNapi::Init(napi_env env, napi_value exports)
{
    napi_status status;
    napi_value ctorObj;
    int32_t refCount = 1;

    napi_property_descriptor album_props[] = {
        DECLARE_NAPI_GETTER("albumId", JSGetAlbumId),
        DECLARE_NAPI_GETTER_SETTER("albumName", JSGetAlbumName, JSAlbumNameSetter),
        DECLARE_NAPI_GETTER("albumUri", JSGetAlbumUri),
        DECLARE_NAPI_GETTER("dateModified", JSGetAlbumDateModified),
        DECLARE_NAPI_GETTER("count", JSGetCount),
        DECLARE_NAPI_GETTER("relativePath", JSGetAlbumRelativePath),
        DECLARE_NAPI_GETTER("coverUri", JSGetCoverUri),
        DECLARE_NAPI_FUNCTION("commitModify", JSCommitModify),
        DECLARE_NAPI_GETTER_SETTER("path", JSGetAlbumPath, JSSetAlbumPath),
        DECLARE_NAPI_GETTER("virtual", JSGetAlbumVirtual),
        DECLARE_NAPI_FUNCTION("getFileAssets", JSGetAlbumFileAssets)
    };

    status = napi_define_class(env, ALBUM_NAPI_CLASS_NAME.c_str(), NAPI_AUTO_LENGTH,
                               AlbumNapiConstructor, nullptr,
                               sizeof(album_props) / sizeof(album_props[PARAM0]),
                               album_props, &ctorObj);
    if (status == napi_ok) {
        status = napi_create_reference(env, ctorObj, refCount, &sConstructor_);
        if (status == napi_ok) {
            status = napi_set_named_property(env, exports, ALBUM_NAPI_CLASS_NAME.c_str(), ctorObj);
            if (status == napi_ok) {
                return exports;
            }
        }
    }

    return nullptr;
}

napi_value AlbumNapi::UserFileMgrInit(napi_env env, napi_value exports)
{
    NapiClassInfo info = {
        .name = USERFILEMGR_ALBUM_NAPI_CLASS_NAME,
        .ref = &userFileMgrConstructor_,
        .constructor = AlbumNapiConstructor,
        .props = {
            DECLARE_NAPI_FUNCTION("getPhotoAssets", UserFileMgrGetAssets),
            DECLARE_NAPI_FUNCTION("commitModify", UserFileMgrCommitModify),
            DECLARE_NAPI_GETTER_SETTER("albumName", JSGetAlbumName, JSAlbumNameSetter),
            DECLARE_NAPI_GETTER("albumUri", JSGetAlbumUri),
            DECLARE_NAPI_GETTER("dateModified", JSGetAlbumDateModified),
            DECLARE_NAPI_GETTER("count", JSGetCount),
            DECLARE_NAPI_GETTER("relativePath", JSGetAlbumRelativePath),
            DECLARE_NAPI_GETTER("coverUri", JSGetCoverUri)
        }
    };

    MediaLibraryNapiUtils::NapiDefineClass(env, exports, info);
    return exports;
}

napi_value AlbumNapi::PhotoAccessHelperInit(napi_env env, napi_value exports)
{
    NapiClassInfo info = {
        .name = PHOTOACCESSHELPER_ALBUM_NAPI_CLASS_NAME,
        .ref = &photoAccessHelperConstructor_,
        .constructor = AlbumNapiConstructor,
        .props = {
            DECLARE_NAPI_FUNCTION("getAssets", PhotoAccessHelperGetAssets),
            DECLARE_NAPI_FUNCTION("commitModify", PhotoAccessHelperCommitModify),
            DECLARE_NAPI_GETTER_SETTER("albumName", JSGetAlbumName, JSAlbumNameSetter),
            DECLARE_NAPI_GETTER("albumUri", JSGetAlbumUri),
            DECLARE_NAPI_GETTER("count", JSGetCount),
            DECLARE_NAPI_GETTER("coverUri", JSGetCoverUri)
        }
    };

    MediaLibraryNapiUtils::NapiDefineClass(env, exports, info);
    return exports;
}

void AlbumNapi::SetAlbumNapiProperties()
{
    albumAssetPtr = std::shared_ptr<AlbumAsset>(sAlbumData_);
}

// Constructor callback
napi_value AlbumNapi::AlbumNapiConstructor(napi_env env, napi_callback_info info)
{
    napi_status status;
    napi_value result = nullptr;
    napi_value thisVar = nullptr;

    napi_get_undefined(env, &result);
    GET_JS_OBJ_WITH_ZERO_ARGS(env, info, status, thisVar);
    if (status == napi_ok && thisVar != nullptr) {
        std::unique_ptr<AlbumNapi> obj = std::make_unique<AlbumNapi>();
        if (obj != nullptr) {
            obj->env_ = env;
            if (sAlbumData_ != nullptr) {
                obj->SetAlbumNapiProperties();
            }

            status = napi_wrap(env, thisVar, reinterpret_cast<void *>(obj.get()),
                               AlbumNapi::AlbumNapiDestructor, nullptr, nullptr);
            if (status == napi_ok) {
                obj.release();
                return thisVar;
            } else {
                NAPI_ERR_LOG("Failure wrapping js to native napi. status: %{public}d", status);
            }
        }
    }

    return result;
}

napi_value AlbumNapi::CreateAlbumNapi(napi_env env, unique_ptr<AlbumAsset> &albumData)
{
    if (albumData == nullptr) {
        NAPI_ERR_LOG("CreateAlbumNapi albumData is null");
        return nullptr;
    }

    napi_value constructor;
    napi_ref constructorRef;
    if (albumData->GetResultNapiType() == ResultNapiType::TYPE_USERFILE_MGR) {
        constructorRef = userFileMgrConstructor_;
    } else if (albumData->GetResultNapiType() == ResultNapiType::TYPE_PHOTOACCESS_HELPER) {
        constructorRef = photoAccessHelperConstructor_;
    } else {
        constructorRef = sConstructor_;
    }
    NAPI_CALL(env, napi_get_reference_value(env, constructorRef, &constructor));

    napi_value result = nullptr;
    sAlbumData_ = albumData.release();
    NAPI_CALL(env, napi_new_instance(env, constructor, 0, nullptr, &result));
    sAlbumData_ = nullptr;
    return result;
}

std::string AlbumNapi::GetAlbumName() const
{
    return albumAssetPtr->GetAlbumName();
}

std::string AlbumNapi::GetAlbumPath() const
{
    return albumAssetPtr->GetAlbumPath();
}

int32_t AlbumNapi::GetAlbumId() const
{
    return albumAssetPtr->GetAlbumId();
}

std::string AlbumNapi::GetAlbumUri() const
{
    return albumAssetPtr->GetAlbumUri();
}

std::string AlbumNapi::GetNetworkId() const
{
    return MediaFileUtils::GetNetworkIdFromUri(GetAlbumUri());
}

#ifdef MEDIALIBRARY_COMPATIBILITY
PhotoAlbumType AlbumNapi::GetAlbumType() const
{
    return albumAssetPtr->GetAlbumType();
}
PhotoAlbumSubType AlbumNapi::GetAlbumSubType() const
{
    return albumAssetPtr->GetAlbumSubType();
}
#endif

napi_value AlbumNapi::JSGetAlbumId(napi_env env, napi_callback_info info)
{
    napi_status status;
    napi_value jsResult = nullptr;
    napi_value undefinedResult = nullptr;
    AlbumNapi* obj = nullptr;
    int32_t id;
    napi_value thisVar = nullptr;

    napi_get_undefined(env, &undefinedResult);
    GET_JS_OBJ_WITH_ZERO_ARGS(env, info, status, thisVar);
    if (status != napi_ok || thisVar == nullptr) {
        NAPI_ERR_LOG("Invalid arguments! status: %{public}d", status);
        return undefinedResult;
    }

    status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
    if (status == napi_ok && obj != nullptr) {
        id = obj->GetAlbumId();
        status = napi_create_int32(env, id, &jsResult);
        if (status == napi_ok) {
            return jsResult;
        }
    }

    return undefinedResult;
}

napi_value AlbumNapi::JSGetAlbumName(napi_env env, napi_callback_info info)
{
    napi_status status;
    napi_value jsResult = nullptr;
    napi_value undefinedResult = nullptr;
    AlbumNapi* obj = nullptr;
    std::string name = "";
    napi_value thisVar = nullptr;
    napi_get_undefined(env, &undefinedResult);
    GET_JS_OBJ_WITH_ZERO_ARGS(env, info, status, thisVar);
    if (status != napi_ok || thisVar == nullptr) {
        NAPI_ERR_LOG("Invalid arguments! status: %{public}d", status);
        return undefinedResult;
    }

    status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
    if (status == napi_ok && obj != nullptr) {
        name = obj->GetAlbumName();
        status = napi_create_string_utf8(env, name.c_str(), NAPI_AUTO_LENGTH, &jsResult);
        if (status == napi_ok) {
            return jsResult;
        }
    }

    return undefinedResult;
}

napi_value AlbumNapi::JSAlbumNameSetter(napi_env env, napi_callback_info info)
{
    napi_status status;
    napi_value jsResult = nullptr;
    size_t argc = ARGS_ONE;
    napi_value argv[ARGS_ONE] = {0};
    size_t res = 0;
    char buffer[FILENAME_MAX];
    AlbumNapi* obj = nullptr;
    napi_value thisVar = nullptr;
    napi_valuetype valueType = napi_undefined;

    napi_get_undefined(env, &jsResult);
    GET_JS_ARGS(env, info, argc, argv, thisVar);
    NAPI_ASSERT(env, argc == ARGS_ONE, "requires 1 parameter");
    if (thisVar == nullptr || napi_typeof(env, argv[PARAM0], &valueType) != napi_ok
        || valueType != napi_string) {
        NAPI_ERR_LOG("Invalid arguments type! valueType: %{public}d", valueType);
        return jsResult;
    }

    napi_get_value_string_utf8(env, argv[PARAM0], buffer, FILENAME_MAX, &res);

    status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
    if (status == napi_ok && obj != nullptr) {
        obj->albumAssetPtr->SetAlbumName(std::string(buffer));
    } else {
        NAPI_ERR_LOG("status = %{public}d", status);
    }
    return jsResult;
}
napi_value AlbumNapi::JSGetAlbumUri(napi_env env, napi_callback_info info)
{
    napi_status status;
    napi_value jsResult = nullptr;
    napi_value undefinedResult = nullptr;
    AlbumNapi* obj = nullptr;
    std::string uri = "";
    napi_value thisVar = nullptr;

    napi_get_undefined(env, &undefinedResult);
    GET_JS_OBJ_WITH_ZERO_ARGS(env, info, status, thisVar);
    if (status != napi_ok || thisVar == nullptr) {
        NAPI_ERR_LOG("Invalid arguments! status: %{public}d", status);
        return undefinedResult;
    }

    status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
    if (status == napi_ok && obj != nullptr) {
        uri = obj->GetAlbumUri();
        status = napi_create_string_utf8(env, uri.c_str(), NAPI_AUTO_LENGTH, &jsResult);
        if (status == napi_ok) {
            return jsResult;
        }
    }

    return undefinedResult;
}
napi_value AlbumNapi::JSGetAlbumDateModified(napi_env env, napi_callback_info info)
{
    napi_status status;
    napi_value jsResult = nullptr;
    napi_value undefinedResult = nullptr;
    AlbumNapi* obj = nullptr;
    int64_t dateModified;
    napi_value thisVar = nullptr;

    napi_get_undefined(env, &undefinedResult);
    GET_JS_OBJ_WITH_ZERO_ARGS(env, info, status, thisVar);
    if (status != napi_ok || thisVar == nullptr) {
        NAPI_ERR_LOG("Invalid arguments! status: %{public}d", status);
        return undefinedResult;
    }

    status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
    if (status == napi_ok && obj != nullptr) {
        dateModified = obj->albumAssetPtr->GetAlbumDateModified() / MSEC_TO_SEC;
        status = napi_create_int64(env, dateModified, &jsResult);
        if (status == napi_ok) {
            return jsResult;
        }
    }

    return undefinedResult;
}
napi_value AlbumNapi::JSGetCount(napi_env env, napi_callback_info info)
{
    napi_status status;
    napi_value jsResult = nullptr;
    napi_value undefinedResult = nullptr;
    AlbumNapi *obj = nullptr;
    int32_t count;
    napi_value thisVar = nullptr;

    napi_get_undefined(env, &undefinedResult);
    GET_JS_OBJ_WITH_ZERO_ARGS(env, info, status, thisVar);
    if (status != napi_ok || thisVar == nullptr) {
        NAPI_ERR_LOG("Invalid arguments! status: %{public}d", status);
        return undefinedResult;
    }
    status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
    if (status == napi_ok && obj != nullptr) {
        count = obj->albumAssetPtr->GetCount();
        status = napi_create_int32(env, count, &jsResult);
        if (status == napi_ok) {
            return jsResult;
        }
    }
    return undefinedResult;
}
napi_value AlbumNapi::JSGetAlbumRelativePath(napi_env env, napi_callback_info info)
{
    napi_status status;
    napi_value jsResult = nullptr;
    napi_value undefinedResult = nullptr;
    AlbumNapi* obj = nullptr;
    std::string relativePath = "";
    napi_value thisVar = nullptr;

    napi_get_undefined(env, &undefinedResult);
    GET_JS_OBJ_WITH_ZERO_ARGS(env, info, status, thisVar);
    if (status != napi_ok || thisVar == nullptr) {
        NAPI_ERR_LOG("Invalid arguments! status: %{public}d", status);
        return undefinedResult;
    }

    status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
    if (status == napi_ok && obj != nullptr) {
        relativePath = obj->albumAssetPtr->GetAlbumRelativePath();
        status = napi_create_string_utf8(env, relativePath.c_str(), NAPI_AUTO_LENGTH, &jsResult);
        if (status == napi_ok) {
            return jsResult;
        }
    }

    return undefinedResult;
}
napi_value AlbumNapi::JSGetCoverUri(napi_env env, napi_callback_info info)
{
    napi_status status;
    napi_value jsResult = nullptr;
    napi_value undefinedResult = nullptr;
    AlbumNapi* obj = nullptr;
    std::string coverUri = "";
    napi_value thisVar = nullptr;

    napi_get_undefined(env, &undefinedResult);
    GET_JS_OBJ_WITH_ZERO_ARGS(env, info, status, thisVar);
    if (status != napi_ok || thisVar == nullptr) {
        NAPI_ERR_LOG("Invalid arguments! status: %{public}d", status);
        return undefinedResult;
    }

    status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
    if (status == napi_ok && obj != nullptr) {
        coverUri = obj->albumAssetPtr->GetCoverUri();
        status = napi_create_string_utf8(env, coverUri.c_str(), NAPI_AUTO_LENGTH, &jsResult);
        if (status == napi_ok) {
            return jsResult;
        }
    }

    return undefinedResult;
}

napi_value AlbumNapi::JSSetAlbumPath(napi_env env, napi_callback_info info)
{
    napi_status status;
    napi_value jsResult = nullptr;
    size_t argc = ARGS_ONE;
    napi_value argv[ARGS_ONE] = {0};
    size_t copied_len = 0;
    char buffer[PATH_MAX] = {0};
    AlbumNapi* obj = nullptr;
    napi_value thisVar = nullptr;
    napi_valuetype valueType = napi_undefined;

    napi_get_undefined(env, &jsResult);
    GET_JS_ARGS(env, info, argc, argv, thisVar);
    NAPI_ASSERT(env, argc == ARGS_ONE, "requires 1 parameter");

    status = napi_typeof(env, argv[PARAM0], &valueType);
    if (thisVar == nullptr || status != napi_ok || valueType != napi_string) {
        NAPI_ERR_LOG("Invalid arguments type! type: %{public}d", valueType);
        return jsResult;
    }

    status = napi_get_value_string_utf8(env, argv[PARAM0], buffer, PATH_MAX - 1, &copied_len);
    if (status != napi_ok || copied_len > PATH_MAX - 1) {
        NAPI_ERR_LOG("Failed to get string value from JS parameter");
        return jsResult;
    }
    buffer[PATH_MAX - 1] = '\0';

    status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
    if (status == napi_ok && obj != nullptr && obj->albumAssetPtr != nullptr) {
        obj->albumAssetPtr->SetAlbumPath(std::string(buffer));
    } else {
        NAPI_ERR_LOG("Failed to unwrap AlbumNapi object or albumAssetPtr is nullptr");
    }

    return jsResult;
}

napi_value AlbumNapi::JSGetAlbumPath(napi_env env, napi_callback_info info)
{
    napi_status status;
    napi_value jsResult = nullptr;
    napi_value undefinedResult = nullptr;
    AlbumNapi* obj = nullptr;
    std::string path = "";
    napi_value thisVar = nullptr;

    napi_get_undefined(env, &undefinedResult);
    GET_JS_OBJ_WITH_ZERO_ARGS(env, info, status, thisVar);
    if (status != napi_ok || thisVar == nullptr) {
        NAPI_ERR_LOG("Invalid arguments! status: %{public}d", status);
        return undefinedResult;
    }

    status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
    if (status == napi_ok && obj != nullptr) {
        path = obj->GetAlbumPath();
        status = napi_create_string_utf8(env, path.c_str(), NAPI_AUTO_LENGTH, &jsResult);
        if (status == napi_ok) {
            return jsResult;
        }
    }

    return undefinedResult;
}

napi_value AlbumNapi::JSGetAlbumVirtual(napi_env env, napi_callback_info info)
{
    napi_status status;
    napi_value jsResult = nullptr;
    napi_value undefinedResult = nullptr;
    AlbumNapi* obj = nullptr;
    bool virtualAlbum = false;
    napi_value thisVar = nullptr;

    napi_get_undefined(env, &undefinedResult);
    GET_JS_OBJ_WITH_ZERO_ARGS(env, info, status, thisVar);
    if (status != napi_ok || thisVar == nullptr) {
        NAPI_ERR_LOG("Invalid arguments! status: %{public}d", status);
        return undefinedResult;
    }

    status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
    if (status == napi_ok && obj != nullptr) {
        virtualAlbum = obj->albumAssetPtr->GetAlbumVirtual();
        status = napi_get_boolean(env, virtualAlbum, &jsResult);
        if (status == napi_ok) {
            return jsResult;
        }
    }

    return undefinedResult;
}

static void GetFetchOptionsParam(napi_env env, napi_value arg, const AlbumNapiAsyncContext &context, bool &err)
{
    AlbumNapiAsyncContext *asyncContext = const_cast<AlbumNapiAsyncContext *>(&context);
    CHECK_NULL_PTR_RETURN_VOID(asyncContext, "Async context is null");
    char buffer[PATH_MAX];
    size_t res;
    uint32_t len = 0;
    napi_value property = nullptr;
    napi_value stringItem = nullptr;
    bool present = false;
    bool boolResult = false;

    string propertyName = "selections";
    string tmp = MediaLibraryNapiUtils::GetStringFetchProperty(env, arg, err, present, propertyName);
    if (!tmp.empty()) {
        asyncContext->selection = tmp;
    }

    propertyName = "order";
    tmp = MediaLibraryNapiUtils::GetStringFetchProperty(env, arg, err, present, propertyName);
    if (!tmp.empty()) {
        asyncContext->order = tmp;
    }

    napi_has_named_property(env, arg, "selectionArgs", &present);
    if (present && napi_get_named_property(env, arg, "selectionArgs", &property) == napi_ok &&
        napi_is_array(env, property, &boolResult) == napi_ok && boolResult) {
        napi_get_array_length(env, property, &len);
        for (size_t i = 0; i < len; i++) {
            napi_get_element(env, property, i, &stringItem);
            napi_get_value_string_utf8(env, stringItem, buffer, PATH_MAX, &res);
            asyncContext->selectionArgs.push_back(std::string(buffer));
            CHECK_IF_EQUAL(memset_s(buffer, PATH_MAX, 0, sizeof(buffer)) == 0, "Memset for buffer failed");
        }
    } else {
        NAPI_ERR_LOG("Could not get the string argument!");
        err = true;
    }
}

static napi_value ConvertJSArgsToNative(napi_env env, size_t argc, const napi_value argv[],
    AlbumNapiAsyncContext &asyncContext)
{
    string str = "";
    std::vector<string> strArr;
    string order = "";
    bool err = false;
    const int32_t refCount = 1;
    napi_value result;
    auto context = &asyncContext;
    CHECK_NULL_PTR_RETURN_UNDEFINED(env, context, result, "Async context is null");
    NAPI_ASSERT(env, argv != nullptr, "Argument list is empty");
    if (argc == ARGS_ONE) {
        napi_valuetype valueType = napi_undefined;
        if (napi_typeof(env, argv[PARAM0], &valueType) == napi_ok &&
            (valueType == napi_undefined || valueType == napi_null)) {
            argc -= 1;
        }
    }

    for (size_t i = PARAM0; i < argc; i++) {
        napi_valuetype valueType = napi_undefined;
        napi_typeof(env, argv[i], &valueType);

        if (i == PARAM0 && valueType == napi_object) {
            GetFetchOptionsParam(env, argv[PARAM0], asyncContext, err);
            if (err) {
                NAPI_ASSERT(env, false, "type mismatch");
            }
        } else if (i == PARAM0 && valueType == napi_function) {
            napi_create_reference(env, argv[i], refCount, &context->callbackRef);
            break;
        } else if (i == PARAM1 && valueType == napi_function) {
            napi_create_reference(env, argv[i], refCount, &context->callbackRef);
            break;
        } else {
            NAPI_ASSERT(env, false, "type mismatch");
        }
    }

    // Return true napi_value if params are successfully obtained
    napi_get_boolean(env, true, &result);
    return result;
}
static napi_value ConvertCommitJSArgsToNative(napi_env env, size_t argc, const napi_value argv[],
    AlbumNapiAsyncContext &asyncContext)
{
    string str = "";
    vector<string> strArr;
    string order = "";
    bool err = false;
    const int32_t refCount = 1;
    napi_value result;
    auto context = &asyncContext;
    CHECK_NULL_PTR_RETURN_UNDEFINED(env, context, result, "Async context is null");
    NAPI_ASSERT(env, argv != nullptr, "Argument list is empty");

    for (size_t i = PARAM0; i < argc; i++) {
        napi_valuetype valueType = napi_undefined;
        napi_typeof(env, argv[i], &valueType);

        if (i == PARAM0 && valueType == napi_object) {
            GetFetchOptionsParam(env, argv[PARAM0], asyncContext, err);
            if (err) {
                NAPI_ERR_LOG("fetch options retrieval failed. err %{public}d", err);
                NAPI_ASSERT(env, false, "type mismatch");
            }
        } else if (i == PARAM0 && valueType == napi_function) {
            napi_create_reference(env, argv[i], refCount, &context->callbackRef);
            break;
        } else if (i == PARAM1 && valueType == napi_function) {
            napi_create_reference(env, argv[i], refCount, &context->callbackRef);
            break;
        } else {
            NAPI_ASSERT(env, false, "type mismatch");
        }
    }

    // Return true napi_value if params are successfully obtained
    napi_get_boolean(env, true, &result);
    return result;
}

#ifdef MEDIALIBRARY_COMPATIBILITY
static void UpdateCompatAlbumSelection(AlbumNapiAsyncContext *context)
{
    PhotoAlbumSubType subType = context->objectPtr->GetAlbumSubType();
    string filterClause;
    switch (subType) {
        case PhotoAlbumSubType::CAMERA: {
            static const string CAMERA_FILTER = PhotoColumn::PHOTO_SUBTYPE + "=" +
                to_string(static_cast<int32_t>(PhotoSubType::CAMERA)) + " AND " + MediaUpgrade::ASSETS_QUERY_FILTER;
            filterClause = CAMERA_FILTER;
            break;
        }
        case PhotoAlbumSubType::SCREENSHOT: {
            static const string SCREENSHOT_FILTER = PhotoColumn::PHOTO_SUBTYPE + "=" +
                to_string(static_cast<int32_t>(PhotoSubType::SCREENSHOT)) + " AND " + MediaUpgrade::ASSETS_QUERY_FILTER;
            filterClause = SCREENSHOT_FILTER;
            break;
        }
        case PhotoAlbumSubType::FAVORITE: {
            static const string FAVORITE_FILTER = PhotoColumn::MEDIA_IS_FAV + " = 1" + " AND " +
                MediaUpgrade::ASSETS_QUERY_FILTER;
            filterClause = FAVORITE_FILTER;
            break;
        }
        case PhotoAlbumSubType::TRASH: {
            static const string TRASH_FILTER =
                PhotoColumn::MEDIA_DATE_TRASHED + " > 0 AND " + CONST_MEDIA_DATA_DB_IS_TRASH + " <> " +
                to_string(static_cast<int32_t>(TRASHED_DIR_CHILD));
            filterClause = TRASH_FILTER;
            break;
        }
        default: {
            NAPI_ERR_LOG("Album subtype not support for compatibility: %{public}d", subType);
            context->SaveError(-EINVAL);
            return;
        }
    }
    if (!context->selection.empty()) {
        context->selection = filterClause + " AND (" + context->selection + ")";
    } else {
        context->selection = filterClause;
    }
    MediaLibraryNapi::ReplaceSelection(context->selection, context->selectionArgs,
        CONST_MEDIA_DATA_DB_RELATIVE_PATH, CONST_MEDIA_DATA_DB_RELATIVE_PATH);
}
#endif

static void UpdateSelection(AlbumNapiAsyncContext *context)
{
    if (context->resultNapiType == ResultNapiType::TYPE_USERFILE_MGR ||
        context->resultNapiType == ResultNapiType::TYPE_PHOTOACCESS_HELPER) {
        context->predicates.EqualTo(CONST_MEDIA_DATA_DB_DATE_TRASHED, 0);
        context->predicates.NotEqualTo(CONST_MEDIA_DATA_DB_MEDIA_TYPE, MEDIA_TYPE_ALBUM);
        context->predicates.EqualTo(CONST_MEDIA_DATA_DB_BUCKET_ID, context->objectPtr->GetAlbumId());
        context->predicates.EqualTo(MediaColumn::MEDIA_TIME_PENDING, to_string(0));
        context->predicates.EqualTo(PhotoColumn::PHOTO_IS_TEMP, to_string(0));
        context->predicates.EqualTo(PhotoColumn::PHOTO_BURST_COVER_LEVEL,
            to_string(static_cast<int32_t>(BurstCoverLevelType::COVER)));
        MediaLibraryNapiUtils::UpdateMediaTypeSelections(context);
    } else {
#ifdef MEDIALIBRARY_COMPATIBILITY
        UpdateCompatAlbumSelection(context);
#else
        string trashPrefix = string(CONST_MEDIA_DATA_DB_DATE_TRASHED) + " = ? ";
        MediaLibraryNapiUtils::AppendFetchOptionSelection(context->selection, trashPrefix);
        context->selectionArgs.emplace_back("0");

        string prefix = string(CONST_MEDIA_DATA_DB_MEDIA_TYPE) + " <> ? ";
        MediaLibraryNapiUtils::AppendFetchOptionSelection(context->selection, prefix);
        context->selectionArgs.emplace_back(to_string(MEDIA_TYPE_ALBUM));

        string idPrefix = string(CONST_MEDIA_DATA_DB_BUCKET_ID) + " = ? ";
        MediaLibraryNapiUtils::AppendFetchOptionSelection(context->selection, idPrefix);
        context->selectionArgs.emplace_back(std::to_string(context->objectPtr->GetAlbumId()));
#endif
    }
}

static void GetFileAssetsNative(napi_env env, void *data)
{
    MediaLibraryTracer tracer;
    tracer.Start("GetFileAssetsNative");

    AlbumNapiAsyncContext *context = static_cast<AlbumNapiAsyncContext*>(data);

    UpdateSelection(context);
    MediaLibraryNapiUtils::FixSpecialDateType(context->selection);
    context->predicates.SetWhereClause(context->selection);
    context->predicates.SetWhereArgs(context->selectionArgs);
    context->predicates.SetOrder(context->order);

    if (context->resultNapiType == ResultNapiType::TYPE_MEDIALIBRARY) {
        context->fetchColumn = FILE_ASSET_COLUMNS;
    } else {
        context->fetchColumn.push_back(CONST_MEDIA_DATA_DB_ID);
        context->fetchColumn.push_back(CONST_MEDIA_DATA_DB_NAME);
        context->fetchColumn.push_back(CONST_MEDIA_DATA_DB_MEDIA_TYPE);
    }

    string queryUri = CONST_MEDIALIBRARY_DATA_ABILITY_PREFIX +
        (MediaFileUtils::GetNetworkIdFromUri(context->objectPtr->GetAlbumUri())) +
        CONST_MEDIALIBRARY_DATA_URI_IDENTIFIER;
    NAPI_DEBUG_LOG("queryUri is = %{private}s", queryUri.c_str());
    Uri uri(queryUri);
    int errCode = 0;
    int userId = context->fetchResult != nullptr ? context->fetchResult->GetUserId() : -1;
    std::shared_ptr<OHOS::DataShare::DataShareResultSet> resultSet = UserFileClient::Query(uri,
        context->predicates, context->fetchColumn, errCode, userId);
    if (resultSet == nullptr) {
        NAPI_ERR_LOG("GetFileAssetsNative called, UserFileClient::Query errorCode is = %{public}d", errCode);
        context->SaveError(errCode);
        return;
    }
    context->fetchResult = std::make_unique<FetchResult<FileAsset>>(move(resultSet));
    context->fetchResult->SetNetworkId(MediaFileUtils::GetNetworkIdFromUri(context->objectPtr->GetAlbumUri()));
    if (context->resultNapiType == ResultNapiType::TYPE_USERFILE_MGR ||
        context->resultNapiType == ResultNapiType::TYPE_PHOTOACCESS_HELPER) {
        context->fetchResult->SetResultNapiType(context->resultNapiType);
    }
}

static void JSGetFileAssetsCompleteCallback(napi_env env, napi_status status, void *data)
{
    MediaLibraryTracer tracer;
    tracer.Start("JSGetFileAssetsCompleteCallback");

    AlbumNapiAsyncContext *context = static_cast<AlbumNapiAsyncContext*>(data);
    CHECK_NULL_PTR_RETURN_VOID(context, "Async context is null");

    std::unique_ptr<JSAsyncContextOutput> jsContext = std::make_unique<JSAsyncContextOutput>();
    jsContext->status = false;
    if (context->fetchResult != nullptr) {
        if (context->fetchResult->GetCount() < 0) {
            napi_get_undefined(env, &jsContext->data);
            MediaLibraryNapiUtils::CreateNapiErrorObject(env, jsContext->error, ERR_MEM_ALLOCATION,
                                                         "find no data by options");
        } else {
            napi_value fetchRes = FetchFileResultNapi::CreateFetchFileResult(env, move(context->fetchResult));
            if (fetchRes == nullptr) {
                NAPI_ERR_LOG("Failed to get file asset napi object");
                napi_get_undefined(env, &jsContext->data);
                MediaLibraryNapiUtils::CreateNapiErrorObject(env, jsContext->error, ERR_MEM_ALLOCATION,
                    "Failed to create js object for FetchFileResult");
            } else {
                jsContext->data = fetchRes;
                napi_get_undefined(env, &jsContext->error);
                jsContext->status = true;
            }
        }
    } else {
        NAPI_ERR_LOG("No fetch file result found!");
        context->HandleError(env, jsContext->error);
        napi_get_undefined(env, &jsContext->data);
    }

    tracer.Finish();
    if (context->work != nullptr) {
        MediaLibraryNapiUtils::InvokeJSAsyncMethod(env, context->deferred, context->callbackRef,
                                                   context->work, *jsContext);
    }
    delete context;
}

static void CommitModifyNative(napi_env env, void *data)
{
    MediaLibraryTracer tracer;
    tracer.Start("CommitModifyNative");

    auto *context = static_cast<AlbumNapiAsyncContext*>(data);
    auto objectPtr = context->objectPtr;
    if (MediaFileUtils::CheckAlbumName(objectPtr->GetAlbumName()) < 0) {
        context->error = JS_E_DISPLAYNAME;
        context->errorMsg = "album name invalid";
        NAPI_ERR_LOG("album name invalid = %{private}s", objectPtr->GetAlbumName().c_str());
        return;
    }
#ifdef MEDIALIBRARY_COMPATIBILITY
    context->changedRows = 0;
#else
    DataSharePredicates predicates;
    DataShareValuesBucket valuesBucket;
    valuesBucket.Put(CONST_MEDIA_DATA_DB_TITLE, objectPtr->GetAlbumName());
    predicates.SetWhereClause(string(CONST_MEDIA_DATA_DB_ID) + " = ? ");
    predicates.SetWhereArgs({ std::to_string(objectPtr->GetAlbumId()) });

    string updateUri = MEDIALIBRARY_DATA_URI + "/" +
        CONST_MEDIA_ALBUMOPRN + "/" + CONST_MEDIA_ALBUMOPRN_MODIFYALBUM + "/" + std::to_string(objectPtr->GetAlbumId());
    Uri uri(updateUri);
    int changedRows = UserFileClient::Update(uri, predicates, valuesBucket);
    if (changedRows > 0) {
        DataSharePredicates filePredicates;
        DataShareValuesBucket fileValuesBucket;
        fileValuesBucket.Put(CONST_MEDIA_DATA_DB_BUCKET_NAME, objectPtr->GetAlbumName());
        filePredicates.SetWhereClause(string(CONST_MEDIA_DATA_DB_BUCKET_ID) + " = ? ");
        filePredicates.SetWhereArgs({ std::to_string(objectPtr->GetAlbumId()) });

        string fileUriStr = MEDIALIBRARY_DATA_URI;
        Uri fileUri(fileUriStr);
        changedRows = UserFileClient::Update(fileUri, filePredicates, fileValuesBucket);
    }
    context->SaveError(changedRows);
    context->changedRows = changedRows;
#endif
}

static void JSCommitModifyCompleteCallback(napi_env env, napi_status status, void *data)
{
    MediaLibraryTracer tracer;
    tracer.Start("JSCommitModifyCompleteCallback");

    AlbumNapiAsyncContext *context = static_cast<AlbumNapiAsyncContext*>(data);
    CHECK_NULL_PTR_RETURN_VOID(context, "Async context is null");
    std::unique_ptr<JSAsyncContextOutput> jsContext = std::make_unique<JSAsyncContextOutput>();
    jsContext->status = false;
    if (context->error == ERR_DEFAULT) {
        napi_create_int32(env, context->changedRows, &jsContext->data);
        napi_get_undefined(env, &jsContext->error);
        jsContext->status = true;
        auto contextUri = make_unique<Uri>(CONST_MEDIALIBRARY_ALBUM_URI);
        UserFileClient::NotifyChange(*contextUri);
    } else {
        napi_get_undefined(env, &jsContext->data);
        context->HandleError(env, jsContext->error);
    }

    tracer.Finish();
    if (context->work != nullptr) {
        MediaLibraryNapiUtils::InvokeJSAsyncMethod(env, context->deferred, context->callbackRef,
                                                   context->work, *jsContext);
    }

    delete context;
}
napi_value AlbumNapi::JSGetAlbumFileAssets(napi_env env, napi_callback_info info)
{
    napi_status status;
    napi_value result = nullptr;
    size_t argc = ARGS_TWO;
    napi_value argv[ARGS_TWO] = {0};
    napi_value thisVar = nullptr;

    MediaLibraryTracer tracer;
    tracer.Start("JSGetAlbumFileAssets");

    GET_JS_ARGS(env, info, argc, argv, thisVar);
    NAPI_ASSERT(env, ((argc == ARGS_ZERO) || (argc == ARGS_ONE) || (argc == ARGS_TWO)),
                "requires 2 parameter maximum");
    napi_get_undefined(env, &result);

    std::unique_ptr<AlbumNapiAsyncContext> asyncContext = std::make_unique<AlbumNapiAsyncContext>();
    asyncContext->resultNapiType = ResultNapiType::TYPE_MEDIALIBRARY;
    status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&asyncContext->objectInfo));
    if (status == napi_ok && asyncContext->objectInfo != nullptr) {
        result = ConvertJSArgsToNative(env, argc, argv, *asyncContext);
        CHECK_NULL_PTR_RETURN_UNDEFINED(env, result, result, "Failed to obtain arguments");
        asyncContext->objectPtr = asyncContext->objectInfo->albumAssetPtr;
        CHECK_NULL_PTR_RETURN_UNDEFINED(env, asyncContext->objectPtr, result, "AlbumAsset is nullptr");

        result = MediaLibraryNapiUtils::NapiCreateAsyncWork(env, asyncContext, "JSGetAlbumFileAssets",
            GetFileAssetsNative, JSGetFileAssetsCompleteCallback);
    }

    return result;
}
napi_value AlbumNapi::JSCommitModify(napi_env env, napi_callback_info info)
{
    napi_status status;
    napi_value result = nullptr;
    size_t argc = ARGS_ONE;
    napi_value argv[ARGS_ONE] = {0};
    napi_value thisVar = nullptr;

    MediaLibraryTracer tracer;
    tracer.Start("JSCommitModify");

    GET_JS_ARGS(env, info, argc, argv, thisVar);
    NAPI_ASSERT(env, (argc == ARGS_ZERO || argc == ARGS_ONE), "requires 1 parameter maximum");
    napi_get_undefined(env, &result);

    std::unique_ptr<AlbumNapiAsyncContext> asyncContext = std::make_unique<AlbumNapiAsyncContext>();
    CHECK_NULL_PTR_RETURN_UNDEFINED(env, asyncContext, result, "asyncContext context is null");
    asyncContext->resultNapiType = ResultNapiType::TYPE_MEDIALIBRARY;
    status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&asyncContext->objectInfo));
    if (status == napi_ok && asyncContext->objectInfo != nullptr) {
        result = ConvertCommitJSArgsToNative(env, argc, argv, *asyncContext);
        CHECK_NULL_PTR_RETURN_UNDEFINED(env, result, result, "JSCommitModify fail ");
        asyncContext->objectPtr = asyncContext->objectInfo->albumAssetPtr;
        CHECK_NULL_PTR_RETURN_UNDEFINED(env, asyncContext->objectPtr, result, "AlbumAsset is nullptr");

        result = MediaLibraryNapiUtils::NapiCreateAsyncWork(env, asyncContext, "JSCommitModify", CommitModifyNative,
            JSCommitModifyCompleteCallback);
    }

    return result;
}

napi_value AlbumNapi::UserFileMgrGetAssets(napi_env env, napi_callback_info info)
{
    napi_value ret = nullptr;
    unique_ptr<AlbumNapiAsyncContext> asyncContext = make_unique<AlbumNapiAsyncContext>();
    CHECK_NULL_PTR_RETURN_UNDEFINED(env, asyncContext, ret, "asyncContext context is null");

    asyncContext->mediaTypes.push_back(MEDIA_TYPE_IMAGE);
    asyncContext->mediaTypes.push_back(MEDIA_TYPE_VIDEO);
    CHECK_ARGS(env, MediaLibraryNapiUtils::ParseAssetFetchOptCallback(env, info, asyncContext),
        JS_ERR_PARAMETER_INVALID);
    asyncContext->resultNapiType = ResultNapiType::TYPE_USERFILE_MGR;
    asyncContext->objectPtr = asyncContext->objectInfo->albumAssetPtr;
    CHECK_NULL_PTR_RETURN_UNDEFINED(env, asyncContext->objectPtr, ret, "AlbumAsset is nullptr");

    return MediaLibraryNapiUtils::NapiCreateAsyncWork(env, asyncContext, "UserFileMgrGetAssets", GetFileAssetsNative,
        JSGetFileAssetsCompleteCallback);
}

napi_value AlbumNapi::UserFileMgrCommitModify(napi_env env, napi_callback_info info)
{
    MediaLibraryTracer tracer;
    tracer.Start("UserFileMgrCommitModify");

    napi_value ret = nullptr;
    unique_ptr<AlbumNapiAsyncContext> asyncContext = make_unique<AlbumNapiAsyncContext>();
    CHECK_NULL_PTR_RETURN_UNDEFINED(env, asyncContext, ret, "asyncContext context is null");
    asyncContext->resultNapiType = ResultNapiType::TYPE_USERFILE_MGR;
    CHECK_ARGS(env, MediaLibraryNapiUtils::ParseArgsOnlyCallBack(env, info, asyncContext), JS_ERR_PARAMETER_INVALID);
    asyncContext->objectPtr = asyncContext->objectInfo->albumAssetPtr;
    CHECK_NULL_PTR_RETURN_UNDEFINED(env, asyncContext->objectPtr, ret, "AlbumAsset is nullptr");

    return MediaLibraryNapiUtils::NapiCreateAsyncWork(env, asyncContext, "UserFileMgrCommitModify", CommitModifyNative,
        JSCommitModifyCompleteCallback);
}

napi_value AlbumNapi::PhotoAccessHelperGetAssets(napi_env env, napi_callback_info info)
{
    napi_value ret = nullptr;
    unique_ptr<AlbumNapiAsyncContext> asyncContext = make_unique<AlbumNapiAsyncContext>();
    CHECK_NULL_PTR_RETURN_UNDEFINED(env, asyncContext, ret, "asyncContext context is null");

    asyncContext->mediaTypes.push_back(MEDIA_TYPE_IMAGE);
    asyncContext->mediaTypes.push_back(MEDIA_TYPE_VIDEO);
    CHECK_ARGS(env, MediaLibraryNapiUtils::ParseAssetFetchOptCallback(env, info, asyncContext),
        JS_ERR_PARAMETER_INVALID);
    asyncContext->resultNapiType = ResultNapiType::TYPE_PHOTOACCESS_HELPER;
    asyncContext->objectPtr = asyncContext->objectInfo->albumAssetPtr;
    CHECK_NULL_PTR_RETURN_UNDEFINED(env, asyncContext->objectPtr, ret, "AlbumAsset is nullptr");

    return MediaLibraryNapiUtils::NapiCreateAsyncWork(env, asyncContext, "UserFileMgrGetAssets", GetFileAssetsNative,
        JSGetFileAssetsCompleteCallback);
}

napi_value AlbumNapi::PhotoAccessHelperCommitModify(napi_env env, napi_callback_info info)
{
    MediaLibraryTracer tracer;
    tracer.Start("UserFileMgrCommitModify");

    napi_value ret = nullptr;
    unique_ptr<AlbumNapiAsyncContext> asyncContext = make_unique<AlbumNapiAsyncContext>();
    CHECK_NULL_PTR_RETURN_UNDEFINED(env, asyncContext, ret, "asyncContext context is null");
    asyncContext->resultNapiType = ResultNapiType::TYPE_PHOTOACCESS_HELPER;
    CHECK_ARGS(env, MediaLibraryNapiUtils::ParseArgsOnlyCallBack(env, info, asyncContext), JS_ERR_PARAMETER_INVALID);
    asyncContext->objectPtr = asyncContext->objectInfo->albumAssetPtr;
    CHECK_NULL_PTR_RETURN_UNDEFINED(env, asyncContext->objectPtr, ret, "AlbumAsset is nullptr");

    return MediaLibraryNapiUtils::NapiCreateAsyncWork(env, asyncContext, "UserFileMgrCommitModify", CommitModifyNative,
        JSCommitModifyCompleteCallback);
}
} // namespace Media
} // namespace OHOS