set(SIMULATOR_INSTALL_PREFIX "" CACHE STRING "Alternative simulator library search path")
set(SIMULATOR_WASM_DIR "" CACHE PATH "Directory containing .wasm simulator modules to bundle")

set(COMPANION_NAME "companion")
set(SIMULATOR_NAME "simulator")
set(APP_NAME_SUFFIX "")

if(${CMAKE_SYSTEM_NAME} MATCHES "(Linux|FreeBSD)")
  set(APP_NAME_SUFFIX "${VERSION_MAJOR}${VERSION_MINOR}")
  set(COMPANION_NAME "${COMPANION_NAME}${APP_NAME_SUFFIX}")
  set(SIMULATOR_NAME "${SIMULATOR_NAME}${APP_NAME_SUFFIX}")
  # by default cmake sets CMAKE_INSTALL_PREFIX to /usr/local however linuxdeploy works best with '/usr' as base directory
  if (${CMAKE_INSTALL_PREFIX} MATCHES "/usr/local")
    set(CMAKE_INSTALL_PREFIX "/usr")
  endif()
  if (NOT ${SIMULATOR_INSTALL_PREFIX} STREQUAL "")
    set(SIMULATOR_LIB_PATH ${SIMULATOR_INSTALL_PREFIX}/lib/${COMPANION_NAME})
  else()
    set(SIMULATOR_LIB_PATH "../lib/${COMPANION_NAME}")
  endif()
  message(STATUS "Simulators library search path: " ${SIMULATOR_LIB_PATH})
endif()

option(DEBUG_STORAGE_IMPORT "Turn on debug output for storage import") # Disabled by default
if(DEBUG_STORAGE_IMPORT)
  add_definitions(-DDEBUG_STORAGE_IMPORT)
  message(STATUS "Storage import debugging enabled")
endif()

if(DEBUG)
  add_definitions(-DDEBUG)
endif()

option(TABLE_LAYOUT "Use QTableWidget for grid layouts") # Disabled by default
if(TABLE_LAYOUT)
  add_definitions(-DTABLE_LAYOUT)
  message(STATUS "Using QTableWidget")
endif()

if(WIN32)
  option(WIN_DO_FULL_INSTALL "'install' target will perform a full installation into CMAKE_INSTALL_PREFIX path, otherwise only supporting DLLs are copied to binaries folder (default)" OFF)
  option(WIN_USE_CONSOLE "Open a Windows command console for Simulator debug output." OFF)
  if(WIN_USE_CONSOLE)
    add_definitions(-DWIN_USE_CONSOLE_STDIO)
  endif()
endif()

if(TARGET SDL2::SDL2)
  add_definitions(-DUSE_SDL)
  set(SDL2_LIBRARIES SDL2::SDL2)
endif()

add_definitions(${APP_COMMON_DEFINES})  # set in top-level CMakeLists

set(WARNING_FLAGS "${WARNING_FLAGS} -Wall")
if(WARNINGS_AS_ERRORS)
  set(WARNING_FLAGS "${WARNING_FLAGS} -Werror")
endif()

if(WIN32)
  if(NOT WIN_USE_CONSOLE)
    set(WIN_EXECUTABLE_TYPE WIN32)  # GUI (WinMain) app
  endif()
  if(MINGW)
    get_filename_component(MINGW_DIR ${CMAKE_CXX_COMPILER} DIRECTORY)
    # struct packing breaks on MinGW w/out -mno-ms-bitfields: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991
    # & http://stackoverflow.com/questions/24015852/struct-packing-and-alignment-with-mingw
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mno-ms-bitfields")
  endif()
else()
  link_directories(/usr/local/lib)
endif()

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}")

include_directories(
  ${CMAKE_BINARY_DIR}
  ${CMAKE_CURRENT_BINARY_DIR}
  ${CMAKE_SOURCE_DIR}
  ${COMPANION_SRC_DIRECTORY}
  ${COMPANION_SRC_DIRECTORY}/shared
  ${SIMU_SRC_DIRECTORY}
  ${COMPANION_SRC_DIRECTORY}/thirdparty/qcustomplot
  ${COMPANION_SRC_DIRECTORY}/storage
  ${COMPANION_SRC_DIRECTORY}/flash
  ${COMPANION_SRC_DIRECTORY}/progress
  ${COMPANION_SRC_DIRECTORY}/print
)

############# Do macro replacements on input files #############

