# Copyright 2025 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

cmake_minimum_required(VERSION 3.21)
project(svs_runtime VERSION 0.5.0 LANGUAGES CXX)
set(TARGET_NAME svs_runtime)

# IVF requires MKL, so it's optional
option(SVS_RUNTIME_ENABLE_IVF "Enable compilation of SVS runtime with IVF support (requires MKL)" OFF)

set(SVS_RUNTIME_HEADERS
    include/svs/runtime/version.h
    include/svs/runtime/api_defs.h
    include/svs/runtime/training.h
    include/svs/runtime/vamana_index.h
    include/svs/runtime/dynamic_vamana_index.h
    include/svs/runtime/flat_index.h
)

set(SVS_RUNTIME_SOURCES
    src/svs_runtime_utils.h
    src/dynamic_vamana_index_impl.h
    src/dynamic_vamana_index_leanvec_impl.h
    src/training_impl.h
    src/flat_index_impl.h
    src/api_defs.cpp
    src/training.cpp
    src/vamana_index.cpp
    src/dynamic_vamana_index.cpp
    src/flat_index.cpp
)

# Add IVF files if enabled
if (SVS_RUNTIME_ENABLE_IVF)
    message(STATUS "SVS runtime will be built with IVF support (requires MKL)")
    list(APPEND SVS_RUNTIME_HEADERS
        include/svs/runtime/ivf_index.h
        include/svs/runtime/dynamic_ivf_index.h
    )
    list(APPEND SVS_RUNTIME_SOURCES
        src/ivf_index_impl.h
        src/dynamic_ivf_index_impl.h
        src/ivf_index.cpp
        src/dynamic_ivf_index.cpp
    )
else()
    message(STATUS "SVS runtime will be built without IVF support")
endif()

# The non-LTO fallback below is correct but slower, so nothing fails and CI stays
# green. Set this where the LTO archive is the point (CI) to make the drop an error.
option(SVS_REQUIRE_LTO_ARCHIVE
    "Fail instead of warn when the compiler cannot consume the LVQ/LeanVec LTO archive" OFF)

option(SVS_RUNTIME_ENABLE_LVQ_LEANVEC "Enable compilation of SVS runtime with LVQ and LeanVec support" ON)
if (SVS_RUNTIME_ENABLE_LVQ_LEANVEC)
    message(STATUS "SVS runtime will be built with LVQ support")
else()
    message(STATUS "SVS runtime will be built without LVQ or LeanVec support")
endif()

add_library(${TARGET_NAME} SHARED
    ${SVS_RUNTIME_HEADERS}
    ${SVS_RUNTIME_SOURCES}
)

target_include_directories(${TARGET_NAME} PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

find_package(OpenMP REQUIRED)
target_link_libraries(${TARGET_NAME} PUBLIC OpenMP::OpenMP_CXX)

target_compile_options(${TARGET_NAME} PRIVATE
    -DSVS_ENABLE_OMP=1
    -fvisibility=hidden
)

# Use cmake/clang-tidy for configuration. Should be ON for releases.
option(SVS_EXPERIMENTAL_CLANG_TIDY
    "Run the clang-tidy static analyzer on the cpp runtime bindings."
    OFF
)
if(SVS_EXPERIMENTAL_CLANG_TIDY)
    list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../cmake")
    include(clang-tidy)
    if(CLANG_TIDY_COMMAND)
        set_target_properties(${TARGET_NAME}
            PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY_COMMAND}"
        )
    endif()
endif()

if(UNIX AND NOT APPLE)
    # Don't export 3rd-party symbols from the lib
    target_link_options(${TARGET_NAME} PRIVATE "SHELL:-Wl,--exclude-libs,ALL")
endif()

target_compile_features(${TARGET_NAME} INTERFACE cxx_std_20)
set_target_properties(${TARGET_NAME} PROPERTIES PUBLIC_HEADER "${SVS_RUNTIME_HEADERS}")
set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD 20)
set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD_REQUIRED ON)
set_target_properties(${TARGET_NAME} PROPERTIES CXX_EXTENSIONS OFF)
set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} )

