# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2025 ai-ad4 and the FreePDFEditor contributors
# spike/CMakeLists.txt
#
# M0 feasibility spikes (§14). Each spike is its own executable so it can be run
# independently and its results reported separately by CI. The spikes link only
# against the leaf libraries they exercise (QPDF here), not against Qt.

# Find QPDF. System packages (Debian libqpdf-dev) install the config under the
# multiarch lib dir; vcpkg installs it under its installed tree. Search both.
find_package(QPDF CONFIG QUIET
    HINTS /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE} /usr/local /usr)

# Fall back to a pkg-config probe if the CMake config wasn't installed.
if(NOT QPDF_FOUND)
    find_package(PkgConfig QUIET)
    if(PkgConfig_FOUND)
        pkg_check_modules(QPDF libqpdf IMPORTED_TARGET)
    endif()
endif()

# FreeType is shared by Spike B step 1 (cmap fallback) and Spike C (subset
# growth). Discover it once here so both can use the PkgConfig::FREETYPE target.
if(PkgConfig_FOUND)
    pkg_check_modules(FREETYPE freetype2 IMPORTED_TARGET)
endif()

if(NOT QPDF_FOUND AND NOT TARGET PkgConfig::QPDF)
    message(STATUS
        "QPDF not found — Spike A (verbatim round-trip) will not be built. "
        "Install qpdf (vcpkg, system package, or from source) to enable it.")
    return()
endif()

# The QPDF CMake config exports `qpdf::libqpdf` (system packages, e.g. Debian's
# libqpdf-dev) and/or `QPDF::qpdf` (vcpkg). pkg-config yields PkgConfig::QPDF.
# Accept any of them.
if(TARGET qpdf::libqpdf)
    set(_fpe_qpdf_target qpdf::libqpdf)
elseif(TARGET QPDF::qpdf)
    set(_fpe_qpdf_target QPDF::qpdf)
elseif(TARGET PkgConfig::QPDF)
    set(_fpe_qpdf_target PkgConfig::QPDF)
else()
    message(WARNING "QPDF found but no known imported target — Spike A skipped.")
    return()
endif()