configure_file(${COMPANION_SRC_DIRECTORY}/version.h.in ${CMAKE_BINARY_DIR}/version.h @ONLY)

############# Translations ###############

### Generate .qm files and assemble resource (qrc) file.

# available Companion translations (src/translations/*.ts)
set(LANGUAGES cs da de en es fi fr he it ja ko nl pl pt ru sv zh_CN zh_TW)
foreach(language ${LANGUAGES})
  list(APPEND companion_TS translations/companion_${language}.ts)
endforeach(language)
# .ts -> .qm
qt_add_translation(companion_QM ${companion_TS})

# add Qt translations if found
if(QT_TRANSLATIONS_DIR)
  # Some languages have all translations in one qt_*.qm file and others break them up into modules, in which case we need qt_base_*.qm
  file(GLOB qtbase_QM "${QT_TRANSLATIONS_DIR}/qtbase_*.qm")
  file(GLOB qt_QM "${QT_TRANSLATIONS_DIR}/qt_??.qm")  # don't want to match "qt_help_*.qm"
  list(APPEND qt_QM "${QT_TRANSLATIONS_DIR}/qt_zh_CN.qm" "${QT_TRANSLATIONS_DIR}/qt_zh_TW.qm")  # exceptions not matched by the GLOB
else()
  message(STATUS "QT_TRANSLATIONS_DIR not found, unable to package pre-built Qt translations with application.")
endif()

set(TRANSLATIONS_QRC "${CMAKE_CURRENT_BINARY_DIR}/translations.qrc")

# Dynamically create translations.qrc file (XML) from all collected translation files.
foreach(qm_file ${companion_QM} ${qt_QM} ${qtbase_QM})
  get_filename_component(qm_file_name ${qm_file} NAME)
  # Add file with full path and file name (w/out path) as alias to be used in actual code
  set(TRANSLATION_FILES_LIST "${TRANSLATION_FILES_LIST}    <file alias=\"${qm_file_name}\">${qm_file}</file>\n")
endforeach()
configure_file(${COMPANION_SRC_DIRECTORY}/translations.qrc.in ${TRANSLATIONS_QRC} @ONLY)

### Add optional "companion_translations" target for updating .ts files
if(QT_LUPDATE_EXECUTABLE)
  add_custom_target(companion_translations
    WORKING_DIRECTORY ${COMPANION_SRC_DIRECTORY}
    COMMAND ${QT_LUPDATE_EXECUTABLE} ${COMPANION_SRC_DIRECTORY} -no-obsolete -ts ${companion_TS}
    )
else()
  message(STATUS "Qt lupdate not found, 'companion_translations' target will not be availabe.")
endif()

############# Import radio hardware definitions ###############

set(HWDEFS_DIR "${CMAKE_SOURCE_DIR}/radio/src/boards/hw_defs")
set(HWDEFS_TMPL "${COMPANION_SRC_DIRECTORY}/hwdefs.qrc.in")
set(HWDEFS_PHDR "HWDEF_JSON_LIST")
set(HWDEFS_QRC "${CMAKE_CURRENT_BINARY_DIR}/hwdefs.qrc")
set(HWDEFS_CMD "${COMPANION_SRC_DIRECTORY}/../util/generate_hwdefs_qrc.py")

############# Common lib ###############

set(common_SRCS
  customdebug.cpp
  helpers.cpp
  translations.cpp
  modeledit/node.cpp  # used in simulator
  modeledit/edge.cpp  # used by node
  )

set(common_MOC_HDRS
  helpers.h
  modeledit/node.h
  )

qt_wrap_cpp(common_SRCS ${common_MOC_HDRS})

# qrc used by Companion and Simulator
add_custom_command(OUTPUT ${HWDEFS_QRC}
  COMMAND ${PYTHON_EXECUTABLE} ${HWDEFS_CMD}
      -d ${HWDEFS_DIR}
      -t ${HWDEFS_TMPL}
      -p ${HWDEFS_PHDR}
      -o ${HWDEFS_QRC}
  DEPENDS ${HWDEFS_TMPL}
)

set(common_RESOURCES
  companion.qrc
  ${TRANSLATIONS_QRC}
  ${HWDEFS_QRC}
)
qt_add_resources(common_RCC ${common_RESOURCES})

add_library(common ${common_SRCS} ${common_RCC})

target_link_libraries(common
  datamodels
  firmwares
  progress
  shared
  simulation
  storage
  maxLibQt
  Qt::Core
  Qt::Xml
  Qt::Widgets
  Qt::SerialPort
  ${SDL2_LIBRARIES}
)

