cmake_minimum_required(VERSION 3.16)
project(cst-agent C)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

option(CST_BUILD_TUI "Build ncurses/PDCurses TUI" ON)
option(CST_NATIVE_ARCH "Pass -march=native to sashidown" OFF)
option(CST_BUNDLE_BUSYBOX "Install placeholder/scripts for busybox beside binary" ON)

set(SD_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vendor/sashidown")
if(CST_NATIVE_ARCH)
  set(_SD_MAKE_EXTRA CST_NATIVE_ARCH=1)
else()
  set(_SD_MAKE_EXTRA "")
endif()

# Separate archive for MinGW so Linux host builds keep libsashidown.a
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
  set(SASHIDOWN_A "${SD_DIR}/libsashidown-mingw.a")
  # Use whatever archiver CMake found for the active toolchain. Hardcoding
  # "x86_64-w64-mingw32-ar" only works on cross-build hosts, where binutils
  # installs prefixed names; MSYS2's mingw-w64 toolchain ships plain ar.exe
  # and ranlib.exe, so the hardcoded names are not found.
  set(_MINGW_AR "${CMAKE_AR}")
  set(_MINGW_RANLIB "${CMAKE_RANLIB}")
  add_custom_command(
    OUTPUT "${SASHIDOWN_A}"
    COMMAND ${CMAKE_MAKE_PROGRAM} -C "${SD_DIR}" clean
    COMMAND ${CMAKE_MAKE_PROGRAM} -C "${SD_DIR}" libsashidown.a
            CC=${CMAKE_C_COMPILER} AR=${_MINGW_AR} RANLIB=${_MINGW_RANLIB}
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
            "${SD_DIR}/libsashidown.a" "${SASHIDOWN_A}"
    COMMAND ${CMAKE_MAKE_PROGRAM} -C "${SD_DIR}" clean
    COMMENT "Building libsashidown for MinGW"
    VERBATIM)
else()
  set(SASHIDOWN_A "${SD_DIR}/libsashidown.a")
  add_custom_command(
    OUTPUT "${SASHIDOWN_A}"
    COMMAND ${CMAKE_MAKE_PROGRAM} -C "${SD_DIR}" libsashidown.a CC=${CMAKE_C_COMPILER} ${_SD_MAKE_EXTRA}
    COMMENT "Building libsashidown.a"
    VERBATIM)
endif()
add_custom_target(sashidown DEPENDS "${SASHIDOWN_A}")

find_package(CURL REQUIRED)
find_package(Threads REQUIRED)

set(CJSON_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vendor/cjson")

set(CST_CORE_SOURCES
  vendor/cjson/cJSON.c
  src/util.c
  src/ctl_parse.c
  src/os_compat.c
  src/sandbox.c
  src/sandbox_exec.c
  src/keymap.c
  src/skills.c
  src/extensions.c
  src/plugins.c
  src/mcp.c
  src/subagent.c
  src/session.c
  src/tools/tools.c
  src/tools/patch.c
  src/tools/registry.c
  src/tools/fs_tools.c
  src/tools/agent_tools.c
  src/tools/web.c
  src/tools/diff.c
  src/undo.c
  src/permissions.c
  src/settings.c
  src/llm_openai.c
  src/agent_loop.c
  src/agent_store.c
  src/json_baseline.c
  src/creds.c
  src/mm_tool.c
  src/models.c
  src/metrics.c
)
set(CST_SOURCES ${CST_CORE_SOURCES} src/main.c)

if(CST_BUILD_TUI)
  list(APPEND CST_SOURCES src/textsel.c src/tui.c)
else()
  list(APPEND CST_SOURCES src/tui_stub.c)
endif()

add_executable(cst-agent ${CST_SOURCES})
add_dependencies(cst-agent sashidown)

target_include_directories(cst-agent PRIVATE
  include
  "${SD_DIR}/include"
  "${CJSON_DIR}"
)

target_compile_definitions(cst-agent PRIVATE _GNU_SOURCE)
if(NOT WIN32)
  target_compile_definitions(cst-agent PRIVATE _XOPEN_SOURCE_EXTENDED)
endif()

target_link_libraries(cst-agent PRIVATE
  "${SASHIDOWN_A}"
  CURL::libcurl
  Threads::Threads
)
if(NOT WIN32)
  target_link_libraries(cst-agent PRIVATE m)
endif()

if(CST_BUILD_TUI)
  if(WIN32)
    # Prefer PDCurses (pdcurses / curses). Users may set PDCurses_ROOT.
    # PATH_SUFFIXES also covers the layout MSYS2's mingw-w64-pdcurses uses,
    # which puts the headers under include/pdcurses/ rather than include/.
    find_path(PDCURSES_INCLUDE_DIR curses.h
      HINTS ENV PDCurses_ROOT "${PDCurses_ROOT}"
      PATH_SUFFIXES include pdcurses include/pdcurses)
    find_library(PDCURSES_LIBRARY
      NAMES pdcurses pdcurses.dll curses
      HINTS ENV PDCurses_ROOT "${PDCurses_ROOT}"
      PATH_SUFFIXES lib)
    if(PDCURSES_INCLUDE_DIR AND PDCURSES_LIBRARY)
      target_include_directories(cst-agent PRIVATE ${PDCURSES_INCLUDE_DIR})
      target_link_libraries(cst-agent PRIVATE ${PDCURSES_LIBRARY})
      # PDCurses suffixes its exported symbols with an ABI tag (endwin_u64_4400
      # etc.). MSYS2 builds the UTF-8 wide variant, so PDC_FORCE_UTF8 has to be
      # defined or curses.h maps to the w64 names and the link fails.
      target_compile_definitions(cst-agent PRIVATE
        CST_USE_PDCURSES=1 PDC_FORCE_UTF8=1)
      message(STATUS "TUI: PDCurses (${PDCURSES_LIBRARY})")
    else()
      message(FATAL_ERROR "CST_BUILD_TUI=ON on Windows requires PDCurses (set PDCurses_ROOT)")
    endif()
  else()
    set(CURSES_NEED_WIDE TRUE)
    find_package(Curses)
    if(CURSES_FOUND)
      target_include_directories(cst-agent PRIVATE ${CURSES_INCLUDE_DIRS})
      target_link_libraries(cst-agent PRIVATE ${CURSES_LIBRARIES})
    else()
      # Fallback common on Linux distros
      target_link_libraries(cst-agent PRIVATE ncursesw)
    endif()
    target_compile_definitions(cst-agent PRIVATE CST_USE_NCURSES=1)
  endif()
else()
  target_compile_definitions(cst-agent PRIVATE CST_HEADLESS_ONLY=1)
endif()

if(CMAKE_SYSTEM_NAME STREQUAL "Windows" OR WIN32)
  # curl Schannel + networking; winmm is needed by PDCurses (PlaySoundW)
  target_link_libraries(cst-agent PRIVATE
    ws2_32 crypt32 secur32 bcrypt advapi32 winmm)
endif()

# --- tests ---
enable_testing()
set(_CST_SMALL_TESTS test_ctl_frontier test_ctl_heredoc test_json_baseline
    test_replay_starvation test_replay_real test_mm_tool test_textsel
    test_markdown)
foreach(t ${_CST_SMALL_TESTS})
  if(t STREQUAL test_textsel)
    add_executable(${t} tests/${t}.c src/textsel.c)
  elseif(t STREQUAL test_markdown)
    add_executable(${t} tests/${t}.c)
    target_include_directories(${t} PRIVATE "${SD_DIR}/include")
    target_link_libraries(${t} PRIVATE "${SASHIDOWN_A}")
    add_dependencies(${t} sashidown)
  elseif(t STREQUAL test_ctl_frontier OR t STREQUAL test_ctl_heredoc)
    add_executable(${t} tests/${t}.c src/ctl_parse.c)
  elseif(t STREQUAL test_json_baseline)
    add_executable(${t} tests/${t}.c src/json_baseline.c vendor/cjson/cJSON.c)
  elseif(t STREQUAL test_replay_starvation OR t STREQUAL test_replay_real)
    add_executable(${t} tests/${t}.c src/ctl_parse.c src/json_baseline.c vendor/cjson/cJSON.c)
  else()
    add_executable(${t} tests/test_mm_tool.c src/mm_tool.c)
  endif()
  target_include_directories(${t} PRIVATE include "${CJSON_DIR}")
  target_compile_definitions(${t} PRIVATE _GNU_SOURCE)
  if(NOT WIN32)
    target_link_libraries(${t} PRIVATE m)
  endif()
  add_test(NAME ${t} COMMAND ${t} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
endforeach()

# Tests that link the whole core (no TUI).
foreach(t test_llm_body test_sse_decode test_apply_patch test_session_roundtrip
          test_shell test_diff test_tools_fs test_registry
          test_undo test_config test_models test_sandbox
          test_keymap test_shell_compact test_skills
          test_plugins test_mcp test_subagent)
  add_executable(${t} tests/${t}.c ${CST_CORE_SOURCES})
  target_include_directories(${t} PRIVATE include "${CJSON_DIR}")
  target_compile_definitions(${t} PRIVATE _GNU_SOURCE)
  target_link_libraries(${t} PRIVATE CURL::libcurl Threads::Threads)
  if(NOT WIN32)
    target_link_libraries(${t} PRIVATE m)
  else()
    target_link_libraries(${t} PRIVATE ws2_32 crypt32 secur32 bcrypt advapi32)
  endif()
  add_test(NAME ${t} COMMAND ${t} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
endforeach()

# --- busybox bundle helpers ---
if(CST_BUNDLE_BUSYBOX)
  install(FILES
    "${CMAKE_CURRENT_SOURCE_DIR}/third_party/busybox/README.md"
    DESTINATION .
    OPTIONAL)
  if(WIN32 AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/busybox/busybox.exe")
    install(PROGRAMS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/busybox/busybox.exe" DESTINATION .)
  endif()
endif()

if(WIN32)
  install(TARGETS cst-agent RUNTIME DESTINATION .)
else()
  include(GNUInstallDirs)
  install(TARGETS cst-agent RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()

message(STATUS "Compiler: ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER}")
message(STATUS "CST_BUILD_TUI=${CST_BUILD_TUI}")
