package org.jetbrains.skiko

import kotlinx.cinterop.COpaquePointer
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.FloatVar
import kotlinx.cinterop.alloc
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.nativeNullPtr
import kotlinx.cinterop.ptr
import kotlinx.cinterop.value
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.jetbrains.skia.ExternalSymbolName
import org.jetbrains.skia.impl.Managed
import org.jetbrains.skia.impl._nInvokeFinalizer
import platform.ArkUI.ArkUI_NativeModule.ArkUI_NodeHandle
import platform.ArkTS.ArkTS_Napi_NativeModule.napi_env
import platform.ArkTS.ArkTS_Napi_NativeModule.napi_value
import kotlin.concurrent.AtomicNativePtr
import kotlin.native.internal.NativePtr
import kotlin.native.ref.createCleaner

abstract class RenderNode protected constructor(val ptr: NativePtr, private val finalizer: NativePtr) :
    Managed(ptr, finalizer, managed = false) {

    private val _ptrAtomic = AtomicNativePtr(ptr)

    @OptIn(kotlin.experimental.ExperimentalNativeApi::class)
    private val mainCleaner = createCleaner(_ptrAtomic to finalizer) { (atomicPtr, fin) ->
        val p = atomicPtr.value
        if (p != nativeNullPtr && atomicPtr.compareAndSet(p, nativeNullPtr)) {
            CoroutineScope(Dispatchers.Main).launch {
                _nInvokeFinalizer(fin, p)
            }
        }
    }

    override fun close() {
        val p = _ptrAtomic.value
        if (p == nativeNullPtr) return
        if (_ptrAtomic.compareAndSet(p, nativeNullPtr)) {
            _ptr = NativePtr.NULL
            CoroutineScope(Dispatchers.Main).launch {
                _nInvokeFinalizer(finalizer, p)
            }
        }
    }

    override val isClosed: Boolean
        get() = _ptrAtomic.value == nativeNullPtr

    val id: Int by lazy { _nRenderNodeGetId(ptr) }

    fun notifyRedraw() {
        _nRenderNodeNotifyRedraw(ptr)
    }
}


class JsRenderNode internal constructor(ptr: NativePtr) : RenderNode(ptr, FINALIZER) {
    companion object {
        val FINALIZER =_nRenderNodeGetJsNodeFinalizer()

        var isContextReady: Boolean = false
            private set

        fun initJsRenderNodeContext(nodeConstructor: napi_value, statusModifyConstructor: napi_value,
                                             drawCallback: COpaquePointer) {
            _nRenderNodeSetNodeConstructor(ArkTsContext.NapiEnv, nodeConstructor)
            _nRenderNodeNodeStatusModifyBuildInstance(ArkTsContext.NapiEnv, statusModifyConstructor)
            _nRenderNodeNodeSetDrawCallback(drawCallback)
            isContextReady = true
        }

        fun setPixelRatio(ratio: Double) {
            _nRenderNodeNodeSetPixelRatio(ratio)
        }

        fun jsNodeDraw(canvas: napi_value, jsNode: napi_value) {
            _nRenderNodeNodeJsNodeDraw(ArkTsContext.NapiEnv, jsNode, canvas)
        }
    }

    constructor() : this(_nRenderNodeNodeCreateJsRenderNode(ArkTsContext.NapiEnv))

    fun getJsNode(): napi_value {
       return _nRenderNodeNodeGetJsNode(ptr)
    }

    fun draw(canvas: napi_value) {
        _nRenderNodeNodeDraw(ptr, canvas)
    }

}

class CRenderNode internal constructor(ptr: NativePtr) : RenderNode(ptr, FINALIZER) {
    companion object {
        val FINALIZER = _nRenderNodeGetCNodeFinalizer()

        fun setCAPIFixed(fixed: Boolean) {
            _nRenderNodeNodeSetCAPIFixed(fixed)
        }
    }

    private var width: Int = 0
    private var height: Int = 0

    constructor(content: napi_value) : this(_nRenderNodeCreateCRenderNode(ArkTsContext.NapiEnv, content)) {}

