cmake_minimum_required(VERSION 3.24)
if(DEFINED reforge_core_VERSION)
    project(ReforgeShaper VERSION "${reforge_core_VERSION}" LANGUAGES CXX)
else()
    project(ReforgeShaper VERSION 2.0.16 LANGUAGES CXX)
endif()

include(CMakePackageConfigHelpers)
include(CTest)
include(GNUInstallDirs)

set(_reforge_shaper_install_bindir "${CMAKE_INSTALL_BINDIR}")
set(_reforge_shaper_install_includedir "${CMAKE_INSTALL_INCLUDEDIR}")
set(_reforge_shaper_install_libdir "${CMAKE_INSTALL_LIBDIR}")
set(_reforge_shaper_python_install_dir "reforge_core/control")
if(SKBUILD)
    set(_reforge_shaper_install_bindir "lib")
    set(_reforge_shaper_install_libdir "lib")
    set(_reforge_shaper_python_install_dir "../control")
endif()

option(
    REFORGE_SHAPER_QUALIFY_CLARABEL
    "Build the private Clarabel qualification adapter and corpus"
    OFF
)
option(
    REFORGE_SHAPER_ENABLE_SANITIZERS
    "Instrument native targets with address and undefined-behavior sanitizers"
    OFF
)
option(
    REFORGE_SHAPER_BUILD_PYTHON_BINDINGS
    "Build the Python extension module for the native Shaper runtime"
    ON
)
option(
    REFORGE_SHAPER_INSTALL_RUNTIME_DEPENDENCIES
    "Install resolved non-system shared library dependencies beside the SDK"
    OFF
)
option(
    REFORGE_SHAPER_CARGO_OFFLINE
    "Require the locked Clarabel Rust build to use only the Cargo cache"
    OFF
)
set(
    REFORGE_SHAPER_SANITIZERS
    "address,undefined"
    CACHE STRING
    "Comma-separated Clang/GCC sanitizers enabled for native targets"
)
if(REFORGE_SHAPER_ENABLE_SANITIZERS)
    if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
        message(FATAL_ERROR
            "REFORGE_SHAPER_ENABLE_SANITIZERS requires Clang or GCC."
        )
    endif()
    add_compile_options(
        "-fsanitize=${REFORGE_SHAPER_SANITIZERS}"
        -fno-omit-frame-pointer
    )
    add_link_options("-fsanitize=${REFORGE_SHAPER_SANITIZERS}")
    # Keep Eigen's inline allocation ABI aligned with the qualified Pinocchio
    # binary when sanitizer instrumentation changes allocation assumptions.
    add_compile_definitions(EIGEN_MALLOC_ALREADY_ALIGNED=1)
endif()
set(
    REFORGE_SHAPER_CLARABEL_ROOT
    ""
    CACHE PATH
    "Materialized checksum-verified Clarabel.cpp source root"
)
set(
    REFORGE_SHAPER_NLOHMANN_JSON_INCLUDE_DIR
    ""
    CACHE PATH
    "nlohmann_json include directory fallback when no CMake package exists"
)
set(
    REFORGE_SHAPER_NLOHMANN_INCLUDE_DIR
    ""
    CACHE PATH
    "Compatibility alias for the nlohmann_json include directory fallback"
)
set(
    REFORGE_SHAPER_INSTALL_RPATH
    "$ORIGIN"
    CACHE STRING
    "Installed runtime search path for Reforge Shaper shared libraries"
)
set(
    REFORGE_SHAPER_RUNTIME_DEPENDENCY_INSTALL_SUBDIR
    ""
    CACHE STRING
    "Optional subdirectory under CMAKE_INSTALL_LIBDIR for copied runtime dependencies"
)

if(
    REFORGE_SHAPER_NLOHMANN_INCLUDE_DIR
    AND REFORGE_SHAPER_NLOHMANN_JSON_INCLUDE_DIR
    AND NOT REFORGE_SHAPER_NLOHMANN_INCLUDE_DIR STREQUAL
        REFORGE_SHAPER_NLOHMANN_JSON_INCLUDE_DIR
)
    message(FATAL_ERROR
        "REFORGE_SHAPER_NLOHMANN_INCLUDE_DIR and "
        "REFORGE_SHAPER_NLOHMANN_JSON_INCLUDE_DIR both set different paths."
    )
endif()
if(
    REFORGE_SHAPER_NLOHMANN_INCLUDE_DIR
    AND NOT REFORGE_SHAPER_NLOHMANN_JSON_INCLUDE_DIR
)
    set(
        REFORGE_SHAPER_NLOHMANN_JSON_INCLUDE_DIR
        "${REFORGE_SHAPER_NLOHMANN_INCLUDE_DIR}"
    )
endif()

