cmake_minimum_required(VERSION 3.20)
project(cnc_sim_wasm_core LANGUAGES C CXX)

option(CNC_SIM_ENABLE_LINUXCNC_BRIDGE "Build the experimental LinuxCNC canon bridge" OFF)
option(CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND "Build the native LinuxCNC librs274 backend" OFF)
option(CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE "Compile the browser-safe subset of LinuxCNC rs274 sources" OFF)
option(CNC_SIM_ENABLE_SMOKE_BACKEND "Build the legacy explicit smoke G-code backend test harness" OFF)
set(CNC_SIM_LINUXCNC_ROOT "" CACHE PATH "LinuxCNC source root for the experimental bridge")
set(CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST
    "${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc-rs274-wasm-source-files.txt"
    CACHE FILEPATH
    "Manifest that partitions LinuxCNC rs274 sources into wasm-safe core and blocked groups"
)

set(cnc_sim_core_sources
    src/canon_event_sink.cpp
    src/cnc_sim_api.cpp
    src/gcode_backend.cpp
    src/linuxcnc_5axiskins_adapter.c
    src/linuxcnc_5axiskins_adapter.cpp
    src/linuxcnc_corexykins_adapter.c
    src/linuxcnc_corexykins_adapter.cpp
    src/linuxcnc_cubic_adapter.c
    src/linuxcnc_cubic_adapter.cpp
    src/linuxcnc_genserfuncs_adapter.c
    src/linuxcnc_genserfuncs_adapter.cpp
    src/linuxcnc_genhexkins_adapter.c
    src/linuxcnc_genhexkins_adapter.cpp
    src/linuxcnc_kins_util_adapter.c
    src/linuxcnc_kins_util_adapter.cpp
    src/linuxcnc_lineardeltakins_adapter.c
    src/linuxcnc_lineardeltakins_adapter.cpp
    src/linuxcnc_maxkins_adapter.c
    src/linuxcnc_maxkins_adapter.cpp
    src/linuxcnc_pentakins_adapter.c
    src/linuxcnc_pentakins_adapter.cpp
    src/linuxcnc_pumakins_adapter.c
    src/linuxcnc_pumakins_adapter.cpp
    src/linuxcnc_rotatekins_adapter.c
    src/linuxcnc_rotatekins_adapter.cpp
    src/linuxcnc_rosekins_adapter.c
    src/linuxcnc_rosekins_adapter.cpp
    src/linuxcnc_rotarydeltakins_adapter.c
    src/linuxcnc_rotarydeltakins_adapter.cpp
    src/linuxcnc_scarakins_adapter.c
    src/linuxcnc_scarakins_adapter.cpp
    src/linuxcnc_scorbot_kins_adapter.c
    src/linuxcnc_scorbot_kins_adapter.cpp
    src/linuxcnc_tripodkins_adapter.c
    src/linuxcnc_tripodkins_adapter.cpp
    src/linuxcnc_trtfuncs_adapter.c
    src/linuxcnc_trtfuncs_adapter.cpp
    src/linuxcnc_userkfuncs_adapter.c
    src/linuxcnc_userkfuncs_adapter.cpp
    src/linuxcnc_xyzab_tdr_kins_adapter.c
    src/linuxcnc_xyzab_tdr_kins_adapter.cpp
    src/rtcp_kinematics.cpp
    src/simulator_gcode_controls.cpp
)

set(cnc_sim_smoke_backend_sources
    src/smoke_gcode_parser.cpp
)

set(cnc_sim_linuxcnc_rs274_backend_sources
    src/linuxcnc_canon_bridge.cpp
    src/linuxcnc_rs274_backend.cpp
    src/linuxcnc_tooldata_fixture.cpp
)

if(CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND)
    if(CNC_SIM_ENABLE_SMOKE_BACKEND)
        message(FATAL_ERROR "CNC_SIM_ENABLE_SMOKE_BACKEND cannot be combined with CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND")
    endif()
    if(EMSCRIPTEN)
        message(FATAL_ERROR "CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND is native-only; it links LinuxCNC librs274")
    endif()
    if(NOT CNC_SIM_LINUXCNC_ROOT)
        message(FATAL_ERROR "Set CNC_SIM_LINUXCNC_ROOT to the LinuxCNC source root")
    endif()
    list(APPEND cnc_sim_core_sources ${cnc_sim_linuxcnc_rs274_backend_sources})
elseif(CNC_SIM_ENABLE_SMOKE_BACKEND)
    list(APPEND cnc_sim_core_sources ${cnc_sim_smoke_backend_sources})
endif()

