cmake_minimum_required(VERSION 3.16)
project(fn_registry_cpp LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# --- Options ---
option(TRACY_ENABLE "Enable Tracy profiling" OFF)
option(FN_BUILD_TESTS "Build C++ e2e tests with Dear ImGui Test Engine" OFF)

# --- Vendor: Dear ImGui ---
set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/imgui)
add_library(imgui STATIC
    ${IMGUI_DIR}/imgui.cpp
    ${IMGUI_DIR}/imgui_draw.cpp
    ${IMGUI_DIR}/imgui_tables.cpp
    ${IMGUI_DIR}/imgui_widgets.cpp
    ${IMGUI_DIR}/imgui_demo.cpp
    ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp
    ${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp
)
target_include_directories(imgui PUBLIC
    ${IMGUI_DIR}
    ${IMGUI_DIR}/backends
)

# When tests are enabled, imgui must be compiled with hooks for the test engine.
# The hooks compile to no-ops if the engine is never started, so this is safe to
# leave on but we still gate it to keep release builds identical to today.
if(FN_BUILD_TESTS)
    target_compile_definitions(imgui PUBLIC IMGUI_ENABLE_TEST_ENGINE)
endif()

# --- Vendor: Dear ImGui Test Engine (opt-in via FN_BUILD_TESTS) ---
# Personal/open-source license (see vendor/imgui_test_engine/LICENSE.txt).
if(FN_BUILD_TESTS)
    set(IMTE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/imgui_test_engine)
    add_library(imgui_test_engine STATIC
        ${IMTE_DIR}/imgui_te_engine.cpp
        ${IMTE_DIR}/imgui_te_context.cpp
        ${IMTE_DIR}/imgui_te_coroutine.cpp
        ${IMTE_DIR}/imgui_te_exporters.cpp
        ${IMTE_DIR}/imgui_te_perftool.cpp
        ${IMTE_DIR}/imgui_te_ui.cpp
        ${IMTE_DIR}/imgui_te_utils.cpp
        ${IMTE_DIR}/imgui_capture_tool.cpp
    )
    target_include_directories(imgui_test_engine PUBLIC
        ${IMTE_DIR}
        ${IMTE_DIR}/thirdparty
    )
    # Use std::thread for coroutines so apps don't have to provide their own.
    target_compile_definitions(imgui_test_engine PUBLIC
        IMGUI_ENABLE_TEST_ENGINE
        IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL=1
        IMGUI_TEST_ENGINE_ENABLE_STD_FUNCTION=1
    )
    target_link_libraries(imgui_test_engine PUBLIC imgui)
    if(UNIX)
        find_package(Threads REQUIRED)
        target_link_libraries(imgui_test_engine PUBLIC Threads::Threads)
    endif()
endif()

# --- Vendor: ImPlot ---
set(IMPLOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/implot)
add_library(implot STATIC
    ${IMPLOT_DIR}/implot.cpp
    ${IMPLOT_DIR}/implot_items.cpp
)
target_include_directories(implot PUBLIC ${IMPLOT_DIR})
target_link_libraries(implot PUBLIC imgui)

# --- Vendor: ImPlot3D ---
# Pinned to v0.4 (commit 41ae3e447c0de20ecab95d38a4b4dc0835a3efc2).
# See cpp/vendor/implot3d.VENDORING.md for update procedure.
set(IMPLOT3D_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/implot3d)
add_library(implot3d STATIC
    ${IMPLOT3D_DIR}/implot3d.cpp
    ${IMPLOT3D_DIR}/implot3d_items.cpp
    ${IMPLOT3D_DIR}/implot3d_meshes.cpp
)
target_include_directories(implot3d PUBLIC ${IMPLOT3D_DIR})
target_link_libraries(implot3d PUBLIC imgui)

# --- Vendor: Tracy (optional) ---
if(TRACY_ENABLE)
    set(TRACY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/tracy)
    add_library(tracy STATIC
        ${TRACY_DIR}/public/TracyClient.cpp
    )
    target_include_directories(tracy PUBLIC ${TRACY_DIR}/public)
    target_compile_definitions(tracy PUBLIC TRACY_ENABLE)
endif()

# --- Vendor: imgui-node-editor ---
set(NODE_EDITOR_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/imgui-node-editor)
add_library(imgui_node_editor STATIC
    ${NODE_EDITOR_DIR}/imgui_node_editor.cpp
    ${NODE_EDITOR_DIR}/imgui_node_editor_api.cpp
    ${NODE_EDITOR_DIR}/imgui_canvas.cpp
    ${NODE_EDITOR_DIR}/crude_json.cpp
)
target_include_directories(imgui_node_editor PUBLIC ${NODE_EDITOR_DIR})
target_link_libraries(imgui_node_editor PUBLIC imgui)

# --- Platform dependencies ---
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
    # Cross-compile: use vendored or system GLFW, link opengl32/gdi32
    find_package(glfw3 QUIET)
    if(NOT glfw3_FOUND)
        # Build GLFW from source if available
        if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/vendor/glfw/CMakeLists.txt)
            set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
            set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
            set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
            add_subdirectory(vendor/glfw)
        else()
            message(FATAL_ERROR "GLFW not found. For Windows cross-compile, add GLFW source to cpp/vendor/glfw/")
        endif()
    endif()
    set(PLATFORM_LIBS glfw opengl32 gdi32 imm32)
