# =============================================================================
# ONNX Backend - generic ONNX Runtime services
# =============================================================================

# Per-engine build toggle owned locally. Emscripten has no in-tree
# ONNX Runtime WASM binary (FetchONNXRuntime expects third_party/onnxruntime-wasm);
# WASM ONNX ships via onnxruntime-web from the frontend. Force off there.
#
# =============================================================================
# TODO: WASM ONNX unblock — exact remaining work
# =============================================================================
#
# To flip this engine on for EMSCRIPTEN the following vendored artifact is
# required:
#
#   core/third_party/onnxruntime-wasm/
#     ├── lib/libonnxruntime.a          (static archive, SIMD build recommended)
#     └── include/                      (C + C++ headers)
#         ├── onnxruntime_c_api.h       (required)
#         ├── onnxruntime_cxx_api.h     (required — used by rac_runtime_onnxrt)
#         ├── onnxruntime_cxx_inline.h
#         ├── onnxruntime_float16.h
#         ├── onnxruntime_session_options_config_keys.h
#         └── onnxruntime_run_options_config_keys.h
#
# Build recipe (upstream Microsoft does NOT publish prebuilt WASM ORT):
#   git clone --depth 1 --branch v1.18.0 https://github.com/microsoft/onnxruntime
#   cd onnxruntime
#   ./build.sh --config Release --build_wasm_static_lib --enable_wasm_simd \
#              --skip_tests --minimal_build --disable_wasm_exception_catching \
#              --disable_rtti --target onnxruntime_webassembly
#
# Emscripten link flags needed (add to engines/onnx/CMakeLists.txt under
# RAC_PLATFORM_WASM branch):
#   -sALLOW_MEMORY_GROWTH=1  (already set at root)
#   -sMAXIMUM_MEMORY=4294967296  (already set)
#
# No Emscripten-specific symbol mappings required — ORT C API is pure C.
# Register symbol: _rac_backend_onnx_register (published via
# rac_plugin_entry_onnx.cpp → RAC_STATIC_PLUGIN_REGISTER(onnx)).
#
# Gate recipe (replace the current force-OFF):
#   set(RAC_ONNX_WASM_RUNTIME_DIR
#       "${RAC_ROOT_DIR}/core/third_party/onnxruntime-wasm")
#   if(EMSCRIPTEN AND NOT EXISTS "${RAC_ONNX_WASM_RUNTIME_DIR}/lib/libonnxruntime.a")
#       set(RAC_BACKEND_ONNX OFF CACHE BOOL "" FORCE)
#   endif()
#
# Also FetchONNXRuntime.cmake already has a scaffold for the EMSCRIPTEN
# branch (INTERFACE target + optional headers); it needs to be extended to
# attach the static lib as IMPORTED when the archive is present.
# =============================================================================
option(RAC_BACKEND_ONNX "Build ONNX backend" ON)
# EXISTS-gated disable under Emscripten: only force OFF when the vendored
# ONNX Runtime WASM static archive is missing. When present, build normally.
# Drop libonnxruntime.a under
# core/third_party/onnxruntime-wasm/lib/ to enable.
if(EMSCRIPTEN)
    if(NOT DEFINED RAC_ONNX_WASM_RUNTIME_DIR OR RAC_ONNX_WASM_RUNTIME_DIR STREQUAL "")
        set(RAC_ONNX_WASM_RUNTIME_DIR
            "${RAC_ROOT_DIR}/core/third_party/onnxruntime-wasm"
            CACHE PATH "Vendored ONNX Runtime WASM root (CPU or WebGPU)" FORCE)
    endif()
    if(NOT EXISTS "${RAC_ONNX_WASM_RUNTIME_DIR}/lib/libonnxruntime.a")
        message(STATUS "RAC_BACKEND_ONNX: no WASM ORT archive at "
                       "${RAC_ONNX_WASM_RUNTIME_DIR}/lib/libonnxruntime.a; "
                       "disabling ONNX backend")
        set(RAC_BACKEND_ONNX OFF CACHE BOOL "" FORCE)
    else()
        message(STATUS "RAC_BACKEND_ONNX: found vendored WASM ORT archive at "
                       "${RAC_ONNX_WASM_RUNTIME_DIR}; "
                       "building ONNX backend for Emscripten")
    endif()
endif()
if(NOT RAC_BACKEND_ONNX)
    return()
endif()

message(STATUS "Configuring ONNX backend...")

# =============================================================================
# Dependencies
# =============================================================================

include(FetchContent)
include(LoadVersions)