set(CPN_COMMON_LIB common)

############# Supporting libraries ###############

include(FetchMiniz)
include(FetchYamlCpp)
include(FetchMaxLibQt)

add_subdirectory(datamodels)
add_subdirectory(firmwares)
add_subdirectory(flash)
add_subdirectory(generaledit)
add_subdirectory(modeledit)
add_subdirectory(print)
add_subdirectory(progress)
add_subdirectory(prefsedit)
add_subdirectory(shared)
add_subdirectory(simulation)
add_subdirectory(storage)
add_subdirectory(thirdparty/qcustomplot)
add_subdirectory(updates)
add_subdirectory(wizard)

############# Companion ###############

include_directories(${maxLibQt_SOURCE_DIR}/widgets)

set(companion_NAMES
  helpers_html
  logsdialog
  mainwindow
  mdichild
  profilechooser
  dialogs/filesyncdialog
  )

foreach(name ${companion_NAMES})
  set(companion_SRCS
    ${companion_SRCS}
    ${name}.cpp
  )

  set(companion_HDRS
    ${companion_HDRS}
    ${name}.h
  )
endforeach()

set(companion_SRCS
  ${companion_SRCS}
  companion.cpp
)

if(WIN32)
  set(icon_RC images/winicons/icon.rc)
endif()

add_executable(${COMPANION_NAME} MACOSX_BUNDLE ${WIN_EXECUTABLE_TYPE}
  ${companion_SRCS}
  ${companion_HDRS}
  ${icon_RC}
)

target_link_libraries(${COMPANION_NAME} PRIVATE
  ${CPN_COMMON_LIB}
  ${SDL2_LIBRARIES}
  generaledit
  modeledit
  qcustomplot
  miniz
  updates
  flash
  wizard
  print
  prefsedit
)

PrintTargetReport("${COMPANION_NAME}")

############# Standalone simulator ###############

set(simu_SRCS simulator.cpp )

add_executable(${SIMULATOR_NAME} MACOSX_BUNDLE ${WIN_EXECUTABLE_TYPE}
  ${simu_SRCS}
  ${icon_RC}
)

target_link_libraries(${SIMULATOR_NAME} PRIVATE
  ${CPN_COMMON_LIB}
  ${SDL2_LIBRARIES}
)

# Copy WASM simulator modules next to executables for local dev
if(SIMULATOR_WASM_DIR)
  foreach(_target ${COMPANION_NAME} ${SIMULATOR_NAME})
    add_custom_command(TARGET ${_target} POST_BUILD
      COMMAND ${CMAKE_COMMAND}
        -DSRC_DIR=${SIMULATOR_WASM_DIR}
        -DDST_DIR=$<TARGET_FILE_DIR:${_target}>
        -P ${CMAKE_SOURCE_DIR}/cmake/CopyWasmModules.cmake
      COMMENT "Copying WASM simulator modules to ${_target} output"
    )
  endforeach()
endif()

add_subdirectory(tests)

############# Install ####################

set(COMPANION_TARGETS_DIR_ROOT "${COMPANION_SRC_DIRECTORY}/../targets")

if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
  set(COMPANION_TARGETS_DIR "${COMPANION_TARGETS_DIR_ROOT}/linux")
elseif(APPLE)
  set(COMPANION_TARGETS_DIR "${COMPANION_TARGETS_DIR_ROOT}/mac")
elseif(WIN32)
  set(COMPANION_TARGETS_DIR "${COMPANION_TARGETS_DIR_ROOT}/windows")
endif()

# updated license file
set(LICENSE_FILE "${CMAKE_CURRENT_BINARY_DIR}/license.txt")
configure_file("${COMPANION_TARGETS_DIR_ROOT}/license.txt.in" ${LICENSE_FILE} @ONLY)