if(SKBUILD AND NOT REFORGE_SHAPER_CLARABEL_ROOT)
    set(
        _reforge_shaper_wheel_deps_root
        "${CMAKE_CURRENT_BINARY_DIR}/wheel-dependencies"
    )
    execute_process(
        COMMAND
            "${CMAKE_COMMAND}"
            "-DREFORGE_SHAPER_DEPS_ROOT=${_reforge_shaper_wheel_deps_root}"
            -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/MaterializeClarabel.cmake"
        RESULT_VARIABLE _reforge_shaper_materialize_result
        OUTPUT_VARIABLE _reforge_shaper_materialize_output
        ERROR_VARIABLE _reforge_shaper_materialize_error
    )
    if(NOT _reforge_shaper_materialize_result EQUAL 0)
        message(FATAL_ERROR
            "Failed to materialize the locked wheel solver sources:\n"
            "${_reforge_shaper_materialize_output}"
            "${_reforge_shaper_materialize_error}"
        )
    endif()

    find_program(_reforge_shaper_rustc rustc REQUIRED)
    find_program(_reforge_shaper_cargo cargo REQUIRED)
    execute_process(
        COMMAND "${_reforge_shaper_rustc}" --version
        RESULT_VARIABLE _reforge_shaper_rustc_result
        OUTPUT_VARIABLE _reforge_shaper_rustc_version
        OUTPUT_STRIP_TRAILING_WHITESPACE
    )
    if(
        NOT _reforge_shaper_rustc_result EQUAL 0
        OR NOT _reforge_shaper_rustc_version MATCHES "^rustc 1\\.84\\.1 "
    )
        message(FATAL_ERROR
            "Native wheels require rustc 1.84.1 exactly; found "
            "'${_reforge_shaper_rustc_version}'."
        )
    endif()

    set(
        _reforge_shaper_wheel_clarabel_root
        "${_reforge_shaper_wheel_deps_root}/Clarabel.cpp"
    )
    set(
        _reforge_shaper_cargo_command
        "${_reforge_shaper_cargo}" build --release --locked
        --no-default-features
    )
    if(REFORGE_SHAPER_CARGO_OFFLINE)
        list(APPEND _reforge_shaper_cargo_command --offline)
    endif()
    execute_process(
        COMMAND ${_reforge_shaper_cargo_command}
        WORKING_DIRECTORY
            "${_reforge_shaper_wheel_clarabel_root}/rust_wrapper"
        RESULT_VARIABLE _reforge_shaper_cargo_result
        OUTPUT_VARIABLE _reforge_shaper_cargo_output
        ERROR_VARIABLE _reforge_shaper_cargo_error
    )
    if(NOT _reforge_shaper_cargo_result EQUAL 0)
        message(FATAL_ERROR
            "Failed to build the locked wheel solver with "
            "rustc 1.84.1:\n${_reforge_shaper_cargo_output}"
            "${_reforge_shaper_cargo_error}"
        )
    endif()
    set(
        REFORGE_SHAPER_CLARABEL_ROOT
        "${_reforge_shaper_wheel_clarabel_root}"
        CACHE PATH
        "Materialized checksum-verified Clarabel.cpp source root"
        FORCE
    )
endif()

find_package(Eigen3 REQUIRED NO_MODULE)
find_package(nlohmann_json 3.12.0 EXACT CONFIG QUIET)
find_package(pinocchio 4 CONFIG REQUIRED)
if(REFORGE_SHAPER_BUILD_PYTHON_BINDINGS)
    set(PYBIND11_FINDPYTHON ON)
    find_package(pybind11 CONFIG QUIET)
endif()

if(NOT TARGET nlohmann_json::nlohmann_json)
    if(REFORGE_SHAPER_NLOHMANN_JSON_INCLUDE_DIR)
        set(_reforge_nlohmann_include_dir
            "${REFORGE_SHAPER_NLOHMANN_JSON_INCLUDE_DIR}")
        set(_reforge_nlohmann_abi_header
            "${_reforge_nlohmann_include_dir}/nlohmann/detail/abi_macros.hpp")
        if(
            EXISTS "${_reforge_nlohmann_include_dir}/nlohmann/json.hpp"
            AND EXISTS "${_reforge_nlohmann_abi_header}"
        )
            file(
                STRINGS "${_reforge_nlohmann_abi_header}"
                _reforge_nlohmann_major_line
                REGEX "^[ \t]*#define[ \t]+NLOHMANN_JSON_VERSION_MAJOR[ \t]+[0-9]+"
            )
            file(
                STRINGS "${_reforge_nlohmann_abi_header}"
                _reforge_nlohmann_minor_line
                REGEX "^[ \t]*#define[ \t]+NLOHMANN_JSON_VERSION_MINOR[ \t]+[0-9]+"
            )
            file(
                STRINGS "${_reforge_nlohmann_abi_header}"
                _reforge_nlohmann_patch_line
                REGEX "^[ \t]*#define[ \t]+NLOHMANN_JSON_VERSION_PATCH[ \t]+[0-9]+"
            )
            string(
                REGEX REPLACE
                ".*NLOHMANN_JSON_VERSION_MAJOR[ \t]+([0-9]+).*"
                "\\1"
                _reforge_nlohmann_major
                "${_reforge_nlohmann_major_line}"
            )
            string(
                REGEX REPLACE
                ".*NLOHMANN_JSON_VERSION_MINOR[ \t]+([0-9]+).*"
                "\\1"
                _reforge_nlohmann_minor
                "${_reforge_nlohmann_minor_line}"
            )
            string(
                REGEX REPLACE
                ".*NLOHMANN_JSON_VERSION_PATCH[ \t]+([0-9]+).*"
                "\\1"
                _reforge_nlohmann_patch
                "${_reforge_nlohmann_patch_line}"
            )
            set(_reforge_nlohmann_version
                "${_reforge_nlohmann_major}.${_reforge_nlohmann_minor}.${_reforge_nlohmann_patch}")
        endif()
    endif()

    if(NOT _reforge_nlohmann_version STREQUAL "3.12.0")
        find_package(Python3 COMPONENTS Interpreter QUIET)
    endif()
    if(
        NOT _reforge_nlohmann_version STREQUAL "3.12.0"
        AND Python3_Interpreter_FOUND
    )
        execute_process(
            COMMAND
                "${Python3_EXECUTABLE}" -c
                "from importlib.metadata import version; import nlohmann_json; print(version('nlohmann-json')); print(nlohmann_json.get_include())"
            RESULT_VARIABLE _reforge_nlohmann_result
            OUTPUT_VARIABLE _reforge_nlohmann_output
            ERROR_QUIET
            OUTPUT_STRIP_TRAILING_WHITESPACE
        )
        if(_reforge_nlohmann_result EQUAL 0)
            string(REPLACE "\n" ";" _reforge_nlohmann_values
                "${_reforge_nlohmann_output}")
            list(LENGTH _reforge_nlohmann_values
                _reforge_nlohmann_value_count)
            if(_reforge_nlohmann_value_count EQUAL 2)
                list(GET _reforge_nlohmann_values 0
                    _reforge_nlohmann_version)
                list(GET _reforge_nlohmann_values 1
                    _reforge_nlohmann_include_dir)
            endif()
        endif()
    endif()

    if(
        NOT _reforge_nlohmann_version STREQUAL "3.12.0"
        OR NOT EXISTS "${_reforge_nlohmann_include_dir}/nlohmann/json.hpp"
    )
        message(FATAL_ERROR
            "nlohmann_json 3.12.0 is required. Install a discoverable CMake "
            "package or the pinned nlohmann-json Python build dependency."
        )
    endif()

    add_library(nlohmann_json::nlohmann_json INTERFACE IMPORTED)
    set_target_properties(nlohmann_json::nlohmann_json PROPERTIES
        INTERFACE_INCLUDE_DIRECTORIES "${_reforge_nlohmann_include_dir}"
    )
