# ---------------------------------------------------------------------------
# Python Bindings Configuration
#
# This CMakeLists.txt configures the Python bindings for SittaArray.
# It requires:
# - Python development headers
# - nanobind library
# - NumPy (optional, for testing)
#
# Usage:
#   cmake -B build -DSITTA_ENABLE_PYTHON=ON
#   cmake --build build
#
# Installation:
#   cmake --install build --component python
#
# Or manually:
#   cp build/python/_sitta_array*.so <python_site_packages>/
# ---------------------------------------------------------------------------

# Find Python
find_package(Python REQUIRED COMPONENTS Development NumPy)

# nanobind should have been found/set up in the parent CMakeLists.txt
# Check that we have a valid NANOBIND_INCLUDE_DIR
if(NOT NANOBIND_INCLUDE_DIR)
    message(FATAL_ERROR "nanobind not configured. Enable SITTA_ENABLE_PYTHON and ensure nanobind is available.")
endif()

if(NOT EXISTS ${NANOBIND_INCLUDE_DIR}/nanobind/nanobind.h)
    message(FATAL_ERROR "nanobind not found at ${NANOBIND_INCLUDE_DIR}. Please set SITTA_NANOBIND_ROOT or enable SITTA_NANOBIND_FETCH.")
endif()

message(STATUS "Using nanobind: ${NANOBIND_INCLUDE_DIR}")

# Get Python module extension
if(Python_MODULE_EXTENSION)
    set(PYTHON_MODULE_EXT ".${Python_MODULE_EXTENSION}")
else()
    set(PYTHON_MODULE_EXT ".so")
endif()

# Free-threaded (nogil) interpreters need nanobind's nb_ft.cpp plus the
# NB_FREE_THREADED define; nanobind_add_module() normally handles this, but
# this target lists nanobind's sources manually instead.
execute_process(
    COMMAND "${Python_EXECUTABLE}" "-c" "import sysconfig; print(sysconfig.get_config_var('Py_GIL_DISABLED') or 0)"
    OUTPUT_VARIABLE SITTA_PY_GIL_DISABLED
    OUTPUT_STRIP_TRAILING_WHITESPACE
)

# The extension is assembled into a real package directory inside the build
# tree, so that `import sitta` works straight from the build with
# PYTHONPATH=${PYTHON_OUTPUT_DIR}. Importing the bare extension instead
# bypasses __init__.py, which is where the SittaHandle factory lives.
set(PYTHON_OUTPUT_DIR ${CMAKE_BINARY_DIR}/python)
set(PYTHON_PACKAGE_DIR ${PYTHON_OUTPUT_DIR}/sitta)

# Create the Python module
add_library(_sitta_array MODULE
    _sitta_array.cpp
    # Include nanobind's compiled source files
    ${NANOBIND_INCLUDE_DIR}/../src/trampoline.cpp
    ${NANOBIND_INCLUDE_DIR}/../src/nb_type.cpp
    ${NANOBIND_INCLUDE_DIR}/../src/common.cpp
    ${NANOBIND_INCLUDE_DIR}/../src/error.cpp
    ${NANOBIND_INCLUDE_DIR}/../src/implicit.cpp
    ${NANOBIND_INCLUDE_DIR}/../src/nb_func.cpp
    ${NANOBIND_INCLUDE_DIR}/../src/nb_enum.cpp
    ${NANOBIND_INCLUDE_DIR}/../src/nb_ndarray.cpp
    ${NANOBIND_INCLUDE_DIR}/../src/nb_internals.cpp
    ${NANOBIND_INCLUDE_DIR}/../src/nb_static_property.cpp
)

if(SITTA_PY_GIL_DISABLED)
    target_sources(_sitta_array PRIVATE ${NANOBIND_INCLUDE_DIR}/../src/nb_ft.cpp)
    target_compile_definitions(_sitta_array PUBLIC NB_FREE_THREADED)
