# CMakeLists.txt — builds `libimage_stitcher.so`, the JNI shim that
# exposes our custom-built OpenCV's `cv::Stitcher` to the Kotlin SDK.
#
# Architecture
# ────────────
# OpenCV's official prebuilt Android `libopencv_java4.so` strips the
# stitching module entirely.  When we rebuild OpenCV with
# BUILD_opencv_stitching=ON the stitching module is COMPILED (yields a
# static archive `libopencv_stitching.a`) but it's NOT included in the
# fat `libopencv_java4.so` either — the fat lib only bundles modules
# that have Java auto-bindings, and the stitching module declares only
# `WRAP python` upstream.
#
# Our shim closes that gap:
#   - libopencv_stitching.a is STATICALLY linked into our shim, so
#     `cv::Stitcher::create()` and related code lives inside
#     `libimage_stitcher.so`.
#   - libopencv_java4.so is DYNAMICALLY linked, providing cv::Mat,
#     cv::imread/imwrite, cv::features2d, cv::calib3d, cv::flann, etc.
#     at runtime — the fat lib already exports all the C++ symbols
#     stitching depends on.
#
# Net result: a ~5-10 MB self-contained JNI shim that uses the
# already-bundled fat .so for core OpenCV.

cmake_minimum_required(VERSION 3.18)
project(image_stitcher CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# Gradle passes OPENCV_ANDROID_SDK as a CMake -D arg.  Points to the
# directory holding `sdk/native/jni/include/`,
# `sdk/native/libs/<ABI>/libopencv_java4.so` and
# `sdk/native/staticlibs/<ABI>/libopencv_stitching.a`.
if(NOT DEFINED OPENCV_ANDROID_SDK)
    message(FATAL_ERROR
        "OPENCV_ANDROID_SDK must be passed in by the Gradle "
        "externalNativeBuild block.  Check the SDK's android/build.gradle.")
endif()

set(OPENCV_INCLUDE_DIR  "${OPENCV_ANDROID_SDK}/sdk/native/jni/include")
set(OPENCV_FAT_SO       "${OPENCV_ANDROID_SDK}/sdk/native/libs/${ANDROID_ABI}/libopencv_java4.so")
set(OPENCV_STITCHING_A  "${OPENCV_ANDROID_SDK}/sdk/native/staticlibs/${ANDROID_ABI}/libopencv_stitching.a")

if(NOT EXISTS "${OPENCV_FAT_SO}")
    message(FATAL_ERROR
        "OpenCV fat shared lib not found at:\n  ${OPENCV_FAT_SO}\n"
        "Run the OpenCV custom build (see scripts/build-opencv-android.sh) "
        "and copy artifacts into vendor/OpenCV-android-sdk/.")
endif()
if(NOT EXISTS "${OPENCV_STITCHING_A}")
    message(FATAL_ERROR
        "OpenCV stitching static archive not found at:\n  ${OPENCV_STITCHING_A}\n"
        "Was the OpenCV build compiled with BUILD_opencv_stitching=ON?")
endif()

# Import the prebuilt OpenCV fat .so (dynamic link target).
add_library(opencv_java SHARED IMPORTED)
set_target_properties(opencv_java PROPERTIES
    IMPORTED_LOCATION "${OPENCV_FAT_SO}")

# Import the static archive for the stitching module (static link target).
add_library(opencv_stitching STATIC IMPORTED)
set_target_properties(opencv_stitching PROPERTIES
    IMPORTED_LOCATION "${OPENCV_STITCHING_A}")

# ── Shared C++ port (KeyframeGate) ────────────────────────────────
#
# `cpp/` at the SDK root holds C++ that's compiled into BOTH the iOS
# pod (via the podspec source globs + HEADER_SEARCH_PATHS) and the
# Android JNI shim (here).  Same source, same algorithm, same
# panorama output — closes the iOS↔Android parity gap that the V16
# Phase 1 frame-counter MVP placeholder left open.
set(SHARED_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../cpp")
if(NOT EXISTS "${SHARED_CPP_DIR}/keyframe_gate.hpp")
    message(FATAL_ERROR
        "Shared C++ port missing at:\n  ${SHARED_CPP_DIR}\n"
        "Expected react-native-image-stitcher/cpp/ — was the package layout broken?")
endif()

# ── React Native prefab packages for JSI ──────────────────────────
#
# v0.8.0 Phase 3 — activating the previously-deferred JSI integration.
# The shared C++ host object (cpp/stitcher_frame_jsi.cpp) depends on
# `facebook::jsi`.  ReactAndroid ships JSI as a prefab starting
# RN 0.71+; the lib targets RN 0.84 so this is always available.
#
# `buildFeatures { prefab true }` in android/build.gradle enables
# consumption + `ANDROID_STL=c++_shared` aligns the STL with what
# the prefabs require.  The Phase-2 STL probe (`llvm-nm
# libopencv_stitching.a | grep '__ndk1'`) confirmed OpenCV's
# stitching archive was already built with c++_shared (768
# __ndk1 symbols + 0 __cxx11 / NSt3) — switching the lib's flag
# from c++_static to c++_shared just aligns + matches.  The
# previous c++_static was working only because the JNI shim's
# `.so` boundary used POD/C types; the new c++_shared is properly
# matched throughout.
find_package(ReactAndroid REQUIRED CONFIG)
find_package(fbjni REQUIRED CONFIG)

# v0.8.0 Phase 4b.ii — react-native-worklets-core prefab.  The
# Gradle module name is `react-native-worklets-core`; inside it
# publishes a library named `rnworklets` (matches vc's consumption
# pattern in node_modules/react-native-vision-camera/android/CMakeLists.txt).
# We consume both the headers (for `WKTJsiWorklet.h` etc.) AND
# the .so (for `RNWorklet::WorkletInvoker` + `JsiWrapper::unwrap`
# symbols, which are defined in worklets-core's WKTJsiWrapper.cpp).
find_package(react-native-worklets-core REQUIRED CONFIG)

# ── Our shim ───────────────────────────────────────────────────────
add_library(image_stitcher SHARED
    image_stitcher_jni.cpp
    keyframe_gate_jni.cpp
    "${SHARED_CPP_DIR}/keyframe_gate.cpp"
    # 2026-05-15 — shared stitcher.cpp, used by BOTH platforms.
    # Owns the cv::Stitcher orchestration + C+D progressive-confidence
    # retry + dimension/memory instrumentation.  Used to live in this
    # file (image_stitcher_jni.cpp).  See cpp/stitcher.hpp for design
    # rationale.
    "${SHARED_CPP_DIR}/stitcher.cpp"
    # v0.8.0 Phase 3 — shared JSI host object for `StitcherFrame`.
    # Compiles to identical dispatch on both platforms; iOS consumes
    # it via the .mm shim at
    # `ios/Sources/RNImageStitcher/StitcherFrameHostObject.mm`.
    # See cpp/stitcher_frame_jsi.hpp for the class API.
    "${SHARED_CPP_DIR}/stitcher_frame_jsi.cpp"
    # v0.8.0 Phase 4b.ii — shared C++ registry of host-supplied
    # worklets + the `globalThis.__stitcherProxy` host object that
    # JS calls into.  iOS picked these up via the podspec glob in
    # Phase 4b.i; Android adds them here.
    "${SHARED_CPP_DIR}/stitcher_worklet_registry.cpp"
    "${SHARED_CPP_DIR}/stitcher_proxy_jsi.cpp"
    # v0.8.0 Phase 4b.iii — shared per-frame fan-out helper.  Posts
    # a `StitcherFrameData` onto worklets-core's default context's
    # worklet thread; iterates the host worklet registry; invalidates
    # the JSI host object after dispatch completes.
    "${SHARED_CPP_DIR}/stitcher_worklet_dispatch.cpp"
    # v0.8.0 Phase 4b.ii — Android JNI bindings for the JSI install
    # (`StitcherJsiInstallerModule.nativeInstall`).  Reaches into the
    # main JS runtime via the `long` JSI handle Kotlin pulls from
    # `ReactApplicationContext.getJavaScriptContextHolder()`.  See
    # worklets-core's `WorkletsModule.java` for the canonical pattern.
    stitcher_jsi_install_jni.cpp)

target_include_directories(image_stitcher PRIVATE
    "${OPENCV_INCLUDE_DIR}"
    # cpp/ holds keyframe_gate.hpp + ar_frame_pose.h that the JNI
    # bindings include without relative-path spelunking.
    "${SHARED_CPP_DIR}")

# Link order matters:
#   1. opencv_stitching (static .a) — pulls in cv::Stitcher code
#   2. opencv_java (dynamic .so)    — resolves cv::Mat, cv::imread,
#                                     cv::features2d, cv::calib3d,
#                                     cv::flann, cv::imgproc, etc. at
#                                     runtime via the host app's
#                                     already-loaded libopencv_java4.so
#   3. log                          — for __android_log_print
#
# `-Wl,--whole-archive` around the static lib forces the linker to
# pull in every .o file even if it can't statically prove they're
# needed.  Without it, cv::Stitcher::create() may be eliminated as
# dead code (it's called via the JNI dispatch which the linker can't
# trace at static-link time).
target_link_libraries(image_stitcher
    -Wl,--whole-archive
    opencv_stitching
    -Wl,--no-whole-archive
    opencv_java
    log
    # v0.8.0 Phase 3 — JSI for the shared C++ host object
    # (cpp/stitcher_frame_jsi.cpp's `facebook::jsi::HostObject`
    # subclass).  fbjni for the Phase 3c JNI bridge between Kotlin
    # worklet runtime + C++ host object construction.
    ReactAndroid::jsi
    fbjni::fbjni
    # v0.8.0 Phase 4b.ii — worklets-core's `RNWorklet::WorkletInvoker`
    # is constructed in the C++ registry's `install` method and
    # invoked from the Android per-frame dispatch path.
    react-native-worklets-core::rnworklets)

target_compile_options(image_stitcher PRIVATE
    -fvisibility=hidden
    -Wno-deprecated-declarations)