    val nodeHandle: ArkUI_NodeHandle
        get() = _nRenderNodeGetNodeHandle(ptr)

    fun reSize(width: Int, height: Int) {
        if (this.width != width || this.height != height) {
            this.width = width
            this.height = height
            _nRenderNodeReSize(ptr, width, height)
        }
    }
}

class ExternalRenderNode internal constructor(val nativePtr: NativePtr) : Managed(nativePtr, FIN, managed = false) {

    private val _ptrAtomic = AtomicNativePtr(nativePtr)

    @OptIn(kotlin.experimental.ExperimentalNativeApi::class)
    private val mainCleaner = createCleaner(_ptrAtomic to FIN) { (atomicPtr, fin) ->
        val p = atomicPtr.value
        if (p != nativeNullPtr && atomicPtr.compareAndSet(p, nativeNullPtr)) {
            CoroutineScope(Dispatchers.Main).launch {
                _nInvokeFinalizer(fin, p)
            }
        }
    }

    override fun close() {
        val p = _ptrAtomic.value
        if (p == nativeNullPtr) return
        if (_ptrAtomic.compareAndSet(p, nativeNullPtr)) {
            _ptr = NativePtr.NULL
            CoroutineScope(Dispatchers.Main).launch {
                _nInvokeFinalizer(FIN, p)
            }
        }
    }

    override val isClosed: Boolean
        get() = _ptrAtomic.value == nativeNullPtr

    companion object {
        private val FIN = _nExternalRenderNodeGetFinalizer()

        // ArkTS path: wraps an ArkTS RenderNode (napi_value) directly as an ExternalRenderNode.
        fun makeForArkTs(node: napi_value): ExternalRenderNode? {
            val ptr = _nExternalRenderNodeMakeForArkTs(node)
            if (ptr == NativePtr.NULL) return null
            return ExternalRenderNode(ptr)
        }

        // CAPI path: converts an ArkTS FrameNode (napi_value) to ArkUI_NodeHandle inside C++, then wraps as an ExternalRenderNode.
        fun makeForCApi(rootNodeId: Int, extNodeId: Int, node: napi_value): ExternalRenderNode? {
            val ptr = _nExternalRenderNodeMakeForCApi(ArkTsContext.NapiEnv, rootNodeId, extNodeId, node)
            if (ptr == NativePtr.NULL) return null
            return ExternalRenderNode(ptr)
        }

        fun makeFromHandle(rootNodeId: Int, extNodeId: Int, handle: ArkUI_NodeHandle): ExternalRenderNode? {
            val ptr = _nExternalRenderNodeMakeFromHandle(rootNodeId, extNodeId, handle)
            if (ptr == NativePtr.NULL) return null
            return ExternalRenderNode(ptr)
        }

        // Helper: mount a CAPI ArkUI_NodeHandle into a NodeContent container (100% width/height).
        // Must be called before makeForCApi when the interop view is a CAPI node.
        fun mountHandleToNodeContent(nodeContent: napi_value, handle: ArkUI_NodeHandle) {
            _nExternalRenderNodeMountHandleToNodeContent(ArkTsContext.NapiEnv, nodeContent, handle)
        }

        fun mapFrameNodePos(rootNodeId: Int, extNodeId: Int, touchX: Double, touchY: Double): Pair<Float, Float> {
            return memScoped {
                val outX = alloc<FloatVar>()
                val outY = alloc<FloatVar>()
                _nExternalRenderNodeMapFrameNodePos(rootNodeId, extNodeId, touchX, touchY, outX.ptr, outY.ptr)
                Pair(outX.value, outY.value)
            }
        }
    }