if(CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE)
    if(NOT CNC_SIM_LINUXCNC_ROOT)
        message(FATAL_ERROR "Set CNC_SIM_LINUXCNC_ROOT to the LinuxCNC source root")
    endif()
    get_filename_component(CNC_SIM_LINUXCNC_ROOT_ABS
        "${CNC_SIM_LINUXCNC_ROOT}"
        ABSOLUTE
        BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.."
    )
    if(NOT IS_DIRECTORY "${CNC_SIM_LINUXCNC_ROOT_ABS}")
        message(FATAL_ERROR "LinuxCNC source root does not exist: ${CNC_SIM_LINUXCNC_ROOT_ABS}")
    endif()
    if(NOT EXISTS "${CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST}")
        message(FATAL_ERROR "Missing LinuxCNC wasm source manifest: ${CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST}")
    endif()
    if(IS_DIRECTORY "${CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST}")
        message(FATAL_ERROR "LinuxCNC wasm source manifest is not a file: ${CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST}")
    endif()

    set(linuxcnc_wasm_safe_probe_sources)
    set(linuxcnc_wasm_blocked_sources)
    file(STRINGS "${CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST}" linuxcnc_wasm_manifest_lines)
    foreach(manifest_line IN LISTS linuxcnc_wasm_manifest_lines)
        string(STRIP "${manifest_line}" manifest_line)
        if(manifest_line STREQUAL "" OR manifest_line MATCHES "^#")
            continue()
        endif()
        if(NOT manifest_line MATCHES "^([^:]+):([^:]+):(.*)$")
            message(FATAL_ERROR "Bad LinuxCNC wasm source manifest line: ${manifest_line}")
        endif()

        set(manifest_group "${CMAKE_MATCH_1}")
        set(manifest_path "${CMAKE_MATCH_2}")
        if(NOT manifest_group STREQUAL "core"
            AND NOT manifest_group STREQUAL "blocked"
            AND NOT manifest_group STREQUAL "header"
            AND NOT manifest_group STREQUAL "metadata"
            AND NOT manifest_group STREQUAL "blocked-header")
            message(FATAL_ERROR "Unknown LinuxCNC wasm source manifest group: ${manifest_group}")
        endif()

        set(manifest_source "${CNC_SIM_LINUXCNC_ROOT_ABS}/${manifest_path}")
        if(NOT EXISTS "${manifest_source}")
            message(FATAL_ERROR "LinuxCNC wasm manifest source does not exist: ${manifest_source}")
        endif()
        if(IS_DIRECTORY "${manifest_source}")
            message(FATAL_ERROR "LinuxCNC wasm manifest source is not a file: ${manifest_source}")
        endif()

        if(manifest_group STREQUAL "core")
            list(APPEND linuxcnc_wasm_safe_probe_sources "${manifest_source}")
        elseif(manifest_group STREQUAL "blocked")
            list(APPEND linuxcnc_wasm_blocked_sources "${manifest_source}")
        endif()
    endforeach()

    list(LENGTH linuxcnc_wasm_safe_probe_sources linuxcnc_wasm_safe_probe_source_count)
    list(LENGTH linuxcnc_wasm_blocked_sources linuxcnc_wasm_blocked_source_count)
    if(linuxcnc_wasm_safe_probe_source_count EQUAL 0)
        message(FATAL_ERROR "LinuxCNC wasm source manifest has no core entries")
    endif()
    if(linuxcnc_wasm_blocked_source_count EQUAL 0)
        message(FATAL_ERROR "LinuxCNC wasm source manifest has no blocked entries")
    endif()
    message(STATUS "LinuxCNC wasm-safe core sources: ${linuxcnc_wasm_safe_probe_source_count}")
    message(STATUS "LinuxCNC wasm blocked sources: ${linuxcnc_wasm_blocked_source_count}")

    set(cnc_sim_wasm_safe_preinclude_options "SHELL:-include wctype.h")
    if(EMSCRIPTEN)
        list(APPEND cnc_sim_wasm_safe_preinclude_options "SHELL:-include libgen.h")
        find_path(CNC_SIM_FMT_INCLUDE_DIR fmt/format.h
            PATHS /usr/include /usr/local/include
            NO_CMAKE_FIND_ROOT_PATH
            REQUIRED
        )
        list(APPEND cnc_sim_wasm_safe_preinclude_options "SHELL:-idirafter ${CNC_SIM_FMT_INCLUDE_DIR}")
    endif()

    set(linuxcnc_wasm_safe_probe_shims
        ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/dlfcn.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/emc_status_shim.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/gettext_shim.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/linuxcnc_runtime_shim.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/python_c_api_shim.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/rtapi_compat.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/tooldata/tooldata_mmap_backend.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/tooldata/tooldata_runtime_stubs.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/pythonplugin/python_plugin.cc
    )

    add_library(linuxcnc_rs274_wasm_safe_probe_objects OBJECT
        ${linuxcnc_wasm_safe_probe_shims}
        ${linuxcnc_wasm_safe_probe_sources}
    )
    target_compile_features(linuxcnc_rs274_wasm_safe_probe_objects PUBLIC cxx_std_20)
    target_compile_definitions(linuxcnc_rs274_wasm_safe_probe_objects PRIVATE ULAPI)
    target_compile_options(linuxcnc_rs274_wasm_safe_probe_objects PRIVATE ${cnc_sim_wasm_safe_preinclude_options})
    target_include_directories(linuxcnc_rs274_wasm_safe_probe_objects
        PRIVATE
            ${CMAKE_CURRENT_SOURCE_DIR}/include
            ${CMAKE_CURRENT_SOURCE_DIR}/src
            ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/nml_intf
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/rs274ngc
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/motion
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/include
    )
    if(EMSCRIPTEN)
        target_compile_definitions(linuxcnc_rs274_wasm_safe_probe_objects PRIVATE FMT_HEADER_ONLY=1)
    endif()

    set(cnc_sim_wasm_runtime_probe_main
        ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/cnc_sim_wasm_runtime_probe_main.cc
    )

    if(linuxcnc_wasm_blocked_sources)
        add_custom_target(linuxcnc_rs274_wasm_blocked_sources
            SOURCES ${linuxcnc_wasm_blocked_sources}
        )
    endif()