endif()

# Set target properties
target_compile_options(_sitta_array PRIVATE
    -fPIC
    -DNANOBIND_MODULE
    -std=c++20
)

# SITTA_VERSION_FULL comes from VERSION.txt via the top-level CMakeLists.txt.
# The full string is used rather than the numeric triple, so a pre-release
# build reports itself honestly to Python.
target_compile_definitions(_sitta_array PRIVATE
    SITTA_VERSION_STRING="${SITTA_VERSION_FULL}"
)

target_include_directories(_sitta_array PRIVATE
    ${Python_INCLUDE_DIRS}
    ${NANOBIND_INCLUDE_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR}/../include
    ${NANOBIND_INCLUDE_DIR}/../ext/robin_map/include
)

# Link libraries
target_link_libraries(_sitta_array PRIVATE
    ${Python_LIBRARIES}
)

# Set output directory and naming
set_target_properties(_sitta_array PROPERTIES
    PREFIX ""
    SUFFIX "${PYTHON_MODULE_EXT}"
    OUTPUT_NAME "_sitta_array"
    LIBRARY_OUTPUT_DIRECTORY ${PYTHON_PACKAGE_DIR}
)

# Complete the package next to the extension.
add_custom_command(TARGET _sitta_array POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
            ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py
            ${CMAKE_CURRENT_SOURCE_DIR}/_sitta_array.pyi
            ${PYTHON_PACKAGE_DIR}/
    COMMENT "Assembling sitta package in ${PYTHON_PACKAGE_DIR}"
)

# ---------------------------------------------------------------------------
# Installation
# ---------------------------------------------------------------------------

option(SITTA_PYTHON_INSTALL "Install Python module" ON)

if(SITTA_PYTHON_INSTALL)
    if(SKBUILD)
        # scikit-build-core (PEP 517 wheel builds) stages `cmake --install`
        # into a temporary prefix and maps the whole tree directly onto the
        # wheel's site-packages root (wheel.install-dir defaults to
        # "site-packages"). DESTINATION must therefore be relative to the
        # prefix, not an absolute site-packages path on the build machine.
        set(SITTA_PYTHON_INSTALL_DIR "sitta")
    else()
        # A direct `cmake --install build` (not going through a wheel
        # build): keep installing straight into the configuring
        # interpreter's real site-packages, exactly as before.
        set(SITTA_PYTHON_INSTALL_DIR "${Python_SITEARCH}/sitta")
    endif()

    # COMPONENT python lets a wheel build (`install.components = ["python"]`
    # in pyproject.toml) install just these rules, not the header-only
    # library's untagged install() rules from the top-level CMakeLists.txt
    # (which would otherwise dump include/ and lib/cmake/ into the wheel's
    # site-packages). Installing with no --component flag, as a plain
    # `cmake --install build` does, still installs everything as before.
    #
    # The extension belongs inside the package, not beside it: __init__.py
    # imports it as a relative submodule.
    install(TARGETS _sitta_array
        LIBRARY DESTINATION ${SITTA_PYTHON_INSTALL_DIR}
        COMPONENT python
    )

    install(FILES
        ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py
        ${CMAKE_CURRENT_SOURCE_DIR}/_sitta_array.pyi
        DESTINATION ${SITTA_PYTHON_INSTALL_DIR}
        COMPONENT python
    )

    message(STATUS "Python package will be installed to: ${SITTA_PYTHON_INSTALL_DIR}")
else()
    message(STATUS "Python module will be built but not installed. Copy manually from: ${PYTHON_OUTPUT_DIR}")
endif()

# ---------------------------------------------------------------------------
# Testing
# ---------------------------------------------------------------------------

if(SITTA_BUILD_TESTS AND SITTA_ENABLE_PYTHON)
    add_subdirectory(tests)
    message(STATUS "Added Python tests")
endif()

message(STATUS "Python bindings configured successfully")