endif()

set(_reforge_shaper_generated_include_dir
    "${CMAKE_CURRENT_BINARY_DIR}/generated/include")
configure_file(
    include/reforge_core/native/common/export.hpp.in
    "${_reforge_shaper_generated_include_dir}/reforge_core/native/common/export.hpp"
    @ONLY
)

add_library(reforge_shaper_common
    src/control/shaper/modal_inference_strategy.cpp
    src/control/shaper/residual_switch_config.cpp
)
add_library(ReforgeShaper::common ALIAS reforge_shaper_common)

set_target_properties(reforge_shaper_common PROPERTIES
    EXPORT_NAME common
    POSITION_INDEPENDENT_CODE ON
)
target_compile_features(reforge_shaper_common PUBLIC cxx_std_17)
target_compile_definitions(reforge_shaper_common
    PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
)
if(NOT BUILD_SHARED_LIBS)
    target_compile_definitions(
        reforge_shaper_common PUBLIC REFORGE_SHAPER_STATIC_DEFINE
    )
endif()
target_include_directories(reforge_shaper_common
    PUBLIC
        "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
        "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
        "$<INSTALL_INTERFACE:${_reforge_shaper_install_includedir}>"
)
target_link_libraries(reforge_shaper_common PUBLIC Eigen3::Eigen)

add_library(reforge_shaper_trajectory
    src/control/shaper/shaper_window_helpers.cpp
    src/util/interpolation.cpp
    src/util/timing/joint_limits.cpp
    src/util/timing/joint_trajectory.cpp
    src/util/timing/resampling.cpp
)
add_library(ReforgeShaper::trajectory ALIAS reforge_shaper_trajectory)

set_target_properties(reforge_shaper_trajectory PROPERTIES
    EXPORT_NAME trajectory
    POSITION_INDEPENDENT_CODE ON
)
target_compile_features(reforge_shaper_trajectory PUBLIC cxx_std_17)
target_compile_definitions(reforge_shaper_trajectory
    PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
)
if(NOT BUILD_SHARED_LIBS)
    target_compile_definitions(
        reforge_shaper_trajectory PUBLIC REFORGE_SHAPER_STATIC_DEFINE
    )
endif()
target_include_directories(reforge_shaper_trajectory
    PUBLIC
        "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
        "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
        "$<INSTALL_INTERFACE:${_reforge_shaper_install_includedir}>"
)
target_link_libraries(
    reforge_shaper_trajectory PUBLIC ReforgeShaper::common
)

add_library(reforge_shaper_model
    src/control/model/map_loader.cpp
    src/control/model/model_types.cpp
)
add_library(ReforgeShaper::model ALIAS reforge_shaper_model)

set_target_properties(reforge_shaper_model PROPERTIES
    EXPORT_NAME model
    POSITION_INDEPENDENT_CODE ON
)
target_compile_features(reforge_shaper_model PUBLIC cxx_std_17)
target_compile_definitions(reforge_shaper_model
    PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
)
if(NOT BUILD_SHARED_LIBS)
    target_compile_definitions(
        reforge_shaper_model PUBLIC REFORGE_SHAPER_STATIC_DEFINE
    )
endif()
target_include_directories(reforge_shaper_model
    PUBLIC
        "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
        "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
        "$<INSTALL_INTERFACE:${_reforge_shaper_install_includedir}>"
)
target_link_libraries(reforge_shaper_model PUBLIC ReforgeShaper::common)
target_link_libraries(
    reforge_shaper_model
    PRIVATE "$<BUILD_INTERFACE:nlohmann_json::nlohmann_json>"
)

add_library(reforge_shaper_runtime
    src/control/runtime/base_shaper.cpp
    src/control/runtime/modal_inference.cpp
    src/control/runtime/task_space.cpp
    src/control/runtime/weighting.cpp
)
add_library(ReforgeShaper::runtime ALIAS reforge_shaper_runtime)

set_target_properties(reforge_shaper_runtime PROPERTIES
    EXPORT_NAME runtime
    POSITION_INDEPENDENT_CODE ON
)
target_compile_features(reforge_shaper_runtime PUBLIC cxx_std_17)
target_compile_definitions(reforge_shaper_runtime
    PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
)
if(NOT BUILD_SHARED_LIBS)
    target_compile_definitions(
        reforge_shaper_runtime PUBLIC REFORGE_SHAPER_STATIC_DEFINE
    )
endif()
target_include_directories(reforge_shaper_runtime
    PUBLIC
        "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
        "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
        "$<INSTALL_INTERFACE:${_reforge_shaper_install_includedir}>"
)
target_link_libraries(
    reforge_shaper_runtime PUBLIC ReforgeShaper::common
)

add_library(reforge_shaper_window
    src/control/window/windowed_buffer.cpp
    src/control/window/shaper_window_handle.cpp
)
add_library(ReforgeShaper::window ALIAS reforge_shaper_window)

set_target_properties(reforge_shaper_window PROPERTIES
    EXPORT_NAME window
    POSITION_INDEPENDENT_CODE ON
)
target_compile_features(reforge_shaper_window PUBLIC cxx_std_17)
target_compile_definitions(reforge_shaper_window
    PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
)
if(NOT BUILD_SHARED_LIBS)
    target_compile_definitions(
        reforge_shaper_window PUBLIC REFORGE_SHAPER_STATIC_DEFINE
    )
