/*
 * Copyright (c) 2023-2025 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 "ohos_js_environment_impl.h"

#include "console.h"
#include "hilog_tag_wrapper.h"
#include "js_utils.h"
#include "js_worker.h"
#include "ohos_loop_handler.h"
#include "sys_timer.h"
#include "worker_info.h"
#include "parameters.h"

namespace OHOS {
namespace AbilityRuntime {
namespace {
    std::shared_ptr<AppExecFwk::EventHandler> g_eventHandler = nullptr;
}
void OHOSJsEnvironmentImpl::PostTaskToHandler(const uv_task_info_t* taskInfo)
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
    if (!taskInfo || !taskInfo->func || !taskInfo->work) {
        TAG_LOGE(AAFwkTag::JSRUNTIME, "Invalid parameters");
        return;
    }

    auto task = [func = taskInfo->func, work = taskInfo->work, status = taskInfo->status]() {
        TAG_LOGD(AAFwkTag::JSRUNTIME, "Do uv work");
        func(work, status);
        TAG_LOGD(AAFwkTag::JSRUNTIME, "Do uv work end");
    };

    AppExecFwk::EventQueue::Priority prio = AppExecFwk::EventQueue::Priority::IMMEDIATE;
    switch (taskInfo->prio) {
        case uv_qos_t::uv_qos_user_interactive:
            prio = AppExecFwk::EventQueue::Priority::VIP;
            break;
        case uv_qos_t::uv_qos_user_initiated:
            prio = AppExecFwk::EventQueue::Priority::IMMEDIATE;
            break;
        case uv_qos_t::uv_qos_utility:
            prio = AppExecFwk::EventQueue::Priority::LOW;
            break;
        case uv_qos_t::uv_qos_background:
            prio = AppExecFwk::EventQueue::Priority::IDLE;
            break;
        default:
            prio = AppExecFwk::EventQueue::Priority::HIGH;
            break;
    }

    if (g_eventHandler == nullptr) {
        TAG_LOGE(AAFwkTag::JSRUNTIME, "Invalid parameters");
        return;
    }
    if (taskInfo->location == UV_POST_TASK_TO_HEAD) {
        g_eventHandler->PostTaskAtFront(task, taskInfo->name ? taskInfo->name : "uv_loop_task", prio);
    } else {
        g_eventHandler->PostTask(task, taskInfo->name ? taskInfo->name : "uv_io_cb", 0, prio);
    }
}

int OHOSJsEnvironmentImpl::CheckPendingHigherEvent(int priority)
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
    if (g_eventHandler == nullptr) {
        TAG_LOGE(AAFwkTag::JSRUNTIME, "g_eventHandler is null");
        return -1;
    }
    return g_eventHandler->HasPendingHigherEvent(priority) ? 0 : -1;
}

OHOSJsEnvironmentImpl::OHOSJsEnvironmentImpl()
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
}

OHOSJsEnvironmentImpl::OHOSJsEnvironmentImpl(const std::shared_ptr<AppExecFwk::EventRunner>& eventRunner)
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
    if (eventRunner != nullptr) {
        TAG_LOGD(AAFwkTag::JSRUNTIME, "Create event handler");
        eventHandler_ = std::make_shared<AppExecFwk::EventHandler>(eventRunner);
        if (eventRunner.get() == AppExecFwk::EventRunner::GetMainEventRunner().get()) {
            g_eventHandler = std::make_shared<AppExecFwk::EventHandler>(eventRunner);
        }
    }
}

OHOSJsEnvironmentImpl::~OHOSJsEnvironmentImpl()
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
}

void OHOSJsEnvironmentImpl::PostTask(const std::function<void()>& task, const std::string& name, int64_t delayTime)
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
    if (eventHandler_ == nullptr) {
        TAG_LOGE(AAFwkTag::JSRUNTIME, "null eventHandler_");
        return;
    }
    eventHandler_->PostTask(task, name, delayTime);
}

void OHOSJsEnvironmentImpl::PostSyncTask(const std::function<void()>& task, const std::string& name)
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
    if (eventHandler_ != nullptr) {
        eventHandler_->PostSyncTask(task, name);
    }
}

void OHOSJsEnvironmentImpl::RemoveTask(const std::string& name)
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
    if (eventHandler_ != nullptr) {
        eventHandler_->RemoveTask(name);
    }
}

void OHOSJsEnvironmentImpl::InitTimerModule(NativeEngine* engine)
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
    CHECK_POINTER(engine);
    auto ret = JsSysModule::Timer::RegisterTime(reinterpret_cast<napi_env>(engine));
    if (!ret) {
        TAG_LOGE(AAFwkTag::JSRUNTIME, "Register timer failed");
    }
}

void OHOSJsEnvironmentImpl::InitConsoleModule(NativeEngine* engine)
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
    JsSysModule::Console::InitConsoleModule(reinterpret_cast<napi_env>(engine));
}

bool OHOSJsEnvironmentImpl::InitLoop(NativeEngine* engine, bool isStage)
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
    CHECK_POINTER_AND_RETURN(engine, false);
    auto uvLoop = engine->GetUVLoop();
    auto fd = uvLoop != nullptr ? uv_backend_fd(uvLoop) : -1;
    if (fd < 0) {
        TAG_LOGE(AAFwkTag::JSRUNTIME, "get fd failed");
        return false;
    }
    uv_run(uvLoop, UV_RUN_NOWAIT);

    if (eventHandler_ != nullptr) {
        uint32_t events = AppExecFwk::FILE_DESCRIPTOR_INPUT_EVENT | AppExecFwk::FILE_DESCRIPTOR_OUTPUT_EVENT;
        eventHandler_->AddFileDescriptorListener(fd, events, std::make_shared<OHOSLoopHandler>(uvLoop), "uvLoopTask");
        TAG_LOGD(AAFwkTag::JSRUNTIME, "uv_register_task_to_event, isStage: %{public}d", isStage);
        if (isStage && (eventHandler_->GetEventRunner()).get() == AppExecFwk::EventRunner::GetMainEventRunner().get()) {
            static const bool canInterrupt = system::GetBoolParameter("persist.sys.uv_can_interrupt", true);
            TAG_LOGD(AAFwkTag::JSRUNTIME, "uv_register_task_to_event with canInterrupt: %{public}d", canInterrupt);
            uv_register_task_to_event(uvLoop, PostTaskToHandler, canInterrupt ? CheckPendingHigherEvent : nullptr);
            // send signal here to trigger uv tasks generated during initialization.
            uv_async_send(&uvLoop->wq_async);
        }
    }

    return true;
}

void OHOSJsEnvironmentImpl::DeInitLoop(NativeEngine* engine)
{
    CHECK_POINTER(engine);
    auto uvLoop = engine->GetUVLoop();
    auto fd = uvLoop != nullptr ? uv_backend_fd(uvLoop) : -1;
    if (fd >= 0 && eventHandler_ != nullptr) {
        eventHandler_->RemoveFileDescriptorListener(fd);
    }
    uv_unregister_task_to_event(uvLoop);
    RemoveTask(TIMER_TASK);
}

void OHOSJsEnvironmentImpl::InitWorkerModule(NativeEngine* engine, std::shared_ptr<JsEnv::WorkerInfo> workerInfo)
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
    CHECK_POINTER(engine);
    CHECK_POINTER(workerInfo);
    engine->SetInitWorkerFunc(InitWorkerFunc);
    engine->SetOffWorkerFunc(OffWorkerFunc);
    engine->SetReleaseWorkerSafeMemFunc(ReleaseWorkerSafeMemFunc);
    engine->SetGetAssetFunc(AssetHelper(workerInfo));
    engine->SetApiVersion(static_cast<int32_t>(workerInfo->apiTargetVersion.GetOriginPointer()));

    engine->SetGetContainerScopeIdFunc(GetContainerId);
    engine->SetInitContainerScopeFunc(UpdateContainerScope);
    engine->SetFinishContainerScopeFunc(RestoreContainerScope);
}

void OHOSJsEnvironmentImpl::InitSyscapModule()
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
}
} // namespace AbilityRuntime
} // namespace OHOS