if (SVS_RUNTIME_ENABLE_LVQ_LEANVEC)

    if(RUNTIME_BINDINGS_PRIVATE_SOURCE_BUILD)
        message(STATUS "Building directly from private sources")
        target_link_libraries(${TARGET_NAME} PRIVATE
            svs::svs
            svs_compile_options
        )
        if(SVS_EXPERIMENTAL_LINK_STATIC_MKL)
            link_mkl_static(${TARGET_NAME})
        endif()
        if(SVS_LVQ_HEADER)
            target_compile_definitions(${TARGET_NAME} PRIVATE
                SVS_LVQ_HEADER="${SVS_LVQ_HEADER}"
            )
        endif()
        if(SVS_LEANVEC_HEADER)
            target_compile_definitions(${TARGET_NAME} PRIVATE
                SVS_LEANVEC_HEADER="${SVS_LEANVEC_HEADER}"
            )
        endif()
    elseif(TARGET svs::svs)
        message(FATAL_ERROR
            "Pre-built LVQ/LeanVec SVS library cannot be used in SVS main build. "
            "Please build SVS Runtime using bindings/cpp directory as CMake source root."
        )
    else()
        # Links to LTO-enabled static library, requires GCC/G++ 11.2
        if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11.2" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "11.3")
            set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/nightly/svs-shared-library-lto-nightly-2026-10-15-1661.tar.gz"
                CACHE STRING "URL to download SVS shared library")
        else()
            if(SVS_REQUIRE_LTO_ARCHIVE)
                set(SVS_LTO_MESSAGE_LEVEL FATAL_ERROR)
            else()
                set(SVS_LTO_MESSAGE_LEVEL WARNING)
            endif()
            message(${SVS_LTO_MESSAGE_LEVEL}
                "Pre-built LVQ/LeanVec SVS library requires GCC/G++ v.11.2 to apply LTO optimizations."
                "Current compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}"
            )
            set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v0.4.0/svs-shared-library.tar.gz"
                CACHE STRING "URL to download SVS shared library")
        endif()
        include(FetchContent)
        # DOWNLOAD_EXTRACT_TIMESTAMP needs CMake 3.24+; 3.22 is still around locally.
        set(SVS_FETCH_EXTRA_ARGS)
        if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24")
            list(APPEND SVS_FETCH_EXTRA_ARGS DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
        endif()
        FetchContent_Declare(
            svs
            URL ${SVS_URL}
            ${SVS_FETCH_EXTRA_ARGS}
        )
        FetchContent_MakeAvailable(svs)
        list(APPEND CMAKE_PREFIX_PATH "${svs_SOURCE_DIR}")
        find_package(svs REQUIRED)

	    # Strip svs::svs_compile_options gcc-only flags for clang-tidy
        if(SVS_EXPERIMENTAL_CLANG_TIDY AND TARGET svs::svs_compile_options)
            get_target_property(_svs_co_opts svs::svs_compile_options INTERFACE_COMPILE_OPTIONS)
            if(_svs_co_opts)
                list(FILTER _svs_co_opts EXCLUDE REGEX "^-fconcepts-diagnostics-depth")
                list(FILTER _svs_co_opts EXCLUDE REGEX "^-ftemplate-backtrace-limit")
                set_property(TARGET svs::svs_compile_options
                    PROPERTY INTERFACE_COMPILE_OPTIONS "${_svs_co_opts}"
                )
            endif()
        endif()

        target_link_libraries(${TARGET_NAME} PRIVATE
            svs::svs
            svs::svs_compile_options
            svs::svs_static_library
        )
    endif()
    target_compile_definitions(${TARGET_NAME} PUBLIC SVS_RUNTIME_HAVE_LVQ_LEANVEC)
else()
    # Include the SVS library directly if needed.
    if (NOT TARGET svs::svs)
        # Pass IVF flag to parent build if IVF is enabled
        if (SVS_RUNTIME_ENABLE_IVF)
            set(SVS_EXPERIMENTAL_ENABLE_IVF ON CACHE BOOL "" FORCE)
        endif()
        add_subdirectory("../.." "${CMAKE_CURRENT_BINARY_DIR}/svs")
    endif()
    target_link_libraries(${TARGET_NAME} PRIVATE
       svs::svs
       svs_compile_options
       svs_x86_options_base
    )
endif()

# IVF requires Intel(R) MKL support
if (SVS_RUNTIME_ENABLE_IVF)
    target_compile_definitions(${TARGET_NAME} PRIVATE SVS_RUNTIME_ENABLE_IVF SVS_HAVE_MKL=1)
endif()

# installing
include(GNUInstallDirs)

set(SVS_RUNTIME_EXPORT_NAME ${TARGET_NAME})
set(VERSION_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/${SVS_RUNTIME_EXPORT_NAME}ConfigVersion.cmake")
set(SVS_RUNTIME_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/svs_runtime)
set(SVS_RUNTIME_COMPONENT_NAME "Runtime")

install(TARGETS ${TARGET_NAME}
    EXPORT ${SVS_RUNTIME_EXPORT_NAME}
    COMPONENT ${SVS_RUNTIME_COMPONENT_NAME}
    LIBRARY DESTINATION lib
    PUBLIC_HEADER DESTINATION include/svs/runtime
    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

install(DIRECTORY include/svs/runtime
    COMPONENT ${SVS_RUNTIME_COMPONENT_NAME}
    DESTINATION include/svs
    FILES_MATCHING PATTERN "*.h"
)

install(EXPORT ${SVS_RUNTIME_EXPORT_NAME}
    COMPONENT ${SVS_RUNTIME_COMPONENT_NAME}
    NAMESPACE svs::
    DESTINATION ${SVS_RUNTIME_CONFIG_INSTALL_DIR}
)

include(CMakePackageConfigHelpers)
configure_package_config_file(
    "${CMAKE_CURRENT_LIST_DIR}/runtimeConfig.cmake.in"
    "${CMAKE_CURRENT_BINARY_DIR}/${SVS_RUNTIME_EXPORT_NAME}Config.cmake"
    INSTALL_DESTINATION "${SVS_RUNTIME_CONFIG_INSTALL_DIR}"
)

# Don't make compatibility guarantees until we reach a compatibility milestone.
write_basic_package_version_file(
    ${VERSION_CONFIG}
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY ExactVersion
)

install(FILES
    "${CMAKE_CURRENT_BINARY_DIR}/${SVS_RUNTIME_EXPORT_NAME}Config.cmake"
    "${VERSION_CONFIG}"
    COMPONENT ${SVS_RUNTIME_COMPONENT_NAME}
    DESTINATION "${SVS_RUNTIME_CONFIG_INSTALL_DIR}"
)

# Build tests if requested
if(SVS_BUILD_RUNTIME_TESTS)
    add_subdirectory(tests)
endif()