endif()
target_include_directories(reforge_shaper_window
    PUBLIC
        "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
        "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
        "$<INSTALL_INTERFACE:${_reforge_shaper_install_includedir}>"
)
target_link_libraries(
    reforge_shaper_window
    PUBLIC ReforgeShaper::common ReforgeShaper::trajectory
)

add_library(reforge_shaper_dynamics
    src/util/robot_dynamics.cpp
)
add_library(ReforgeShaper::dynamics ALIAS reforge_shaper_dynamics)

set_target_properties(reforge_shaper_dynamics PROPERTIES
    EXPORT_NAME dynamics
    POSITION_INDEPENDENT_CODE ON
)
target_compile_features(reforge_shaper_dynamics PUBLIC cxx_std_17)
target_compile_definitions(reforge_shaper_dynamics
    PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
)
if(NOT BUILD_SHARED_LIBS)
    target_compile_definitions(
        reforge_shaper_dynamics PUBLIC REFORGE_SHAPER_STATIC_DEFINE
    )
endif()
target_include_directories(reforge_shaper_dynamics
    PUBLIC
        "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
        "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
        "$<INSTALL_INTERFACE:${_reforge_shaper_install_includedir}>"
)
target_link_libraries(
    reforge_shaper_dynamics
    PUBLIC ReforgeShaper::common
    PRIVATE pinocchio::pinocchio
)
target_link_libraries(
    reforge_shaper_runtime
    PRIVATE
        ReforgeShaper::dynamics
        ReforgeShaper::model
        ReforgeShaper::trajectory
)

if(REFORGE_SHAPER_QUALIFY_CLARABEL AND NOT REFORGE_SHAPER_CLARABEL_ROOT)
    message(FATAL_ERROR
        "REFORGE_SHAPER_QUALIFY_CLARABEL requires "
        "REFORGE_SHAPER_CLARABEL_ROOT."
    )
endif()

if(REFORGE_SHAPER_CLARABEL_ROOT)
    if(NOT IS_DIRECTORY "${REFORGE_SHAPER_CLARABEL_ROOT}/include")
        message(FATAL_ERROR
            "REFORGE_SHAPER_CLARABEL_ROOT must name the locked "
            "Clarabel.cpp source root."
        )
    endif()
    set(
        _reforge_clarabel_static_library
        "${REFORGE_SHAPER_CLARABEL_ROOT}/rust_wrapper/target/release/libclarabel_c.a"
    )
    if(NOT EXISTS "${_reforge_clarabel_static_library}")
        message(FATAL_ERROR
            "The locked Clarabel static library is missing: "
            "${_reforge_clarabel_static_library}"
        )
    endif()

    add_library(reforge_clarabel_c_static STATIC IMPORTED GLOBAL)
    set_target_properties(reforge_clarabel_c_static PROPERTIES
        IMPORTED_LOCATION "${_reforge_clarabel_static_library}"
        INTERFACE_INCLUDE_DIRECTORIES
            "${REFORGE_SHAPER_CLARABEL_ROOT}/include"
    )

    add_library(reforge_shaper_solver
        src/control/shaper/modal_switch_optimizer.cpp
        src/solver/clarabel_solver.cpp
        src/util/trajopt/traj_qp.cpp
    )
    add_library(ReforgeShaper::solver ALIAS reforge_shaper_solver)
    set_target_properties(reforge_shaper_solver PROPERTIES
        EXPORT_NAME solver
        POSITION_INDEPENDENT_CODE ON
    )
    target_compile_features(reforge_shaper_solver PUBLIC cxx_std_17)
    target_compile_definitions(
        reforge_shaper_solver PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
    )
    if(NOT BUILD_SHARED_LIBS)
        target_compile_definitions(
            reforge_shaper_solver PUBLIC REFORGE_SHAPER_STATIC_DEFINE
        )
    endif()
    target_include_directories(reforge_shaper_solver
        PUBLIC
            "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
            "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
            "$<INSTALL_INTERFACE:${_reforge_shaper_install_includedir}>"
        PRIVATE
            "${CMAKE_CURRENT_SOURCE_DIR}/src"
            "${REFORGE_SHAPER_CLARABEL_ROOT}/include"
    )
    target_link_libraries(
        reforge_shaper_solver
        PUBLIC ReforgeShaper::trajectory
        PRIVATE reforge_clarabel_c_static
    )
    target_sources(
        reforge_shaper_runtime PRIVATE
            src/control/shaper/residual_alignment.cpp
            src/control/shaper/residual_switch.cpp
            src/control/shaper/windowing.cpp
    )
    target_link_libraries(
        reforge_shaper_runtime PUBLIC
            ReforgeShaper::solver
            ReforgeShaper::trajectory
            ReforgeShaper::window
    )

    add_library(reforge_shaper_backend
        src/control/shaper/backend/native_shaper.cpp
    )
    add_library(ReforgeShaper::backend ALIAS reforge_shaper_backend)
    set_target_properties(reforge_shaper_backend PROPERTIES
        EXPORT_NAME backend
        POSITION_INDEPENDENT_CODE ON
    )
    target_compile_features(reforge_shaper_backend PUBLIC cxx_std_17)
    target_compile_definitions(
        reforge_shaper_backend PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
    )
    if(NOT BUILD_SHARED_LIBS)
        target_compile_definitions(
            reforge_shaper_backend PUBLIC REFORGE_SHAPER_STATIC_DEFINE
        )
    endif()
    target_include_directories(reforge_shaper_backend
        PUBLIC
            "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
            "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
            "$<INSTALL_INTERFACE:${_reforge_shaper_install_includedir}>"
    )
    target_link_libraries(
        reforge_shaper_backend
        PUBLIC
            ReforgeShaper::common
            ReforgeShaper::dynamics
            ReforgeShaper::model
            ReforgeShaper::runtime
            ReforgeShaper::solver
            ReforgeShaper::trajectory
            ReforgeShaper::window
    )
endif()