else()
    # Linux native
    find_package(glfw3 REQUIRED)
    find_package(OpenGL REQUIRED)
    set(PLATFORM_LIBS glfw OpenGL::GL ${CMAKE_DL_LIBS})
endif()

target_link_libraries(imgui PUBLIC ${PLATFORM_LIBS})

# --- SQLite3 (shared by every app that uses it, including fn_framework for
# layout_storage) ---
# System on Linux, vendored amalgamation on Windows cross-compile.
find_package(SQLite3 QUIET)
if(NOT SQLite3_FOUND AND NOT TARGET sqlite3_vendored)
    set(SQLITE3_AMALG_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/sqlite3)
    add_library(sqlite3_vendored STATIC ${SQLITE3_AMALG_DIR}/sqlite3.c)
    target_include_directories(sqlite3_vendored PUBLIC ${SQLITE3_AMALG_DIR})
    target_compile_definitions(sqlite3_vendored PRIVATE
        SQLITE_THREADSAFE=1
        SQLITE_ENABLE_FTS5
        SQLITE_ENABLE_JSON1
    )
    add_library(SQLite::SQLite3 ALIAS sqlite3_vendored)
endif()

# --- DuckDB (precompiled libs — see cpp/vendor/duckdb/README.md) ---
# Header en vendor/duckdb/include/. Lib dinamica en linux/ o windows/ segun
# el target. Las libs estan gitignored — ejecutar
# `cpp/vendor/duckdb/download_duckdb.sh` la primera vez.
if(NOT TARGET duckdb_vendored)
    set(DUCKDB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/duckdb)
    if(WIN32)
        set(DUCKDB_LIB     ${DUCKDB_DIR}/windows/duckdb.lib)
        set(DUCKDB_RUNTIME ${DUCKDB_DIR}/windows/duckdb.dll)
    else()
        set(DUCKDB_LIB     ${DUCKDB_DIR}/linux/libduckdb.so)
        set(DUCKDB_RUNTIME ${DUCKDB_DIR}/linux/libduckdb.so)
    endif()
    if(NOT EXISTS ${DUCKDB_LIB})
        message(WARNING "[duckdb] ${DUCKDB_LIB} no existe — corre cpp/vendor/duckdb/download_duckdb.sh")
    endif()
    add_library(duckdb_vendored INTERFACE)
    target_include_directories(duckdb_vendored INTERFACE ${DUCKDB_DIR}/include)
    target_link_libraries(duckdb_vendored INTERFACE ${DUCKDB_LIB})
    add_library(DuckDB::DuckDB ALIAS duckdb_vendored)

    # Helper para que las apps copien la runtime lib al lado del exe.
    # add_imgui_app puede invocarlo, o cada app lo hace en su CMakeLists.
    function(duckdb_copy_runtime target)
        if(EXISTS ${DUCKDB_RUNTIME})
            add_custom_command(TARGET ${target} POST_BUILD
                COMMAND ${CMAKE_COMMAND} -E copy_if_different
                        ${DUCKDB_RUNTIME} $<TARGET_FILE_DIR:${target}>
                COMMENT "Copying DuckDB runtime next to ${target}")
        endif()
    endfunction()
endif()

# --- Framework ---
# Incluye tokens.cpp (identidad visual Mantine dark + indigo), icon_font.cpp
# (Karla/Roboto/... + Tabler), app_settings.cpp (persistencia y ventana de
# settings) y fps_overlay.cpp (overlay opcional). Ver cpp/DESIGN_SYSTEM.md
add_library(fn_framework STATIC
    framework/app_base.cpp
    functions/core/tokens.cpp
    functions/core/icon_font.cpp
    functions/core/app_settings.cpp
    functions/core/app_about.cpp
    functions/core/fps_overlay.cpp
    functions/core/panel_menu.cpp
    functions/core/layouts_menu.cpp
    functions/core/app_menubar.cpp
    functions/core/logger.cpp
    functions/core/log_window.cpp
    functions/gfx/gl_loader.cpp
    functions/core/layout_storage.cpp
    functions/core/selectable_text.cpp
)
target_include_directories(fn_framework PUBLIC
    ${CMAKE_CURRENT_SOURCE_DIR}/framework
    ${CMAKE_CURRENT_SOURCE_DIR}/functions
)
# FN_CPP_ROOT permite que icon_font.cpp localice vendor/tabler-icons/tabler-icons.ttf
# en builds de desarrollo desde el repo (en deploys, la TTF se copia junto al exe).
target_compile_definitions(fn_framework PUBLIC
    FN_CPP_ROOT="${CMAKE_CURRENT_SOURCE_DIR}"
)
target_link_libraries(fn_framework PUBLIC imgui implot implot3d SQLite::SQLite3)
if(TRACY_ENABLE)
    target_link_libraries(fn_framework PUBLIC tracy)
