cmake_minimum_required(VERSION 3.20...3.30)
project(gpuastar_cpp LANGUAGES CXX)

# We do not use C++ modules. Disabling scans avoids fragile .ddi and .ddi.d depfile paths
# on some CMake/Ninja/compiler combinations.
if(POLICY CMP0155)
  cmake_policy(SET CMP0155 NEW)
endif()
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Default to Release if unspecified to match guideline
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "RelWithDebInfo" "MinSizeRel")
endif()

if(NOT SKBUILD)
  message(WARNING
    "This CMake file is intended to be driven by scikit-build-core. Use 'pip install .' or 'python -m build' instead of calling CMake directly.")
endif()

# Pin the interpreter to the one driving the build (scikit-build-core exports PYTHON_EXECUTABLE).
if(DEFINED Python_EXECUTABLE AND NOT EXISTS "${Python_EXECUTABLE}")
  unset(Python_EXECUTABLE CACHE)
endif()
if(DEFINED ENV{PYTHON_EXECUTABLE} AND EXISTS "$ENV{PYTHON_EXECUTABLE}")
  set(Python_EXECUTABLE "$ENV{PYTHON_EXECUTABLE}" CACHE FILEPATH "Python interpreter (from build frontend)" FORCE)
endif()
set(Python_FIND_STRATEGY LOCATION CACHE STRING "Prefer the provided interpreter path")
set(Python_FIND_REGISTRY NEVER CACHE STRING "Avoid host registry picks on Windows")
set(Python_FIND_UNVERSIONED_NAMES LAST CACHE STRING "Search unversioned Python names after versioned names")

find_package(
  Python 3.10 REQUIRED
  COMPONENTS Interpreter Development.Module
  OPTIONAL_COMPONENTS Development.SABIModule
)

execute_process(
  COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
  OUTPUT_VARIABLE nanobind_DIR
  OUTPUT_STRIP_TRAILING_WHITESPACE
)
find_package(nanobind CONFIG REQUIRED)

include("${CMAKE_CURRENT_LIST_DIR}/cmake/GPUAStarPythonTorch.cmake")
gpuastar_configure_python_torch()
include(CheckIPOSupported)
check_ipo_supported(RESULT gpuastar_ipo_supported OUTPUT gpuastar_ipo_error)
if(gpuastar_ipo_supported)
  set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)
endif()

find_package(nlohmann_json CONFIG QUIET)
if(NOT TARGET nlohmann_json::nlohmann_json)
  # Resolve third_party include path before using it below
  set(_gpuastar_cpp_dir ${CMAKE_CURRENT_SOURCE_DIR}/cxx)
  set(_gpuastar_third_party ${_gpuastar_cpp_dir}/third_party)
  add_library(gpuastar_nlohmann_json INTERFACE)
  target_include_directories(gpuastar_nlohmann_json INTERFACE ${_gpuastar_third_party})
  add_library(nlohmann_json::nlohmann_json ALIAS gpuastar_nlohmann_json)
endif()
find_package(OpenMP)

set(_gpuastar_cpp_dir ${CMAKE_CURRENT_SOURCE_DIR}/cxx)
set(_gpuastar_third_party ${_gpuastar_cpp_dir}/third_party)

add_library(gpuastar_env STATIC
  ${_gpuastar_cpp_dir}/environment_registry.cpp
  ${_gpuastar_cpp_dir}/aoti_heuristic.cpp
  ${_gpuastar_cpp_dir}/aoti_manifest.cpp
  ${_gpuastar_cpp_dir}/profiler.cpp
)
set_property(TARGET gpuastar_env PROPERTY POSITION_INDEPENDENT_CODE ON)
set_property(TARGET gpuastar_env PROPERTY CXX_SCAN_FOR_MODULES OFF)

target_include_directories(gpuastar_env PUBLIC
  ${_gpuastar_cpp_dir}
  ${_gpuastar_third_party}
)

target_link_libraries(gpuastar_env PUBLIC ${TORCH_LIBRARIES} nlohmann_json::nlohmann_json)
if(OpenMP_CXX_FOUND)
  target_link_libraries(gpuastar_env PUBLIC OpenMP::OpenMP_CXX)
endif()

add_library(gpuastar_solver STATIC
  ${_gpuastar_cpp_dir}/parallel_weighted_astar.cpp
  ${_gpuastar_cpp_dir}/torch_gpu_astar.cpp
)
set_property(TARGET gpuastar_solver PROPERTY POSITION_INDEPENDENT_CODE ON)
set_property(TARGET gpuastar_solver PROPERTY CXX_SCAN_FOR_MODULES OFF)
target_compile_definitions(gpuastar_solver PRIVATE
  GPUASTAR_USE_PY_PRINT_REDIRECT=1
)
target_link_libraries(gpuastar_solver PUBLIC gpuastar_env ${TORCH_LIBRARIES} Python::Module)
if(OpenMP_CXX_FOUND)
  target_link_libraries(gpuastar_solver PUBLIC OpenMP::OpenMP_CXX)
endif()
target_include_directories(gpuastar_solver PUBLIC ${_gpuastar_cpp_dir})
if(MSVC)
  target_compile_options(gpuastar_solver PRIVATE /O2)
  target_compile_options(gpuastar_env PRIVATE /O2)