if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
  message(STATUS "Install " ${CMAKE_BINARY_DIR} " to " ${CMAKE_INSTALL_PREFIX})
  install(TARGETS ${COMPANION_NAME}
          DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})
  install(TARGETS ${SIMULATOR_NAME}
          DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})

  # configure custom AppRun script from template
  configure_file(${COMPANION_TARGETS_DIR}/AppRun.in ${CMAKE_BINARY_DIR}/AppDir/AppRun @ONLY
  )

  # Make it executable
  file(CHMOD ${CMAKE_BINARY_DIR}/AppDir/AppRun
      PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
                  GROUP_READ GROUP_EXECUTE
                  WORLD_READ WORLD_EXECUTE
  )

  # configure and set variables used by package script
  set(COMPANION_DESKTOP_FILE ${CMAKE_CURRENT_BINARY_DIR}/${COMPANION_NAME}.desktop)
  configure_file(${COMPANION_TARGETS_DIR}/companion.desktop.in ${COMPANION_DESKTOP_FILE} @ONLY)

  set(SIMULATOR_DESKTOP_FILE ${CMAKE_CURRENT_BINARY_DIR}/${SIMULATOR_NAME}.desktop)
  configure_file(${COMPANION_TARGETS_DIR}/simulator.desktop.in ${SIMULATOR_DESKTOP_FILE} @ONLY)

  # libsim*.so files
  install(DIRECTORY ${CMAKE_BINARY_DIR}/plugins/
          DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/${COMPANION_NAME})

  # application icons
  set(icon_sizes "16;22;24;32;48;64;128;256;512")
  foreach(icon_size ${icon_sizes})
    install(FILES "images/linuxicons/${icon_size}x${icon_size}/companion.png"
            DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/${icon_size}x${icon_size}/apps"
            RENAME "${COMPANION_NAME}.png")
  endforeach()

  unset(icon_size)
  unset(icon_sizes)

  install(FILES "images/linuxicons/scalable/companion.svg"
          DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps"
          RENAME "${COMPANION_NAME}.svg")

  if(OPENSSL_FOUND)
    get_filename_component(OPENSSL_SSL_LIBRARY_DIR ${OPENSSL_SSL_LIBRARY} DIRECTORY)
    install(DIRECTORY ${OPENSSL_SSL_LIBRARY_DIR}/
            DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
            FILES_MATCHING
              PATTERN "libcrypto.so.*"
              PATTERN "libssl.so.*")
  endif()

  set(OperatingSystem "Linux")
  # Shortcut target
  add_custom_target(companion DEPENDS ${COMPANION_NAME})
  add_custom_target(simulator DEPENDS ${SIMULATOR_NAME})

elseif(WIN32)
  set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/_install")
  message(STATUS "Install to " ${CMAKE_INSTALL_PREFIX})

  # companion and simulator apps
  install(TARGETS
            ${COMPANION_NAME}
            ${SIMULATOR_NAME}
            DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})

  # install libsim dlls into same directory as apps
  install(DIRECTORY ${CMAKE_BINARY_DIR}/plugins/
          DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})

  install(FILES ${LICENSE_FILE}
          DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})

  if(OPENSSL_FOUND)
    cmake_path(GET OPENSSL_SSL_LIBRARY
               PARENT_PATH
               OPENSSL_SSL_LIBRARY_DIR)

    if(MINGW)
      # the dlls are stored in the bin directory
      set(OPENSSL_SSL_LIBRARY_DIR "${OPENSSL_SSL_LIBRARY_DIR}/../bin")
    endif()

    if(EXISTS "${OPENSSL_SSL_LIBRARY_DIR}")
      install(DIRECTORY ${OPENSSL_SSL_LIBRARY_DIR}/
              DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}
              FILES_MATCHING
                PATTERN "libcrypto-*.dll"
                PATTERN "libssl-*.dll")

    endif()
  endif(OPENSSL_FOUND)

  if(TARGET SDL2::SDL2)
    install(IMPORTED_RUNTIME_ARTIFACTS SDL2::SDL2
            RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})
  endif()

  include(FindMSVCRedist)

  if(MSVC_REDIST_EXE)
    install(FILES ${MSVC_REDIST_EXE}
            DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})
  endif()