endif()
if(FN_BUILD_TESTS)
    # Public so apps that include fn_framework headers see the same hooks.
    target_compile_definitions(fn_framework PUBLIC IMGUI_ENABLE_TEST_ENGINE)
    target_link_libraries(fn_framework PUBLIC imgui_test_engine)
endif()

# --- OpenMP (opcional) ---
# Habilita #pragma omp en las funciones del registry que lo declaren bajo
# guardia _OPENMP. Si el compilador no lo soporta (no debiera, gcc/clang
# y mingw-w64 lo traen), los pragmas se ignoran sin romper el build.
find_package(OpenMP QUIET)
if(OpenMP_CXX_FOUND)
    target_link_libraries(fn_framework PUBLIC OpenMP::OpenMP_CXX)
    message(STATUS "OpenMP enabled for fn_framework (${OpenMP_CXX_VERSION})")
else()
    message(STATUS "OpenMP NOT found — force layout fallback to single-thread")
endif()

# --- Macro for creating ImGui apps ---
# Capturamos la raiz del modulo cpp/ para que add_imgui_app la use desde
# subdirectorios (donde CMAKE_CURRENT_SOURCE_DIR apunta al app, no al root).
set(FN_CPP_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "fn_registry cpp root")

function(add_imgui_app target)
    add_executable(${target} ${ARGN})
    target_link_libraries(${target} PRIVATE fn_framework)
    target_include_directories(${target} PRIVATE
        ${FN_CPP_ROOT_DIR}/functions
    )
    # Convencion de layout (cpp_apps.md §7):
    #   <exe_dir>/<app>.exe + <app>.dll     (binario + DLLs Windows convention)
    #   <exe_dir>/assets/                   (read-only: ttfs, enrichers, runtime, etc.)
    #   <exe_dir>/local_files/              (creado en runtime: ini, db, projects)
    #
    # add_imgui_app copia las TTFs a <exe_dir>/assets/. La app las
    # encuentra en runtime via fn::asset_path() (icon_font.cpp).
    set(_ASSETS_DIR $<TARGET_FILE_DIR:${target}>/assets)
    add_custom_command(TARGET ${target} POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E make_directory ${_ASSETS_DIR}
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
                ${FN_CPP_ROOT_DIR}/vendor/imgui/misc/fonts/Karla-Regular.ttf
                ${_ASSETS_DIR}/Karla-Regular.ttf
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
                ${FN_CPP_ROOT_DIR}/vendor/imgui/misc/fonts/Roboto-Medium.ttf
                ${_ASSETS_DIR}/Roboto-Medium.ttf
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
                ${FN_CPP_ROOT_DIR}/vendor/imgui/misc/fonts/DroidSans.ttf
                ${_ASSETS_DIR}/DroidSans.ttf
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
                ${FN_CPP_ROOT_DIR}/vendor/imgui/misc/fonts/Cousine-Regular.ttf
                ${_ASSETS_DIR}/Cousine-Regular.ttf
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
                ${FN_CPP_ROOT_DIR}/vendor/tabler-icons/tabler-icons.ttf
                ${_ASSETS_DIR}/tabler-icons.ttf
        VERBATIM
    )
endfunction()

# --- Function libraries (headers for composition) ---
# Functions are compiled as part of apps that use them via add_imgui_app.
# Each function is a .h/.cpp pair included by the app's CMakeLists.txt.

# --- Demo app ---
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/apps/chart_demo/CMakeLists.txt)
    add_subdirectory(apps/chart_demo)
endif()

# --- Shaders Lab ---
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/apps/shaders_lab/CMakeLists.txt)
    add_subdirectory(apps/shaders_lab)
endif()

# --- Primitives Gallery (catalogo visual de primitivos core/viz/gfx) ---
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/apps/primitives_gallery/CMakeLists.txt)
    add_subdirectory(apps/primitives_gallery)
endif()