endif()

if(CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE)
    list(APPEND cnc_sim_core_sources ${cnc_sim_linuxcnc_rs274_backend_sources})
endif()

add_library(cnc_sim_objects OBJECT
    ${cnc_sim_core_sources}
)

target_include_directories(cnc_sim_objects
    PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include
    PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/src
        ${CMAKE_CURRENT_SOURCE_DIR}/../../linuxcnc/include
)

target_compile_features(cnc_sim_objects PUBLIC cxx_std_17)
set(cnc_sim_linuxcnc_kinematics_c_sources
    src/linuxcnc_5axiskins_adapter.c
    src/linuxcnc_corexykins_adapter.c
    src/linuxcnc_cubic_adapter.c
    src/linuxcnc_genserfuncs_adapter.c
    src/linuxcnc_genhexkins_adapter.c
    src/linuxcnc_kins_util_adapter.c
    src/linuxcnc_lineardeltakins_adapter.c
    src/linuxcnc_maxkins_adapter.c
    src/linuxcnc_pentakins_adapter.c
    src/linuxcnc_pumakins_adapter.c
    src/linuxcnc_rotatekins_adapter.c
    src/linuxcnc_rosekins_adapter.c
    src/linuxcnc_rotarydeltakins_adapter.c
    src/linuxcnc_scarakins_adapter.c
    src/linuxcnc_scorbot_kins_adapter.c
    src/linuxcnc_tripodkins_adapter.c
    src/linuxcnc_trtfuncs_adapter.c
    src/linuxcnc_userkfuncs_adapter.c
    src/linuxcnc_xyzab_tdr_kins_adapter.c
)
set_source_files_properties(
    ${cnc_sim_linuxcnc_kinematics_c_sources}
    PROPERTIES
        COMPILE_OPTIONS "$<$<COMPILE_LANGUAGE:CXX>:-fpermissive>"
)

if(CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND)
    find_package(Python3 REQUIRED COMPONENTS Development)
    target_compile_definitions(cnc_sim_objects
        PRIVATE
            CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND
    )
    target_include_directories(cnc_sim_objects
        PRIVATE
            ${Python3_INCLUDE_DIRS}
            ${CNC_SIM_LINUXCNC_ROOT}/src
            ${CNC_SIM_LINUXCNC_ROOT}/src/emc
            ${CNC_SIM_LINUXCNC_ROOT}/src/emc/nml_intf
            ${CNC_SIM_LINUXCNC_ROOT}/src/emc/rs274ngc
            ${CNC_SIM_LINUXCNC_ROOT}/src/emc/motion
            ${CNC_SIM_LINUXCNC_ROOT}/include
    )
endif()

if(CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE)
    target_compile_definitions(cnc_sim_objects
        PRIVATE
            CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND
            CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE
            ULAPI
    )
    target_compile_options(cnc_sim_objects PRIVATE ${cnc_sim_wasm_safe_preinclude_options})
    target_include_directories(cnc_sim_objects
        PRIVATE
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/nml_intf
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/rs274ngc
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/motion
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/include
            ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims
    )
    if(EMSCRIPTEN)
        target_compile_definitions(cnc_sim_objects PRIVATE FMT_HEADER_ONLY=1)
    endif()