if(REFORGE_SHAPER_QUALIFY_CLARABEL)
    add_library(reforge_shaper_clarabel_qualification STATIC
        src/solver/clarabel_qualification_adapter.cpp
    )
    set_target_properties(
        reforge_shaper_clarabel_qualification
        PROPERTIES POSITION_INDEPENDENT_CODE ON
    )
    target_compile_features(
        reforge_shaper_clarabel_qualification PRIVATE cxx_std_17
    )
    target_include_directories(
        reforge_shaper_clarabel_qualification
        PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src"
    )
    target_link_libraries(
        reforge_shaper_clarabel_qualification
        PUBLIC Eigen3::Eigen
        PRIVATE ReforgeShaper::solver
    )
endif()

if(REFORGE_SHAPER_BUILD_PYTHON_BINDINGS AND pybind11_FOUND)
    pybind11_add_module(_native_shaper NO_EXTRAS
        bindings/python/reforge_core/control/native_shaper_module.cpp
        bindings/python/reforge_core/control/bind_base_shaper.cpp
        bindings/python/reforge_core/control/bind_runtime_services.cpp
        bindings/python/reforge_core/control/bind_map_loader.cpp
        bindings/python/reforge_core/control/bind_model_types.cpp
        bindings/python/reforge_core/control/bind_shaper_foundation.cpp
        bindings/python/reforge_core/control/bind_windowed_buffer.cpp
        bindings/python/reforge_core/util/bind_interpolation.cpp
        bindings/python/reforge_core/util/bind_robot_dynamics.cpp
        bindings/python/reforge_core/util/timing/bind_timing.cpp
    )
    target_compile_features(_native_shaper PRIVATE cxx_std_17)
    set_target_properties(
        _native_shaper PROPERTIES INSTALL_RPATH_USE_LINK_PATH ON
    )
    target_include_directories(
        _native_shaper PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/bindings/python"
    )
    target_link_libraries(
        _native_shaper
        PRIVATE
            ReforgeShaper::dynamics
            ReforgeShaper::model
            ReforgeShaper::runtime
            ReforgeShaper::trajectory
            ReforgeShaper::window
    )
    if(TARGET reforge_shaper_solver)
        target_sources(
            _native_shaper PRIVATE
                bindings/python/reforge_core/control/bind_residual.cpp
                bindings/python/reforge_core/control/bind_solver.cpp
        )
        target_compile_definitions(
            _native_shaper PRIVATE REFORGE_SHAPER_SOLVER_AVAILABLE
        )
        target_link_libraries(_native_shaper PRIVATE ReforgeShaper::solver)
    endif()
    if(TARGET reforge_shaper_backend)
        target_sources(
            _native_shaper PRIVATE
                bindings/python/reforge_core/control/bind_native_backend.cpp
        )
        target_compile_definitions(
            _native_shaper PRIVATE REFORGE_SHAPER_BACKEND_AVAILABLE
        )
        target_link_libraries(
            _native_shaper PRIVATE ReforgeShaper::backend
        )
    endif()
    if(REFORGE_SHAPER_QUALIFY_CLARABEL)
        target_compile_definitions(
            _native_shaper PRIVATE REFORGE_SHAPER_CLARABEL_QUALIFICATION
        )
        target_include_directories(
            _native_shaper PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src"
        )
        target_link_libraries(
            _native_shaper PRIVATE reforge_shaper_clarabel_qualification
        )
    endif()
    install(TARGETS _native_shaper
        LIBRARY DESTINATION "${_reforge_shaper_python_install_dir}"
        RUNTIME DESTINATION "${_reforge_shaper_python_install_dir}"
    )
    set(_reforge_shaper_complete_backend_available "false")
    if(TARGET reforge_shaper_backend)
        set(_reforge_shaper_complete_backend_available "true")
    endif()
    configure_file(
        qualification/native_build_manifest.json.in
        "${CMAKE_CURRENT_BINARY_DIR}/native_build_manifest.json"
        @ONLY
    )
    install(
        FILES
            "${CMAKE_CURRENT_BINARY_DIR}/native_build_manifest.json"
            qualification/THIRD_PARTY_NOTICES.txt
        DESTINATION "${_reforge_shaper_python_install_dir}/native"
    )
endif()