    fun mapPointFromGlobal(x: Double, y: Double): Pair<Float, Float> {
        return memScoped {
            val outX = alloc<FloatVar>()
            val outY = alloc<FloatVar>()
            _nExternalRenderNodeMapPointFromGlobal(nativePtr, x, y, outX.ptr, outY.ptr)
            Pair(outX.value, outY.value)
        }
    }
}

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nRenderNodeGetNodeId")
private external fun _nRenderNodeGetId(ptr: NativePtr): Int

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nRenderNodeNotifyRedraw")
private external fun _nRenderNodeNotifyRedraw(ptr: NativePtr)

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nGetJsNodeFinalizer")
private external fun _nRenderNodeGetJsNodeFinalizer(): NativePtr

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nSetNodeConstructor")
private external fun _nRenderNodeSetNodeConstructor(env: napi_env, function: napi_value)

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nNodeStatusModifyBuildInstance")
private external fun _nRenderNodeNodeStatusModifyBuildInstance(env: napi_env, function: napi_value)

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nSetDrawCallback")
private external fun _nRenderNodeNodeSetDrawCallback(callback: COpaquePointer)

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nCreateJsRenderNode")
private external fun _nRenderNodeNodeCreateJsRenderNode(env: napi_env): NativePtr

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nSetPixelRatio")
private external fun _nRenderNodeNodeSetPixelRatio(ratio: Double)

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nRenderNodeGetJsNode")
private external fun _nRenderNodeNodeGetJsNode(ptr: NativePtr): napi_value

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nRenderNodeDraw")
private external fun _nRenderNodeNodeDraw(ptr: NativePtr, canvas: napi_value)

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nRenderNodeJsNodeDraw")
private external fun _nRenderNodeNodeJsNodeDraw(env: napi_env, node: napi_value, canvas: napi_value)

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nCreateCRenderNode")
private external fun _nRenderNodeCreateCRenderNode(napiEnv: napi_env, content: napi_value): NativePtr

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nRenderNodeReSize")
private external fun _nRenderNodeReSize(ptr: NativePtr, width: Int, height: Int)

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nRenderNodeGetNodeHandle")
private external fun _nRenderNodeGetNodeHandle(ptr: NativePtr): ArkUI_NodeHandle

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nGetCNodeFinalizer")
private external fun _nRenderNodeGetCNodeFinalizer(): NativePtr

@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nSetCAPIFixed")
private external fun _nRenderNodeNodeSetCAPIFixed(fixed: Boolean)

@ExternalSymbolName("org_jetbrains_skia_ExternalRenderNode__1nGetExternalRenderNodeFinalizer")
private external fun _nExternalRenderNodeGetFinalizer(): NativePtr

@ExternalSymbolName("org_jetbrains_skia_ExternalRenderNode__1nMakeForArkTs")
private external fun _nExternalRenderNodeMakeForArkTs(ptr: napi_value): NativePtr

@ExternalSymbolName("org_jetbrains_skia_ExternalRenderNode__1nMakeForCApi")
private external fun _nExternalRenderNodeMakeForCApi(env: napi_env, rootNodeId: Int, extNodeId: Int, ptr: napi_value): NativePtr

@ExternalSymbolName("org_jetbrains_skia_ExternalRenderNode__1nMakeFromHandle")
private external fun _nExternalRenderNodeMakeFromHandle(rootNodeId: Int, extNodeId: Int, handle: ArkUI_NodeHandle): NativePtr

@ExternalSymbolName("org_jetbrains_skia_ExternalRenderNode__1nMountHandleToNodeContent")
private external fun _nExternalRenderNodeMountHandleToNodeContent(env: napi_env, nodeContent: napi_value, handle: ArkUI_NodeHandle)

@ExternalSymbolName("org_jetbrains_skia_ExternalRenderNode__1nMapFrameNodePos")
private external fun _nExternalRenderNodeMapFrameNodePos(
    rootNodeId: Int, extNodeId: Int, touchX: Double, touchY: Double,
    outX: CPointer<FloatVar>, outY: CPointer<FloatVar>
)

@ExternalSymbolName("org_jetbrains_skia_ExternalRenderNode__1nMapPointFromGlobal")
private external fun _nExternalRenderNodeMapPointFromGlobal(
    ptr: NativePtr, x: Double, y: Double,
    outX: CPointer<FloatVar>, outY: CPointer<FloatVar>
)
