cmake_minimum_required(VERSION 3.20...3.30)
project(gpuastar_experiments_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()

# Resolve the gpuastar_cpp public headers: prefer the in-repo checkout, fall
# back to the headers installed inside the gpuastar_cpp package.
if(NOT GPUASTAR_CPP_INCLUDE_DIR)
  set(_gpuastar_repo_cxx "${CMAKE_CURRENT_SOURCE_DIR}/../../cpp/cxx")
  if(EXISTS "${_gpuastar_repo_cxx}/environment_registry.h")
    set(GPUASTAR_CPP_INCLUDE_DIR "${_gpuastar_repo_cxx}")
  else()
    execute_process(
      COMMAND "${Python_EXECUTABLE}" -c "import importlib.util,pathlib,sys; spec = importlib.util.find_spec('gpuastar_cpp'); sys.exit(1) if spec is None or spec.origin is None else sys.stdout.write(str(pathlib.Path(spec.origin).parent / 'include'))"
      OUTPUT_VARIABLE GPUASTAR_CPP_INCLUDE_DIR
      OUTPUT_STRIP_TRAILING_WHITESPACE
      RESULT_VARIABLE _gpuastar_include_lookup_result
    )
    if(NOT _gpuastar_include_lookup_result EQUAL 0)
      message(FATAL_ERROR "Unable to locate gpuastar_cpp headers. Install gpuastar-cpp or set GPUASTAR_CPP_INCLUDE_DIR.")
    endif()
    unset(_gpuastar_include_lookup_result)
  endif()
  unset(_gpuastar_repo_cxx)
endif()
if(NOT EXISTS "${GPUASTAR_CPP_INCLUDE_DIR}/environment_registry.h")
  message(FATAL_ERROR "gpuastar_cpp headers not found in '${GPUASTAR_CPP_INCLUDE_DIR}'.")
endif()
message(STATUS "Using gpuastar_cpp headers from ${GPUASTAR_CPP_INCLUDE_DIR}")

set(_gpuastar_plugin_csrc ${CMAKE_CURRENT_SOURCE_DIR}/csrc)

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(
  _impl
  ${_nb_lto}
  FREE_THREADED
  ${_gpuastar_plugin_csrc}/cpu_environments.cpp
  ${_gpuastar_plugin_csrc}/cpu_factories.cpp
  ${_gpuastar_plugin_csrc}/torch_environments.cpp
  ${_gpuastar_plugin_csrc}/plugin_module.cpp
)
set_property(TARGET _impl PROPERTY CXX_SCAN_FOR_MODULES OFF)

target_include_directories(_impl PRIVATE
  ${_gpuastar_plugin_csrc}
  ${GPUASTAR_CPP_INCLUDE_DIR}
)
target_link_libraries(_impl PRIVATE ${TORCH_LIBRARIES})
if(MSVC)
  target_compile_options(_impl PRIVATE /O2)
else()
  target_compile_options(_impl PRIVATE -O3)
endif()

# Torch-provided compile flags stay target-scoped to avoid polluting unrelated compilation units.
if(_torch_compile_options)
  target_compile_options(_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_plugin_build_rpath "")
set(_gpuastar_plugin_install_rpath "")
if(APPLE)
  list(APPEND _gpuastar_plugin_build_rpath "@loader_path")
  list(APPEND _gpuastar_plugin_install_rpath "@loader_path" "@loader_path/../torch/lib")
elseif(UNIX AND NOT APPLE)
  list(APPEND _gpuastar_plugin_build_rpath "$ORIGIN")
  list(APPEND
    _gpuastar_plugin_install_rpath
    "$ORIGIN"
    "$ORIGIN/../torch/lib"
  )
  if(SKBUILD_STATE STREQUAL "editable")
    list(APPEND _gpuastar_plugin_install_rpath "$ORIGIN/../../..")
  endif()
endif()
if(_gpuastar_torch_lib_dir)
  list(APPEND _gpuastar_plugin_build_rpath "${_gpuastar_torch_lib_dir}")
endif()
if(_gpuastar_plugin_build_rpath OR _gpuastar_plugin_install_rpath)
  set_target_properties(_impl PROPERTIES
    BUILD_RPATH "${_gpuastar_plugin_build_rpath}"
    INSTALL_RPATH "${_gpuastar_plugin_install_rpath}"
    INSTALL_REMOVE_ENVIRONMENT_RPATH TRUE
    INSTALL_RPATH_USE_LINK_PATH FALSE
  )
endif()
unset(_gpuastar_plugin_build_rpath)
unset(_gpuastar_plugin_install_rpath)
unset(_gpuastar_torch_lib_dir)

install(TARGETS _impl LIBRARY DESTINATION gpuastar_experiments_cpp)

install(
  FILES
    ${CMAKE_CURRENT_SOURCE_DIR}/src/gpuastar_experiments_cpp/_impl.pyi
    ${CMAKE_CURRENT_SOURCE_DIR}/src/gpuastar_experiments_cpp/py.typed
  DESTINATION gpuastar_experiments_cpp
)

if(APPLE)
  install(CODE [[
    set(_gpuastar_plugin_path "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/gpuastar_experiments_cpp/$<TARGET_FILE_NAME:_impl>")
    if(EXISTS "${_gpuastar_plugin_path}")
      execute_process(
        COMMAND /usr/bin/codesign --force --sign - "${_gpuastar_plugin_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_plugin_path}: ${_gpuastar_codesign_error}")
      endif()
    endif()
    unset(_gpuastar_plugin_path)
  ]])
endif()