if(BUILD_TESTING)
    find_package(Threads REQUIRED)

    add_executable(
        reforge_shaper_common_tests
        tests/control/shaper/test_foundation.cpp
    )
    target_compile_features(reforge_shaper_common_tests PRIVATE cxx_std_17)
    target_link_libraries(
        reforge_shaper_common_tests PRIVATE ReforgeShaper::common
    )
    add_test(
        NAME reforge_shaper_common_tests
        COMMAND reforge_shaper_common_tests
    )

    add_executable(
        reforge_shaper_common_header_independence
        tests/native/header_independence.cpp
    )
    target_compile_features(
        reforge_shaper_common_header_independence PRIVATE cxx_std_17
    )
    target_link_libraries(
        reforge_shaper_common_header_independence
        PRIVATE
            ReforgeShaper::dynamics
            ReforgeShaper::model
            ReforgeShaper::runtime
            ReforgeShaper::trajectory
            ReforgeShaper::window
    )
    if(TARGET reforge_shaper_backend)
        target_link_libraries(
            reforge_shaper_common_header_independence
            PRIVATE ReforgeShaper::backend
        )
    endif()
    add_test(
        NAME reforge_shaper_common_header_independence
        COMMAND reforge_shaper_common_header_independence
    )

    add_executable(
        reforge_shaper_trajectory_tests
        tests/util/test_trajectory_foundation.cpp
    )
    target_compile_features(
        reforge_shaper_trajectory_tests PRIVATE cxx_std_17
    )
    target_link_libraries(
        reforge_shaper_trajectory_tests
        PRIVATE ReforgeShaper::trajectory
    )
    add_test(
        NAME reforge_shaper_trajectory_tests
        COMMAND reforge_shaper_trajectory_tests
    )

    add_executable(
        reforge_shaper_model_tests
        tests/control/model/test_model_types.cpp
    )
    target_compile_features(
        reforge_shaper_model_tests PRIVATE cxx_std_17
    )
    target_link_libraries(
        reforge_shaper_model_tests PRIVATE ReforgeShaper::model
    )
    add_test(
        NAME reforge_shaper_model_tests
        COMMAND reforge_shaper_model_tests
    )

    add_executable(
        reforge_shaper_map_loader_tests
        tests/control/model/test_map_loader.cpp
    )
    target_compile_features(
        reforge_shaper_map_loader_tests PRIVATE cxx_std_17
    )
    target_compile_definitions(
        reforge_shaper_map_loader_tests
        PRIVATE
            REFORGE_MODEL_FIXTURE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/models"
    )
    target_link_libraries(
        reforge_shaper_map_loader_tests PRIVATE ReforgeShaper::model
    )
    add_test(
        NAME reforge_shaper_map_loader_tests
        COMMAND reforge_shaper_map_loader_tests
    )

    add_executable(
        reforge_shaper_runtime_tests
        tests/control/runtime/test_base_shaper.cpp
    )
    target_compile_features(
        reforge_shaper_runtime_tests PRIVATE cxx_std_17
    )
    target_link_libraries(
        reforge_shaper_runtime_tests PRIVATE ReforgeShaper::runtime
    )
    add_test(
        NAME reforge_shaper_runtime_tests
        COMMAND reforge_shaper_runtime_tests
    )

    add_executable(
        reforge_shaper_runtime_service_tests
        tests/control/runtime/test_runtime_services.cpp
    )
    target_compile_features(
        reforge_shaper_runtime_service_tests PRIVATE cxx_std_17
    )
    target_link_libraries(
        reforge_shaper_runtime_service_tests
        PRIVATE
            ReforgeShaper::runtime
            ReforgeShaper::dynamics
            ReforgeShaper::model
    )
    add_test(
        NAME reforge_shaper_runtime_service_tests
        COMMAND reforge_shaper_runtime_service_tests
    )

    add_executable(
        reforge_shaper_task_space_tests
        tests/control/runtime/test_task_space.cpp
    )
    target_compile_features(
        reforge_shaper_task_space_tests PRIVATE cxx_std_17
    )
    target_link_libraries(
        reforge_shaper_task_space_tests
        PRIVATE ReforgeShaper::runtime ReforgeShaper::dynamics
    )
    add_test(
        NAME reforge_shaper_task_space_tests
        COMMAND reforge_shaper_task_space_tests
    )

    add_executable(
        reforge_shaper_window_tests
        tests/control/window/test_windowed_buffer.cpp
    )
    target_compile_features(
        reforge_shaper_window_tests PRIVATE cxx_std_17
    )
    target_link_libraries(
        reforge_shaper_window_tests
        PRIVATE ReforgeShaper::window Threads::Threads
    )
    add_test(
        NAME reforge_shaper_window_tests
        COMMAND reforge_shaper_window_tests
    )

    add_executable(
        reforge_shaper_dynamics_tests
        tests/util/test_robot_dynamics.cpp
    )
    target_compile_features(
        reforge_shaper_dynamics_tests PRIVATE cxx_std_17
    )
    target_compile_definitions(
        reforge_shaper_dynamics_tests
        PRIVATE
            REFORGE_DYNAMICS_FIXTURE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/urdf"
    )
    target_link_libraries(
        reforge_shaper_dynamics_tests
        PRIVATE ReforgeShaper::dynamics Threads::Threads
    )
    add_test(
        NAME reforge_shaper_dynamics_tests
        COMMAND reforge_shaper_dynamics_tests
    )

    if(REFORGE_SHAPER_QUALIFY_CLARABEL)
        add_executable(
            reforge_shaper_clarabel_qualification_tests
            tests/solver/test_clarabel_qualification.cpp
        )
        target_compile_features(
            reforge_shaper_clarabel_qualification_tests PRIVATE cxx_std_17
        )
        target_include_directories(
            reforge_shaper_clarabel_qualification_tests
            PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src"
        )
        target_link_libraries(
            reforge_shaper_clarabel_qualification_tests
            PRIVATE reforge_shaper_clarabel_qualification
        )
        add_test(
            NAME reforge_shaper_clarabel_qualification_tests
            COMMAND reforge_shaper_clarabel_qualification_tests
        )
    endif()
    if(TARGET reforge_shaper_backend)
        add_executable(
            reforge_shaper_backend_tests
            tests/control/shaper/backend/test_native_shaper.cpp
        )
        target_compile_features(
            reforge_shaper_backend_tests PRIVATE cxx_std_17
        )
        target_link_libraries(
            reforge_shaper_backend_tests PRIVATE ReforgeShaper::backend
        )
        add_test(
            NAME reforge_shaper_backend_tests
            COMMAND reforge_shaper_backend_tests
        )
        add_executable(
            reforge_shaper_backend_task_space_tests
            tests/control/shaper/backend/test_native_shaper_task_space.cpp
        )
        target_compile_features(
            reforge_shaper_backend_task_space_tests PRIVATE cxx_std_17
        )
        target_link_libraries(
            reforge_shaper_backend_task_space_tests
            PRIVATE ReforgeShaper::backend
        )
        add_test(
            NAME reforge_shaper_backend_task_space_tests
            COMMAND reforge_shaper_backend_task_space_tests
        )
    endif()
    if(TARGET reforge_shaper_solver)
        add_executable(
            reforge_shaper_solver_tests
            tests/util/trajopt/test_traj_qp.cpp
        )
        target_compile_features(
            reforge_shaper_solver_tests PRIVATE cxx_std_17
        )
        target_link_libraries(
            reforge_shaper_solver_tests PRIVATE ReforgeShaper::solver
        )
        add_test(
            NAME reforge_shaper_solver_tests
            COMMAND reforge_shaper_solver_tests
        )
        add_executable(
            reforge_shaper_residual_tests
            tests/control/runtime/test_residual.cpp
        )
        target_compile_features(
            reforge_shaper_residual_tests PRIVATE cxx_std_17
        )
        target_link_libraries(
            reforge_shaper_residual_tests
            PRIVATE ReforgeShaper::runtime
        )
        add_test(
            NAME reforge_shaper_residual_tests
            COMMAND reforge_shaper_residual_tests
        )
        add_executable(
            reforge_shaper_windowing_tests
            tests/control/runtime/test_windowing.cpp
        )
        target_compile_features(
            reforge_shaper_windowing_tests PRIVATE cxx_std_17
        )
        target_link_libraries(
            reforge_shaper_windowing_tests PRIVATE ReforgeShaper::runtime
        )
        add_test(
            NAME reforge_shaper_windowing_tests
            COMMAND reforge_shaper_windowing_tests
        )
    endif()