add_executable(spike_a_verbatim_roundtrip
    common/SpikeRunner.cpp
    common/SpikeRunner.h
    A_verbatim_roundtrip/main.cpp
    A_verbatim_roundtrip/Roundtrip.cpp
    A_verbatim_roundtrip/Roundtrip.h
)
target_include_directories(spike_a_verbatim_roundtrip PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(spike_a_verbatim_roundtrip PRIVATE ${_fpe_qpdf_target})
target_compile_features(spike_a_verbatim_roundtrip PRIVATE cxx_std_20)
freepdfeditor_apply_warnings(spike_a_verbatim_roundtrip)
freepdfeditor_apply_hardening(spike_a_verbatim_roundtrip)

# --- Spike B: glyph→Unicode + line/paragraph reconstruction (§14 step 5) ---
# The reconstruction pipeline (§4.1 steps 2-5) is pure C++ on the synthetic
# in-memory model, so it has no third-party dependency for the M0 spike. The
# glyph→Unicode step 1 (font cmap / ToUnicode) is exercised separately once
# FreeType/HarfBuzz are wired into the production L3 interpreter in M2.
add_executable(spike_b_reconstruction
    common/SpikeRunner.cpp
    common/SpikeRunner.h
    B_reconstruction/main.cpp
    B_reconstruction/Reconstruct.cpp
    B_reconstruction/Reconstruct.h
    B_reconstruction/Corpus.cpp
    B_reconstruction/Corpus.h
    B_reconstruction/GlyphRun.h
)
target_include_directories(spike_b_reconstruction PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_features(spike_b_reconstruction PRIVATE cxx_std_20)
freepdfeditor_apply_warnings(spike_b_reconstruction)
freepdfeditor_apply_hardening(spike_b_reconstruction)

# --- Spike B step 1: glyph→Unicode via ToUnicode + cmap fallback (§4.1 step 1) ---
# Exercises the glyph→Unicode priority ladder on a real embedded font: parse
# the ToUnicode CMap via QPDF (step 1), fall back to the embedded font's cmap
# via FreeType (step 4). The highest-risk reconstruction step, not covered by
# Spike B's synthetic corpus. Reuses the QPDF and FreeType targets found above
# (Spike A's QPDF, Spike C's FreeType).
if(TARGET PkgConfig::QPDF AND TARGET PkgConfig::FREETYPE)
    add_executable(spike_b1_glyph_unicode
        common/SpikeRunner.cpp
        common/SpikeRunner.h
        B1_glyph_unicode/main.cpp
        B1_glyph_unicode/GlyphUnicode.cpp
        B1_glyph_unicode/GlyphUnicode.h
    )
    target_include_directories(spike_b1_glyph_unicode PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
    target_link_libraries(spike_b1_glyph_unicode PRIVATE
        ${_fpe_qpdf_target} PkgConfig::FREETYPE)
    target_compile_features(spike_b1_glyph_unicode PRIVATE cxx_std_20)
    freepdfeditor_apply_warnings(spike_b1_glyph_unicode)
    freepdfeditor_apply_hardening(spike_b1_glyph_unicode)
endif()

# --- Spike C: hb-subset growth of an embedded subset font (§14 step 6) ---
# Grow an existing subset font with a new glyph using hb-subset and verify the
# result renders via FreeType. §4.2 resolution ladder step 1: when the user
# types a character not in the embedded subset, grow the subset from the
# installed full font and embed the new subset (never mutating the original).
find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)
    pkg_check_modules(HARFBUZZ harfbuzz harfbuzz-subset IMPORTED_TARGET)
endif()

if(TARGET PkgConfig::HARFBUZZ AND TARGET PkgConfig::FREETYPE)
    add_executable(spike_c_subset_growth
        common/SpikeRunner.cpp
        common/SpikeRunner.h
        C_subset_growth/main.cpp
        C_subset_growth/SubsetGrowth.cpp
        C_subset_growth/SubsetGrowth.h
    )
    target_include_directories(spike_c_subset_growth PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
    target_link_libraries(spike_c_subset_growth PRIVATE PkgConfig::HARFBUZZ PkgConfig::FREETYPE)
    target_compile_features(spike_c_subset_growth PRIVATE cxx_std_20)
    freepdfeditor_apply_warnings(spike_c_subset_growth)
    freepdfeditor_apply_hardening(spike_c_subset_growth)
else()
    message(STATUS
        "HarfBuzz/FreeType not found — Spike C (subset growth) will not be "
        "built. Install libharfbuzz-dev and libfreetype-dev to enable it.")
endif()

# --- Spike E: sandbox + IPC bring-up (§14 step 8) ---
# Two-process model (ADR-0004): a sandboxed document process (seccomp-bpf) and
# a UI process, talking over a socketpair via Cap'n Proto two-party RPC.
# This is the one thing the plan says is genuinely painful to retrofit, so it
# is de-risked at M0. Linux-only at this stage (seccomp-bpf); macOS App Sandbox
# and Windows AppContainer land in M1.
#
# Integration note: the server side uses the low-level TwoPartyVatNetwork +
# makeRpcServer over wrapSocketFd (NOT EzRpcServer, which calls accept() on a
# listening socket and fails on a connected socketpair end with EINVAL — the
# original Spike E stall, now resolved). The client uses EzRpcClient(fd),
# which is designed for an already-connected socket. See
# spike/E_sandbox/DocumentProcess.cpp for the full note.
find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)
    pkg_check_modules(SECCOMP libseccomp IMPORTED_TARGET)
    pkg_check_modules(CAPNP capnp-rpc capnp IMPORTED_TARGET)
endif()
# The capnp compiler is needed to generate the C++ from the schema. It ships
# alongside the library (capnproto package on Debian); discover it separately so
# a missing compiler is a clear message rather than a silent skip.
find_program(CAPNP_EXECUTABLE NAMES capnp capnpc)

if(TARGET PkgConfig::SECCOMP AND TARGET PkgConfig::CAPNP AND CAPNP_EXECUTABLE)
    # Generate the C++ from the IPC schema with the capnp compiler directly.
    # (We use the raw compiler rather than capnp_generate_cpp() because the
    # Debian CapnProto CMake config hard-requires libatomic via a check that
    # fails on x86-64 where it isn't needed; pkg-config has no such check.)
    set(_e_schema ${CMAKE_CURRENT_SOURCE_DIR}/E_sandbox/ipc.capnp)
    set(_e_gen_dir ${CMAKE_CURRENT_BINARY_DIR}/E_sandbox_generated)
    set(_e_gen_hdr ${_e_gen_dir}/ipc.capnp.h)
    set(_e_gen_src ${_e_gen_dir}/ipc.capnp.c++)
    file(MAKE_DIRECTORY ${_e_gen_dir})
    # --src-prefix strips the given prefix from the input path so the output
    # is written flat into the output dir (ipc.capnp.h / ipc.capnp.c++) rather
    # than mirroring the spike/E_sandbox/ source tree.
    add_custom_command(
        OUTPUT ${_e_gen_hdr} ${_e_gen_src}
        COMMAND ${CAPNP_EXECUTABLE} compile
                --src-prefix=${CMAKE_CURRENT_SOURCE_DIR}/E_sandbox
                -I${CAPNP_INCLUDE_DIRS}
                -oc++:${_e_gen_dir}
                ${_e_schema}
        DEPENDS ${_e_schema}
        COMMENT "Generating Cap'n Proto C++ for Spike E IPC schema"
        VERBATIM)

    add_executable(spike_e_sandbox
        common/SpikeRunner.cpp
        common/SpikeRunner.h
        E_sandbox/main.cpp
        E_sandbox/Sandbox.cpp
        E_sandbox/Sandbox.h
        E_sandbox/DocumentProcess.cpp
        E_sandbox/DocumentProcess.h
        E_sandbox/UIProcess.cpp
        E_sandbox/UIProcess.h
        ${_e_gen_src}
    )
    target_include_directories(spike_e_sandbox PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}
        ${_e_gen_dir})
    target_link_libraries(spike_e_sandbox PRIVATE
        PkgConfig::SECCOMP
        PkgConfig::CAPNP)
    target_compile_features(spike_e_sandbox PRIVATE cxx_std_20)
    freepdfeditor_apply_warnings(spike_e_sandbox)
    freepdfeditor_apply_hardening(spike_e_sandbox)
else()
    message(STATUS
        "libseccomp, Cap'n Proto, or the capnp compiler not found — Spike E "
        "(sandbox) will not be built. Install libseccomp-dev, libcapnp-dev, "
        "and capnproto to enable it.")
endif()

# --- Spike F: Rust-vs-C++ build-friction probe (ADR-0005 input, §15) ---
# Not a product spike; provides the empirical build-cost data for the
# Rust-vs-C++ leaf-decoder decision. Has its own CMakeLists so it can be
# excluded from builds without Rust.
include(${CMAKE_CURRENT_SOURCE_DIR}/F_rust_ffi_probe/CMakeLists.txt)