elseif(APPLE)
  # Qt + Cmake + Mac is poorly documented. A lot of this is guesswork
  # and trial and error. Do not hesitate to fix it for the better
  set(COMPANION_DISPLAY_NAME "EdgeTX Companion")
  # This the name that the user will see in the generated DMG and what the application
  # will be called under /Applications. We include the version string to make installing
  # different versions side-by-side
  set(COMPANION_OSX_APP_BUNDLE_NAME "${COMPANION_DISPLAY_NAME} ${VERSION_FAMILY}")
  set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.edgetx.companion")

  set_target_properties(${COMPANION_NAME} PROPERTIES
      OUTPUT_NAME "${COMPANION_OSX_APP_BUNDLE_NAME}"
      MACOSX_RPATH TRUE
      MACOSX_BUNDLE TRUE
      MACOSX_BUNDLE_BUNDLE_NAME "${COMPANION_DISPLAY_NAME}"
      MACOSX_BUNDLE_INFO_PLIST ${COMPANION_TARGETS_DIR}/MacOSXBundleInfo.plist.in
      INSTALL_RPATH "@executable_path/../Frameworks"
      BUILD_WITH_INSTALL_RPATH TRUE
  )

  set(companion_app_dir "${COMPANION_OSX_APP_BUNDLE_NAME}.app")
  set(companion_res_dir "${companion_app_dir}/Contents/Resources")

  # Only Companion is distributed for macOS
  install(TARGETS ${COMPANION_NAME}
          BUNDLE DESTINATION . COMPONENT Runtime)

  # libsims
  install(DIRECTORY ${CMAKE_BINARY_DIR}/plugins/
          DESTINATION "${companion_res_dir}"
          COMPONENT Runtime
  )

  # menu file
  install(DIRECTORY ${COMPANION_TARGETS_DIR}/qt_menu.nib
          DESTINATION "${companion_res_dir}"
          COMPONENT Runtime
  )

  # Add icon
  set(MAC_ICON_FILE ${COMPANION_SRC_DIRECTORY}/images/iconmac.icns)
  set_source_files_properties(
    ${MAC_ICON_FILE}
    PROPERTIES MACOSX_PACKAGE_LOCATION Resources
  )

  install(FILES ${MAC_ICON_FILE}
          DESTINATION "${companion_res_dir}"
          COMPONENT Runtime)
endif()

############# Packaging ####################

string(TOLOWER "${PROJECT_NAME}" PROJECT_NAME_LOWERCASE)

# Variables common to all CPack generators
set(CPACK_GENERATOR)
set(CPACK_PACKAGE_NAME "companion${APP_NAME_SUFFIX}")
string(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_PACKAGE_NAME_LOWERCASE)
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Models and settings editor for the ${PROJECT_NAME} open source firmware")
set(CPACK_PACKAGE_VENDOR "${PROJECT_NAME}")
set(CPACK_PACKAGE_CONTACT "${PROJECT_NAME} Project Steering Committee <psc@${PROJECT_NAME_LOWERCASE}.org>")
set(CPACK_PACKAGE_HOMEPAGE_URL "https://${PROJECT_NAME_LOWERCASE}.org")
set(CPACK_PACKAGE_VERSION "${VERSION_FAMILY}")
set(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${VERSION_REVISION})
set(CPACK_RESOURCE_FILE_LICENSE ${LICENSE_FILE})
# The file stripping is deliberately disabled, with the stripped file we get
# very poor trace-backs from the users when they report Companion crash
set(CPACK_STRIP_FILES FALSE)

if(WIN32)
  set(companion_executable_path "\${CMAKE_INSTALL_BINDIR}/$<TARGET_FILE_NAME:companion>")
  set(simulator_executable_path "\${CMAKE_INSTALL_BINDIR}/$<TARGET_FILE_NAME:simulator>")
  cmake_path(GET CMAKE_CXX_COMPILER PARENT_PATH MINGW_BIN_DIR)

  qt_generate_deploy_script(
    TARGET companion
    OUTPUT_SCRIPT qt_deploy_script
    CONTENT "
      set(QT_DEPLOY_PREFIX ${CMAKE_INSTALL_PREFIX})
      qt_deploy_runtime_dependencies(
        EXECUTABLE ${companion_executable_path}
        ADDITIONAL_EXECUTABLES ${simulator_executable_path}
        NO_TRANSLATIONS
        NO_COMPILER_RUNTIME
        GENERATE_QT_CONF
        VERBOSE
      )
    "
  )

  # Create Windows installer with NSIS
  # TODO use in-built CPack NSIS
  find_program(NSIS_EXE makensis.exe
    PATHS
      "C:/Program Files/NSIS"
      "C:/Program Files (x86)/NSIS"
      "C:/Programs/NSIS"
      "${WIN_EXTRA_LIBS_PATH}/NSIS"
  )

  if(NSIS_EXE)
    # list(APPEND CPACK_GENERATOR "NSIS")
    # Set CPACK_NSIS_variables

    cmake_path(NATIVE_PATH CMAKE_INSTALL_PREFIX NORMALIZE NSIS_DISTRO)   # all files in here will be included in installer
    cmake_path(NATIVE_PATH COMPANION_TARGETS_DIR NORMALIZE NSIS_TARGETS_DIR)   # all files in here will be included in installer
    cmake_path(GET MSVC_REDIST_EXE FILENAME NSIS_MSVC_REDIST_EXE)
    set(NSI_FILE "${PROJECT_BINARY_DIR}/companion/companion.nsi")
    configure_file("${COMPANION_TARGETS_DIR}/companion.nsi.in" "${NSI_FILE}" @ONLY)

    # The 'install' target prepares all the distro files, make sure it has been executed first.
    add_custom_target(installer
      COMMAND "${CMAKE_COMMAND}" -P "${qt_deploy_script}"
      COMMAND "${NSIS_EXE}" "${NSI_FILE}"
      DEPENDS install "${NSI_FILE}" "${qt_deploy_script}"
      COMMENT "Building Windows NSIS installer..."
    )
  endif()

