/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "bridge/cj_frontend/cppview/native_view.h"

#include <inttypes.h>
#include "bridge/cj_frontend/interfaces/cj_ffi/cj_layout_inspector_ffi.h"
#include "bridge/cj_frontend/runtime/cj_runtime_delegate.h"
#include "core/common/container_scope.h"
#include "core/common/layout_inspector.h"
#include "core/components_ng/base/ui_node.h"
#include "core/components_ng/base/view_partial_update_model.h"
#include "core/components_ng/base/view_stack_model.h"
#include "core/components_ng/pattern/custom/custom_measure_layout_node.h"
#include "core/components_ng/pattern/custom/custom_node.h"
#include "core/components_ng/pattern/recycle_view/recycle_dummy_node.h"

namespace OHOS::Ace::Framework {

std::string GetProcessViewId(int64_t id)
{
    return ViewStackModel::GetInstance()->ProcessViewId(std::to_string(id));
}

static void HandleProfilerNameMapping(NativeView* self)
{
    if (self == nullptr) {
        return;
    }
    if (!OHOS::Ace::LayoutInspector::GetStateProfilerStatus()) {
        return;
    }
    auto node = self->GetViewNode();
    if (!node) {
        LOGW("NativeView:HandleProfilerNameMapping node null skip flush");
        return;
    }
    auto uiNode = AceType::DynamicCast<NG::UINode>(node);
    if (!uiNode) {
        LOGW("NativeView:HandleProfilerNameMapping uiNode null skip flush");
        return;
    }
    int32_t viewId = uiNode->GetId();
    std::string tag;
    auto customNode = AceType::DynamicCast<NG::CustomNode>(uiNode);
    if (customNode && !customNode->GetJSViewName().empty()) {
        tag = customNode->GetJSViewName();
    } else {
        tag = uiNode->GetTag();
        if (tag.empty()) {
            tag = "NativeView";
        }
    }
    const auto& profilerViewName = self->GetCjProfilerViewName();
    if (!profilerViewName.empty()) {
        CjProfilerRegisterViewElementName(viewId, profilerViewName.c_str());
    }
    LOGI("NativeView:HandleProfilerNameMapping profiler keep name mapping viewId=%{public}d tag=%{public}s",
        viewId, tag.c_str());
}

NativeView::NativeView(sptr<RemoteView> cjView) : cjView_(std::move(cjView))
{
    LOGD("Native View constructed: %{public}" PRId64 ".", GetID());
    useNewPipeline_ = Container::IsCurrentUseNewPipeline();
    instanceId_ = Container::CurrentId();
#if !defined(PREVIEW) && defined(OHOS_PLATFORM)
    EnsureCangjieProfilerStatusHookedForHybridAndEmbedded();
#endif
}

NativeView::~NativeView()
{
    LOGD("Native View Destroyed: %{public}" PRId64 ".", GetID());
};

void NativeView::GetDeletedElemtIds(std::vector<int64_t>& vec)
{
    LOGD("NativeView, getting elmtIds of all deleted Elements from ElementRegister:");
}

void NativeView::DeletedElmtIdsHaveBeenPurged(std::vector<int64_t>& vec)
{
    LOGD("NativeView, getting elmtIds of all deleted Elements from ElementRegister:");
}

RefPtr<AceType> NativeView::CreateUI()
{
    wptr<NativeView> weakThis = this;
    NodeInfoPU partialUpdateCallbacks {
        .appearFunc = [weakThis]() -> void {
            auto self = weakThis.promote();
            CHECK_NULL_VOID(self);
            ContainerScope scope(self->instanceId_);
            self->cjView_->OnAppear();
        },
        .didBuildFunc =
            [weakThis]() {
                auto self = weakThis.promote();
                CHECK_NULL_VOID(self);
                ContainerScope scope(self->instanceId_);
                self->cjView_->OnDidBuild();
            },
        .renderFunc = [weakThis](int64_t deadline, bool& isTimeout) -> RefPtr<AceType> {
            auto self = weakThis.promote();
            CHECK_NULL_RETURN(self, nullptr);
            ContainerScope scope(self->instanceId_);
            if (!self->isFirstRender_) {
                LOGW("the view has already called initial render");
                return nullptr;
            }
            self->isFirstRender_ = false;
            return self->InitialUIRender();
        },
        .updateFunc = [weakThis]() -> void {
            auto self = weakThis.promote();
            CHECK_NULL_VOID(self);
            ContainerScope scope(self->instanceId_);
            if (!self->needsUpdate_) {
                LOGW("the view does not need to update");
                return;
            }
            self->needsUpdate_ = false;
            self->cjView_->Rerender();
            for (const UpdateTask& updateTask : self->pendingUpdateTasks_) {
                ViewPartialUpdateModel::GetInstance()->FlushUpdateTask(updateTask);
            }
            self->pendingUpdateTasks_.clear();
            HandleProfilerNameMapping(self);
        },
        .removeFunc = [weakThis]() {
            auto self = weakThis.promote();
            CHECK_NULL_VOID(self);
            self->Destroy();
        },
        .reloadFunc = [weakThis](bool deep) {
            auto self = weakThis.promote();
            CHECK_NULL_VOID(self);
            ContainerScope scope(self->instanceId_);
            self->cjView_->Reload(deep);
        },
        .completeReloadFunc = [weakThis](int64_t deadline, bool& isTimeout) -> RefPtr<AceType> {
            auto view = weakThis.promote();
            CHECK_NULL_RETURN(view, nullptr);
            ContainerScope scope(view->instanceId_);
            return view->InitialUIRender();
        },
        .recycleCustomNodeFunc = [weakThis](const RefPtr<NG::CustomNodeBase>& recycleNode) -> void {
            auto self = weakThis.promote();
            CHECK_NULL_VOID(self);
            ContainerScope scope(self->instanceId_);
            auto name = self->GetRecycleCustomNodeName();
            if (name.empty()) {
                return;
            }
            auto recycleUINode = AceType::DynamicCast<NG::UINode>(recycleNode);
            recycleUINode->SetActive(false);
            self->SetRecycleCustomNode(recycleNode);
            self->cjView_->RecycleSelf(name);
            if (!recycleNode->HasRecycleRenderFunc() && self->recycleCustomNode_) {
                recycleUINode->SetJSViewActive(false, false, true);
                self->cjView_->AboutToRecycle();
            }
            recycleNode->ResetRecycle();
        },
        .recycleFunc =
            [weakThis]() {
                auto self = weakThis.promote();
                CHECK_NULL_VOID(self);
                ContainerScope scope(self->instanceId_);
                self->cjView_->AboutToRecycle();
            },
        .reuseFunc =
            [weakThis](void* params) {
                auto self = weakThis.promote();
                CHECK_NULL_VOID(self);
                ContainerScope scope(self->instanceId_);
                // only support string yet in cj
                std::string* val = static_cast<std::string*>(params);
                CHECK_NULL_VOID(val);
                self->cjView_->AboutToReuse(*val);
            }
    };
    partialUpdateCallbacks.jsViewName = cjProfilerViewName_;
    auto node = ViewPartialUpdateModel::GetInstance()->CreateNode(std::move(partialUpdateCallbacks));

    node_ = node;

    auto uiNode = AceType::DynamicCast<NG::UINode>(node);
    if (uiNode) {
        profilerElementId_ = uiNode->GetId();
        if (!cjProfilerViewName_.empty()) {
            CjProfilerRegisterViewElementName(profilerElementId_, cjProfilerViewName_.c_str());
        }
    }

    {
        auto uiNode = AceType::DynamicCast<NG::UINode>(node);
        if (uiNode) {
            CjProfilerRegisterViewToElementId(GetID(), uiNode->GetId());
        }
    }

    return node;
}

RefPtr<AceType> NativeView::InitialUIRender()
{
    needsUpdate_ = false;
    {
        cjView_->OnAboutToRender();
    }
    if (!cjProfilerViewName_.empty()) {
        CjProfilerRegisterViewElementName(profilerElementId_, cjProfilerViewName_.c_str());
    }
    {
        cjView_->Render();
    }
    {
        cjView_->OnAfterRender();
        if (onRenderDone_) {
            onRenderDone_();
        }
    }
    return ViewStackModel::GetInstance()->Finish();
}

void NativeView::SyncInstanceId()
{
    restoreInstanceId_ = Container::CurrentId();
    ContainerScope::UpdateCurrent(instanceId_);
}

void NativeView::RestoreInstanceId()
{
    ContainerScope::UpdateCurrent(restoreInstanceId_);
}

/**
 * marks the NativeView's composed component as needing update / rerender
 */
void NativeView::MarkNeedUpdate()
{
    needsUpdate_ = ViewPartialUpdateModel::GetInstance()->MarkNeedUpdate(node_);
}

void NativeView::FlushReload()
{
    auto node = node_.Upgrade();
    if (!node) {
        LOGE("fail to update due to custom Node is null");
        return;
    }
    if (AceType::InstanceOf<NG::CustomNode>(node)) {
        auto customNode = AceType::DynamicCast<NG::CustomNode>(node);
        customNode->FlushReload();
    }
}

void NativeView::FinishUpdateFunc(int32_t elmtId)
{
    wptr<NativeView> weakThis = this;
    ViewPartialUpdateModel::GetInstance()->FinishUpdate(node_, elmtId, [weakThis](const UpdateTask& task) {
        auto cjView = weakThis.promote();
        if (cjView) {
            cjView->pendingUpdateTasks_.push_back(task);
        }
    });
}

void NativeView::Destroy()
{
    if (!cjView_) {
        LOGE("NativeView::Destroy error, nativeId: %{public}" PRId64 " cj view not exist.", GetID());
        return;
    }
    auto holder = node_.Upgrade();
    if (holder) {
        auto uiNode = AceType::DynamicCast<NG::UINode>(holder);
        if (uiNode) {
            int32_t elmtId = uiNode->GetId();
            LOGI("NativeView:Destroy unregister profiler elmtId=%{public}d", elmtId);
            CjProfilerUnregisterViewElementName(elmtId);
        }
    }
    CjProfilerUnregisterViewToElementId(GetID());
    LOGD("NativeView::Destroy start, nativeId: %{public}" PRId64 ", cjId: %{public}" PRId64, GetID(), cjView_->GetID());
    {
        cjView_->OnDisappear();
    }
    {
        cjView_->OnAboutToBeDeleted();
    }
    pendingUpdateTasks_.clear();
    LOGD("NativeView::Destroy end");
}

void NativeView::Create(const sptr<NativeView>& view)
{
    ViewStackModel::GetInstance()->Push(view->CreateUI(), true);
}

void NativeView::CreateRecycle(
    const sptr<NativeView>& view, bool isRecycling, const std::string& nodeName, std::function<void()> callback)
{
    auto recycleUpdateFunc = [cjView = view, func = std::move(callback)]() -> void {
        CHECK_NULL_VOID(cjView);
        cjView->SetIsRecycleRerender(true);
        func();
        cjView->SetIsRecycleRerender(false);
    };
    view->SetRecycleCustomNodeName(nodeName);
    RefPtr<AceType> node;

    if (isRecycling) {
        node = view->GetCachedRecycleNode();
        AceType::DynamicCast<NG::CustomNodeBase>(node)->SetRecycleRenderFunc(std::move(recycleUpdateFunc));
    } else {
        node = view->CreateUI();
    }

    auto customNodeBase = AceType::DynamicCast<NG::CustomNodeBase>(node);
    if (customNodeBase) {
        customNodeBase->SetReuseId(nodeName);
    }
    auto* stack = NG::ViewStackProcessor::GetInstance();
    auto dummyNode = NG::RecycleDummyNode::WrapRecycleDummyNode(node, stack->GetRecycleNodeId());
    ViewStackModel::GetInstance()->Push(dummyNode, true);
}

void NativeView::CleanUpAbandonedChild()
{
    LOGD("NativeView::CleanUpAbandonedChild");
}

void NativeView::FireOnShow()
{
    if (!cjView_) {
        LOGE("NativeView::FireOnShow fail, no cj view on %{public}" PRId64 ".", GetID());
        return;
    }
    {
        cjView_->OnShow();
    }
}

void NativeView::FireOnHide()
{
    if (!cjView_) {
        LOGE("NativeView::FireOnHide fail, no cj view on %{public}" PRId64 ".", GetID());
        return;
    }
    {
        cjView_->OnHide();
    }
}

bool NativeView::FireOnBackPress()
{
    if (!cjView_) {
        LOGE("NativeView::FireOnBackPress fail, no cj view on %{public}" PRId64 ".", GetID());
        return false;
    }
    {
        return cjView_->OnBackPress();
    }
}

void NativeView::FireOnTransition()
{
    if (!cjView_) {
        LOGE("NativeView::FireOnTransition fail, no cj view on %{public}" PRId64 ".", GetID());
        return;
    }
    cjView_->OnTransition();
}

void NativeView::ExecuteUpdateWithValueParams(const std::string& jsonData)
{
    if (!cjView_) {
        LOGE("NativeView::ExecuteUpdateWithValueParams fail, no cj view on %{public}" PRId64 ".", GetID());
        return;
    }
    cjView_->UpdateWithJson(jsonData);
}

void RemoteView::Reload(bool deep)
{
    auto forceCompleteRerenderFunc =
        CJRuntimeDelegate::GetInstance()->GetCJFuncs().atCOHOSAceFrameworkRemoteViewForceCompleteRerender;
    if (!forceCompleteRerenderFunc) {
        LOGE("CJFunc: RemoteView::ForceCompleteRerender is empty.");
        return;
    }
    forceCompleteRerenderFunc(GetID(), deep);
}

void RemoteView::VoidCallback(void (*cjFunc)(int64_t), const char* funcName)
{
    if (!cjFunc) {
        LOGE("CJFunc: RemoteView::%{public}s is empty.", funcName);
        return;
    }
    cjFunc(GetID());
}

void RemoteView::Render()
{
    VoidCallback(CJRuntimeDelegate::GetInstance()->GetCJFuncs().atCOHOSAceFrameworkRemoteViewRender, "Render");
}

void RemoteView::Rerender()
{
    VoidCallback(CJRuntimeDelegate::GetInstance()->GetCJFuncs().atCOHOSAceFrameworkRemoteViewRerender, "Rerender");
}

void RemoteView::OnShow()
{
    VoidCallback(CJRuntimeDelegate::GetInstance()->GetCJFuncs().atCOHOSAceFrameworkRemoteViewOnShow, "OnShow");
}

void RemoteView::OnHide()
{
    VoidCallback(CJRuntimeDelegate::GetInstance()->GetCJFuncs().atCOHOSAceFrameworkRemoteViewOnHide, "OnHide");
}

bool RemoteView::OnBackPress()
{
    auto onBackPressFunc = CJRuntimeDelegate::GetInstance()->GetCJFuncs().atCOHOSAceFrameworkRemoteViewOnBackPress;
    if (!onBackPressFunc) {
        LOGE("CJFunc: RemoteView::OnBackPress is empty.");
        return false;
    }
    return onBackPressFunc(GetID());
}

void RemoteView::UpdateWithJson(const std::string& value)
{
    auto updateWithJsonFunc =
        CJRuntimeDelegate::GetInstance()->GetCJFuncs().atCOHOSAceFrameworkRemoteViewUpdateWithJson;
    if (!updateWithJsonFunc) {
        LOGE("CJFunc: RemoteView::UpdateWithJson is empty.");
        return;
    }
    updateWithJsonFunc(GetID(), value.c_str());
}

void RemoteView::OnAppear()
{
    VoidCallback(CJRuntimeDelegate::GetInstance()->GetCJFuncs().atCOHOSAceFrameworkRemoteViewOnAppear, "OnAppear");
}

void RemoteView::OnTransition()
{
    VoidCallback(
        CJRuntimeDelegate::GetInstance()->GetCJFuncs().atCOHOSAceFrameworkRemoteViewOnTransition, "OnTransition");
}

void RemoteView::OnAboutToRender()
{
    VoidCallback(
        CJRuntimeDelegate::GetInstance()->GetCJFuncs().atCOHOSAceFrameworkRemoteViewOnAboutToRender, "OnAboutToRender");
}

void RemoteView::OnAboutToBeDeleted()
{
    VoidCallback(CJRuntimeDelegate::GetInstance()->GetCJFuncs().atCOHOSAceFrameworkRemoteViewOnAboutToBeDeleted,
        "OnAboutToBeDeleted");
}

void RemoteView::OnAfterRender()
{
    VoidCallback(
        CJRuntimeDelegate::GetInstance()->GetCJFuncs().atCOHOSAceFrameworkRemoteViewOnAfterRender, "OnAfterRender");
}

void RemoteView::OnDisappear()
{
    VoidCallback(
        CJRuntimeDelegate::GetInstance()->GetCJFuncs().atCOHOSAceFrameworkRemoteViewOnDisappear, "OnDisappear");
}

void RemoteView::OnDidBuild()
{
    VoidCallback(
        CJRuntimeDelegate::GetInstance()->GetCJFuncsV2().atCOHOSAceFrameworkRemoteViewOnDidBuild, "OnDidBuild");
}

void RemoteView::AboutToReuse(const std::string& value)
{
    auto aboutToReuseFunc = CJRuntimeDelegate::GetInstance()->GetCJFuncsV2().atCOHOSAceFrameworkRemoteViewAboutToReuse;
    if (!aboutToReuseFunc) {
        LOGE("CJFunc: RemoteView::AboutToReuse is empty.");
        return;
    }
    aboutToReuseFunc(GetID(), value.c_str());
}

void RemoteView::AboutToRecycle()
{
    VoidCallback(
        CJRuntimeDelegate::GetInstance()->GetCJFuncsV2().atCOHOSAceFrameworkRemoteViewAboutToRecycle, "AboutToRecycle");
}

void RemoteView::RecycleSelf(const std::string& value)
{
    auto recycleSelfFunc = CJRuntimeDelegate::GetInstance()->GetCJFuncsV2().atCOHOSAceFrameworkRemoteViewRecycleSelf;
    if (!recycleSelfFunc) {
        LOGE("CJFunc: RemoteView::RecycleSelf is empty.");
        return;
    }
    recycleSelfFunc(GetID(), value.c_str());
}
} // namespace OHOS::Ace::Framework