FetchContent_Declare(
    nlohmann_json
    GIT_REPOSITORY https://github.com/nlohmann/json.git
    GIT_TAG        v${RAC_NLOHMANN_JSON_VERSION}
    GIT_SHALLOW    TRUE
)
set(JSON_BuildTests OFF CACHE BOOL "" FORCE)
set(JSON_Install OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(nlohmann_json)

# Workaround: nlohmann_json may use target_compile_features which fails with Xcode generator
# Override to use set_target_properties instead
if(TARGET nlohmann_json AND RAC_PLATFORM_IOS)
    set_target_properties(nlohmann_json PROPERTIES
        CXX_STANDARD 11
        CXX_STANDARD_REQUIRED YES
        CXX_EXTENSIONS NO
    )
endif()

# =============================================================================
# ONNX Backend Library (generic ONNX Runtime + RAG embeddings)
# =============================================================================
# Diffusion is Apple CoreML-only; no ONNX diffusion in this backend.
# Target declaration migrated to rac_add_engine_plugin() macro
# (cmake/plugins.cmake).

include(${RAC_ROOT_DIR}/cmake/plugins.cmake)

# Resolve the runanywhere-commons root for include paths.
get_filename_component(RAC_COMMONS_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../core" ABSOLUTE)

set(ONNX_BACKEND_SOURCES
    rac_backend_onnx_register.cpp
    rac_plugin_entry_onnx.cpp
    onnx_segmentation_provider.cpp
    rac_onnx_segmentation_register.cpp
    onnx_diarization_provider.cpp
    rac_onnx_diarization_register.cpp
)

# ONNX embedding provider: engine-owned primitive. The
# ONNXEmbeddingProvider class + its embeddings_service vtable adapter
# live in this directory so the engine fully owns its primitives
# (no cross-module surgery into commons/src/features/rag/). The files
# are still gated by RAC_BACKEND_RAG because the RAG pipeline is the
# sole consumer of the embeddings primitive today; when RAG is OFF the
# plugin entry NULLs its embedding_ops vtable slot to match.
set(ONNX_EXTRA_INCLUDES "")
set(ONNX_RAG_PRESENT OFF)
if(RAC_BACKEND_RAG AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/onnx_embedding_provider.cpp")
    list(APPEND ONNX_BACKEND_SOURCES
        onnx_embedding_provider.cpp
        rac_onnx_embeddings_register.cpp
    )
    set(ONNX_RAG_PRESENT ON)
    message(STATUS "  ONNX embedding provider: Compiled into rac_backend_onnx (engine-owned)")
endif()

# Compose link libs + compile defs. ORT C API ownership lives in
# runtimes/onnxrt; this engine uses only its thin session wrapper.
set(ONNX_LINK_LIBS rac_runtime_onnxrt nlohmann_json::nlohmann_json)
# FEATURE flags only — shared by rac_backend_onnx and the thin carrier, which
# compile rac_plugin_entry_onnx.cpp from the same source and must agree on
# macro state. RAC_ONNX_BUILDING is deliberately NOT here: it flips
# RAC_ONNX_API to dllexport and belongs to the backend target alone (the
# carrier needs the dllimport side to reach g_onnx_*_ops across the DLL
# boundary). Same split as RAC_LLAMACPP_BUILDING in engines/llamacpp.
set(ONNX_COMPILE_DEFS RAC_HAS_ONNX)
# Propagate RAG state so rac_plugin_entry_onnx.cpp can NULL the embeddings
# slot when onnx_embedding_provider.cpp is not compiled in (otherwise the
# extern symbol g_onnx_embeddings_ops is unresolved at link time).
if(ONNX_RAG_PRESENT)
    list(APPEND ONNX_COMPILE_DEFS RAC_BACKEND_RAG=1)
endif()
if(NOT TARGET rac_runtime_onnxrt)
    message(FATAL_ERROR "RAC_BACKEND_ONNX requires rac_runtime_onnxrt. Enable RAC_RUNTIME_ONNXRT or disable RAC_BACKEND_ONNX.")
endif()

# TARGET_NAME=rac_backend_onnx preserves the historical CMake
# target name so the 52 existing references across tests + sample apps +
# RN Android linker config + WASM exports keep working without renaming.
# CXX_STANDARD=20 because the ONNX backend uses std::span / concepts.
# SHARED_ONLY → always a standalone library target (never folded into
# rac_commons). Required because:
#   * the platform-specific `target_link_libraries(rac_backend_onnx ...)`
#     blocks below assume the target exists, and
#   * the Swift xcframework layout distributes the ONNX backend as a
#     SEPARATE xcframework (RABackendONNX) from RACommons.
# Default linkage is STATIC unless RAC_BUILD_SHARED=ON, which matches the
# previously hand-rolled CMakeLists exactly and produces the .a archive
# the xcframework packager consumes on iOS.
rac_add_engine_plugin(onnx
    TARGET_NAME         rac_backend_onnx
    CXX_STANDARD        20
    SHARED_ONLY
    SOURCES             ${ONNX_BACKEND_SOURCES}
    LINK_LIBRARIES      ${ONNX_LINK_LIBS}
    INCLUDE_DIRECTORIES ${RAC_COMMONS_ROOT_DIR}/include
                        ${RAC_COMMONS_ROOT_DIR}/include/rac/backends
                        ${RAC_COMMONS_ROOT_DIR}/src
                        ${ONNX_EXTRA_INCLUDES}
    COMPILE_DEFINITIONS ${ONNX_COMPILE_DEFS} RAC_ONNX_BUILDING
    AVAILABILITY        PUBLIC
    PACKAGE_OWNER       runanywhere
    PACKAGE_NAME        rac_backend_onnx
)

# =============================================================================
# Self-registration carrier
# =============================================================================
# Matches the llamacpp / sherpa pattern: thin shim that wraps
# RAC_STATIC_PLUGIN_REGISTER(onnx). On RAC_STATIC_PLUGINS=ON hosts
# (iOS / WASM) the ctor lands inside rac_commons and fires before main().
# On dynamic hosts the shim becomes a SHARED library whose only export is
# the entry symbol; the actual op functions live in rac_backend_onnx.
if(RAC_STATIC_PLUGINS)
    target_sources(rac_commons PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/rac_static_register_onnx.cpp
    )
    target_include_directories(rac_commons PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}
        ${RAC_COMMONS_ROOT_DIR}/include
    )
else()
    add_library(runanywhere_onnx SHARED
        ${CMAKE_CURRENT_SOURCE_DIR}/rac_static_register_onnx.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/rac_plugin_entry_onnx.cpp
    )
    set_target_properties(runanywhere_onnx PROPERTIES
        OUTPUT_NAME           runanywhere_onnx
        C_VISIBILITY_PRESET   hidden
        CXX_VISIBILITY_PRESET hidden
    )
    target_compile_definitions(runanywhere_onnx PRIVATE RAC_BUILDING_PLUGIN=1)
    # rac_plugin_entry_onnx.cpp is compiled TWICE — once into rac_backend_onnx
    # (which gets ${ONNX_COMPILE_DEFS} via rac_add_engine_plugin, applied
    # PRIVATE — see cmake/plugins.cmake) and once into this carrier. The
    # carrier is the copy that ships as librunanywhere_onnx, because
    # rac_registry_load_plugin resolves rac_plugin_entry_onnx out of THIS
    # image.
    #
    # Without RAC_BACKEND_RAG the carrier's copy NULLs embedding_ops and
    # drops RAC_PRIMITIVE_EMBED from the manifest, even when
    # rac_backend_onnx right next to it was built with the embeddings
    # provider. Same invariant as sherpa: one TU compiled into two targets
    # must see identical macro state in both.
    target_compile_definitions(runanywhere_onnx PRIVATE ${ONNX_COMPILE_DEFS})
    target_link_libraries(runanywhere_onnx PUBLIC
        rac_backend_onnx
        rac_commons
    )
    if(APPLE)
        set_property(TARGET runanywhere_onnx APPEND PROPERTY BUILD_RPATH "@loader_path")
        set_target_properties(runanywhere_onnx PROPERTIES INSTALL_RPATH "@loader_path")
    elseif(UNIX)
        set_property(TARGET runanywhere_onnx APPEND PROPERTY BUILD_RPATH "$ORIGIN")
        set_target_properties(runanywhere_onnx PROPERTIES INSTALL_RPATH "$ORIGIN")
    endif()
    rac_apply_android_page_alignment(runanywhere_onnx)
    install(TARGETS runanywhere_onnx LIBRARY DESTINATION lib)
endif()

# =============================================================================
# Platform-specific configuration
# =============================================================================

if(RAC_PLATFORM_WASM)
    # WASM: CPU-only, no platform acceleration
    target_compile_definitions(rac_backend_onnx PRIVATE RAC_PLATFORM_WASM=1)
    message(STATUS "ONNX Backend: CPU-only EP for WASM")

elseif(RAC_PLATFORM_IOS)
    target_link_libraries(rac_backend_onnx PUBLIC
        "-framework Foundation"
        "-framework CoreML"
        "-framework Accelerate"
    )
    # CoreML framework remains linked for runtime packaging; ORT API usage is
    # centralized in runtimes/onnxrt.
    message(STATUS "ONNX Backend: CoreML framework linked for iOS (EP availability probed by onnxrt runtime)")

elseif(RAC_PLATFORM_ANDROID)
    target_link_libraries(rac_backend_onnx PRIVATE log)
    # Don't use -fvisibility=hidden here - JNI bridge needs these symbols
    target_compile_options(rac_backend_onnx PRIVATE -O3 -ffunction-sections -fdata-sections)
    # 16KB page alignment for Android 15+ (API 35) compliance - required Nov 2025
    rac_apply_android_page_alignment(rac_backend_onnx)
    # The onnxrt runtime's NNAPI selector is currently a capability stub and
    # does not append the NNAPI EP to SessionOptions. Report CPU truthfully.
    message(STATUS "ONNX Backend: CPU EP for Android (NNAPI EP not wired)")

elseif(RAC_PLATFORM_MACOS)
    target_link_libraries(rac_backend_onnx PUBLIC
        "-framework Foundation"
        "-framework CoreML"
        "-framework Accelerate"
    )
    # CoreML framework remains linked for runtime packaging; ORT API usage is
    # centralized in runtimes/onnxrt.
    message(STATUS "ONNX Backend: CoreML framework linked for macOS (EP availability probed by onnxrt runtime)")

elseif(RAC_PLATFORM_LINUX)
    target_link_libraries(rac_backend_onnx PUBLIC pthread dl)

elseif(RAC_PLATFORM_WINDOWS)
    # No extra link libraries needed on Windows
    message(STATUS "ONNX Backend: CPU EP for Windows")
endif()

# =============================================================================
# JNI TARGET (Android)
# =============================================================================

if(RAC_PLATFORM_ANDROID AND RAC_BUILD_SHARED AND RAC_BUILD_JNI)
    if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
        set(_RAC_ANDROID_NDK_HOST_TAG "darwin-x86_64")
    elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
        set(_RAC_ANDROID_NDK_HOST_TAG "linux-x86_64")
    elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
        set(_RAC_ANDROID_NDK_HOST_TAG "windows-x86_64")
    else()
        message(FATAL_ERROR "Unsupported host platform for Android JNI bridge: ${CMAKE_HOST_SYSTEM_NAME}")
    endif()

    set(_RAC_ANDROID_NDK_SYSROOT_INCLUDE
        "${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/${_RAC_ANDROID_NDK_HOST_TAG}/sysroot/usr/include")
    if(NOT EXISTS "${_RAC_ANDROID_NDK_SYSROOT_INCLUDE}/jni.h")
        message(FATAL_ERROR "Could not locate Android NDK JNI headers at ${_RAC_ANDROID_NDK_SYSROOT_INCLUDE}")
    endif()

    message(STATUS "Building ONNX JNI bridge for Android")

    add_library(rac_backend_onnx_jni SHARED jni/rac_backend_onnx_jni.cpp)

    target_include_directories(rac_backend_onnx_jni PRIVATE
        ${_RAC_ANDROID_NDK_SYSROOT_INCLUDE}
        ${_RAC_ANDROID_NDK_SYSROOT_INCLUDE}/android
        ${CMAKE_CURRENT_SOURCE_DIR}
        ${RAC_COMMONS_ROOT_DIR}/include
    )

    target_link_libraries(rac_backend_onnx_jni PRIVATE
        rac_backend_onnx
        log
    )

    # Propagate the linked ONNX Runtime version so nativeGetVersion() no longer
    # returns a stale hard-coded literal. Android ships RAC_ONNX_VERSION_ANDROID from
    # the central VERSIONS file.
    if(DEFINED RAC_ONNX_VERSION_ANDROID AND NOT "${RAC_ONNX_VERSION_ANDROID}" STREQUAL "")
        target_compile_definitions(rac_backend_onnx_jni PRIVATE
            RAC_ONNXRUNTIME_VERSION="${RAC_ONNX_VERSION_ANDROID}")
    endif()

    target_compile_options(rac_backend_onnx_jni PRIVATE -O3 -fvisibility=hidden -ffunction-sections -fdata-sections)
    # 16KB page alignment for Android 15+ (API 35) compliance - required Nov 2025
    rac_apply_android_page_alignment(rac_backend_onnx_jni)
endif()

# =============================================================================
# Summary
# =============================================================================

message(STATUS "ONNX Backend Configuration:")
message(STATUS "  ONNX Runtime: Enabled")
message(STATUS "  Speech services: owned by engines/sherpa")
message(STATUS "  Segmentation: SegFormer FP32 via onnxrt")
message(STATUS "  Diarization:  Streaming Sortformer 4spk v2.1 via onnxrt")
message(STATUS "  Diffusion:    Not in ONNX backend (Apple CoreML only)")
message(STATUS "  Platform: ${RAC_PLATFORM_NAME}")
