/*
 * Copyright (c) 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 <ani.h>
#include <string>

#include "ani_utils.h"
#include "overlay_params.h"

#include "base/log/log_wrapper.h"
#include "bridge/arkts_frontend/ani_local_scope.h"
#include "core/common/container.h"
#include "core/components_ng/base/frame_node.h"
#include "core/components_ng/pattern/overlay/dialog_manager_static.h"
#include "core/components_ng/pattern/overlay/overlay_manager.h"
#include "frameworks/core/interfaces/native/ani/frame_node_peer_impl.h"
#include "core/pipeline_ng/pipeline_context.h"
#include "ui/base/referenced.h"

using namespace OHOS::Ace;
using OHOS::Ace::AniUtils::IsUndefinedObject;

static void PostOverlayTask(
    std::function<void(RefPtr<NG::OverlayManager>)>&& task, int32_t containerId, const std::string& name)
{
    auto context = NG::PipelineContext::GetContextByContainerId(containerId);
    CHECK_NULL_VOID(context);
    auto overlayManager = context->GetOverlayManager();
    CHECK_NULL_VOID(overlayManager);
    context->GetTaskExecutor()->PostTask(
        [task = std::move(task), weak = WeakPtr<NG::OverlayManager>(overlayManager), containerId] {
            ContainerScope scope(containerId);
            auto overlayManager = weak.Upgrade();
            CHECK_NULL_VOID(overlayManager);
            task(overlayManager);
        },
        TaskExecutor::TaskType::UI, name);
}

static bool IsOverlayManagerOptionsObject(ani_env *env, ani_object object)
{
    ani_class optionsClass = nullptr;
    if (ANI_OK != env->FindClass("@ohos.arkui.UIContext.OverlayManagerOptions", &optionsClass)) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "Cannot find class OverlayManagerOptions.");
        return false;
    }
    ani_boolean isOptions = ANI_FALSE;
    if (env->Object_InstanceOf(object, optionsClass, &isOptions)) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "The object is not OverlayManagerOptions.");
        return false;
    }
    return (bool)isOptions;
}

static ani_status GetBoolFromAni(ani_env* env, ani_object obj, const char* name, bool* value)
{
    ani_status status = ANI_ERROR;
    if (value == nullptr) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "The value is null.");
        return status;
    }
    ani_ref refValue = nullptr;
    status = env->Object_GetPropertyByName_Ref(obj, name, &refValue);
    if (status != ANI_OK) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "Get property renderRootOverlay failed(%{public}d).", status);
        return status;
    }
    if (IsUndefinedObject(env, refValue)) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "The refValue is undefined.");
        return status;
    }
    ani_boolean boolValue = ANI_FALSE;
    status = env->Object_CallMethodByName_Boolean(static_cast<ani_object>(refValue), "toBoolean", ":z", &boolValue);
    if (status != ANI_OK) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "Unbox bool value failed");
        return status;
    }
    *value = static_cast<bool>(boolValue);
    return status;
}

static bool GetOverlayManagerInfo(ani_env* env, ani_object options, NG::OverlayManagerInfo& overlayInfo)
{
    if (IsUndefinedObject(env, options)) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "The options is undefined");
        return false;
    }

    if (!IsOverlayManagerOptionsObject(env, options)) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "The type of options is not right.");
        return false;
    }

    if (GetBoolFromAni(env, options, "renderRootOverlay", &overlayInfo.renderRootOverlay) != ANI_OK) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "Get property renderRootOverlay failed.");
        return false;
    }

    if (GetBoolFromAni(env, options, "enableBackPressedEvent", &overlayInfo.enableBackPressedEvent) != ANI_OK) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "Get property enableBackPressedEvent failed.");
        return false;
    }

    ani_ref onBackPressRef = nullptr;
    ani_status status = env->Object_GetPropertyByName_Ref(options, "onBackPress", &onBackPressRef);
    if (status != ANI_OK || IsUndefinedObject(env, onBackPressRef)) {
        return true;
    }
    ani_ref globalRef = nullptr;
    if (env->GlobalReference_Create(onBackPressRef, &globalRef) != ANI_OK) {
        return true;
    }
    ani_vm* vm = nullptr;
    env->GetVM(&vm);
    struct AniGlobalRefHolder {
        ani_vm* vm;
        ani_ref ref;
        AniGlobalRefHolder(ani_vm* v, ani_ref r) : vm(v), ref(r) {}
        ~AniGlobalRefHolder()
        {
            ani_env* delEnv = nullptr;
            vm->GetEnv(ANI_VERSION_1, &delEnv);
            if (delEnv) {
                delEnv->GlobalReference_Delete(ref);
            }
        }
    };
    auto globalRefHolder = std::make_shared<AniGlobalRefHolder>(vm, globalRef);
    overlayInfo.onBackPress = [vm, globalRefHolder]() -> bool {
        ani_env* env = nullptr;
        ani_status status = vm->GetEnv(ANI_VERSION_1, &env);
        if (status != ANI_OK || env == nullptr) {
            return false;
        }
        ScopedAniLocalScope localScope(env);
        ani_fn_object fnObj = reinterpret_cast<ani_fn_object>(globalRefHolder->ref);
        ani_ref fnReturnVal = nullptr;
        ani_status callStatus = env->FunctionalObject_Call(fnObj, 0, nullptr, &fnReturnVal);
        if (callStatus != ANI_OK) {
            TAG_LOGW(AceLogTag::ACE_OVERLAY, "onBackPress callback call failed");
            return false;
        }
        if (IsUndefinedObject(env, fnReturnVal)) {
            return false;
        }
        ani_boolean resultValue = ANI_FALSE;
        callStatus = env->Object_CallMethodByName_Boolean(
            static_cast<ani_object>(fnReturnVal), "toBoolean", ":z", &resultValue);
        if (callStatus != ANI_OK) {
            return false;
        }
        return static_cast<bool>(resultValue);
    };
    return true;
}

static ani_boolean SetOverlayManagerOptions(ani_env* env, ani_object options)
{
    TAG_LOGD(AceLogTag::ACE_OVERLAY, "ani SetOverlayManagerOptions enter");
    ani_boolean ret = ANI_FALSE;
    RefPtr<NG::PipelineContext> context = NG::PipelineContext::GetCurrentContextSafelyWithCheck();
    CHECK_NULL_RETURN(context, ret);
    auto overlayManager = context->GetOverlayManager();
    CHECK_NULL_RETURN(overlayManager, ret);

    auto overlayInfo = NG::OverlayManagerInfo {
        .renderRootOverlay = true,
        .enableBackPressedEvent = false,
    };
    if (!GetOverlayManagerInfo(env, options, overlayInfo)) {
        return ret;
    }

    return overlayManager->SetOverlayManagerOptions(overlayInfo) ? ANI_TRUE : ANI_FALSE;
}

static ani_status CreateAniBoolean(ani_env* env, bool value, ani_object& result)
{
    ani_status state;
    ani_class booleanClass;
    if ((state = env->FindClass("std.core.Boolean", &booleanClass)) != ANI_OK) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "FindClass std.core.Boolean failed, %{public}d", state);
        return state;
    }
    ani_method booleanClassCtor;
    if ((state = env->Class_FindMethod(booleanClass, "<ctor>", "z:", &booleanClassCtor)) != ANI_OK) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "Class_FindMethod Boolean ctor failed, %{public}d", state);
        return state;
    }
    ani_boolean aniValue = value;
    if ((state = env->Object_New(booleanClass, booleanClassCtor, &result, aniValue)) != ANI_OK) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "New Boolean object failed, %{public}d", state);
        return state;
    }
    return state;
}

static ani_object GetOverlayManagerOptions(ani_env* env)
{
    TAG_LOGD(AceLogTag::ACE_OVERLAY, "ani GetOverlayManagerOptions enter");
    ani_object options = {};
    RefPtr<NG::PipelineContext> context = NG::PipelineContext::GetCurrentContextSafelyWithCheck();
    CHECK_NULL_RETURN(context, options);
    auto overlayManager = context->GetOverlayManager();
    CHECK_NULL_RETURN(overlayManager, options);

    auto overlayInfo = overlayManager->GetOverlayManagerOptions();
    if (!overlayInfo.has_value()) {
        overlayInfo = NG::OverlayManagerInfo {
            .renderRootOverlay = true,
            .enableBackPressedEvent = false,
        };
    }

    ani_class cls;
    if (ANI_OK != env->FindClass("@ohos.arkui.UIContext.OverlayManagerOptionsInner", &cls)) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "FindClass OverlayManagerOptionsInner failed");
        return options;
    }
    ani_method ctor;
    if (ANI_OK != env->Class_FindMethod(cls, "<ctor>", ":", &ctor)) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "Cannot find OverlayManagerOptionsInner ctor");
        return options;
    }
    if (ANI_OK != env->Object_New(cls, ctor, &options)) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "Create OverlayManagerOptionsInner object failed");
        return options;
    }

    ani_object renderRootOverlayObj;
    CreateAniBoolean(env, overlayInfo->renderRootOverlay, renderRootOverlayObj);
    if (ANI_OK != env->Object_SetPropertyByName_Ref(options, "renderRootOverlay", renderRootOverlayObj)) {
        TAG_LOGW(AceLogTag::ACE_OVERLAY, "Set property renderRootOverlay failed");
    }

    ani_object enableBackPressedEventObj;
    CreateAniBoolean(env, overlayInfo->enableBackPressedEvent, enableBackPressedEventObj);
    if (ANI_OK != env->Object_SetPropertyByName_Ref(options, "enableBackPressedEvent", enableBackPressedEventObj)) {
        TAG_LOGW(AceLogTag::ACE_OVERLAY, "Set property enableBackPressedEvent failed");
    }

    return options;
}

static void AddComponentContent(ani_env* env, ani_long aniNode, ani_int aniIndex)
{
    TAG_LOGD(AceLogTag::ACE_OVERLAY, "ani AddComponentContent enter: index: %{public}d", (int32_t)aniIndex);
    FrameNodePeer* peerNode = (FrameNodePeer*)aniNode;
    auto frameNode = FrameNodePeer::GetFrameNodeByPeer(peerNode);
    CHECK_NULL_VOID(frameNode);
    auto index = static_cast<int>(aniIndex);
    auto context = frameNode->GetContextRefPtr();
    CHECK_NULL_VOID(context);
    auto instanceId = context->GetInstanceId();
    auto task = [weak = Referenced::WeakClaim(Referenced::RawPtr(frameNode)), index](
            const RefPtr<NG::OverlayManager>& overlayManager) {
        auto frameNode = weak.Upgrade();
        CHECK_NULL_VOID(frameNode);
        overlayManager->AddFrameNodeToOverlay(frameNode, index);
    };
    PostOverlayTask(std::move(task), instanceId, "ani AddComponentContent");
}

static void AddComponentContentWithOrder(ani_env* env, ani_long aniNode, ani_object aniLevelOrder)
{
    TAG_LOGD(AceLogTag::ACE_OVERLAY, "ani AddComponentContentWithOrder enter");
    std::optional<double> orderNumber = std::nullopt;
    if (aniLevelOrder != 0) {
        ani_class levelOrderCls;
        ani_status status = env->FindClass("@ohos.promptAction.LevelOrder", &levelOrderCls);
        if (status != ANI_OK) {
            TAG_LOGE(AceLogTag::ACE_OVERLAY, "Find LevelOrder failed(%{public}d)", status);
            return;
        }

        ani_method getOrderMethod;
        status = env->Class_FindMethod(levelOrderCls, "getOrder", nullptr, &getOrderMethod);
        if (status != ANI_OK) {
            TAG_LOGE(AceLogTag::ACE_OVERLAY, "Find getOrder failed(%{public}d)", status);
            return;
        }

        ani_double orderValue;
        status = env->Object_CallMethod_Double(aniLevelOrder, getOrderMethod, &orderValue);
        if (status != ANI_OK) {
            TAG_LOGE(AceLogTag::ACE_OVERLAY, "Call getOrder failed(%{public}d)", status);
            return;
        }
        orderNumber = std::make_optional(orderValue);
    }

    FrameNodePeer* peerNode = (FrameNodePeer*)aniNode;
    auto frameNode = FrameNodePeer::GetFrameNodeByPeer(peerNode);
    CHECK_NULL_VOID(frameNode);
    auto context = frameNode->GetContextRefPtr();
    CHECK_NULL_VOID(context);
    auto overlayManager = context->GetOverlayManager();
    CHECK_NULL_VOID(overlayManager);
    overlayManager->AddFrameNodeWithOrder(frameNode, orderNumber);
}

static void RemoveComponentContent(ani_env* env, ani_long aniNode)
{
    TAG_LOGD(AceLogTag::ACE_OVERLAY, "ani RemoveComponentContent enter");
    FrameNodePeer* peerNode = (FrameNodePeer*)aniNode;
    auto frameNode = FrameNodePeer::GetFrameNodeByPeer(peerNode);
    CHECK_NULL_VOID(frameNode);
    auto context = frameNode->GetContextRefPtr();
    CHECK_NULL_VOID(context);
    auto instanceId = context->GetInstanceId();
    auto task = [weak = Referenced::WeakClaim(Referenced::RawPtr(frameNode))](
            const RefPtr<NG::OverlayManager>& overlayManager) {
        auto frameNode = weak.Upgrade();
        CHECK_NULL_VOID(frameNode);
        overlayManager->RemoveFrameNodeOnOverlay(frameNode);
    };
    PostOverlayTask(std::move(task), instanceId, "ani RemoveComponentContent");
}

static void ShowComponentContent(ani_env* env, ani_long aniNode)
{
    TAG_LOGD(AceLogTag::ACE_OVERLAY, "ani ShowComponentContent enter");
    FrameNodePeer* peerNode = (FrameNodePeer*)aniNode;
    auto frameNode = FrameNodePeer::GetFrameNodeByPeer(peerNode);
    CHECK_NULL_VOID(frameNode);
    auto context = frameNode->GetContextRefPtr();
    CHECK_NULL_VOID(context);
    auto instanceId = context->GetInstanceId();
    auto task = [weak = Referenced::WeakClaim(Referenced::RawPtr(frameNode))](
            const RefPtr<NG::OverlayManager>& overlayManager) {
        auto frameNode = weak.Upgrade();
        CHECK_NULL_VOID(frameNode);
        overlayManager->ShowNodeOnOverlay(frameNode);
    };
    PostOverlayTask(std::move(task), instanceId, "ani ShowComponentContent");
}

static void HideComponentContent(ani_env* env, ani_long aniNode)
{
    TAG_LOGD(AceLogTag::ACE_OVERLAY, "ani HideComponentContent enter");
    FrameNodePeer* peerNode = (FrameNodePeer*)aniNode;
    auto frameNode = FrameNodePeer::GetFrameNodeByPeer(peerNode);
    CHECK_NULL_VOID(frameNode);
    auto context = frameNode->GetContextRefPtr();
    CHECK_NULL_VOID(context);
    auto instanceId = context->GetInstanceId();
    auto task = [weak = Referenced::WeakClaim(Referenced::RawPtr(frameNode))](
            const RefPtr<NG::OverlayManager>& overlayManager) {
        auto frameNode = weak.Upgrade();
        CHECK_NULL_VOID(frameNode);
        overlayManager->HideNodeOnOverlay(frameNode);
    };
    PostOverlayTask(std::move(task), instanceId, "ani HideComponentContent");
}

static void ShowAllComponentContents(ani_env* env)
{
    TAG_LOGD(AceLogTag::ACE_OVERLAY, "ani ShowAllComponentContents enter");
    RefPtr<NG::PipelineContext> context = NG::PipelineContext::GetCurrentContextSafelyWithCheck();
    CHECK_NULL_VOID(context);
    auto instanceId = context->GetInstanceId();
    auto task = [](const RefPtr<NG::OverlayManager>& overlayManager) {
        overlayManager->ShowAllNodesOnOverlay();
    };
    PostOverlayTask(std::move(task), instanceId, "ani ShowAllComponentContents");
}

static void HideAllComponentContents(ani_env* env)
{
    TAG_LOGD(AceLogTag::ACE_OVERLAY, "ani HideAllComponentContents enter");
    RefPtr<NG::PipelineContext> context = NG::PipelineContext::GetCurrentContextSafelyWithCheck();
    CHECK_NULL_VOID(context);
    auto instanceId = context->GetInstanceId();
    auto task = [instanceId](const RefPtr<NG::OverlayManager>& overlayManager) {
        overlayManager->HideAllNodesOnOverlay();
    };
    PostOverlayTask(std::move(task), instanceId, "ani HideAllComponentContents");
}

static ani_object OpenOrderOverlay(ani_env* env, ani_long aniNode, ani_object options, ani_object optionsInternal)
{
    TAG_LOGD(AceLogTag::ACE_OVERLAY, "ani OpenOrderOverlay enter");
    auto asyncContext = std::make_shared<OverlayAsyncContext>();
    ani_status status = env->GetVM(&asyncContext->vm);
    if (status != ANI_OK || asyncContext->vm == nullptr) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "[ANI] GetVM fail. status: %{public}d", status);
        return nullptr;
    }
    asyncContext->instanceId = Container::CurrentIdSafely();
    ani_object result = {};
    status = env->Promise_New(&asyncContext->deferred, &result);
    if (status != ANI_OK) {
        TAG_LOGW(AceLogTag::ACE_OVERLAY, "[ANI] Promise_New fail. status: %{public}d", status);
        return nullptr;
    }

    FrameNodePeer* peerNode = (FrameNodePeer*)aniNode;
    auto frameNode = FrameNodePeer::GetFrameNodeByPeer(peerNode);
    CHECK_NULL_RETURN(frameNode, nullptr);
    auto contentNode = AceType::WeakClaim(AceType::RawPtr(frameNode));

    NG::OrderOverlayOptions overlayOptions;
    if (!GetOrderOverlayOptions(env, options, overlayOptions)) {
        TAG_LOGE(AceLogTag::ACE_OVERLAY, "Failed to parse OrderOverlayOptions.");
        return nullptr;
    }
    GetOrderOverlayOptionsInternal(env, optionsInternal, overlayOptions);

    std::function<void(int32_t)> finishCallback = GetOpenOrderOverlayPromise(asyncContext);
    NG::DialogManagerStatic::OpenOrderOverlayStatic(
        contentNode, overlayOptions, INSTANCE_ID_UNDEFINED, std::move(finishCallback));
    return result;
}

ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result)
{
    TAG_LOGD(OHOS::Ace::AceLogTag::ACE_OVERLAY, "ani OverlayManager ANI_Constructor start");
    ani_env *env;
    if (ANI_OK != vm->GetEnv(ANI_VERSION_1, &env)) {
        return ANI_ERROR;
    }

    ani_namespace ns;
    if (ANI_OK != env->FindNamespace("@ohos.overlayManager.overlayManager", &ns)) {
        return ANI_ERROR;
    }
    std::array methods = {
        ani_native_function {"setOverlayManagerOptions", nullptr, reinterpret_cast<void *>(SetOverlayManagerOptions)},
        ani_native_function {"getOverlayManagerOptions", nullptr, reinterpret_cast<void *>(GetOverlayManagerOptions)},
        ani_native_function {"addComponentContent", nullptr, reinterpret_cast<void *>(AddComponentContent)},
        ani_native_function {"addComponentContentWithOrder", nullptr,
            reinterpret_cast<void *>(AddComponentContentWithOrder)},
        ani_native_function {"removeComponentContent", nullptr, reinterpret_cast<void *>(RemoveComponentContent)},
        ani_native_function {"showComponentContent", nullptr, reinterpret_cast<void *>(ShowComponentContent)},
        ani_native_function {"hideComponentContent", nullptr, reinterpret_cast<void *>(HideComponentContent)},
        ani_native_function {"showAllComponentContents", nullptr, reinterpret_cast<void *>(ShowAllComponentContents)},
        ani_native_function {"hideAllComponentContents", nullptr, reinterpret_cast<void *>(HideAllComponentContents)},
        ani_native_function {"openOrderOverlay", nullptr, reinterpret_cast<void *>(OpenOrderOverlay)},
    };
    if (ANI_OK != env->Namespace_BindNativeFunctions(ns, methods.data(), methods.size())) {
        return ANI_ERROR;
    }

    *result = ANI_VERSION_1;
    TAG_LOGD(OHOS::Ace::AceLogTag::ACE_OVERLAY, "ani OverlayManager ANI_Constructor end");
    return ANI_OK;
}