else()
  target_compile_options(gpuastar_solver PRIVATE -O3)
  target_compile_options(gpuastar_env PRIVATE -O3)
endif()

set(_torch_compile_options "")
if(TORCH_CXX_FLAGS)
  separate_arguments(_torch_flag_list UNIX_COMMAND "${TORCH_CXX_FLAGS}")
  if(UNIX AND NOT APPLE)
    list(FILTER _torch_flag_list EXCLUDE REGEX "^-Wl,-z,defs$")
  endif()
  set(_torch_compile_options ${_torch_flag_list})
endif()
unset(_torch_flag_list)

set(_nb_lto)
if(gpuastar_ipo_supported)
  set(_nb_lto LTO)
endif()

nanobind_add_module(
  _backend_impl
  ${_nb_lto}
  FREE_THREADED
  src/bindings.cpp
)
set_property(TARGET _backend_impl PROPERTY CXX_SCAN_FOR_MODULES OFF)

target_link_libraries(_backend_impl PRIVATE gpuastar_solver ${TORCH_LIBRARIES})

# Torch-provided compile flags stay target-scoped to avoid polluting unrelated compilation units.
if(_torch_compile_options)
  target_compile_options(gpuastar_env PRIVATE ${_torch_compile_options})
  target_compile_options(gpuastar_solver PRIVATE ${_torch_compile_options})
  target_compile_options(_backend_impl PRIVATE ${_torch_compile_options})
endif()
unset(_torch_compile_options)

set(_gpuastar_torch_lib_dir "")
if(TORCH_CMAKE_PREFIX)
  get_filename_component(_gpuastar_torch_root "${TORCH_CMAKE_PREFIX}/../.." ABSOLUTE)
  set(_gpuastar_torch_lib_candidate "${_gpuastar_torch_root}/lib")
  if(EXISTS "${_gpuastar_torch_lib_candidate}")
    set(_gpuastar_torch_lib_dir "${_gpuastar_torch_lib_candidate}")
  endif()
  unset(_gpuastar_torch_lib_candidate)
  unset(_gpuastar_torch_root)
endif()

set(_gpuastar_backend_build_rpath "")
set(_gpuastar_backend_install_rpath "")
if(APPLE)
  list(APPEND _gpuastar_backend_build_rpath "@loader_path")
  list(APPEND _gpuastar_backend_install_rpath "@loader_path" "@loader_path/../torch/lib")
elseif(UNIX AND NOT APPLE)
  list(APPEND _gpuastar_backend_build_rpath "$ORIGIN")
  list(APPEND
    _gpuastar_backend_install_rpath
    "$ORIGIN"
    "$ORIGIN/../torch/lib"
  )
  if(SKBUILD_STATE STREQUAL "editable")
    list(APPEND _gpuastar_backend_install_rpath "$ORIGIN/../../..")
  endif()
endif()
if(_gpuastar_torch_lib_dir)
  list(APPEND _gpuastar_backend_build_rpath "${_gpuastar_torch_lib_dir}")
endif()
if(_gpuastar_backend_build_rpath OR _gpuastar_backend_install_rpath)
  set_target_properties(_backend_impl PROPERTIES
    BUILD_RPATH "${_gpuastar_backend_build_rpath}"
    INSTALL_RPATH "${_gpuastar_backend_install_rpath}"
    INSTALL_REMOVE_ENVIRONMENT_RPATH TRUE
    INSTALL_RPATH_USE_LINK_PATH FALSE
  )
endif()
unset(_gpuastar_backend_build_rpath)
unset(_gpuastar_backend_install_rpath)
unset(_gpuastar_torch_lib_dir)

install(TARGETS _backend_impl LIBRARY DESTINATION gpuastar_cpp)

install(
  FILES
    ${CMAKE_CURRENT_SOURCE_DIR}/src/gpuastar_cpp/_backend_impl.pyi
    ${CMAKE_CURRENT_SOURCE_DIR}/src/gpuastar_cpp/py.typed
  DESTINATION gpuastar_cpp
)

# Public headers consumed by compiled environment plugin builds.
install(
  FILES
    ${_gpuastar_cpp_dir}/environments.h
    ${_gpuastar_cpp_dir}/torch_env.h
    ${_gpuastar_cpp_dir}/environment_registry.h
  DESTINATION gpuastar_cpp/include
)

if(APPLE)
  install(CODE [[
    set(_gpuastar_backend_path "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/gpuastar_cpp/$<TARGET_FILE_NAME:_backend_impl>")
    if(EXISTS "${_gpuastar_backend_path}")
      execute_process(
        COMMAND /usr/bin/codesign --force --sign - "${_gpuastar_backend_path}"
        RESULT_VARIABLE _gpuastar_codesign_result
        OUTPUT_QUIET
        ERROR_VARIABLE _gpuastar_codesign_error
      )
      if(NOT _gpuastar_codesign_result EQUAL 0)
        message(WARNING "Failed to ad-hoc sign ${_gpuastar_backend_path}: ${_gpuastar_codesign_error}")
      endif()
    endif()
    unset(_gpuastar_backend_path)
  ]])
endif()
