cmake_minimum_required(VERSION 3.20)
project(cf_datahive_cpp LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(GNUInstallDirs)

function(_cf_datahive_find_repo_native_duckdb_root out_var)
  set(_search_roots
    "${CMAKE_SOURCE_DIR}"
    "${PROJECT_SOURCE_DIR}"
    "${CMAKE_CURRENT_SOURCE_DIR}"
  )

  foreach(_search_root IN LISTS _search_roots)
    if (_search_root STREQUAL "" OR NOT IS_DIRECTORY "${_search_root}")
      continue()
    endif()

    set(_cursor "${_search_root}")
    while (TRUE)
      set(_candidate "${_cursor}/.native_deps/duckdb")
      if (EXISTS "${_candidate}")
        set(${out_var} "${_candidate}" PARENT_SCOPE)
        return()
      endif()

      get_filename_component(_parent "${_cursor}" DIRECTORY)
      if (_parent STREQUAL "${_cursor}")
        break()
      endif()
      set(_cursor "${_parent}")
    endwhile()
  endforeach()

  set(${out_var} "" PARENT_SCOPE)
endfunction()

if (NOT DEFINED CF_SDK_INCLUDE OR CF_SDK_INCLUDE STREQUAL "")
  find_package(Python3 REQUIRED COMPONENTS Interpreter)
  execute_process(
    COMMAND ${Python3_EXECUTABLE} -c "import cogniflow_pipeline_sdk as s; print(getattr(s, 'cf_sdk_include_path', lambda: '')())"
    OUTPUT_VARIABLE CF_SDK_INCLUDE
    OUTPUT_STRIP_TRAILING_WHITESPACE
  )
endif()

set(_cf_sdk_vendor_include "${CMAKE_CURRENT_SOURCE_DIR}/../../../../cf_pipeline/cf_pipeline_sdk/vendor/cf_pipeline_engine/include")
if ((CF_SDK_INCLUDE STREQUAL "" OR NOT EXISTS "${CF_SDK_INCLUDE}/cf_step_utils.h") AND EXISTS "${_cf_sdk_vendor_include}/cf_step_utils.h")
  set(CF_SDK_INCLUDE "${_cf_sdk_vendor_include}")
endif()

if (NOT EXISTS "${CF_SDK_INCLUDE}/cf_step_utils.h")
  message(FATAL_ERROR "Cogniflow SDK headers not found in '${CF_SDK_INCLUDE}'. Reinstall cf-pipeline-sdk.")
endif()

if (DEFINED ENV{CF_DATAHIVE_CPP_DUCKDB_LINKAGE})
  set(_duckdb_linkage_default "$ENV{CF_DATAHIVE_CPP_DUCKDB_LINKAGE}")
else()
  set(_duckdb_linkage_default "static")
endif()

if (DEFINED ENV{CF_DATAHIVE_CPP_DUCKDB_INCLUDE})
  set(_duckdb_include_default "$ENV{CF_DATAHIVE_CPP_DUCKDB_INCLUDE}")
else()
  set(_duckdb_include_default "")
endif()

if (DEFINED ENV{CF_DATAHIVE_CPP_DUCKDB_LIB})
  set(_duckdb_lib_default "$ENV{CF_DATAHIVE_CPP_DUCKDB_LIB}")
else()
  set(_duckdb_lib_default "")
endif()

if (DEFINED ENV{CF_DATAHIVE_CPP_DUCKDB_SOURCE})
  set(_duckdb_source_default "$ENV{CF_DATAHIVE_CPP_DUCKDB_SOURCE}")
else()
  set(_duckdb_source_default "")
endif()

if (DEFINED ENV{CF_DATAHIVE_CPP_DUCKDB_DLL})
  set(_duckdb_dll_default "$ENV{CF_DATAHIVE_CPP_DUCKDB_DLL}")
else()
  set(_duckdb_dll_default "")
endif()

set(CF_DATAHIVE_CPP_DUCKDB_LINKAGE "${_duckdb_linkage_default}" CACHE STRING "DuckDB linkage mode: static, shared, or auto")
set_property(CACHE CF_DATAHIVE_CPP_DUCKDB_LINKAGE PROPERTY STRINGS static shared auto)
set(CF_DATAHIVE_CPP_DUCKDB_INCLUDE_DIR "${_duckdb_include_default}" CACHE PATH "Path to DuckDB include directory")
set(CF_DATAHIVE_CPP_DUCKDB_LIBRARY "${_duckdb_lib_default}" CACHE FILEPATH "Path to DuckDB library file")
set(CF_DATAHIVE_CPP_DUCKDB_SOURCE "${_duckdb_source_default}" CACHE FILEPATH "Path to DuckDB amalgamation source file (duckdb.cpp)")
set(CF_DATAHIVE_CPP_DUCKDB_RUNTIME_DLL "${_duckdb_dll_default}" CACHE FILEPATH "Path to the DuckDB runtime DLL for shared-link consumer staging")

_cf_datahive_find_repo_native_duckdb_root(_native_duckdb_root)
if (_native_duckdb_root STREQUAL "")
  set(_native_duckdb_root "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../.native_deps/duckdb")
endif()
set(_native_duckdb_include "${_native_duckdb_root}/include")
set(_native_duckdb_include_flat "${_native_duckdb_root}")
set(_native_duckdb_src "${_native_duckdb_root}/src")
set(_native_duckdb_lib_win "${_native_duckdb_root}/lib/duckdb.lib")
set(_native_duckdb_lib_mingw "${_native_duckdb_root}/lib/libduckdb.a")
set(_native_duckdb_lib_flat "${_native_duckdb_root}/duckdb.lib")
set(_native_duckdb_src_cpp "${_native_duckdb_src}/duckdb.cpp")
set(_native_duckdb_runtime_dll "${_native_duckdb_root}/bin/duckdb.dll")

string(TOLOWER "${CF_DATAHIVE_CPP_DUCKDB_LINKAGE}" _duckdb_linkage_mode)
if (NOT _duckdb_linkage_mode STREQUAL "static" AND
    NOT _duckdb_linkage_mode STREQUAL "shared" AND
    NOT _duckdb_linkage_mode STREQUAL "auto")
  message(FATAL_ERROR "Invalid CF_DATAHIVE_CPP_DUCKDB_LINKAGE='${CF_DATAHIVE_CPP_DUCKDB_LINKAGE}'. Allowed values: static, shared, auto.")
endif()

if (CF_DATAHIVE_CPP_DUCKDB_SOURCE STREQUAL "" AND EXISTS "${_native_duckdb_src_cpp}")
  set(CF_DATAHIVE_CPP_DUCKDB_SOURCE "${_native_duckdb_src_cpp}")
endif()

if (CF_DATAHIVE_CPP_DUCKDB_INCLUDE_DIR STREQUAL "")
  if (EXISTS "${_native_duckdb_src}/duckdb.h")
    set(CF_DATAHIVE_CPP_DUCKDB_INCLUDE_DIR "${_native_duckdb_src}")
  elseif (EXISTS "${_native_duckdb_include}/duckdb.h")
    set(CF_DATAHIVE_CPP_DUCKDB_INCLUDE_DIR "${_native_duckdb_include}")
  elseif (EXISTS "${_native_duckdb_include_flat}/duckdb.h")
    set(CF_DATAHIVE_CPP_DUCKDB_INCLUDE_DIR "${_native_duckdb_include_flat}")
  endif()
endif()

if (CF_DATAHIVE_CPP_DUCKDB_LIBRARY STREQUAL "")
  if (EXISTS "${_native_duckdb_lib_win}")
    set(CF_DATAHIVE_CPP_DUCKDB_LIBRARY "${_native_duckdb_lib_win}")
  elseif (EXISTS "${_native_duckdb_lib_mingw}")
    set(CF_DATAHIVE_CPP_DUCKDB_LIBRARY "${_native_duckdb_lib_mingw}")
  elseif (EXISTS "${_native_duckdb_lib_flat}")
    set(CF_DATAHIVE_CPP_DUCKDB_LIBRARY "${_native_duckdb_lib_flat}")
  endif()
endif()

set(_duckdb_use_static OFF)
if (_duckdb_linkage_mode STREQUAL "static")
  set(_duckdb_use_static ON)
elseif (_duckdb_linkage_mode STREQUAL "auto")
  if (NOT CF_DATAHIVE_CPP_DUCKDB_SOURCE STREQUAL "" AND EXISTS "${CF_DATAHIVE_CPP_DUCKDB_SOURCE}")
    set(_duckdb_use_static ON)
  endif()
endif()

if (_duckdb_use_static)
  if (CF_DATAHIVE_CPP_DUCKDB_SOURCE STREQUAL "" OR NOT EXISTS "${CF_DATAHIVE_CPP_DUCKDB_SOURCE}")
    message(FATAL_ERROR "DuckDB static mode selected but source file not found. Set CF_DATAHIVE_CPP_DUCKDB_SOURCE or stage .native_deps/duckdb/src/duckdb.cpp.")
  endif()
endif()

if (CF_DATAHIVE_CPP_DUCKDB_INCLUDE_DIR STREQUAL "" OR NOT EXISTS "${CF_DATAHIVE_CPP_DUCKDB_INCLUDE_DIR}/duckdb.h")
  message(FATAL_ERROR "DuckDB headers not found. Set CF_DATAHIVE_CPP_DUCKDB_INCLUDE or stage headers in .native_deps/duckdb.")
endif()

if ((NOT _duckdb_use_static) AND (CF_DATAHIVE_CPP_DUCKDB_LIBRARY STREQUAL "" OR NOT EXISTS "${CF_DATAHIVE_CPP_DUCKDB_LIBRARY}"))
  message(FATAL_ERROR "DuckDB shared mode selected but library not found. Set CF_DATAHIVE_CPP_DUCKDB_LIB or install .native_deps duckdb.")
endif()

if (_duckdb_use_static)
  set(CF_DATAHIVE_CPP_DUCKDB_RUNTIME_DLL "" CACHE FILEPATH "Path to the DuckDB runtime DLL for shared-link consumer staging" FORCE)
elseif (WIN32)
  if (CF_DATAHIVE_CPP_DUCKDB_RUNTIME_DLL STREQUAL "" AND EXISTS "${_native_duckdb_runtime_dll}")
    set(CF_DATAHIVE_CPP_DUCKDB_RUNTIME_DLL "${_native_duckdb_runtime_dll}" CACHE FILEPATH "Path to the DuckDB runtime DLL for shared-link consumer staging" FORCE)
  endif()

  if (CF_DATAHIVE_CPP_DUCKDB_RUNTIME_DLL STREQUAL "")
    get_filename_component(_duckdb_lib_dir "${CF_DATAHIVE_CPP_DUCKDB_LIBRARY}" DIRECTORY)
    set(_duckdb_runtime_candidates
      "${_duckdb_lib_dir}/duckdb.dll"
      "${_duckdb_lib_dir}/../bin/duckdb.dll"
      "${_duckdb_lib_dir}/../duckdb.dll"
    )
    foreach(_duckdb_candidate IN LISTS _duckdb_runtime_candidates)
      if (EXISTS "${_duckdb_candidate}")
        set(CF_DATAHIVE_CPP_DUCKDB_RUNTIME_DLL "${_duckdb_candidate}" CACHE FILEPATH "Path to the DuckDB runtime DLL for shared-link consumer staging" FORCE)
        break()
      endif()
    endforeach()
  endif()

  if (CF_DATAHIVE_CPP_DUCKDB_RUNTIME_DLL STREQUAL "")
    message(FATAL_ERROR "DuckDB shared mode selected but duckdb.dll was not found. Set CF_DATAHIVE_CPP_DUCKDB_DLL or stage a runtime DLL in .native_deps/duckdb/bin.")
  endif()
endif()

add_library(cf_datahive_cpp SHARED
  src/cf_datahive_cpp.cpp
)

target_compile_definitions(cf_datahive_cpp PRIVATE CF_DATAHIVE_CPP_EXPORTS)

target_include_directories(cf_datahive_cpp PUBLIC
  ${CMAKE_CURRENT_SOURCE_DIR}/include
  ${CF_SDK_INCLUDE}
  ${CF_DATAHIVE_CPP_DUCKDB_INCLUDE_DIR}
)

if (_duckdb_use_static)
  add_library(cf_datahive_duckdb_static STATIC
    ${CF_DATAHIVE_CPP_DUCKDB_SOURCE}
  )
  set_target_properties(cf_datahive_duckdb_static PROPERTIES POSITION_INDEPENDENT_CODE ON)
  set(_duckdb_static_compile_defs
    DUCKDB_STATIC_BUILD
  )
  if (WIN32)
    list(APPEND _duckdb_static_compile_defs
      WIN32_LEAN_AND_MEAN
      NOMINMAX
      NOGDI
      _CRT_SECURE_NO_WARNINGS
    )
    if (MSVC)
      target_compile_options(cf_datahive_duckdb_static PRIVATE /bigobj)
    elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
      target_compile_options(cf_datahive_duckdb_static PRIVATE -Wa,-mbig-obj)
    endif()
  endif()
  target_compile_definitions(cf_datahive_duckdb_static PRIVATE
    ${_duckdb_static_compile_defs}
  )
  if (WIN32)
    target_link_libraries(cf_datahive_duckdb_static PUBLIC
      Rstrtmgr
    )
  endif()
  target_include_directories(cf_datahive_duckdb_static PUBLIC
    ${CF_DATAHIVE_CPP_DUCKDB_INCLUDE_DIR}
  )
  target_compile_definitions(cf_datahive_cpp PUBLIC
    DUCKDB_STATIC_BUILD
    CF_DATAHIVE_CPP_STATIC
  )
  target_link_libraries(cf_datahive_cpp PUBLIC
    cf_datahive_duckdb_static
  )
  message(STATUS "cf_datahive_cpp DuckDB linkage mode: static (${CF_DATAHIVE_CPP_DUCKDB_SOURCE})")
else()
  target_link_libraries(cf_datahive_cpp PUBLIC
    ${CF_DATAHIVE_CPP_DUCKDB_LIBRARY}
  )
  message(STATUS "cf_datahive_cpp DuckDB linkage mode: shared (${CF_DATAHIVE_CPP_DUCKDB_LIBRARY})")
endif()

include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/cf_datahive_consumer.cmake)

install(TARGETS cf_datahive_cpp
  RUNTIME DESTINATION cf_datahive/native/bin
  LIBRARY DESTINATION cf_datahive/native/lib
  ARCHIVE DESTINATION cf_datahive/native/lib
)
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION cf_datahive/native/include)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/cf_datahive_consumer.cmake DESTINATION cf_datahive/native/cmake)

if (WIN32 AND NOT _duckdb_use_static AND NOT CF_DATAHIVE_CPP_DUCKDB_RUNTIME_DLL STREQUAL "")
  install(FILES ${CF_DATAHIVE_CPP_DUCKDB_RUNTIME_DLL} DESTINATION cf_datahive/native/bin)
endif()