endif()

if(CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE)
    add_executable(linuxcnc_rs274_wasm_safe_probe EXCLUDE_FROM_ALL
        $<TARGET_OBJECTS:linuxcnc_rs274_wasm_safe_probe_objects>
        $<TARGET_OBJECTS:cnc_sim_objects>
        ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/rs274ngc_pre_probe_main.cc
    )
    target_compile_features(linuxcnc_rs274_wasm_safe_probe PRIVATE cxx_std_20)
    target_compile_definitions(linuxcnc_rs274_wasm_safe_probe
        PRIVATE
            CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND
            CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE
            ULAPI
    )
    target_compile_options(linuxcnc_rs274_wasm_safe_probe PRIVATE ${cnc_sim_wasm_safe_preinclude_options})
    target_include_directories(linuxcnc_rs274_wasm_safe_probe
        PRIVATE
            ${CMAKE_CURRENT_SOURCE_DIR}/include
            ${CMAKE_CURRENT_SOURCE_DIR}/src
            ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/nml_intf
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/rs274ngc
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/motion
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/include
    )
    if(EMSCRIPTEN)
        target_compile_definitions(linuxcnc_rs274_wasm_safe_probe PRIVATE FMT_HEADER_ONLY=1)
    else()
        target_link_libraries(linuxcnc_rs274_wasm_safe_probe PRIVATE fmt)
    endif()

    add_executable(cnc_sim_wasm_runtime_probe EXCLUDE_FROM_ALL
        $<TARGET_OBJECTS:linuxcnc_rs274_wasm_safe_probe_objects>
        $<TARGET_OBJECTS:cnc_sim_objects>
        ${cnc_sim_wasm_runtime_probe_main}
    )
    target_compile_features(cnc_sim_wasm_runtime_probe PRIVATE cxx_std_20)
    target_compile_definitions(cnc_sim_wasm_runtime_probe PRIVATE ULAPI)
    target_compile_options(cnc_sim_wasm_runtime_probe PRIVATE ${cnc_sim_wasm_safe_preinclude_options})
    target_include_directories(cnc_sim_wasm_runtime_probe
        PRIVATE
            ${CMAKE_CURRENT_SOURCE_DIR}/include
            ${CMAKE_CURRENT_SOURCE_DIR}/src
            ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/nml_intf
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/rs274ngc
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/motion
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/include
    )
    if(EMSCRIPTEN)
        target_compile_definitions(cnc_sim_wasm_runtime_probe PRIVATE FMT_HEADER_ONLY=1)
    else()
        target_link_libraries(cnc_sim_wasm_runtime_probe PRIVATE fmt)
    endif()
endif()

if(CNC_SIM_ENABLE_SMOKE_BACKEND)
    target_compile_definitions(cnc_sim_objects
        PRIVATE
            CNC_SIM_ENABLE_SMOKE_BACKEND
    )
endif()

add_library(cnc_sim_core STATIC
    $<TARGET_OBJECTS:cnc_sim_objects>
)