# --- Tables playground (vive dentro de primitives_gallery/playground/tables/) ---
# No es un app del registry; sirve para iterar mejoras sobre table_view_cpp_viz
# antes de promover una API v2 y migrar las apps C++ que hoy usan ImGui::BeginTable raw.
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/apps/primitives_gallery/playground/tables/CMakeLists.txt)
    add_subdirectory(apps/primitives_gallery/playground/tables)
endif()

# --- text_editor + file_watcher smoke test (issue 0025) ---
# Build gate para validar que text_editor.cpp + file_watcher.cpp + vendor enlazan.
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/apps/text_editor_smoke/CMakeLists.txt)
    add_subdirectory(apps/text_editor_smoke)
endif()

# --- AltSnap viewport-jitter regression test ---
# Headless harness que conduce glfwSetWindowPos cada frame y verifica que
# ImGui viewport->Pos sigue al OS dentro de 1px. Sin la patch del framework
# (callback GLFW + per-frame sync) este test falla exit=1.
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/apps/altsnap_jitter_test/CMakeLists.txt)
    add_subdirectory(apps/altsnap_jitter_test)
endif()

# --- gamedev stack (SDL3 + sokol_gfx + miniaudio, issue 0072) ---
# Apps standalone, no usan fn_framework. Vendor SDL3 se compila una vez aqui;
# las apps solo linkan SDL3::SDL3-static.
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/vendor/sdl3/CMakeLists.txt
   AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/vendor/sokol/sokol_gfx.h)
    set(SDL_SHARED OFF CACHE BOOL "" FORCE)
    set(SDL_STATIC ON CACHE BOOL "" FORCE)
    set(SDL_TEST_LIBRARY OFF CACHE BOOL "" FORCE)
    set(SDL_TESTS OFF CACHE BOOL "" FORCE)
    set(SDL_EXAMPLES OFF CACHE BOOL "" FORCE)
    set(SDL_INSTALL OFF CACHE BOOL "" FORCE)
    set(SDL_X11_XSCRNSAVER OFF CACHE BOOL "" FORCE)
    add_subdirectory(vendor/sdl3 EXCLUDE_FROM_ALL)
    if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/apps/engine_smoke/CMakeLists.txt)
        add_subdirectory(apps/engine_smoke)
    endif()
    if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/apps/runtime_test/CMakeLists.txt)
        add_subdirectory(apps/runtime_test)
    endif()
endif()

# --- Registry Dashboard (lives in projects/fn_monitoring/apps/) ---
# _DASH_DIR puede sobreescribirse via -D_DASH_DIR=<path> para apuntar a un
# worktree (parallel-fix-issues u otros flujos que aislen builds).
if(NOT DEFINED _DASH_DIR)
    set(_DASH_DIR ${CMAKE_SOURCE_DIR}/../projects/fn_monitoring/apps/registry_dashboard)
endif()
if(EXISTS ${_DASH_DIR}/CMakeLists.txt)
    add_subdirectory(${_DASH_DIR} ${CMAKE_BINARY_DIR}/apps/registry_dashboard)
endif()

# --- Graph Explorer (lives in projects/osint_graph/apps/) ---
# _GE_DIR puede sobreescribirse via -D_GE_DIR=<path> para apuntar a un
# worktree (parallel-fix-issues u otros flujos que aislen builds).
if(NOT DEFINED _GE_DIR)
    set(_GE_DIR ${CMAKE_SOURCE_DIR}/../projects/osint_graph/apps/graph_explorer)
endif()
if(EXISTS ${_GE_DIR}/CMakeLists.txt)
    add_subdirectory(${_GE_DIR} ${CMAKE_BINARY_DIR}/apps/graph_explorer)
endif()

# --- odr_console (lives in projects/online_data_recopilation/apps/) ---
if(NOT DEFINED _ODR_DIR)
    set(_ODR_DIR ${CMAKE_SOURCE_DIR}/../projects/online_data_recopilation/apps/odr_console)
endif()
if(EXISTS ${_ODR_DIR}/CMakeLists.txt)
    add_subdirectory(${_ODR_DIR} ${CMAKE_BINARY_DIR}/apps/odr_console)
endif()

# --- navegator_dashboard (lives in projects/navegator/apps/) ---
# Windows-only — el propio CMakeLists.txt hace return() en non-WIN32.
if(NOT DEFINED _NAVD_DIR)
    set(_NAVD_DIR ${CMAKE_SOURCE_DIR}/../projects/navegator/apps/navegator_dashboard)
endif()
if(EXISTS ${_NAVD_DIR}/CMakeLists.txt)
    add_subdirectory(${_NAVD_DIR} ${CMAKE_BINARY_DIR}/apps/navegator_dashboard)
endif()

# --- Tests (Catch2 amalgamated, ctest-driven) ---
option(BUILD_TESTING "Build C++ tests" ON)
if(BUILD_TESTING)
    enable_testing()
    add_subdirectory(tests)
endif()
