/*
 * Copyright (c) 2023 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 "js_inspector.h"
#include "base/log/log_wrapper.h"
#include "core/common/ace_engine.h"
#include "js_native_api_types.h"
namespace OHOS::Ace::Napi {
namespace {
constexpr size_t STR_BUFFER_SIZE = 1024;
constexpr uint8_t PARA_COUNT = 2;
constexpr double JS_INT32_MAX = static_cast<double>(INT32_MAX);
constexpr double JS_INT32_MIN = 0.0;
constexpr double EPSILON = 1e-9;
} // namespace

static ComponentObserver* GetObserver(napi_env env, napi_value thisVar)
{
    ComponentObserver* observer = nullptr;
    napi_unwrap(env, thisVar, (void**)&observer);
    if (!observer) {
        return nullptr;
    }
    observer->Initialize(env, thisVar);

    return observer;
}

static size_t ParseDrawChildrenArgs(napi_env& env, napi_callback_info& info, napi_value& thisVar, napi_value& cb)
{
    const size_t argNum = 1;
    size_t argc = argNum;
    napi_value argv[argNum] = { 0 };
    void* data = nullptr;
    napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);

    if (argc != 1) {
        return argc;
    }

    napi_valuetype napiType;
    NAPI_CALL_BASE(env, napi_typeof(env, argv[0], &napiType), 0);
    if (napiType == napi_undefined) {
        return 0;
    }
    NAPI_ASSERT_BASE(env, napiType == napi_function, "type mismatch for parameter 1", 0);
    cb = argv[0];
    return argc;
}

static size_t ParseLayoutChildrenArgs(napi_env& env, napi_callback_info& info, napi_value& thisVar, napi_value& cb)
{
    const size_t argNum = 1;
    size_t argc = argNum;
    napi_value argv[argNum] = { 0 };
    void* data = nullptr;
    napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
 
    if (argc != 1) {
        return argc;
    }
 
    napi_valuetype napiType;
    NAPI_CALL_BASE(env, napi_typeof(env, argv[0], &napiType), 0);
    if (napiType == napi_undefined) {
        TAG_LOGI(AceLogTag::ACE_KEYBOARD, "ParseLayoutChildrenArgs 1");
        return 0;
    }
    TAG_LOGI(AceLogTag::ACE_KEYBOARD, "ParseLayoutChildrenArgs 2");
    NAPI_ASSERT_BASE(env, napiType == napi_function, "type mismatch for parameter 1", 0);
    cb = argv[0];
    return argc;
}

static size_t ParseArgs(
    napi_env& env, napi_callback_info& info, napi_value& thisVar, napi_value& cb, CalloutType& calloutType)
{
    const size_t argNum = 2;
    size_t argc = argNum;
    napi_value argv[argNum] = { 0 };
    void* data = nullptr;
    napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
    NAPI_ASSERT_BASE(env, argc > 0, "too few parameter", 0);

    napi_valuetype napiType;
    NAPI_CALL_BASE(env, napi_typeof(env, argv[0], &napiType), 0);
    NAPI_ASSERT_BASE(env, napiType == napi_string, "parameter 1 should be string", 0);
    char type[STR_BUFFER_SIZE] = { 0 };
    size_t len = 0;
    napi_get_value_string_utf8(env, argv[0], type, STR_BUFFER_SIZE, &len);
    NAPI_ASSERT_BASE(env, len < STR_BUFFER_SIZE, "condition string too long", 0);
    NAPI_ASSERT_BASE(
        env, (strcmp("layout", type) == 0 || strcmp("draw", type) == 0 || strcmp("drawChildren", type) == 0),
        "type mismatch('layout' or 'draw')", 0);
    if (strcmp("layout", type) == 0) {
        calloutType = CalloutType::LAYOUTCALLOUT;
    } else if (strcmp("draw", type) == 0) {
        calloutType = CalloutType::DRAWCALLOUT;
    } else if (strcmp("drawChildren", type) == 0) {
        calloutType = CalloutType::DRAWCHILDRENCALLOUT;
    } else {
        calloutType = CalloutType::UNKNOW;
    }

    if (argc <= 1) {
        return argc;
    }

    NAPI_CALL_BASE(env, napi_typeof(env, argv[1], &napiType), 0);
    NAPI_ASSERT_BASE(env, napiType == napi_function, "type mismatch for parameter 2", 0);
    cb = argv[1];
    return argc;
}

void ComponentObserver::callUserFunction(napi_env env, std::list<napi_ref>& cbList)
{
    napi_handle_scope scope = nullptr;
    napi_open_handle_scope(env, &scope);
    if (scope == nullptr) {
        return;
    }

    std::list<napi_value> cbs;
    for (auto& cbRef : cbList) {
        napi_value cb = nullptr;
        if (napi_get_reference_value(env, cbRef, &cb) != napi_ok || cb == nullptr) {
            continue;
        }
        cbs.emplace_back(cb);
    }
    for (auto& cb : cbs) {
        napi_value resultArg = nullptr;
        napi_value result = nullptr;
        napi_call_function(env, nullptr, cb, 1, &resultArg, &result);
    }
    napi_close_handle_scope(env, scope);
}

void ComponentObserver::callUserFunction(napi_env env, std::list<napi_ref>& cbList,
    const std::vector<int32_t>& childIds)
{
    napi_handle_scope scope = nullptr;
    napi_open_handle_scope(env, &scope);
    if (scope == nullptr) {
        return;
    }

    napi_value childIdsArray = nullptr;
    napi_create_array_with_length(env, childIds.size(), &childIdsArray);
    if (childIdsArray == nullptr) {
        napi_close_handle_scope(env, scope);
        return;
    }
    for (size_t index = 0; index < childIds.size(); ++index) {
        napi_value element = nullptr;
        napi_create_int32(env, childIds[index], &element);
        napi_set_element(env, childIdsArray, index, element);
    }

    std::list<napi_value> cbs;
    for (auto& cbRef : cbList) {
        napi_value cb = nullptr;
        if (napi_get_reference_value(env, cbRef, &cb) != napi_ok || cb == nullptr) {
            continue;
        }
        cbs.emplace_back(cb);
    }
    for (auto& cb : cbs) {
        napi_value result = nullptr;
        napi_call_function(env, nullptr, cb, 1, &childIdsArray, &result);
    }
    napi_close_handle_scope(env, scope);
}

std::list<napi_ref>::iterator ComponentObserver::FindCbList(napi_env env, napi_value cb, CalloutType calloutType)
{
    if (calloutType == CalloutType::LAYOUTCALLOUT) {
        return std::find_if(cbLayoutList_.begin(), cbLayoutList_.end(), [env, cb](const napi_ref& item) -> bool {
            bool result = false;
            napi_value refItem;
            napi_get_reference_value(env, item, &refItem);
            napi_strict_equals(env, refItem, cb, &result);
            return result;
        });
    } else if (calloutType == CalloutType::DRAWCALLOUT) {
        return std::find_if(cbDrawList_.begin(), cbDrawList_.end(), [env, cb](const napi_ref& item) -> bool {
            bool result = false;
            napi_value refItem;
            napi_get_reference_value(env, item, &refItem);
            napi_strict_equals(env, refItem, cb, &result);
            return result;
        });
    } else if (calloutType == CalloutType::LAYOUTCHILDRENCALLOUT) {
        return std::find_if(
            cbLayoutChildrenList_.begin(), cbLayoutChildrenList_.end(), [env, cb](const napi_ref& item) -> bool {
                bool result = false;
                napi_value refItem;
                napi_get_reference_value(env, item, &refItem);
                napi_strict_equals(env, refItem, cb, &result);
                return result;
            });
    } else if (calloutType == CalloutType::DRAWCHILDRENWITHPARAMETERCALLOUT) {
        return std::find_if(cbDrawChildrenWithParameterList_.begin(),
            cbDrawChildrenWithParameterList_.end(), [env, cb](const napi_ref& item) -> bool {
            bool result = false;
            napi_value refItem;
            napi_get_reference_value(env, item, &refItem);
            napi_strict_equals(env, refItem, cb, &result);
            return result;
        });
    } else {
        return std::find_if(cbDrawChildrenList_.begin(),
            cbDrawChildrenList_.end(), [env, cb](const napi_ref& item) -> bool {
            bool result = false;
            napi_value refItem;
            napi_get_reference_value(env, item, &refItem);
            napi_strict_equals(env, refItem, cb, &result);
            return result;
        });
    }
}

void ComponentObserver::UpdateDrawLayoutChildObserver(bool isClearLayoutObserver, bool isClearDrawObserver)
{
    auto container = AceEngine::Get().GetContainer(instanceId_);
    CHECK_NULL_VOID(container);
    auto context = container->GetPipelineContext();
    if (context == nullptr) {
        LOGE("js_inspector can not get context by %{public}d", instanceId_);
        return;
    }
    if (uniqueId_ > 0) {
        context->UpdateDrawLayoutChildObserver(uniqueId_, isClearLayoutObserver, isClearDrawObserver);
    } else {
        context->UpdateDrawLayoutChildObserver(componentId_, isClearLayoutObserver, isClearDrawObserver);
    }
}

void ComponentObserver::AddCallbackToList(
    napi_value cb, std::list<napi_ref>& cbList, CalloutType calloutType, napi_env env, napi_handle_scope scope)
{
    auto iter = FindCbList(env, cb, calloutType);
    if (iter != cbList.end()) {
        napi_close_handle_scope(env, scope);
        return;
    }
    napi_ref ref = nullptr;
    napi_create_reference(env, cb, 1, &ref);
    cbList.emplace_back(ref);
    napi_close_handle_scope(env, scope);
}

void ComponentObserver::DeleteCallbackFromList(
    size_t argc, std::list<napi_ref>& cbList, CalloutType calloutType, napi_value cb, napi_env env)
{
    if (argc == 1) {
        for (auto& item : cbList) {
            napi_delete_reference(env, item);
        }
        cbList.clear();
    } else {
        NAPI_ASSERT_RETURN_VOID(env, (argc == PARA_COUNT && cb != nullptr), "Invalid arguments");
        auto iter = FindCbList(env, cb, calloutType);
        if (iter != cbList.end()) {
            napi_delete_reference(env, *iter);
            cbList.erase(iter);
        }
    }
}

void ComponentObserver::DeleteOnDrawChildrenCallbackFromList(
    size_t argc, std::list<napi_ref>& cbList, CalloutType calloutType, napi_value cb, napi_env env)
{
    if (argc == 0) {
        for (auto& item : cbList) {
            napi_delete_reference(env, item);
        }
        cbList.clear();
    } else {
        NAPI_ASSERT_RETURN_VOID(env, (argc == 1 && cb != nullptr), "Invalid arguments");
        auto iter = FindCbList(env, cb, calloutType);
        if (iter != cbList.end()) {
            napi_delete_reference(env, *iter);
            cbList.erase(iter);
        }
    }
}

void ComponentObserver::DeleteLayoutChildrenCallbackFromList(
    size_t argc, std::list<napi_ref>& cbList, CalloutType calloutType, napi_value cb, napi_env env)
{
    if (argc == 0) {
        for (auto& item : cbList) {
            napi_delete_reference(env, item);
        }
        cbList.clear();
    } else {
        NAPI_ASSERT_RETURN_VOID(env, (argc == 1 && cb != nullptr), "Invalid arguments");
        auto iter = FindCbList(env, cb, calloutType);
        if (iter != cbList.end()) {
            napi_delete_reference(env, *iter);
            cbList.erase(iter);
        }
    }
}

void ComponentObserver::FunctionOnLayoutChildren(napi_env& env, napi_value result)
{
    const char* funName = "onLayoutChildren";
    napi_value funcValue = nullptr;
    TAG_LOGI(AceLogTag::ACE_KEYBOARD, "FunctionOnLayoutChildren 1");
    auto On = [](napi_env env, napi_callback_info info) -> napi_value {
        auto jsEngine = EngineHelper::GetCurrentEngineSafely();
        if (!jsEngine) {
            return nullptr;
        }

        napi_handle_scope scope = nullptr;
        napi_open_handle_scope(env, &scope);
        CHECK_NULL_RETURN(scope, nullptr);
        napi_value thisVar = nullptr;
        napi_value cb = nullptr;
        size_t argc = ParseLayoutChildrenArgs(env, info, thisVar, cb);
        NAPI_ASSERT(env, (argc == 1 && thisVar != nullptr && cb != nullptr), "Invalid arguments");
        TAG_LOGI(AceLogTag::ACE_KEYBOARD, "FunctionOnLayoutChildren 2");
        ComponentObserver* observer = GetObserver(env, thisVar);
        if (!observer) {
            napi_close_handle_scope(env, scope);
            return nullptr;
        }
        TAG_LOGI(AceLogTag::ACE_KEYBOARD, "FunctionOnLayoutChildren 3");
        observer->AddCallbackToList(
            cb, observer->cbLayoutChildrenList_, CalloutType::LAYOUTCHILDRENCALLOUT, env, scope);
        observer->UpdateDrawLayoutChildObserver(false, false);
        return nullptr;
    };
    napi_create_function(env, funName, NAPI_AUTO_LENGTH, On, nullptr, &funcValue);
    napi_set_named_property(env, result, funName, funcValue);
}

void ComponentObserver::FunctionOffLayoutChildren(napi_env& env, napi_value result)
{
    const char* funName = "offLayoutChildren";
    napi_value funcValue = nullptr;
    auto Off = [](napi_env env, napi_callback_info info) -> napi_value {
        napi_value thisVar = nullptr;
        napi_value cb = nullptr;
        napi_handle_scope scope = nullptr;
        napi_open_handle_scope(env, &scope);
        CHECK_NULL_RETURN(scope, nullptr);
        size_t argc = ParseLayoutChildrenArgs(env, info, thisVar, cb);
        ComponentObserver* observer = GetObserver(env, thisVar);
        if (!observer) {
            napi_close_handle_scope(env, scope);
            return nullptr;
        }
        observer->DeleteLayoutChildrenCallbackFromList(
            argc, observer->cbLayoutChildrenList_, CalloutType::LAYOUTCHILDRENCALLOUT, cb, env);
        if (observer->cbLayoutChildrenList_.empty()) {
            observer->UpdateDrawLayoutChildObserver(true, false);
        }
        napi_close_handle_scope(env, scope);
        return nullptr;
    };

    napi_create_function(env, funName, NAPI_AUTO_LENGTH, Off, nullptr, &funcValue);
    napi_set_named_property(env, result, funName, funcValue);
}

void ComponentObserver::FunctionOnDrawChildren(napi_env& env, napi_value result)
{
    const char* funName = "onDrawChildren";
    napi_value funcValue = nullptr;
    auto On = [](napi_env env, napi_callback_info info) -> napi_value {
        auto jsEngine = EngineHelper::GetCurrentEngineSafely();
        if (!jsEngine) {
            return nullptr;
        }

        napi_handle_scope scope = nullptr;
        napi_open_handle_scope(env, &scope);
        CHECK_NULL_RETURN(scope, nullptr);
        napi_value thisVar = nullptr;
        napi_value cb = nullptr;
        size_t argc = ParseDrawChildrenArgs(env, info, thisVar, cb);
        NAPI_ASSERT_BASE(env, (argc == 1 && thisVar != nullptr && cb != nullptr), "Invalid arguments",
            (napi_close_handle_scope(env, scope), nullptr));
        ComponentObserver* observer = GetObserver(env, thisVar);
        if (!observer) {
            napi_close_handle_scope(env, scope);
            return nullptr;
        }
        observer->AddCallbackToList(
            cb, observer->cbDrawChildrenWithParameterList_, CalloutType::DRAWCHILDRENWITHPARAMETERCALLOUT, env, scope);
        observer->UpdateDrawLayoutChildObserver(false, false);
        return nullptr;
    };
    napi_create_function(env, funName, NAPI_AUTO_LENGTH, On, nullptr, &funcValue);
    napi_set_named_property(env, result, funName, funcValue);
}

void ComponentObserver::FunctionOffDrawChildren(napi_env& env, napi_value result)
{
    const char* funName = "offDrawChildren";
    napi_value funcValue = nullptr;
    auto Off = [](napi_env env, napi_callback_info info) -> napi_value {
        napi_value thisVar = nullptr;
        napi_value cb = nullptr;
        napi_handle_scope scope = nullptr;
        napi_open_handle_scope(env, &scope);
        CHECK_NULL_RETURN(scope, nullptr);
        size_t argc = ParseDrawChildrenArgs(env, info, thisVar, cb);
        ComponentObserver* observer = GetObserver(env, thisVar);
        if (!observer) {
            napi_close_handle_scope(env, scope);
            return nullptr;
        }
        observer->DeleteOnDrawChildrenCallbackFromList(argc, observer->cbDrawChildrenWithParameterList_,
            CalloutType::DRAWCHILDRENWITHPARAMETERCALLOUT, cb, env);
        if (observer->cbDrawChildrenWithParameterList_.empty()) {
            observer->UpdateDrawLayoutChildObserver(false, true);
        }
        napi_close_handle_scope(env, scope);
        return nullptr;
    };

    napi_create_function(env, funName, NAPI_AUTO_LENGTH, Off, nullptr, &funcValue);
    napi_set_named_property(env, result, funName, funcValue);
}

void ComponentObserver::FunctionOn(napi_env& env, napi_value result, const char* funName)
{
    napi_value funcValue = nullptr;
    auto On = [](napi_env env, napi_callback_info info) -> napi_value {
        auto jsEngine = EngineHelper::GetCurrentEngineSafely();
        if (!jsEngine) {
            return nullptr;
        }

        napi_handle_scope scope = nullptr;
        napi_open_handle_scope(env, &scope);
        CHECK_NULL_RETURN(scope, nullptr);
        napi_value thisVar = nullptr;
        napi_value cb = nullptr;
        CalloutType calloutType = CalloutType::UNKNOW;
        size_t argc = ParseArgs(env, info, thisVar, cb, calloutType);
        NAPI_ASSERT(env, (argc == 2 && thisVar != nullptr && cb != nullptr), "Invalid arguments");

        ComponentObserver* observer = GetObserver(env, thisVar);
        if (!observer) {
            napi_close_handle_scope(env, scope);
            return nullptr;
        }

        if (calloutType == CalloutType::LAYOUTCALLOUT) {
            observer->AddCallbackToList(cb, observer->cbLayoutList_, calloutType, env, scope);
        } else if (calloutType == CalloutType::DRAWCALLOUT) {
            observer->AddCallbackToList(cb, observer->cbDrawList_, calloutType, env, scope);
        } else if (calloutType == CalloutType::DRAWCHILDRENCALLOUT) {
            observer->AddCallbackToList(cb, observer->cbDrawChildrenList_, calloutType, env, scope);
            observer->UpdateDrawLayoutChildObserver(false, false);
        }
        return nullptr;
    };
    napi_create_function(env, funName, NAPI_AUTO_LENGTH, On, nullptr, &funcValue);
    napi_set_named_property(env, result, funName, funcValue);
}

void ComponentObserver::FunctionOff(napi_env& env, napi_value result, const char* funName)
{
    napi_value funcValue = nullptr;
    auto Off = [](napi_env env, napi_callback_info info) -> napi_value {
        napi_value thisVar = nullptr;
        napi_value cb = nullptr;
        CalloutType calloutType = CalloutType::UNKNOW;
        napi_handle_scope scope = nullptr;
        napi_open_handle_scope(env, &scope);
        CHECK_NULL_RETURN(scope, nullptr);
        size_t argc = ParseArgs(env, info, thisVar, cb, calloutType);
        ComponentObserver* observer = GetObserver(env, thisVar);
        if (!observer) {
            napi_close_handle_scope(env, scope);
            return nullptr;
        }
        if (calloutType == CalloutType::LAYOUTCALLOUT) {
            observer->DeleteCallbackFromList(argc, observer->cbLayoutList_, calloutType, cb, env);
        } else if (calloutType == CalloutType::DRAWCALLOUT) {
            observer->DeleteCallbackFromList(argc, observer->cbDrawList_, calloutType, cb, env);
        } else if (calloutType == CalloutType::DRAWCHILDRENCALLOUT) {
            observer->DeleteCallbackFromList(argc, observer->cbDrawChildrenList_, calloutType, cb, env);
            if (observer->cbDrawChildrenList_.empty()) {
                observer->UpdateDrawLayoutChildObserver(false, true);
            }
        }
        napi_close_handle_scope(env, scope);
        return nullptr;
    };

    napi_create_function(env, funName, NAPI_AUTO_LENGTH, Off, nullptr, &funcValue);
    napi_set_named_property(env, result, funName, funcValue);
}

void ComponentObserver::NapiSerializer(napi_env& env, napi_value& result)
{
    napi_create_object(env, &result);
    napi_handle_scope scope = nullptr;
    napi_open_handle_scope(env, &scope);
    if (scope == nullptr) {
        return;
    }

    napi_value componentIdVal = nullptr;
    napi_create_string_utf8(env, componentId_.c_str(), componentId_.size(), &componentIdVal);
    napi_set_named_property(env, result, "componentId", componentIdVal);

    napi_value uniqueIdVal = nullptr;
    napi_create_int32(env, uniqueId_, &uniqueIdVal);
    napi_set_named_property(env, result, "uniqueId", uniqueIdVal);

    napi_wrap(
        env, result, this,
        [](napi_env env, void* data, void* hint) {
            ComponentObserver* observer = static_cast<ComponentObserver*>(data);
            observer->Destroy(env);
            if (observer != nullptr) {
                delete observer;
            }
        },
        nullptr, nullptr);

    FunctionOn(env, result, "on");
    FunctionOff(env, result, "off");
    FunctionOnDrawChildren(env, result);
    FunctionOffDrawChildren(env, result);
    FunctionOnLayoutChildren(env, result);
    FunctionOffLayoutChildren(env, result);
    napi_close_handle_scope(env, scope);
}

void ComponentObserver::Destroy(napi_env env)
{
    for (auto& layoutitem : cbLayoutList_) {
        napi_delete_reference(env, layoutitem);
    }
    for (auto& drawitem : cbDrawList_) {
        napi_delete_reference(env, drawitem);
    }
    for (auto& drawChildrenitem : cbDrawChildrenList_) {
        napi_delete_reference(env, drawChildrenitem);
    }
    for (auto& drawChildrenWithParameteritem : cbDrawChildrenWithParameterList_) {
        napi_delete_reference(env, drawChildrenWithParameteritem);
    }
    for (auto& layoutChildrenitem : cbLayoutChildrenList_) {
        napi_delete_reference(env, layoutChildrenitem);
    }
    cbLayoutList_.clear();
    cbDrawList_.clear();
    cbDrawChildrenList_.clear();
    cbDrawChildrenWithParameterList_.clear();
    cbLayoutChildrenList_.clear();
    auto jsEngine = weakEngine_.Upgrade();
    if (!jsEngine) {
        return;
    }
    jsEngine->UnregisterLayoutInspectorCallback(layoutEvent_, componentId_);
    jsEngine->UnregisterDrawInspectorCallback(drawEvent_, componentId_);
    jsEngine->UnregisterDrawChildrenInspectorCallback(drawChildrenEvent_, componentId_);
    jsEngine->UnregisterDrawChildrenWithParameterInspectorCallback(drawChildrenWithParameterEvent_, componentId_);
    jsEngine->UnregisterLayoutChildrenInspectorCallback(layoutChildrenEvent_, componentId_);
 
    jsEngine->UnregisterLayoutInspectorCallback(layoutEvent_, uniqueId_);
    jsEngine->UnregisterDrawInspectorCallback(drawEvent_, uniqueId_);
    jsEngine->UnregisterDrawChildrenInspectorCallback(drawChildrenEvent_, uniqueId_);
    jsEngine->UnregisterLayoutChildrenInspectorCallback(layoutChildrenEvent_, uniqueId_);
}

void ComponentObserver::Initialize(napi_env env, napi_value thisVar)
{
    napi_handle_scope scope = nullptr;
    napi_open_handle_scope(env, &scope);
    if (scope == nullptr) {
        return;
    }
    napi_close_handle_scope(env, scope);
}

bool isInt32Range(double d)
{
    if (d < JS_INT32_MIN || d > JS_INT32_MAX) {
        return false;
    }
    double intPart;
    double fracPart = std::modf(d, &intPart);
    return std::fabs(fracPart) <= EPSILON;
}

static ComponentObserver* createObserver(napi_env env, napi_valuetype type, napi_value argv)
{
    if (type == napi_string) {
        char componentId[STR_BUFFER_SIZE] = { 0 };
        size_t len = 0;
        napi_get_value_string_utf8(env, argv, componentId, STR_BUFFER_SIZE, &len);
        NAPI_ASSERT(env, len < STR_BUFFER_SIZE, "condition string too long");
        std::string componentIdStr(componentId, len);
        return new ComponentObserver(componentIdStr);
    } else {
        double uniqueIdDouble = -1.0;
        auto status = napi_get_value_double(env, argv, &uniqueIdDouble);
        NAPI_ASSERT(env, status == napi_ok, "invalid unique id type.");
        NAPI_ASSERT(env, isInt32Range(uniqueIdDouble), "invalid unique id value.");
        int32_t uniqueId = static_cast<int32_t>(uniqueIdDouble);
        return new ComponentObserver(uniqueId);
    }
}

static napi_value JSCreateComponentObserver(napi_env env, napi_callback_info info)
{
    auto jsEngine = EngineHelper::GetCurrentEngineSafely();
    if (!jsEngine) {
        return nullptr;
    }
    /* Get arguments */
    size_t argc = 1;
    napi_value argv = nullptr;
    napi_value thisVar = nullptr;
    void* data = nullptr;
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &argv, &thisVar, &data));
    NAPI_ASSERT(env, argc == 1, "requires 1 parameter");

    /* Checkout arguments */
    napi_valuetype type;
    NAPI_CALL(env, napi_typeof(env, argv, &type));
    NAPI_ASSERT(env, type == napi_string || type == napi_number, "type mismatch");
    ComponentObserver* observer = createObserver(env, type, argv);
    if (!observer) {
        return nullptr;
    }
    observer->instanceId_ = ContainerScope::CurrentId();
    napi_value result = nullptr;
    observer->NapiSerializer(env, result);
    if (!result) {
        delete observer;
        return nullptr;
    }
    auto layoutCallback = [observer, env]() { observer->callUserFunction(env, observer->cbLayoutList_); };
    auto layoutCallbackCounter = [observer]() -> bool { return observer->cbLayoutList_.empty(); };
    observer->layoutEvent_ =
        AceType::MakeRefPtr<InspectorEvent>(std::move(layoutCallback), std::move(layoutCallbackCounter));

    auto drawCallback = [observer, env]() { observer->callUserFunction(env, observer->cbDrawList_); };
    auto drawCallbackCounter = [observer]() -> bool { return observer->cbDrawList_.empty(); };
    observer->drawEvent_ = AceType::MakeRefPtr<InspectorEvent>(std::move(drawCallback), std::move(drawCallbackCounter));
    auto drawChildrenCallback = [observer, env]() { observer->callUserFunction(env, observer->cbDrawChildrenList_); };
    auto drawChildrenCallbackCounter = [observer]() -> bool { return observer->cbDrawChildrenList_.empty(); };
    observer->drawChildrenEvent_ =
        AceType::MakeRefPtr<InspectorEvent>(std::move(drawChildrenCallback), std::move(drawChildrenCallbackCounter));
    auto drawChildrenWithParameterCallback = [observer, env](std::vector<int32_t> childIds) {
        observer->callUserFunction(env, observer->cbDrawChildrenWithParameterList_, childIds);
    };
    auto drawChildrenWithParameterCallbackCounter =
        [observer]() -> bool { return observer->cbDrawChildrenWithParameterList_.empty(); };
    observer->drawChildrenWithParameterEvent_ =
        AceType::MakeRefPtr<InspectorEvent>(std::move(drawChildrenWithParameterCallback),
        std::move(drawChildrenWithParameterCallbackCounter));
    auto layoutChildrenCallback = [observer, env]() {
        observer->callUserFunction(env, observer->cbLayoutChildrenList_);
    };
    auto layoutChildrenCallbackCounter = [observer]() -> bool { return observer->cbLayoutChildrenList_.empty(); };
    observer->layoutChildrenEvent_ = AceType::MakeRefPtr<InspectorEvent>(
        std::move(layoutChildrenCallback), std::move(layoutChildrenCallbackCounter));
    if (type == napi_string) {
        jsEngine->RegisterLayoutInspectorCallback(observer->layoutEvent_, observer->componentId_);
        jsEngine->RegisterDrawInspectorCallback(observer->drawEvent_, observer->componentId_);
        jsEngine->RegisterDrawChildrenInspectorCallback(observer->drawChildrenEvent_, observer->componentId_);
        jsEngine->RegisterDrawChildrenWithParameterInspectorCallback(observer->drawChildrenWithParameterEvent_,
            observer->componentId_);
        jsEngine->RegisterLayoutChildrenInspectorCallback(observer->layoutChildrenEvent_, observer->componentId_);
    } else {
        jsEngine->RegisterLayoutInspectorCallback(observer->layoutEvent_, observer->uniqueId_);
        jsEngine->RegisterDrawInspectorCallback(observer->drawEvent_, observer->uniqueId_);
        jsEngine->RegisterDrawChildrenInspectorCallback(observer->drawChildrenEvent_, observer->uniqueId_);
        jsEngine->RegisterLayoutChildrenInspectorCallback(observer->layoutChildrenEvent_, observer->uniqueId_);
    }
    observer->SetEngine(jsEngine);
#if defined(PREVIEW)
    layoutCallback();
    drawCallback();
#endif
    return result;
}

static napi_value Export(napi_env env, napi_value exports)
{
    napi_property_descriptor properties[] = { DECLARE_NAPI_FUNCTION(
        "createComponentObserver", JSCreateComponentObserver) };

    NAPI_CALL(env, napi_define_properties(env, exports, sizeof(properties) / sizeof(properties[0]), properties));
    return exports;
}

static napi_module inspector_module = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Export,
    .nm_modname = "arkui.inspector", // relative to the dynamic library's name
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterInspector()
{
    napi_module_register(&inspector_module);
}
} // namespace OHOS::Ace::Napi