# Compile the nanobind python bindings for roboplan_ros_visualization.

# For nanobind python bindings, ensure stable ABI module is loaded
# if it is available to match the upstream.
find_package(Python 3.8 COMPONENTS Interpreter Development
             OPTIONAL_COMPONENTS Development.SABIModule REQUIRED)
find_package(ament_cmake_python REQUIRED)
find_package(builtin_interfaces REQUIRED)
find_package(rosidl_generator_cpp REQUIRED)

# Rely on tl_expected if found, else look at the roboplan alias.
find_package(tl_expected QUIET)
if (NOT tl_expected_FOUND)
  add_library(tl_expected::tl_expected ALIAS roboplan::tl_expected)
endif()

# Detect the installed nanobind package and import it into CMake
execute_process(
  COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
  OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT)
find_package(nanobind CONFIG REQUIRED)

# Optionally set STABLE_ABI depending on upstream RoboPlan's build.
# Essentially, the ROS build farm will have it off by default, but other
# builds (Conda / Robostack) will require it.
option(ROBOPLAN_ROS_STABLE_ABI
  "Build nanobind extensions with the stable Python ABI, to match roboplan core built via RoboStack/conda-forge."
  OFF)
set(_stable_abi_arg)
if(ROBOPLAN_ROS_STABLE_ABI)
  set(_stable_abi_arg STABLE_ABI)
endif()

# Compile the nanobind python bindings as a subpackage of the python package.
nanobind_add_module(
  _visualization_ext
  ${_stable_abi_arg}
  NB_STATIC
  src/roboplan_ros_visualization_bindings.cpp
)
target_include_directories(_visualization_ext PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
  ${rosidl_generator_cpp_INCLUDE_DIRS}
)
set_property(TARGET _visualization_ext PROPERTY CXX_STANDARD 20)
target_link_libraries(_visualization_ext PRIVATE
  roboplan_ros_visualization
  roboplan_ros_cpp::roboplan_ros_cpp
  roboplan::roboplan
  ${rosidl_generator_cpp_LIBRARIES}
  tl_expected::tl_expected
)

# Manually install rather than rely on ament python packaging to match core roboplan's
# standard for ROS bindings. This will put bindings into `roboplan_ros.<package>`.
ament_get_python_install_dir(_ament_py_dir)
if(_ament_py_dir MATCHES "dist-packages")
  set(PYTHON_DESTINATION
      "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages")
else()
  set(PYTHON_DESTINATION "${_ament_py_dir}")
endif()
install(TARGETS _visualization_ext
  LIBRARY DESTINATION "${PYTHON_DESTINATION}/roboplan_ros/visualization"
)

# Ensure the extension can find workspace dylibs via @rpath on macOS, as it seems that
# SIP will strip DYLD_LIBRARY_PATH from subprocesses, which will break stub generation.
if(APPLE)
  file(RELATIVE_PATH _ext_to_lib
    "${CMAKE_INSTALL_PREFIX}/${PYTHON_DESTINATION}/roboplan_ros/visualization"
    "${CMAKE_INSTALL_PREFIX}/lib"
  )
  set_target_properties(_visualization_ext PROPERTIES
    INSTALL_RPATH "@loader_path/${_ext_to_lib}"
    INSTALL_RPATH_USE_LINK_PATH TRUE
  )
endif()

# Generate Python stub files from the bindings.
# Older nanobind versions (which can come from rosdep on certain distros) do not provide
# nanobind_add_stub, so skip generation rather than failing the configure step.
# See https://github.com/ros/rosdistro/pull/49894 for more details.
# Default OFF on Windows: stub generation imports the extension from the build
# tree, where the DLLs it depends on are not yet on the loader search path.
# The committed stubs are generated on Unix.
if(WIN32)
  set(generate_stubs_default OFF)
else()
  set(generate_stubs_default ON)
endif()
option(GENERATE_PYTHON_STUBS "Generate the Python stubs associated with the bindings" ${generate_stubs_default})
if(GENERATE_PYTHON_STUBS AND NOT COMMAND nanobind_add_stub)
  message(WARNING
    "Skipping Python stub generation: the installed nanobind version does not "
    "provide nanobind_add_stub. Upgrade nanobind to generate stubs.")
endif()

if(GENERATE_PYTHON_STUBS AND COMMAND nanobind_add_stub)
  nanobind_add_stub(
    _visualization_ext_stub
    MODULE _visualization_ext
    OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/python/roboplan_ros/visualization/_visualization_ext.pyi"
    MARKER_FILE "${CMAKE_CURRENT_SOURCE_DIR}/python/roboplan_ros/visualization/py.typed"
    PYTHON_PATH $<TARGET_FILE_DIR:_visualization_ext>
    DEPENDS _visualization_ext
  )
endif()

install(DIRECTORY python/roboplan_ros/ DESTINATION "${PYTHON_DESTINATION}/roboplan_ros")

# Add the unit tests, if enabled.
option(BUILD_TESTING "" ON)
if(BUILD_TESTING)
  find_package(ament_cmake_pytest REQUIRED)
  find_package(roboplan_example_models REQUIRED)
  ament_add_pytest_test(roboplan_visualizer_test_py test/roboplan_visualizer_test.py)
  ament_add_pytest_test(roboplan_ik_marker_test test/roboplan_ik_marker_test.py)
endif()