endif()

if(TARGET reforge_shaper_common)
    if(REFORGE_SHAPER_INSTALL_RUNTIME_DEPENDENCIES AND NOT BUILD_SHARED_LIBS)
        message(FATAL_ERROR
            "REFORGE_SHAPER_INSTALL_RUNTIME_DEPENDENCIES requires "
            "BUILD_SHARED_LIBS=ON so private dependencies can remain behind "
            "the installed SDK shared-library boundary."
        )
    endif()

    set(_reforge_shaper_install_targets
        reforge_shaper_common
        reforge_shaper_trajectory
        reforge_shaper_model
        reforge_shaper_dynamics
        reforge_shaper_runtime
        reforge_shaper_window
    )
    if(TARGET reforge_shaper_solver)
        list(APPEND _reforge_shaper_install_targets reforge_shaper_solver)
    endif()
    if(SKBUILD AND TARGET reforge_shaper_backend)
        list(APPEND _reforge_shaper_install_targets reforge_shaper_backend)
    endif()

    if(SKBUILD)
        if(APPLE)
            set(_reforge_shaper_sdk_install_rpath "@loader_path")
            set(
                _reforge_shaper_extension_install_rpath
                "@loader_path/../cpp/lib"
            )
        elseif(UNIX)
            set(_reforge_shaper_sdk_install_rpath "$ORIGIN")
            set(
                _reforge_shaper_extension_install_rpath
                "$ORIGIN/../cpp/lib"
            )
        endif()
        if(DEFINED _reforge_shaper_sdk_install_rpath)
            set_target_properties(
                ${_reforge_shaper_install_targets}
                PROPERTIES
                    INSTALL_RPATH "${_reforge_shaper_sdk_install_rpath}"
                    INSTALL_RPATH_USE_LINK_PATH OFF
            )
            if(TARGET _native_shaper)
                set_target_properties(
                    _native_shaper
                    PROPERTIES
                        INSTALL_RPATH
                            "${_reforge_shaper_extension_install_rpath}"
                        INSTALL_RPATH_USE_LINK_PATH OFF
                )
            endif()
        endif()
    elseif(BUILD_SHARED_LIBS)
        set_target_properties(
            ${_reforge_shaper_install_targets}
            PROPERTIES INSTALL_RPATH "${REFORGE_SHAPER_INSTALL_RPATH}"
        )
    endif()

    set(_reforge_shaper_runtime_dependency_set_args)
    if(REFORGE_SHAPER_INSTALL_RUNTIME_DEPENDENCIES)
        set(
            _reforge_shaper_runtime_dependency_set_args
            RUNTIME_DEPENDENCY_SET ReforgeShaperRuntimeDependencies
        )
    endif()
    install(
        TARGETS ${_reforge_shaper_install_targets}
        EXPORT ReforgeShaperTargets
        ${_reforge_shaper_runtime_dependency_set_args}
        ARCHIVE DESTINATION "${_reforge_shaper_install_libdir}"
        LIBRARY DESTINATION "${_reforge_shaper_install_libdir}"
        RUNTIME DESTINATION "${_reforge_shaper_install_bindir}"
        INCLUDES DESTINATION "${_reforge_shaper_install_includedir}"
    )
    if(TARGET reforge_shaper_solver)
        install(
            FILES "${_reforge_clarabel_static_library}"
            DESTINATION "${_reforge_shaper_install_libdir}"
        )
    endif()
    set(_reforge_shaper_public_headers
        include/reforge_core/control/model/map_loader.hpp
        include/reforge_core/control/model/model_types.hpp
        include/reforge_core/control/python/windowed_buffer.hpp
        include/reforge_core/control/runtime/base_shaper.hpp
        include/reforge_core/control/runtime/modal_inference.hpp
        include/reforge_core/control/runtime/task_space.hpp
        include/reforge_core/control/runtime/weighting.hpp
        include/reforge_core/control/shaper/config.hpp
        include/reforge_core/control/shaper/modal_inference_strategy.hpp
        include/reforge_core/control/shaper/residual_switch_config.hpp
        include/reforge_core/control/shaper/shaper_window_helpers.hpp
        include/reforge_core/control/window/shaper_window_handle.hpp
        include/reforge_core/native/common/array_types.hpp
        include/reforge_core/native/common/errors.hpp
        include/reforge_core/util/interpolation.hpp
        include/reforge_core/util/robot_dynamics.hpp
        include/reforge_core/util/timing/joint_limits.hpp
        include/reforge_core/util/timing/joint_trajectory.hpp
        include/reforge_core/util/timing/resampling.hpp
    )
    if(TARGET reforge_shaper_solver)
        list(APPEND _reforge_shaper_public_headers
            include/reforge_core/control/shaper/modal_switch_optimizer.hpp
            include/reforge_core/control/shaper/residual_alignment.hpp
            include/reforge_core/control/shaper/residual_switch.hpp
            include/reforge_core/control/shaper/windowing.hpp
            include/reforge_core/util/trajopt/traj_qp.hpp
        )
    endif()
    if(SKBUILD)
        install(
            DIRECTORY include/reforge_core
            DESTINATION "${_reforge_shaper_install_includedir}"
            FILES_MATCHING PATTERN "*.hpp"
        )
    else()
        foreach(_reforge_shaper_header IN LISTS _reforge_shaper_public_headers)
            get_filename_component(
                _reforge_shaper_header_directory
                "${_reforge_shaper_header}"
                DIRECTORY
            )
            file(
                RELATIVE_PATH
                _reforge_shaper_header_install_directory
                "${CMAKE_CURRENT_SOURCE_DIR}/include"
                "${CMAKE_CURRENT_SOURCE_DIR}/${_reforge_shaper_header_directory}"
            )
            install(
                FILES "${_reforge_shaper_header}"
                DESTINATION
                    "${_reforge_shaper_install_includedir}/${_reforge_shaper_header_install_directory}"
            )
        endforeach()
    endif()
    if(REFORGE_SHAPER_INSTALL_RUNTIME_DEPENDENCIES)
        set(_reforge_shaper_runtime_dependency_destination
            "${_reforge_shaper_install_libdir}"
        )
        if(REFORGE_SHAPER_RUNTIME_DEPENDENCY_INSTALL_SUBDIR)
            if(IS_ABSOLUTE
                "${REFORGE_SHAPER_RUNTIME_DEPENDENCY_INSTALL_SUBDIR}"
            )
                message(FATAL_ERROR
                    "REFORGE_SHAPER_RUNTIME_DEPENDENCY_INSTALL_SUBDIR must "
                    "be relative."
                )
            endif()
            set(_reforge_shaper_runtime_dependency_destination
                "${_reforge_shaper_install_libdir}/${REFORGE_SHAPER_RUNTIME_DEPENDENCY_INSTALL_SUBDIR}"
            )
        endif()
        set(_reforge_shaper_dependency_include_regexes)
        foreach(_reforge_shaper_dependency_prefix IN LISTS CMAKE_PREFIX_PATH)
            if(IS_DIRECTORY "${_reforge_shaper_dependency_prefix}/lib")
                list(APPEND _reforge_shaper_dependency_include_regexes
                    "^${_reforge_shaper_dependency_prefix}/lib/.*"
                )
            endif()
        endforeach()
        if(NOT _reforge_shaper_dependency_include_regexes)
            message(FATAL_ERROR
                "REFORGE_SHAPER_INSTALL_RUNTIME_DEPENDENCIES requires at "
                "least one CMAKE_PREFIX_PATH entry with a lib directory."
            )
        endif()
        install(
            RUNTIME_DEPENDENCY_SET ReforgeShaperRuntimeDependencies
            DESTINATION "${_reforge_shaper_runtime_dependency_destination}"
            POST_INCLUDE_REGEXES
                ${_reforge_shaper_dependency_include_regexes}
            POST_EXCLUDE_REGEXES
                "^/lib/.*"
                "^/lib64/.*"
                "^/usr/lib/.*"
                "^/usr/lib64/.*"
        )
    endif()
    install(
        FILES
            "${_reforge_shaper_generated_include_dir}/reforge_core/native/common/export.hpp"
        DESTINATION
            "${_reforge_shaper_install_includedir}/reforge_core/native/common"
    )
    if(NOT SKBUILD)
        install(
            DIRECTORY examples/reforge_shaper_demo/
            DESTINATION
                "${CMAKE_INSTALL_DATADIR}/doc/reforge-core-shaper/examples"
            FILES_MATCHING
                PATTERN "CMakeLists.txt"
                PATTERN "README.md"
                PATTERN "*.cpp"
        )
    endif()

    set(REFORGE_SHAPER_CONFIG_DEPENDENCY_DECLARATIONS
        "find_dependency(Eigen3 REQUIRED NO_MODULE)\n"
    )
    if(NOT BUILD_SHARED_LIBS)
        string(APPEND REFORGE_SHAPER_CONFIG_DEPENDENCY_DECLARATIONS
            "find_dependency(pinocchio 4 CONFIG REQUIRED)\n"
        )
        if(TARGET reforge_shaper_solver)
            string(APPEND REFORGE_SHAPER_CONFIG_DEPENDENCY_DECLARATIONS
                "\n"
                "if(\n"
                "    EXISTS \"\${PACKAGE_PREFIX_DIR}/${_reforge_shaper_install_libdir}/libclarabel_c.a\"\n"
                "    AND NOT TARGET reforge_clarabel_c_static\n"
                ")\n"
                "    add_library(reforge_clarabel_c_static STATIC IMPORTED)\n"
                "    set_target_properties(reforge_clarabel_c_static PROPERTIES\n"
                "        IMPORTED_LOCATION\n"
                "            \"\${PACKAGE_PREFIX_DIR}/${_reforge_shaper_install_libdir}/libclarabel_c.a\"\n"
                "    )\n"
                "endif()\n"
            )
        endif()
    endif()

    configure_package_config_file(
        cmake/ReforgeShaperConfig.cmake.in
        "${CMAKE_CURRENT_BINARY_DIR}/ReforgeShaperConfig.cmake"
        INSTALL_DESTINATION
            "${_reforge_shaper_install_libdir}/cmake/ReforgeShaper"
    )
    write_basic_package_version_file(
        "${CMAKE_CURRENT_BINARY_DIR}/ReforgeShaperConfigVersion.cmake"
        VERSION "${PROJECT_VERSION}"
        COMPATIBILITY SameMajorVersion
    )
    install(
        EXPORT ReforgeShaperTargets
        FILE ReforgeShaperTargets.cmake
        NAMESPACE ReforgeShaper::
        DESTINATION
            "${_reforge_shaper_install_libdir}/cmake/ReforgeShaper"
    )
    install(
        FILES
            "${CMAKE_CURRENT_BINARY_DIR}/ReforgeShaperConfig.cmake"
            "${CMAKE_CURRENT_BINARY_DIR}/ReforgeShaperConfigVersion.cmake"
        DESTINATION
            "${_reforge_shaper_install_libdir}/cmake/ReforgeShaper"
    )
endif()
