/*
 * Copyright (c) 2026 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 <atomic>
#include <arkui/native_node_napi.h>
#include <arkui/native_interface.h>
#include "common.h"
#include "Utils.h"
#include "include/oh/OHRenderNode.h"
#include "SkCanvas.h"

static ArkUI_NativeNodeAPI_1* GetNativeNodeAPI() {
    static ArkUI_NativeNodeAPI_1* s_api = nullptr;
    if (s_api == nullptr) {
        OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, s_api);
    }
    return s_api;
}

// Mounts an ArkUI_NodeHandle into a NodeContent container
static void mountHandleToNodeContent(napi_env env, napi_value nodeContent, ArkUI_NodeHandle handle) {
    ArkUI_NativeNodeAPI_1 *arkUINativeNodeApi = GetNativeNodeAPI();
    if (arkUINativeNodeApi == nullptr) {
        Error("mountHandleToNodeContent: Failed to get ArkUI_NativeNodeAPI_1");
        return;
    }
    ArkUI_NumberValue value[] = {{.f32 = 1.0f}};
    ArkUI_AttributeItem item = {value, 1};
    arkUINativeNodeApi->setAttribute(handle, NODE_WIDTH_PERCENT, &item);
    arkUINativeNodeApi->setAttribute(handle, NODE_HEIGHT_PERCENT, &item);

    ArkUI_NodeContentHandle contentHandle;
    OH_ArkUI_GetNodeContentFromNapiValue(env, nodeContent, &contentHandle);
    OH_ArkUI_NodeContent_AddNode(contentHandle, handle);
}

class ExternalRenderNode {
public:
    std::shared_ptr<OHRenderNode> ptr;
    ArkUI_NodeHandle nodeHandle = nullptr;
    // Type 1: ArkTS path — wrap ArkTS FrameNode directly via CreateExternalNode.
    explicit ExternalRenderNode(napi_value node) {
        ptr = OHRenderNode::CreateExternalNode(node);
    }

    // Type 2: C-API path — wrap outerFrameNode in a CUSTOM parent node via CreateExternalNodeC.
    ExternalRenderNode(napi_env env, int32_t rootNodeId, int32_t extNodeId, napi_value node)
        : ExternalRenderNode(rootNodeId, extNodeId, [&] {
            ArkUI_NodeHandle handle = nullptr;
            OH_ArkUI_GetNodeHandleFromNapiValue(env, node, &handle);
            return handle;
        }()) {}

    ExternalRenderNode(int32_t rootNodeId, int32_t extNodeId, ArkUI_NodeHandle handle) {
        this->nodeHandle = handle;

        ArkUI_NativeNodeAPI_1 *arkUINativeNodeApi = GetNativeNodeAPI();
        if (arkUINativeNodeApi == nullptr) {
            Error("ExternalRenderNode: Failed to get ArkUI_NativeNodeAPI_1");
            return;
        }
        ArkUI_NodeHandle handleExtra = arkUINativeNodeApi->createNode(ARKUI_NODE_CUSTOM);
        arkUINativeNodeApi->addChild(handleExtra, handle);

        RootRenderNodeManager::Instance().AdoptNode(rootNodeId, handleExtra);
        ptr = OHRenderNode::CreateExternalNodeC(handleExtra);
        std::weak_ptr<OHRenderNode> weakNode = ptr;
        RootRenderNodeManager::Instance().MapAdoptNode(rootNodeId, extNodeId, weakNode);
    }

    ~ExternalRenderNode() {
        // C-API path only (Type 2/3): nodeHandle is the user's outer FrameNode (JS-owned),
        // which we wrapped under a C-side handleExtra in the constructor:
        //
        //   handleExtra (C-API CUSTOM, owned by RootRenderNodeManager.adoptedChildren_)
        //     └── nodeHandle == outerFrameNode (JS-owned)
        //           └── ArkUIView (JS-owned, has its own .dispose())
        //
        // Detach ArkUIView from outerFrameNode here, before either side runs its own
        // teardown. If we leave the C-side parent/child link in place, the GC ordering
        // between (a) ArkUIView.dispose() in ets, (b) JS GC of outerFrameNode, and
        // (c) RootRenderNodeManager.UnregisterNode -> disposeNode(handleExtra) is
        // unspecified, and any disposeNode walking through stale child pointers
        // crashes. Breaking the link on the Main thread (cleaner schedules here) lets
        // each owner clean up its own subgraph in isolation.
        //
        // NOTE: handleExtra itself is not freed here — it is reaped only when its
        // root node is unregistered via RootRenderNodeManager.UnregisterNode (see
        // OHRender's RenderNodeImplC.cpp UnregisterNodeInternal). There is no
        // per-extNode unadopt API today, so handleExtra entries accumulate inside
        // adoptedChildren_/adoptedNodeMap_ for the lifetime of the rootNodeId.
        //
        // Type 1 (ArkTS) keeps nodeHandle == nullptr and skips this branch entirely.
        if (this->nodeHandle != nullptr) {
            ArkUI_NativeNodeAPI_1 *arkUINativeNodeApi = GetNativeNodeAPI();
            if (arkUINativeNodeApi != nullptr) {
                arkUINativeNodeApi->removeAllChildren(this->nodeHandle);
            }
            this->nodeHandle = nullptr;
        }
        ptr = nullptr;
    }
};

static void freeExternalRenderNode(ExternalRenderNode* node) {
    delete node;
}

SKIKO_EXPORT KNativePointer org_jetbrains_skia_ExternalRenderNode__1nMakeForArkTs
    (napi_value node) {
    ExternalRenderNode* renderNode = new ExternalRenderNode(node);
    if (renderNode->ptr == nullptr) {
        delete renderNode;
        return nullptr;
    }
    return reinterpret_cast<KNativePointer>(renderNode);
}

SKIKO_EXPORT KNativePointer org_jetbrains_skia_ExternalRenderNode__1nMakeForCApi
    (napi_env env, int32_t rootNodeId, int32_t extNodeId, napi_value node) {
    ExternalRenderNode* renderNode = new ExternalRenderNode(env, rootNodeId, extNodeId, node);
    if (renderNode->ptr == nullptr) {
        delete renderNode;
        return nullptr;
    }
    return reinterpret_cast<KNativePointer>(renderNode);
}

SKIKO_EXPORT KNativePointer org_jetbrains_skia_ExternalRenderNode__1nMakeFromHandle
    (int32_t rootNodeId, int32_t extNodeId, ArkUI_NodeHandle handle)
{
    ExternalRenderNode* renderNode = new ExternalRenderNode(rootNodeId, extNodeId, handle);
    if (renderNode->ptr == nullptr) {
        delete renderNode;
        return nullptr;
    }
    return reinterpret_cast<KNativePointer>(renderNode);
}

SKIKO_EXPORT void org_jetbrains_skia_ExternalRenderNode__1nMountHandleToNodeContent
    (napi_env env, napi_value nodeContent, ArkUI_NodeHandle handle) {
    mountHandleToNodeContent(env, nodeContent, handle);
}

SKIKO_EXPORT KNativePointer org_jetbrains_skia_ExternalRenderNode__1nGetExternalRenderNodeFinalizer() {
    return reinterpret_cast<KNativePointer>(freeExternalRenderNode);
}

SKIKO_EXPORT void org_jetbrains_skia_Canvas__1nDrawRenderNode
    (SkCanvas* canvas, ExternalRenderNode* node, KFloat left, KFloat top, KFloat right, KFloat bottom) {
    if (canvas == nullptr || node == nullptr) {
        return;
    }
    SkRect rect{left, top, right, bottom};
    canvas->drawRenderNode(node->ptr, rect);
}

SKIKO_EXPORT void org_jetbrains_skia_ExternalRenderNode__1nMapFrameNodePos
    (int32_t rootNodeId, int32_t extNodeId, double touchX, double touchY,
     float* outX, float* outY) {
    SkPoint gPoint = SkPoint::Make(touchX, touchY);
    SkPoint lPoint = SkPoint::Make(0, 0);
    if (auto node = RootRenderNodeManager::Instance().GetAdoptOHRenderNode(rootNodeId, extNodeId)) {
        node->mapPointFromGlobal(gPoint, &lPoint);
    }
    *outX = lPoint.x();
    *outY = lPoint.y();
}

SKIKO_EXPORT void org_jetbrains_skia_ExternalRenderNode__1nMapPointFromGlobal
    (ExternalRenderNode* renderNode, double globalX, double globalY,
    float* outX, float* outY) {
    SkPoint gPoint = SkPoint::Make((float)globalX, (float)globalY);
    SkPoint lPoint = SkPoint::Make(0, 0);
    if (renderNode != nullptr && renderNode->ptr != nullptr) {
        renderNode->ptr->mapPointFromGlobal(gPoint, &lPoint);
    }
    *outX = lPoint.x();
    *outY = lPoint.y();
}