elseif(APPLE)
  set(CPACK_GENERATOR "DragNDrop")
  set(CPACK_BINARY_DRAGNDROP ON)

  set(CPACK_DMG_VOLUME_NAME "${PROJECT_NAME} Companion")
  set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME_LOWERCASE}-${CPACK_PACKAGE_NAME_LOWERCASE}-${VERSION}")
  set(CPACK_DMG_DS_STORE ${COMPANION_TARGETS_DIR}/DS_Store)

  qt_generate_deploy_script(
    TARGET companion
    OUTPUT_SCRIPT qt_deploy_companion
    CONTENT "
      qt_deploy_runtime_dependencies(
        EXECUTABLE \"${companion_app_dir}\"
        NO_TRANSLATIONS
        NO_COMPILER_RUNTIME
        GENERATE_QT_CONF
      )
    "
  )

  # Run macdeployqt
  install(SCRIPT ${qt_deploy_companion} COMPONENT Runtime)

  option(COMPANION_VERIFY_BUNDLE
         "Fail the install if the macOS bundle references libraries outside itself" ON)
  set(COMPANION_VERIFY_BUNDLE_ALLOW "" CACHE STRING
      "Regex of absolute library paths the bundle verification may ignore")

  # Bundle SDL2 (copy the real dylib; IMPORTED_RUNTIME_ARTIFACTS keeps
  # Homebrew symlinks and breaks the app inside a .dmg). Homebrew's
  # sdl2-compat also loads SDL3 from @loader_path/libSDL3.dylib at runtime.
  set(_bundle_expects_sdl3 FALSE)
  set(_bundled_sdl2 FALSE)
  if(TARGET SDL2::SDL2)
    get_target_property(_sdl2_location SDL2::SDL2 IMPORTED_LOCATION)
    if(NOT _sdl2_location)
      get_target_property(_sdl2_location SDL2::SDL2 IMPORTED_LOCATION_RELEASE)
    endif()
    if(_sdl2_location AND _sdl2_location MATCHES "\\.framework(/|$)")
      # Genuine upstream SDL2.framework (a manual framework install): no SDL3 dlopen
      # shim to worry about, and IMPORTED_RUNTIME_ARTIFACTS copies frameworks
      # correctly — it's only the dylib case below whose symlinks it mangles.
      # CI no longer takes this path; setup-sdl.sh builds sdl2-compat + SDL3 instead.
      message(STATUS "Bundling SDL2 framework: ${_sdl2_location}")
      install(IMPORTED_RUNTIME_ARTIFACTS SDL2::SDL2
              FRAMEWORK DESTINATION "${companion_app_dir}/Contents/Frameworks"
              LIBRARY   DESTINATION "${companion_app_dir}/Contents/Frameworks"
              COMPONENT Runtime)
      set(_bundled_sdl2 TRUE)
    elseif(_sdl2_location)
      message(STATUS "Bundling SDL2 library: ${_sdl2_location}")
      set(_bundled_sdl2 TRUE)
      get_filename_component(_sdl2_real "${_sdl2_location}" REALPATH)
      get_filename_component(_sdl2_name "${_sdl2_location}" NAME)
      install(FILES "${_sdl2_real}"
              DESTINATION "${companion_app_dir}/Contents/Frameworks"
              RENAME "${_sdl2_name}"
              COMPONENT Runtime)

      set(_sdl3_real "")
      find_package(SDL3 CONFIG QUIET)
      if(TARGET SDL3::SDL3-shared)
        get_target_property(_sdl3_location SDL3::SDL3-shared IMPORTED_LOCATION_RELEASE)
        if(NOT _sdl3_location)
          get_target_property(_sdl3_location SDL3::SDL3-shared IMPORTED_LOCATION)
        endif()
        if(_sdl3_location)
          get_filename_component(_sdl3_real "${_sdl3_location}" REALPATH)
        endif()
      endif()
      if(NOT _sdl3_real)
        set(_sdl3_hints "")
        # SDL3 usually lives right next to SDL2 (setup-sdl.sh installs both to one prefix) -
        # check there before falling back to Homebrew heuristics.
        get_filename_component(_sdl2_libdir "${_sdl2_location}" DIRECTORY)
        list(APPEND _sdl3_hints "${_sdl2_libdir}")
        if(DEFINED ENV{HOMEBREW_PREFIX})
          list(APPEND _sdl3_hints
            "$ENV{HOMEBREW_PREFIX}/opt/sdl3/lib"
            "$ENV{HOMEBREW_PREFIX}/lib")
        endif()
        find_program(_brew_EXECUTABLE brew)
        if(_brew_EXECUTABLE)
          execute_process(
            COMMAND "${_brew_EXECUTABLE}" --prefix sdl3
            OUTPUT_VARIABLE _brew_sdl3_prefix
            OUTPUT_STRIP_TRAILING_WHITESPACE
            ERROR_QUIET
            RESULT_VARIABLE _brew_sdl3_result
          )
          if(_brew_sdl3_result EQUAL 0 AND _brew_sdl3_prefix)
            list(APPEND _sdl3_hints "${_brew_sdl3_prefix}/lib")
          endif()
        endif()
        find_library(_sdl3_library
          NAMES SDL3.0 SDL3
          HINTS ${_sdl3_hints}
          NO_DEFAULT_PATH
        )
        if(NOT _sdl3_library)
          find_library(_sdl3_library NAMES SDL3.0 SDL3)
        endif()
        if(_sdl3_library)
          get_filename_component(_sdl3_real "${_sdl3_library}" REALPATH)
        endif()
      endif()
      if(_sdl3_real)
        install(FILES "${_sdl3_real}"
                DESTINATION "${companion_app_dir}/Contents/Frameworks"
                RENAME "libSDL3.dylib"
                COMPONENT Runtime)
        set(_bundle_expects_sdl3 TRUE)
      else()
        message(FATAL_ERROR
          "SDL3 not found, but the bundled SDL2 (${_sdl2_location}) is sdl2-compat, which "
          "dlopens libSDL3.dylib at runtime - Companion would not start without it. Install "
          "SDL3, or point CMAKE_PREFIX_PATH/HOMEBREW_PREFIX at a prefix that has it.")
      endif()

      install(CODE "
        set(_fw \"\${CMAKE_INSTALL_PREFIX}/${companion_app_dir}/Contents/Frameworks\")
        execute_process(
          COMMAND install_name_tool -id \"@executable_path/../Frameworks/${_sdl2_name}\" \"\${_fw}/${_sdl2_name}\"
          RESULT_VARIABLE _sdl2_id_result
          ERROR_VARIABLE _sdl2_id_error
        )
        if(NOT _sdl2_id_result EQUAL 0)
          message(FATAL_ERROR \"install_name_tool failed for ${_sdl2_name}: \${_sdl2_id_error}\")
        endif()
        if(EXISTS \"\${_fw}/libSDL3.dylib\")
          execute_process(
            COMMAND install_name_tool -id \"@executable_path/../Frameworks/libSDL3.dylib\" \"\${_fw}/libSDL3.dylib\"
            RESULT_VARIABLE _sdl3_id_result
            ERROR_VARIABLE _sdl3_id_error
          )
          if(NOT _sdl3_id_result EQUAL 0)
            message(FATAL_ERROR \"install_name_tool failed for libSDL3.dylib: \${_sdl3_id_error}\")
          endif()
        endif()
      " COMPONENT Runtime)
    else()
      message(WARNING "SDL2::SDL2 has no IMPORTED_LOCATION; SDL2 will not be bundled")
    endif()

    # Point the executable at the SDL copies bundled above. qt_deploy_runtime_dependencies
    # ran before they existed, and install_name_tool -id only fixes each dylib's own id, so
    # nothing else guarantees this. Runs for both the framework and dylib branches, and must
    # stay ahead of the re-sign below so the signature covers the rewritten load commands.
    # Skipped when nothing was bundled - rewriting to Frameworks would then point the
    # executable at files that are not there.
    if(_bundled_sdl2)
      configure_file(
        "${COMPANION_TARGETS_DIR}/bundle_sdl_fixup.cmake.in"
        "${CMAKE_CURRENT_BINARY_DIR}/bundle_sdl_fixup.cmake"
        @ONLY
      )
      install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/bundle_sdl_fixup.cmake" COMPONENT Runtime)
    endif()
  endif()

  # Re-sign bundle after all contents are installed
  install(CODE "
      set(app_dir \"\${CMAKE_INSTALL_PREFIX}/${companion_app_dir}\")
      execute_process(COMMAND \${CMAKE_COMMAND} -E echo \"Re-signing: \${app_dir}\")
      execute_process(
        COMMAND codesign --remove-signature \${app_dir}
        COMMAND codesign --force --deep -s - \${app_dir}
      )
  " COMPONENT Runtime)

  # Last gate: a bundle that still points at /opt/homebrew or a build-machine Qt prefix
  # starts on the machine that built it and nowhere else. Fail here rather than ship it.
  if(COMPANION_VERIFY_BUNDLE)
    configure_file(
      "${COMPANION_TARGETS_DIR}/bundle_verify.cmake.in"
      "${CMAKE_CURRENT_BINARY_DIR}/bundle_verify.cmake"
      @ONLY
    )
    install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/bundle_verify.cmake" COMPONENT Runtime)
  endif()

else()
  # Detect architecture for linuxdeploy AppImage
  if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
    set(LINUXDEPLOY_ARCH "aarch64")
  else()
    set(LINUXDEPLOY_ARCH "x86_64")
  endif()

  set(LINUXDEPLOY_APPIMAGE "linuxdeploy-${LINUXDEPLOY_ARCH}.AppImage")
  set(LINUXDEPLOY_PLUGIN_QT "linuxdeploy-plugin-qt-${LINUXDEPLOY_ARCH}.AppImage")
  set(LINUXDEPLOY_URL "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous")
  set(LINUXDEPLOY_PLUGIN_QT_URL "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous")
  set(LINUXDEPLOY_DIRECTORY "${CMAKE_BINARY_DIR}/linuxdeploy")
  set(LINUXDEPLOY_APP "${LINUXDEPLOY_DIRECTORY}/usr/bin/linuxdeploy")

  if(NOT EXISTS "${LINUXDEPLOY_DIRECTORY}")
    message(STATUS "Downloading linuxdeploy and plugins...")

    file(DOWNLOAD "${LINUXDEPLOY_URL}/${LINUXDEPLOY_APPIMAGE}" "${CMAKE_BINARY_DIR}/${LINUXDEPLOY_APPIMAGE}")
    file(DOWNLOAD "${LINUXDEPLOY_PLUGIN_QT_URL}/${LINUXDEPLOY_PLUGIN_QT}" "${CMAKE_BINARY_DIR}/${LINUXDEPLOY_PLUGIN_QT}")

    message(STATUS "Downloading linuxdeploy and plugins finished")

    # for security reasons by default AppImages are prohibited from running in Docker containers
    # so extract Appimage contents and run as normal apps

    file(GLOB linuxdeployfiles ${CMAKE_BINARY_DIR}/linuxdeploy*.AppImage)

    foreach(file ${linuxdeployfiles})
      execute_process(
        COMMAND chmod +x "${file}"
        WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")

      execute_process(
        COMMAND "${file}" --appimage-extract
        WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")

      file(REMOVE "${file}")
    endforeach()

    file(REMOVE_RECURSE "${LINUXDEPLOY_DIRECTORY}")

    execute_process(
      COMMAND mv "${CMAKE_BINARY_DIR}/squashfs-root" "${LINUXDEPLOY_DIRECTORY}"
      WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")

    # test installation
    execute_process(
      COMMAND ${LINUXDEPLOY_APP} --list-plugins
      WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
  endif()

  list(APPEND CPACK_GENERATOR "External")
  set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
  set(APPIMAGE_DIR "${CPACK_PACKAGE_DIRECTORY}/_CPack_Packages/Linux/External/AppImage")
  configure_file("${COMPANION_TARGETS_DIR}/CPackLinuxDeploy.cmake.in" "${CMAKE_BINARY_DIR}/CPackExternal.cmake" @ONLY)
  set(CPACK_EXTERNAL_PACKAGE_SCRIPT "${CMAKE_BINARY_DIR}/CPackExternal.cmake")
endif()

include(CPack)
