cmake_minimum_required(VERSION 3.26)
project(focal LANGUAGES CXX)

# The version lives in pyproject.toml and nowhere else.  scikit-build-core
# hands it to us here; a standalone cmake build (no pip) gets a marker instead
# of a stale hard-coded number.
if(DEFINED SKBUILD_PROJECT_VERSION)
  set(FOCAL_VERSION "${SKBUILD_PROJECT_VERSION}")
else()
  set(FOCAL_VERSION "0.0.0+local")
endif()

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)   # for clangd / IDE tooling

if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()

# The C++ library.  Globbed with CONFIGURE_DEPENDS so new files under src/
# get picked up without editing this file.
file(GLOB_RECURSE FOCAL_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(focal_core STATIC ${FOCAL_SOURCES})
target_include_directories(focal_core PUBLIC
  "${CMAKE_CURRENT_SOURCE_DIR}/include"
  "${CMAKE_CURRENT_SOURCE_DIR}/third_party")
target_compile_definitions(focal_core PRIVATE FOCAL_VERSION="${FOCAL_VERSION}")
target_compile_options(focal_core PRIVATE
  # -Wno-unknown-pragmas: the omp pragmas are inert when OpenMP is off, and
  # warning about them on every build is noise.
  $<$<CXX_COMPILER_ID:AppleClang,Clang,GNU>:-Wall -Wextra -Wno-unknown-pragmas>)

# OpenMP is optional -- without it the omp pragmas are inert and everything
# still builds and runs single-threaded.  Apple ships no libomp and Homebrew's
# is keg-only, so the stock find_package misses it there.
#
# Forced off for macOS wheel builds (see [tool.cibuildwheel.macos] in
# pyproject.toml): Homebrew's libomp is arm64-only, so linking it into the
# cross-compiled x86_64 wheel fails outright.
option(FOCAL_USE_OPENMP "Build with OpenMP if available" ON)

set(FOCAL_OPENMP_STATUS "disabled by FOCAL_USE_OPENMP=OFF")

if(FOCAL_USE_OPENMP)
  set(FOCAL_OPENMP_STATUS "not found, building single-threaded")

  find_package(OpenMP QUIET)
  if(OpenMP_CXX_FOUND)
    target_link_libraries(focal_core PUBLIC OpenMP::OpenMP_CXX)
    set(FOCAL_OPENMP_STATUS "enabled")
  elseif(APPLE)
    execute_process(COMMAND brew --prefix libomp
                    OUTPUT_VARIABLE LIBOMP_PREFIX
                    OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
    if(LIBOMP_PREFIX AND EXISTS "${LIBOMP_PREFIX}/include/omp.h")
      target_compile_options(focal_core PRIVATE -Xpreprocessor -fopenmp)
      target_include_directories(focal_core PRIVATE "${LIBOMP_PREFIX}/include")
      target_link_libraries(focal_core PUBLIC "${LIBOMP_PREFIX}/lib/libomp.dylib")
      set(FOCAL_OPENMP_STATUS "enabled via Homebrew libomp at ${LIBOMP_PREFIX}")
    endif()
  endif()
endif()

message(STATUS "focal: OpenMP ${FOCAL_OPENMP_STATUS}")

if(MSVC)
  # windows.h (pulled in by stb under STBI_WINDOWS_UTF8) defines min/max as
  # macros, which breaks std::min and std::max at every call site.  stb also
  # uses fopen/sprintf, which MSVC flags as deprecated.
  target_compile_definitions(focal_core PRIVATE NOMINMAX _CRT_SECURE_NO_WARNINGS)
endif()

# The Python extension module.
# Development.SABIModule is what nanobind links against for STABLE_ABI below;
# without it the STABLE_ABI flag is silently ignored and you get one wheel per
# Python version instead of one per platform.
find_package(Python 3.12 REQUIRED
             COMPONENTS Interpreter Development.Module Development.SABIModule)

execute_process(
  COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
  OUTPUT_VARIABLE NANOBIND_CMAKE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE
  COMMAND_ERROR_IS_FATAL ANY)
list(APPEND CMAKE_PREFIX_PATH "${NANOBIND_CMAKE_DIR}")
find_package(nanobind CONFIG REQUIRED)

file(GLOB_RECURSE FOCAL_BINDINGS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/python/*.cpp")
nanobind_add_module(_focal STABLE_ABI NB_STATIC ${FOCAL_BINDINGS})
target_link_libraries(_focal PRIVATE focal_core)

install(TARGETS _focal LIBRARY DESTINATION focal)