target_include_directories(cnc_sim_core
    PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include
    PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

if(CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND)
    target_link_directories(cnc_sim_core
        PUBLIC
            ${CNC_SIM_LINUXCNC_ROOT}/lib
    )
    target_link_libraries(cnc_sim_core
        PUBLIC
            rs274
            tooldata
            Python3::Python
    )
    target_link_options(cnc_sim_core
        PUBLIC
            "-Wl,-rpath,${CNC_SIM_LINUXCNC_ROOT}/lib"
    )
endif()

if(NOT EMSCRIPTEN)
    if(CNC_SIM_ENABLE_SMOKE_BACKEND)
    add_executable(cnc_sim_api_smoke
        tests/cnc_sim_api_smoke.cpp
    )
    target_link_libraries(cnc_sim_api_smoke PRIVATE cnc_sim_core)
    target_include_directories(cnc_sim_api_smoke PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
    endif()

    add_executable(canon_event_sink_smoke
        tests/canon_event_sink_smoke.cpp
    )
    target_link_libraries(canon_event_sink_smoke PRIVATE cnc_sim_core)
    target_include_directories(canon_event_sink_smoke PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)

    add_executable(rtcp_kinematics_smoke
        tests/rtcp_kinematics_smoke.cpp
    )
    target_link_libraries(rtcp_kinematics_smoke PRIVATE cnc_sim_core)
    target_include_directories(rtcp_kinematics_smoke PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)

    add_executable(linuxcnc_gees_table_smoke
        tests/linuxcnc_gees_table_smoke.cpp
    )
    target_include_directories(linuxcnc_gees_table_smoke PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)

    add_executable(simulator_gcode_controls_smoke
        tests/simulator_gcode_controls_smoke.cpp
    )
    target_link_libraries(simulator_gcode_controls_smoke PRIVATE cnc_sim_core)
    target_include_directories(simulator_gcode_controls_smoke PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)

    if(CNC_SIM_ENABLE_SMOKE_BACKEND)
    add_executable(cnc_sim_dump
        tools/cnc_sim_dump.cpp
    )
    target_link_libraries(cnc_sim_dump PRIVATE cnc_sim_core)
    target_include_directories(cnc_sim_dump PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
    endif()

    if(CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND)
        add_executable(cnc_sim_api_linuxcnc_rs274_smoke
            tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp
        )
        target_link_libraries(cnc_sim_api_linuxcnc_rs274_smoke PRIVATE cnc_sim_core)
        target_include_directories(cnc_sim_api_linuxcnc_rs274_smoke PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
    endif()
endif()

if(CNC_SIM_ENABLE_LINUXCNC_BRIDGE)
    if(NOT CNC_SIM_LINUXCNC_ROOT)
        message(FATAL_ERROR "Set CNC_SIM_LINUXCNC_ROOT to the LinuxCNC source root")
    endif()
    add_library(cnc_sim_linuxcnc_canon_bridge STATIC
        src/linuxcnc_canon_bridge.cpp
    )
    target_include_directories(cnc_sim_linuxcnc_canon_bridge
        PRIVATE
            ${CMAKE_CURRENT_SOURCE_DIR}/include
            ${CMAKE_CURRENT_SOURCE_DIR}/src
            ${CNC_SIM_LINUXCNC_ROOT}/src/emc
            ${CNC_SIM_LINUXCNC_ROOT}/src/emc/nml_intf
            ${CNC_SIM_LINUXCNC_ROOT}/src/emc/rs274ngc
            ${CNC_SIM_LINUXCNC_ROOT}/src
            ${CNC_SIM_LINUXCNC_ROOT}/include
    )
    target_link_directories(cnc_sim_linuxcnc_canon_bridge
        PRIVATE
            ${CNC_SIM_LINUXCNC_ROOT}/lib
    )
    target_link_libraries(cnc_sim_linuxcnc_canon_bridge PRIVATE cnc_sim_core tooldata)
    target_link_options(cnc_sim_linuxcnc_canon_bridge
        PRIVATE
            "-Wl,-rpath,${CNC_SIM_LINUXCNC_ROOT}/lib"
    )
endif()

if(EMSCRIPTEN)
    if(NOT CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE)
        message(FATAL_ERROR "WASM builds must enable CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE so LinuxCNC rs274 sources are linked")
    endif()
    add_executable(cnc_sim_wasm
        $<TARGET_OBJECTS:linuxcnc_rs274_wasm_safe_probe_objects>
        $<TARGET_OBJECTS:cnc_sim_objects>
    )

    target_include_directories(cnc_sim_wasm
        PRIVATE
            ${CMAKE_CURRENT_SOURCE_DIR}/include
            ${CMAKE_CURRENT_SOURCE_DIR}/src
            ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/nml_intf
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/rs274ngc
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/motion
            ${CNC_SIM_LINUXCNC_ROOT_ABS}/include
    )
    target_compile_definitions(cnc_sim_wasm PRIVATE FMT_HEADER_ONLY=1)

    set_target_properties(cnc_sim_wasm PROPERTIES
        OUTPUT_NAME "cnc_sim"
        SUFFIX ".js"
    )

    target_link_options(cnc_sim_wasm PRIVATE
        "--no-entry"
        "-sMODULARIZE=1"
        "-sEXPORT_NAME=createCncSimModule"
        "-sALLOW_MEMORY_GROWTH=1"
        "-sALLOW_TABLE_GROWTH=1"
        "-sFORCE_FILESYSTEM=1"
        "-sEXPORTED_FUNCTIONS=['_malloc','_free','_cnc_sim_create','_cnc_sim_destroy','_cnc_sim_reset','_cnc_sim_set_dialect','_cnc_sim_set_event_callback','_cnc_sim_load_config_json','_cnc_sim_parse_program','_cnc_sim_last_error']"
        "-sEXPORTED_RUNTIME_METHODS=['FS','ccall','cwrap','UTF8ToString','stringToUTF8','lengthBytesUTF8','addFunction','removeFunction']"
    )
endif()
