# Copyright (C) 2020-2026 Savoir-faire Linux Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.

cmake_minimum_required(VERSION 3.19)

if(APPLE)
  project(Jami)
else()
  project(jami)
endif()

set(CMAKE_SCRIPTS_DIR ${PROJECT_SOURCE_DIR}/extras/build/cmake)
include(${CMAKE_SCRIPTS_DIR}/extra_tools.cmake)

option(WITH_DAEMON_SUBMODULE "Build with daemon submodule" ON)
option(JAMICORE_AS_SUBDIR "Build Jami-core as a subdir dependency" ON)
option(WITH_WEBENGINE "Build with WebEngine" ON)
option(ENABLE_LIBWRAP "Enable libwrap (single process mode)" ON)
option(JAMI_CQFD_BUILD "Configure the Docker-backed CQFD test build" OFF)
get_filename_component(JAMI_BINARY_DIR_NAME "${CMAKE_BINARY_DIR}" NAME)
if(JAMI_BINARY_DIR_NAME STREQUAL "build-cqfd" AND NOT JAMI_CQFD_BUILD)
  message(FATAL_ERROR
    "build-cqfd is reserved for the Docker-backed CQFD test build and cannot "
    "be configured by a host IDE. Use the CMake preset 'host-dev' or configure "
    "the host build in '${PROJECT_SOURCE_DIR}/build' instead. The CQFD runner "
    "now uses '${PROJECT_SOURCE_DIR}/.build-cqfd' by default.")
endif()
if(NOT (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
    OR ENABLE_LIBWRAP
    # because mocks use the same interface present in qtwrapper/
    OR ENABLE_TEST)
  set(ENABLE_LIBWRAP true)
endif()
option(ENABLE_ASAN "Enable address sanitization" OFF)
if(ENABLE_ASAN AND NOT MSVC)
  message(STATUS "Address sanitization enabled for client")
  # Add AddressSanitizer flags for both compiler and linker
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
endif()

# Enable this option when building for production.
option(ENABLE_CRASHREPORTS "Enable crash reports" OFF)

# These values are exposed to QML and are better off being defined as values.
define_macro_with_value(WITH_WEBENGINE)
define_macro_with_value(APPSTORE)
define_macro_with_value(ENABLE_CRASHREPORTS)

# jami-core
if(NOT WITH_DAEMON_SUBMODULE)
  set(DAEMON_DIR ${PROJECT_SOURCE_DIR}/../daemon)
else()
  set(DAEMON_DIR ${PROJECT_SOURCE_DIR}/daemon)
endif()

if(JAMICORE_AS_SUBDIR)
  add_subdirectory(${DAEMON_DIR} EXCLUDE_FROM_ALL)
  set(LIBJAMI_LIB jami-core)
endif()

# init some variables for includes, libs, etc.
set(CLIENT_INCLUDE_DIRS, "")
set(CLIENT_LINK_DIRS, "")
set(CLIENT_LIBS, "")

include(${PROJECT_SOURCE_DIR}/extras/build/cmake/contrib_tools.cmake)
set(EXTRA_PATCHES_DIR ${PROJECT_SOURCE_DIR}/extras/patches)

list(APPEND QWINDOWKIT_OPTIONS
  QWINDOWKIT_BUILD_WIDGETS OFF
  QWINDOWKIT_INSTALL OFF
  QWINDOWKIT_BUILD_STATIC ON
  QWINDOWKIT_BUILD_QUICK ON
)

if(WIN32)
  # Beta config
  if(BETA)
    message(STATUS "Beta config enabled")
    add_definitions(-DBETA)
  endif()
  set(JAMI_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/x64/${CMAKE_BUILD_TYPE})
  # Prevent windows.h from defining min/max macros that conflict with std::min/std::max
  add_definitions(-DNOMINMAX)
endif()

if(WIN32)
  list(APPEND QWINDOWKIT_OPTIONS QWINDOWKIT_ENABLE_WINDOWS_SYSTEM_BORDERS OFF)
endif()

# qwindowkit (frameless window)
add_fetch_content(
  TARGET QWindowKit
  URL https://github.com/atraczyk/qwindowkit.git
  BRANCH fix-multiarch-libdir
  PATCHES ${QWINDOWKIT_PATCHES}
  OPTIONS ${QWINDOWKIT_OPTIONS}
)

list(APPEND CLIENT_LIBS QWindowKit::Quick)

# If ENABLE_CRASHREPORTS is enabled, we will use crashpad_cmake for now.
if(ENABLE_CRASHREPORTS)
  set(ENABLE_CRASHPAD ON)
  set(CRASH_REPORT_URL "http://localhost:8080/submit" CACHE STRING "URL for crash handler uploads")
endif()
add_definitions(-DCRASH_REPORT_URL="${CRASH_REPORT_URL}")

# Crash-report client: crashpad
if(ENABLE_CRASHPAD)
  message(STATUS "Crashpad enabled for client")
  if(WIN32)
    set(CMAKE_OBJECT_PATH_MAX 256)
  endif()
  add_fetch_content(
    TARGET crashpad_cmake
    URL https://github.com/TheAssemblyArmada/crashpad-cmake.git
    BRANCH 80573adcc845071401c73c99eaec7fd9847d45fb
  )
  add_definitions(-DENABLE_CRASHPAD)
  if (WIN32)
    # This makes sure the console window doesn't show up when running the
    # crashpad_handler executable.
    set_target_properties(crashpad_handler PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS")
    # Set the output directory for the crashpad_handler executable. On Windows,
    # we use either the Release or Beta directory depending on the BETA option
    # which is set above.
    set_target_properties(crashpad_handler PROPERTIES
        RUNTIME_OUTPUT_DIRECTORY_RELEASE "${JAMI_OUTPUT_DIRECTORY_RELEASE}")
  endif()
endif()

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(NOT MSVC)
  string(APPEND CMAKE_CXX_FLAGS_DEBUG " -Og")
endif()

# Use ccache when available to speed up incremental builds.
find_program(CCACHE_EXECUTABLE ccache)
if(CCACHE_EXECUTABLE)
  message(STATUS "ccache found: ${CCACHE_EXECUTABLE}")
  set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_EXECUTABLE})
  set(CMAKE_C_COMPILER_LAUNCHER   ${CCACHE_EXECUTABLE})
endif()

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)

# src
set(CORE_SRC_DIR ${PROJECT_SOURCE_DIR}/src/core)
set(DBUS_SRC_DIR ${PROJECT_SOURCE_DIR}/src/dbus)
set(QTWRAPPER_SRC_DIR ${PROJECT_SOURCE_DIR}/src/qtwrapper)
set(APP_SRC_DIR ${PROJECT_SOURCE_DIR}/src/app)
set(VERSION_INFO_DIR ${PROJECT_SOURCE_DIR}/src/version_info)
# doc
set(DOC_DIR ${PROJECT_SOURCE_DIR}/doc)
# extras
set(EXTRAS_DIR ${PROJECT_SOURCE_DIR}/extras)
set(DATA_DIR ${EXTRAS_DIR}/data)
set(PACKAGING_DIR ${EXTRAS_DIR}/packaging)
set(SCRIPTS_DIR ${EXTRAS_DIR}/scripts)
# tests
set(TESTS_DIR ${PROJECT_SOURCE_DIR}/tests)

# Here we let find_package(<PackageName>...) try to find Qt 6,
# If it is found, find_package will succeed, and the CMake variable
# QT_VERSION_MAJOR will be defined 6.
set(QT6_MINVER_MINOR 10)
if(QT6_VER AND QT6_PATH)
  find_package(QT NAMES Qt6 REQUIRED
    PATHS ${QT6_PATH} NO_DEFAULT_PATH)
else()
  message(STATUS "Looking for Qt 6 in ${CMAKE_PREFIX_PATH}")
  find_package(QT NAMES Qt6 REQUIRED)
endif()
if (${QT_VERSION_MINOR} GREATER_EQUAL ${QT6_MINVER_MINOR})
  message(STATUS "Found a suitable Qt version ${QT_VERSION}")
else()
  message(FATAL_ERROR "Qt 6.10 or higher is required. Found ${QT_VERSION}")
endif()

# Feature flags (previously in libclient CMakeLists.txt)
if(NOT (${ENABLE_VIDEO} MATCHES false))
  message(STATUS "VIDEO enabled")
  set(ENABLE_VIDEO 1 CACHE BOOL "Enable video")
  add_definitions(-DENABLE_VIDEO=true)
endif()

if(NOT (${ENABLE_PLUGIN} MATCHES false))
  message(STATUS "PLUGIN enabled")
  set(ENABLE_PLUGIN 1 CACHE BOOL "Enable plugin")
  add_definitions(-DENABLE_PLUGIN=true)
endif()

# libjamiclient (sources integrated directly into the jami target)
set(LIBCLIENT_SOURCES
  ${CORE_SRC_DIR}/uri.cpp
  ${CORE_SRC_DIR}/vcard.cpp
  ${CORE_SRC_DIR}/contactmodel.cpp
  ${CORE_SRC_DIR}/callparticipantsmodel.cpp
  ${CORE_SRC_DIR}/callmodel.cpp
  ${CORE_SRC_DIR}/devicemodel.cpp
  ${CORE_SRC_DIR}/codecmodel.cpp
  ${CORE_SRC_DIR}/conversationmodel.cpp
  ${CORE_SRC_DIR}/conversationmodel_call.cpp
  ${CORE_SRC_DIR}/conversationmodel_contacts.cpp
  ${CORE_SRC_DIR}/conversationmodel_messaging.cpp
  ${CORE_SRC_DIR}/conversationmodel_transfer.cpp
  ${CORE_SRC_DIR}/database.cpp
  ${CORE_SRC_DIR}/authority/daemon.cpp
  ${CORE_SRC_DIR}/authority/storagehelper.cpp
  ${CORE_SRC_DIR}/lrc.cpp
  ${CORE_SRC_DIR}/accountmodel.cpp
  ${CORE_SRC_DIR}/peerdiscoverymodel.cpp
  ${CORE_SRC_DIR}/callbackshandler.cpp
  ${CORE_SRC_DIR}/behaviorcontroller.cpp
  ${CORE_SRC_DIR}/datatransfermodel.cpp
  ${CORE_SRC_DIR}/messagelistmodel.cpp
  ${CORE_SRC_DIR}/callInformationListModel.cpp
  ${CORE_SRC_DIR}/rendererinformationlistmodel.cpp
  ${DBUS_SRC_DIR}/configurationmanager.cpp
  ${DBUS_SRC_DIR}/callmanager.cpp
  ${DBUS_SRC_DIR}/instancemanager.cpp
  ${DBUS_SRC_DIR}/networkservicemanager.cpp
  ${DBUS_SRC_DIR}/videomanager.cpp
  ${DBUS_SRC_DIR}/presencemanager.cpp
  ${DBUS_SRC_DIR}/pluginmanager.cpp
  ${CORE_SRC_DIR}/globalinstances.cpp
  ${CORE_SRC_DIR}/pixmapmanipulatordefault.cpp
  ${CORE_SRC_DIR}/dbuserrorhandlerdefault.cpp
  ${CORE_SRC_DIR}/avmodel.cpp
  ${CORE_SRC_DIR}/pluginmodel.cpp
  ${CORE_SRC_DIR}/interaction.cpp
  ${CORE_SRC_DIR}/namedirectory.cpp
  ${CORE_SRC_DIR}/renderer.cpp)

set(LIBCLIENT_HEADERS
  ${CORE_SRC_DIR}/uri.h
  ${CORE_SRC_DIR}/globalinstances.h
  ${CORE_SRC_DIR}/pixmapmanipulatordefault.h
  ${CORE_SRC_DIR}/dbuserrorhandlerdefault.h
  ${CORE_SRC_DIR}/vcard.h
  ${CORE_SRC_DIR}/namedirectory.h
  ${CORE_SRC_DIR}/callInformationListModel.h
  ${CORE_SRC_DIR}/rendererinformationlistmodel.h
  ${CORE_SRC_DIR}/interfaces/pixmapmanipulatori.h
  ${CORE_SRC_DIR}/interfaces/dbuserrorhandleri.h
  ${CORE_SRC_DIR}/typedefs.h
  ${CORE_SRC_DIR}/containerview.h
  ${CORE_SRC_DIR}/renderer.h
  ${CORE_SRC_DIR}/api/account.h
  ${CORE_SRC_DIR}/api/avmodel.h
  ${CORE_SRC_DIR}/api/behaviorcontroller.h
  ${CORE_SRC_DIR}/api/call.h
  ${CORE_SRC_DIR}/api/contact.h
  ${CORE_SRC_DIR}/api/conversation.h
  ${CORE_SRC_DIR}/api/contactmodel.h
  ${CORE_SRC_DIR}/api/conversationmodel.h
  ${CORE_SRC_DIR}/api/datatransfermodel.h
  ${CORE_SRC_DIR}/api/messagelistmodel.h
  ${CORE_SRC_DIR}/api/datatransfer.h
  ${CORE_SRC_DIR}/api/interaction.h
  ${CORE_SRC_DIR}/api/lrc.h
  ${CORE_SRC_DIR}/api/member.h
  ${CORE_SRC_DIR}/api/accountmodel.h
  ${CORE_SRC_DIR}/api/callmodel.h
  ${CORE_SRC_DIR}/api/callparticipantsmodel.h
  ${CORE_SRC_DIR}/api/codecmodel.h
  ${CORE_SRC_DIR}/api/devicemodel.h
  ${CORE_SRC_DIR}/api/pluginmodel.h
  ${CORE_SRC_DIR}/api/peerdiscoverymodel.h
  ${CORE_SRC_DIR}/api/profile.h
  ${CORE_SRC_DIR}/api/video.h)

if(ENABLE_LIBWRAP)
  list(APPEND LIBCLIENT_SOURCES ${CORE_SRC_DIR}/directrenderer.cpp)
  list(APPEND LIBCLIENT_HEADERS ${CORE_SRC_DIR}/directrenderer.h)
else()
  list(APPEND LIBCLIENT_SOURCES ${CORE_SRC_DIR}/shmrenderer.cpp)
  list(APPEND LIBCLIENT_HEADERS ${CORE_SRC_DIR}/shmrenderer.h)

  # Build DBus API
  if(DEFINED RING_XML_INTERFACES_DIR)
    set(dbus_xml_introspecs_path ${RING_XML_INTERFACES_DIR})
  elseif(DEFINED LIBJAMI_XML_INTERFACES_DIR)
    set(dbus_xml_introspecs_path ${LIBJAMI_XML_INTERFACES_DIR})
  elseif(EXISTS "${DAEMON_DIR}/bin/dbus")
    set(dbus_xml_introspecs_path ${DAEMON_DIR}/bin/dbus)
  else()
    set(dbus_xml_introspecs_path
      ${CMAKE_INSTALL_PREFIX}/share/dbus-1/interfaces)
  endif()
  message(STATUS "Using Jami (Ring) DBus-XML interfaces in \
${dbus_xml_introspecs_path}")

  set(presencemanager_xml
    ${dbus_xml_introspecs_path}/cx.ring.Ring.PresenceManager.xml)
  set(dbus_metatype_path "${DBUS_SRC_DIR}/metatypes.h")
  set_source_files_properties(
    ${presencemanager_xml}
    PROPERTIES
    CLASSNAME PresenceManagerInterface
    INCLUDE ${dbus_metatype_path})

  set(configurationmanager_xml
    ${dbus_xml_introspecs_path}/cx.ring.Ring.ConfigurationManager.xml)
  set_source_files_properties(
    ${configurationmanager_xml}
    PROPERTIES
    CLASSNAME ConfigurationManagerInterface
    INCLUDE ${dbus_metatype_path})

  set(callmanager_xml
    ${dbus_xml_introspecs_path}/cx.ring.Ring.CallManager.xml)
  set_source_files_properties(
    ${callmanager_xml}
    PROPERTIES
    CLASSNAME CallManagerInterface
    INCLUDE ${dbus_metatype_path})

  set(video_xml
    ${dbus_xml_introspecs_path}/cx.ring.Ring.VideoManager.xml)
  set_source_files_properties(
    ${video_xml}
    PROPERTIES
    CLASSNAME VideoManagerInterface
    INCLUDE ${dbus_metatype_path})

  set(plugin_xml
    ${dbus_xml_introspecs_path}/cx.ring.Ring.PluginManagerInterface.xml)
  set_source_files_properties(
    ${plugin_xml}
    PROPERTIES
    CLASSNAME PluginManagerInterface
    INCLUDE ${dbus_metatype_path})

  set(instance_xml
    ${dbus_xml_introspecs_path}/cx.ring.Ring.Instance.xml)
  set_source_files_properties(
    ${instance_xml}
    PROPERTIES
    CLASSNAME InstanceManagerInterface
    INCLUDE ${dbus_metatype_path})

  set(networkservicemanager_xml
    ${dbus_xml_introspecs_path}/cx.ring.Ring.NetworkServiceManager.xml)
  set_source_files_properties(
    ${networkservicemanager_xml}
    PROPERTIES
    CLASSNAME NetworkServiceManagerInterface
    INCLUDE ${dbus_metatype_path})

  # Note: qt_add_dbus_interface calls are done after Qt6 component find below.
endif()

# Note: qt_wrap_cpp for explicit moc headers is done after Qt6 component find below.

set(LIBCLIENT_HEADERS_EXPLICIT_MOC
  ${CORE_SRC_DIR}/api/account.h)
set_source_files_properties(
  ${LIBCLIENT_HEADERS_EXPLICIT_MOC}
  PROPERTIES SKIP_AUTOMOC ON)

# Manually wrap libjamiclient's private headers.
set(LIBCLIENT_HEADERS_PRIVATE
  ${CORE_SRC_DIR}/private/namedirectory_p.h)

if(ENABLE_LIBWRAP)
  list(APPEND LIBCLIENT_HEADERS_PRIVATE
    ${QTWRAPPER_SRC_DIR}/callmanager_wrap.h
    ${QTWRAPPER_SRC_DIR}/configurationmanager_wrap.h
    ${QTWRAPPER_SRC_DIR}/instancemanager_wrap.h
    ${QTWRAPPER_SRC_DIR}/networkservicemanager_wrap.h
    ${QTWRAPPER_SRC_DIR}/presencemanager_wrap.h
    ${QTWRAPPER_SRC_DIR}/videomanager_wrap.h
    ${QTWRAPPER_SRC_DIR}/pluginmanager_wrap.h)
endif()

set(QT_MODULES
  Quick
  Network
  Svg
  Gui
  Qml
  QmlModels
  Sql
  Concurrent
  Core
  Core5Compat
  Multimedia
  Widgets
  Positioning
  HttpServer
  WebSockets)

if(WITH_WEBENGINE)
  list(APPEND QT_MODULES
    WebEngineCore
    WebEngineQuick
    WebChannel
    WebEngineWidgets)
endif()

set(CMAKE_CXX_FLAGS
  ${CMAKE_CXX_FLAGS} ${Qt6Widgets_EXECUTABLE_COMPILE_FLAGS})

# Add the auto-determined directories (those that are in the linker
# search path or contain linked libraries) outside the build tree to
# the RPATH.
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH true)

# # Augment the install RPATH for local (non-global) installs.
list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES
  "${CMAKE_INSTALL_PREFIX}/lib"
  isSystemDir)
if("${isSystemDir}" STREQUAL "-1")
    set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
endif()

if (WITH_DAEMON_SUBMODULE AND NOT EXISTS ${DAEMON_DIR}/src)
  message(FATAL_ERROR
    "Jami Daemon sources not found in ${DAEMON_DIR}/src.  If you \
cloned this repository via git, perhaps you did not initialize its \
submodules afterwards.  You can do so by running \"git submodule \
update --init\" in the repository.")
endif()
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/extras/build/cmake/modules)
if (NOT JAMICORE_AS_SUBDIR)
  find_package(LibJami REQUIRED)
  if(LIBJAMI_FOUND)
    include_directories(${LIBJAMI_INCLUDE_DIR})
  endif()
endif()

include(FindPython3)
find_package(Python3 3.6 REQUIRED COMPONENTS Interpreter)
set(PYTHON_EXEC ${Python3_EXECUTABLE})

# Versioning and build ID generation
set(VERSION_FILE ${CMAKE_CURRENT_BINARY_DIR}/version_info.cpp)
# Touch the file to ensure it exists at configure time as
# we add it to the target_sources below.
file(TOUCH ${VERSION_FILE})
add_custom_target(
  generate_version_info ALL
  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
  COMMAND ${CMAKE_COMMAND}
  -DAPP_SOURCE_DIR=${CMAKE_SOURCE_DIR}
  -DAPP_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}
  -DCORE_SOURCE_DIR=${DAEMON_DIR}
  -DCPP_INT_FILE=${VERSION_INFO_DIR}/version_info.cpp.in
  -DBUILD_VERSION=${BUILD_VERSION}
  -P ${CMAKE_SCRIPTS_DIR}/generate_version_info.cmake
)
list(APPEND CLIENT_INCLUDE_DIRS ${VERSION_INFO_DIR})

# Resource auto-gen
# Only include WebEngine resources if specified.
if(WITH_WEBENGINE)
  set(GEN_QRC_ARGS "--with-webengine")
endif()

# Generate the .qrc manifests BEFORE the CONFIGURE_DEPENDS globs below.
# Both manifests are written into ${APP_SRC_DIR}, so they are matched by the
# "${APP_SRC_DIR}/*" glob. Generating them first means the globbed file set is
# identical at configure time and at the build-time glob re-check; otherwise
# the freshly generated .qrc files would appear only on the re-check and force
# a spurious reconfigure during the build -- which, with the daemon built as a
# subdirectory, re-runs the daemon contrib build (contrib effectively built
# twice per clean build).
#
# gen_resources_qrc.py must run first because it generates JamiResources.qml
# into ${APP_SRC_DIR}/net/jami/Constants/, which gen_qml_qrc.py then picks up
# when scanning for .qml files to include in qml.qrc.
execute_process(
  COMMAND ${PYTHON_EXEC} ${SCRIPTS_DIR}/gen_resources_qrc.py ${GEN_QRC_ARGS}
  WORKING_DIRECTORY ${APP_SRC_DIR})
set(QML_RESOURCES ${APP_SRC_DIR}/resources.qrc)
execute_process(
  COMMAND
  ${PYTHON_EXEC} ${SCRIPTS_DIR}/gen_qml_qrc.py ${GEN_QRC_ARGS}
  WORKING_DIRECTORY ${APP_SRC_DIR})
set(QML_RESOURCES_QML ${APP_SRC_DIR}/qml.qrc)

# QML and related code files: force a reconfigure when the source set changes.
file(GLOB_RECURSE
  QML_FILES CONFIGURE_DEPENDS
  ${APP_SRC_DIR}/*)

# Image and misc. resources: force a reconfigure when they change.
file(GLOB_RECURSE
  RES_FILES CONFIGURE_DEPENDS
  ${PROJECT_SOURCE_DIR}/resources/*)

# Find modules (QtCreator) under the root source dir.
list(APPEND QML_DIRS ${APP_SRC_DIR})
set(QML_IMPORT_PATH ${QML_DIRS}
  CACHE STRING "Qt Creator extra qml import paths" FORCE)

# library compatibility (boost, libnotify, etc.)
add_definitions(-DQT_NO_KEYWORDS)

set(COMMON_SOURCES
  ${VERSION_FILE}
  ${APP_SRC_DIR}/bannedlistmodel.cpp
  ${APP_SRC_DIR}/accountlistmodel.cpp
  ${APP_SRC_DIR}/networkmanager.cpp
  ${APP_SRC_DIR}/instancemanager.cpp
  ${APP_SRC_DIR}/main.cpp
  ${APP_SRC_DIR}/smartlistmodel.cpp
  ${APP_SRC_DIR}/utils.cpp
  ${APP_SRC_DIR}/mainapplication.cpp
  ${APP_SRC_DIR}/messagesadapter.cpp
  ${APP_SRC_DIR}/positionobject.cpp
  ${APP_SRC_DIR}/positionmanager.cpp
  ${APP_SRC_DIR}/accountadapter.cpp
  ${APP_SRC_DIR}/calladapter.cpp
  ${APP_SRC_DIR}/conversationsadapter.cpp
  ${APP_SRC_DIR}/avadapter.cpp
  ${APP_SRC_DIR}/contactadapter.cpp
  ${APP_SRC_DIR}/sharedservicesadapter.cpp
  ${APP_SRC_DIR}/pluginadapter.cpp
  ${APP_SRC_DIR}/deviceitemlistmodel.cpp
  ${APP_SRC_DIR}/pluginlistmodel.cpp
  ${APP_SRC_DIR}/pluginstorelistmodel.cpp
  ${APP_SRC_DIR}/pluginhandlerlistmodel.cpp
  ${APP_SRC_DIR}/preferenceitemlistmodel.cpp
  ${APP_SRC_DIR}/mediacodeclistmodel.cpp
  ${APP_SRC_DIR}/currentaccounttomigrate.cpp
  ${APP_SRC_DIR}/audiodevicemodel.cpp
  ${APP_SRC_DIR}/pluginlistpreferencemodel.cpp
  ${APP_SRC_DIR}/audiomanagerlistmodel.cpp
  ${APP_SRC_DIR}/audioconfiglistmodel.cpp
  ${APP_SRC_DIR}/qmlregister.cpp
  ${APP_SRC_DIR}/utilsadapter.cpp
  ${APP_SRC_DIR}/moderatorlistmodel.cpp
  ${APP_SRC_DIR}/screensaver.cpp
  ${APP_SRC_DIR}/systemtray.cpp
  ${APP_SRC_DIR}/appsettingsmanager.cpp
  ${APP_SRC_DIR}/accountsettingsmanager.cpp
  ${APP_SRC_DIR}/lrcinstance.cpp
  ${APP_SRC_DIR}/selectablelistproxymodel.cpp

  ${APP_SRC_DIR}/conversationlistproxymodel.cpp
  ${APP_SRC_DIR}/searchresultslistmodel.cpp
  ${APP_SRC_DIR}/calloverlaymodel.cpp
  ${APP_SRC_DIR}/spellcheckdictionarylistmodel.cpp
  ${APP_SRC_DIR}/spellcheckadapter.cpp
  ${APP_SRC_DIR}/filestosendlistmodel.cpp
  ${APP_SRC_DIR}/wizardviewstepmodel.cpp
  ${APP_SRC_DIR}/avatarregistry.cpp
  ${APP_SRC_DIR}/currentconversation.cpp
  ${APP_SRC_DIR}/callpipwindowmanager.cpp
  ${APP_SRC_DIR}/currentconversationmembers.cpp
  ${APP_SRC_DIR}/currentaccount.cpp
  ${APP_SRC_DIR}/videodevices.cpp
  ${APP_SRC_DIR}/videoprovider.cpp
  ${APP_SRC_DIR}/callparticipantsmodel.cpp
  ${APP_SRC_DIR}/tipsmodel.cpp
  ${APP_SRC_DIR}/positioning.cpp
  ${APP_SRC_DIR}/currentcall.cpp
  ${APP_SRC_DIR}/messageparser.cpp
  ${APP_SRC_DIR}/previewengine.cpp
  ${APP_SRC_DIR}/filedownloader.cpp
  ${APP_SRC_DIR}/pluginversionmanager.cpp
  ${APP_SRC_DIR}/connectioninfolistmodel.cpp
  ${APP_SRC_DIR}/conversationstatusmodel.cpp
  ${APP_SRC_DIR}/trackedmembersmodel.cpp
  ${APP_SRC_DIR}/pluginversionmanager.cpp
  ${APP_SRC_DIR}/linkdevicemodel.cpp
  ${APP_SRC_DIR}/qrcodescannermodel.cpp
  ${APP_SRC_DIR}/spellchecker.cpp
  ${APP_SRC_DIR}/apiserver.cpp
  ${APP_SRC_DIR}/apitokenmanager.cpp
  ${APP_SRC_DIR}/apitokenlistmodel.cpp)

set(COMMON_HEADERS
  ${APP_SRC_DIR}/global.h
  ${APP_SRC_DIR}/avatarimageprovider.h
  ${APP_SRC_DIR}/networkmanager.h
  ${APP_SRC_DIR}/smartlistmodel.h
  ${APP_SRC_DIR}/appversionmanager.h
  ${APP_SRC_DIR}/utils.h
  ${APP_SRC_DIR}/bannedlistmodel.h
  ${APP_SRC_DIR}/accountlistmodel.h
  ${APP_SRC_DIR}/instancemanager.h
  ${APP_SRC_DIR}/connectivitymonitor.h
  ${APP_SRC_DIR}/jamiavatartheme.h
  ${APP_SRC_DIR}/mainapplication.h
  ${APP_SRC_DIR}/qrimageprovider.h
  ${APP_SRC_DIR}/messagesadapter.h
  ${APP_SRC_DIR}/positionobject.h
  ${APP_SRC_DIR}/positionmanager.h
  ${APP_SRC_DIR}/accountadapter.h
  ${APP_SRC_DIR}/calladapter.h
  ${APP_SRC_DIR}/conversationsadapter.h
  ${APP_SRC_DIR}/qmladapterbase.h
  ${APP_SRC_DIR}/avadapter.h
  ${APP_SRC_DIR}/contactadapter.h
  ${APP_SRC_DIR}/pluginadapter.h
  ${APP_SRC_DIR}/deviceitemlistmodel.h
  ${APP_SRC_DIR}/pluginlistmodel.h
  ${APP_SRC_DIR}/pluginstorelistmodel.h
  ${APP_SRC_DIR}/pluginhandlerlistmodel.h
  ${APP_SRC_DIR}/preferenceitemlistmodel.h
  ${APP_SRC_DIR}/mediacodeclistmodel.h
  ${APP_SRC_DIR}/currentaccounttomigrate.h
  ${APP_SRC_DIR}/audiodevicemodel.h
  ${APP_SRC_DIR}/pluginlistpreferencemodel.h
  ${APP_SRC_DIR}/audiomanagerlistmodel.h
  ${APP_SRC_DIR}/audioconfiglistmodel.h
  ${APP_SRC_DIR}/qmlregister.h
  ${APP_SRC_DIR}/abstractlistmodelbase.h
  ${APP_SRC_DIR}/quickimageproviderbase.h
  ${APP_SRC_DIR}/qtutils.h
  ${APP_SRC_DIR}/utilsadapter.h
  ${APP_SRC_DIR}/moderatorlistmodel.h
  ${APP_SRC_DIR}/screensaver.h
  ${APP_SRC_DIR}/systemtray.h
  ${APP_SRC_DIR}/appsettingsmanager.h
  ${APP_SRC_DIR}/accountsettingsmanager.h
  ${APP_SRC_DIR}/lrcinstance.h
  ${APP_SRC_DIR}/selectablelistproxymodel.h

  ${APP_SRC_DIR}/conversationlistproxymodel.h
  ${APP_SRC_DIR}/searchresultslistmodel.h
  ${APP_SRC_DIR}/calloverlaymodel.h
  ${APP_SRC_DIR}/spellcheckdictionarylistmodel.h
  ${APP_SRC_DIR}/spellcheckadapter.h
  ${APP_SRC_DIR}/filestosendlistmodel.h
  ${APP_SRC_DIR}/wizardviewstepmodel.h
  ${APP_SRC_DIR}/avatarregistry.h
  ${APP_SRC_DIR}/currentconversation.h
  ${APP_SRC_DIR}/callpipwindowmanager.h
  ${APP_SRC_DIR}/currentconversationmembers.h
  ${APP_SRC_DIR}/currentaccount.h
  ${APP_SRC_DIR}/videodevices.h
  ${APP_SRC_DIR}/videoprovider.h
  ${APP_SRC_DIR}/callparticipantsmodel.h
  ${APP_SRC_DIR}/tipsmodel.h
  ${APP_SRC_DIR}/positioning.h
  ${APP_SRC_DIR}/currentcall.h
  ${APP_SRC_DIR}/messageparser.h
  ${APP_SRC_DIR}/htmlparser.h
  ${APP_SRC_DIR}/filedownloader.h
  ${APP_SRC_DIR}/pluginversionmanager.h
  ${APP_SRC_DIR}/connectioninfolistmodel.h
  ${APP_SRC_DIR}/conversationstatusmodel.h
  ${APP_SRC_DIR}/trackedmembersmodel.h
  ${APP_SRC_DIR}/pttlistener.h
  ${APP_SRC_DIR}/crashreportclient.h
  ${APP_SRC_DIR}/crashreporter.h
  ${APP_SRC_DIR}/linkdevicemodel.h
  ${APP_SRC_DIR}/qrcodescannermodel.h
  ${APP_SRC_DIR}/spellchecker.h
  ${APP_SRC_DIR}/accountsettingspropertymap.h
  ${APP_SRC_DIR}/apiserver.h
  ${APP_SRC_DIR}/apitokenmanager.h
  ${APP_SRC_DIR}/apitokenlistmodel.h
)

set(CONTRIB_DARWIN_FOLDER "")
if(APPLE)
  list(LENGTH CMAKE_OSX_ARCHITECTURES ARCH_LEN)
  if(ARCH_LEN EQUAL 1)
    # single-arch -> use <arch>-apple-darwin<ver>
    set(DARWIN_ARCH "${CMAKE_OSX_ARCHITECTURES}")
    set(CONTRIB_DARWIN_FOLDER "${DARWIN_ARCH}-apple-darwin${CMAKE_SYSTEM_VERSION}")
  else() # multi-arch -> use apple-darwin<ver>
    set(CONTRIB_DARWIN_FOLDER "apple-darwin${CMAKE_SYSTEM_VERSION}")
  endif()
endif()

# For libavutil/avframe.
set(LIBJAMI_CONTRIB_DIR "${DAEMON_DIR}/contrib")

find_package(Vulkan)
if(Vulkan_FOUND)
  add_definitions(-DHAS_VULKAN)
  include_directories(${Vulkan_INCLUDE_DIR})
endif()
# Define PREFER_VULKAN to prefer Vulkan over the default API
# on GNU/Linux and Windows. Metal is always preferred on macOS.

if(ENABLE_CRASHREPORTS)
  set(CRASHREPORT_CLIENT_DIR ${APP_SRC_DIR}/crashreportclients)
  if(ENABLE_CRASHPAD)
    list(APPEND CLIENT_LIBS crashpad_client)
    list(APPEND COMMON_SOURCES ${CRASHREPORT_CLIENT_DIR}/crashpad.cpp)
    list(APPEND COMMON_HEADERS ${CRASHREPORT_CLIENT_DIR}/crashpad.h)
  endif()
endif()

find_package(PkgConfig REQUIRED)

# hunspell
pkg_search_module(hunspell IMPORTED_TARGET hunspell)
if(hunspell_FOUND)
  message(STATUS "hunspell found")
  set(HUNSPELL_LIBRARIES PkgConfig::hunspell)
else()
  message(STATUS "hunspell not found - building hunspell")

  set(HUNSPELL_DIR ${PROJECT_SOURCE_DIR}/3rdparty/hunspell)

  # Build using the submodule and its CMakeLists.txt
  add_subdirectory(${HUNSPELL_DIR} hunspell_build EXCLUDE_FROM_ALL)

  set(HUNSPELL_INCLUDE_DIR ${HUNSPELL_DIR}/src)
  set(HUNSPELL_LIBRARIES hunspell::hunspell)
endif()

if(MSVC)
  # Enforce minimum SDK 10.0.26100.0
  set(MIN_WIN_SDK "10.0.26100.0")

  # Set minimum SDK version hint
  set(CMAKE_SYSTEM_VERSION "${MIN_WIN_SDK}" CACHE STRING "" FORCE)

  # After SDK selection, validate what CMake chose
  if (DEFINED CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION)
      if (CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION VERSION_LESS MIN_WIN_SDK)
          message(FATAL_ERROR
              "Windows SDK ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION} is too old. "
              "Minimum required: ${MIN_WIN_SDK}")
      endif()
  endif()

  set(WINDOWS_SYS_LIBS
    windowsapp.lib
    dwmapi.lib
    Shell32.lib
    Ole32.lib
    Advapi32.lib
    Shlwapi.lib
    User32.lib
    Gdi32.lib
    Crypt32.lib
    Strmiids.lib)

  list(APPEND COMMON_SOURCES
    ${APP_SRC_DIR}/connectivitymonitor.cpp
    ${APP_SRC_DIR}/appversionmanager.cpp)
  # preprocessor defines
  add_definitions(-DUNICODE -DQT_NO_DEBUG -DNDEBUG)

  # compiler options
  add_compile_options(
    /wd4068 /wd4099 /wd4189 /wd4267 /wd4577 /wd4467 /wd4715 /wd4828)
  add_compile_options(
    /MP /GS /W3 /Gy /Zc:__cplusplus /Zc:wchar_t /Zc:externConstexpr /Zi /Gm- /O2 /Zc:inline /fp:precise)
  add_compile_options(/Gd /Oi /MD /FC /EHsc /nologo /sdl /constexpr:steps16777216)

  # linker options
  add_link_options(
    "/IGNORE:4006,4049,4078,4098"
    "/FORCE:MULTIPLE"
    "/INCREMENTAL:NO"
    "/DEBUG"
    "/LTCG"
    "/NODEFAULTLIB:LIBCMT")

  # daemon
  set(JAMID_SRC_PATH ${DAEMON_DIR}/contrib/msvc/include)
  set(GNUTLS_LIB ${DAEMON_DIR}/contrib/msvc/lib/x64/libgnutls.lib)

  include_directories(
    ${JAMID_SRC_PATH}
    ${CORE_SRC_DIR}
  )
elseif (NOT APPLE)
  list(APPEND COMMON_SOURCES
    ${APP_SRC_DIR}/xrectsel.c
    ${APP_SRC_DIR}/connectivitymonitor.cpp
    ${APP_SRC_DIR}/dbuserrorhandler.cpp
    ${APP_SRC_DIR}/appversionmanager.cpp
    ${APP_SRC_DIR}/screencastportal.cpp)
  list(APPEND COMMON_HEADERS
    ${APP_SRC_DIR}/xrectsel.h
    ${APP_SRC_DIR}/dbuserrorhandler.h
    ${APP_SRC_DIR}/screencastportal.h)
  list(APPEND QT_MODULES DBus)

  pkg_check_modules(GLIB REQUIRED glib-2.0)
  if(GLIB_FOUND)
    add_definitions(${GLIB_CFLAGS_OTHER})
  endif()

  pkg_check_modules(GIO REQUIRED gio-2.0)
  if(GIO_FOUND)
    add_definitions(${GIO_CFLAGS})
  endif()

  pkg_check_modules(GIOUNIX REQUIRED gio-unix-2.0)
  if(GIOUNIX_FOUND)
    add_definitions(${GIOUNIX_CFLAGS})
  endif()

  pkg_check_modules(LIBNM libnm)
  if(LIBNM_FOUND)
    add_definitions(-DUSE_LIBNM)
  endif()

  pkg_check_modules(LIBNOTIFY libnotify>=0.7.6)
  if(LIBNOTIFY_FOUND)
    add_definitions(-DUSE_LIBNOTIFY)
    add_definitions(${LIBNOTIFY_CFLAGS})
    pkg_check_modules(LIBGDKPIXBUF gdk-pixbuf-2.0>=2.40.0)
    add_definitions(${LIBGDKPIXBUF_CFLAGS})
  endif()

  include_directories(
    ${CORE_SRC_DIR}
    ${LIBNM_INCLUDE_DIRS}
    ${LIBNOTIFY_INCLUDE_DIRS}
    ${LIBGDKPIXBUF_INCLUDE_DIRS}
    ${GLIB_INCLUDE_DIRS})

  set(JAMI_DATA_PREFIX "${CMAKE_INSTALL_PREFIX}/share")

  find_library(X11 X11)
else() # APPLE
  list(APPEND COMMON_SOURCES
    ${APP_SRC_DIR}/os/macos/appversionmanager.mm
    ${APP_SRC_DIR}/os/macos/connectivitymonitor.mm
    ${APP_SRC_DIR}/os/macos/macutils.mm)
  list(APPEND COMMON_HEADERS
    ${APP_SRC_DIR}/os/macos/macutils.h)
  include_directories(${CORE_SRC_DIR})

  set(JAMI_DATA_PREFIX "${CMAKE_INSTALL_PREFIX}/share")

  find_library(SYSTEM_CONFIGURATUION SystemConfiguration)
  set(myApp_ICON
    ${CMAKE_CURRENT_SOURCE_DIR}/resources/images/jami.icns)
  set_source_files_properties(
    ${myApp_ICON}
    PROPERTIES
    MACOSX_PACKAGE_LOCATION Resources)
  # Regenerate and commit Assets.car after editing AppIcon.icon/: extras/scripts/regenerate-macos-icon.sh
  set(myApp_ASSETS_CAR
    ${CMAKE_CURRENT_SOURCE_DIR}/resources/images/Assets.car)
  set_source_files_properties(
    ${myApp_ASSETS_CAR}
    PROPERTIES
    MACOSX_PACKAGE_LOCATION Resources)
  if(ENABLE_SPARKLE)
    message("Sparkle auto-update enabled")
    set(sparkle_dir "${PACKAGING_DIR}/update/sparkle")
    find_library(SPARKLE_FRAMEWORK
      NAMES Sparkle
      HINTS ${sparkle_dir})
    add_definitions(-DENABLE_SPARKLE)
    message("Sparkle is here:" ${SPARKLE_FRAMEWORK})
  endif()
  if(BETA)
    message(STATUS "Beta config enabled")
    add_definitions(-DBETA)
  endif()
endif()

message(STATUS "Adding HUNSPELL_INCLUDE_DIR" ${HUNSPELL_INCLUDE_DIR})
list(APPEND CLIENT_INCLUDE_DIRS ${HUNSPELL_INCLUDE_DIR} ${CMAKE_BINARY_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/hunspell/src)

message(STATUS "Adding HUNSPELL_LIBRARIES" ${HUNSPELL_INCLUDE_DIR})
list(APPEND CLIENT_LIBS ${HUNSPELL_LIBRARIES})

# Qt find package
if(QT6_VER AND QT6_PATH)
  message(STATUS "Using custom Qt version")
  find_package(Qt${QT_VERSION_MAJOR}
    REQUIRED COMPONENTS ${QT_MODULES}
    OPTIONAL_COMPONENTS LinguistTools
    PATHS ${QT6_PATH} NO_DEFAULT_PATH)
else()
  find_package(Qt${QT_VERSION_MAJOR}
    REQUIRED COMPONENTS ${QT_MODULES}
    OPTIONAL_COMPONENTS LinguistTools)
endif()

# Define ENABLE_LIBWRAP before qtwrapper so dbus headers take the right path
if(ENABLE_LIBWRAP)
  add_definitions(-DENABLE_LIBWRAP=true)
endif()

# qtwrapper (unified/single-process build, must be after Qt6 is found)
if(ENABLE_LIBWRAP)
  add_subdirectory(${QTWRAPPER_SRC_DIR})
  if(JAMICORE_AS_SUBDIR)
    add_dependencies(qtwrapper jami-core)
  endif()
endif()

# libclient Qt-dependent processing (must be after Qt6 component find)
qt_wrap_cpp(LIBCLIENT_HEADERS_MOC
  ${LIBCLIENT_HEADERS_EXPLICIT_MOC}
  ${LIBCLIENT_HEADERS_PRIVATE})

if(NOT ENABLE_LIBWRAP)
  qt_add_dbus_interface(
    LIBCLIENT_SOURCES
    ${presencemanager_xml}
    presencemanager_dbus_interface)
  qt_add_dbus_interface(
    LIBCLIENT_SOURCES
    ${configurationmanager_xml}
    configurationmanager_dbus_interface)
  qt_add_dbus_interface(
    LIBCLIENT_SOURCES
    ${callmanager_xml}
    callmanager_dbus_interface)
  qt_add_dbus_interface(
    LIBCLIENT_SOURCES
    ${video_xml}
    video_dbus_interface)
  qt_add_dbus_interface(
    LIBCLIENT_SOURCES
    ${plugin_xml}
    plugin_dbus_interface)
  qt_add_dbus_interface(
    LIBCLIENT_SOURCES
    ${instance_xml}
    instance_dbus_interface)
  qt_add_dbus_interface(
    LIBCLIENT_SOURCES
    ${networkservicemanager_xml}
    networkservicemanager_dbus_interface)
endif()

if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
    if (DEFINED ENV{XDG_SESSION_TYPE})
        if ($ENV{XDG_SESSION_TYPE} STREQUAL "x11")
            set(PTT_PLATFORM "x11")
            list(APPEND COMMON_HEADER ${APP_SRC_DIR}/platform/X11/xcbkeyboard.H)
        # TODO: add Wayland support
        endif ()
    endif ()
elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows")
    set(PTT_PLATFORM "windows")
elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
    set(PTT_PLATFORM "macos")
endif ()

if (NOT ${PTT_PLATFORM} STREQUAL "")
    message(STATUS "Platform: ${PTT_PLATFORM}")
    add_definitions(-DHAVE_GLOBAL_PTT)
    list(APPEND COMMON_SOURCES ${APP_SRC_DIR}/platform/${PTT_PLATFORM}/pttlistener.cpp)
else ()
    message(WARNING "Global push-to-talk not supported.")
    list(APPEND COMMON_SOURCES ${APP_SRC_DIR}/platform/local/pttlistener.cpp)
endif ()

# common includes
include_directories(
  ${PROJECT_SOURCE_DIR}
  ${PROJECT_SOURCE_DIR}/src
  ${APP_SRC_DIR}
  ${CORE_SRC_DIR}
  ${DBUS_SRC_DIR}
  ${CMAKE_CURRENT_BINARY_DIR})

if(ENABLE_LIBWRAP)
  include_directories(${QTWRAPPER_SRC_DIR})
  list(APPEND COMMON_HEADERS
    ${QTWRAPPER_SRC_DIR}/instancemanager_wrap.h)
endif()

# md4c
find_package(md4c)
if(md4c_FOUND)
  message(STATUS "Using system-provided md4c-html")
  list(APPEND CLIENT_LIBS md4c::md4c-html)
else()
  message("Using bundled md4c-html library")
  set(BUILD_MD2HTML_EXECUTABLE OFF CACHE BOOL "Don't build md2html executable" FORCE)
  set(BUILD_SHARED_LIBS OFF CACHE BOOL "Don't build shared md4c library" FORCE)
  add_subdirectory(3rdparty/md4c EXCLUDE_FROM_ALL)
  list(APPEND CLIENT_LINK_DIRS ${MD4C_BINARY_DIR}/src)
  list(APPEND CLIENT_INCLUDE_DIRS ${MD4C_SOURCE_DIR}/src)
  list(APPEND CLIENT_LIBS md4c-html)
endif()

# tidy-html5
pkg_check_modules(tidy IMPORTED_TARGET tidy)
if(tidy_FOUND)
  message(STATUS "Using system-provided tidy")
  list(APPEND CLIENT_LIBS PkgConfig::tidy)
else()
  message("Using bundled tidy library")
  set(BUILD_SHARED_LIB OFF CACHE BOOL "Don't build shared tidy library" FORCE)
  set(SUPPORT_CONSOLE_APP OFF CACHE BOOL "Don't build tidy console app" FORCE)
  add_subdirectory(3rdparty/tidy-html5 EXCLUDE_FROM_ALL)
  list(APPEND CLIENT_LINK_DIRS ${tidy_BINARY_DIR}/Release)
  list(APPEND CLIENT_INCLUDE_DIRS ${tidy_SOURCE_DIR}/include)
  list(APPEND CLIENT_LIBS tidy-static)
endif()

# ZXing-cpp configuration
pkg_check_modules(zxing IMPORTED_TARGET zxing)
if(zxing_FOUND)
  message(STATUS "Using system-provided ZXing-cpp")
  list(APPEND CLIENT_INCLUDE_DIRS ${zxing_INCLUDE_DIRS})
  list(APPEND CLIENT_LIBS PkgConfig::zxing)
else()
  set(ZXING_EXAMPLES OFF CACHE BOOL "")
  set(ZXING_BLACKBOX_TESTS OFF CACHE BOOL "")
  add_subdirectory(3rdparty/zxing-cpp EXCLUDE_FROM_ALL)
  list(APPEND CLIENT_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/zxing-cpp/core/src)
  list(APPEND CLIENT_LIBS ZXing)
endif()

# common executable sources
qt_add_executable(
  ${PROJECT_NAME}
  MANUAL_FINALIZATION
  ${APP_SRC_DIR}/main.cpp
  ${COMMON_HEADERS}
  ${COMMON_SOURCES}
  ${LIBCLIENT_SOURCES}
  ${LIBCLIENT_HEADERS}
  ${LIBCLIENT_HEADERS_MOC}
  ${QML_RESOURCES}
  ${QML_RESOURCES_QML}
  src/app/spellcheckadapter.h src/app/spellcheckadapter.cpp)

#add_dependencies(${PROJECT_NAME} hunspell)

# Ensure the generated version file can be found.
add_dependencies(${PROJECT_NAME} generate_version_info)

# Dependencies and link libraries from libclient
if(JAMICORE_AS_SUBDIR)
  if(ENABLE_LIBWRAP)
    add_dependencies(${PROJECT_NAME} qtwrapper)
  else()
    add_dependencies(${PROJECT_NAME} jami-core)
  endif()
endif()

if(NOT(APPLE OR MSVC))
  pkg_check_modules(XCB REQUIRED IMPORTED_TARGET xcb)
  list(APPEND CLIENT_LIBS PkgConfig::XCB)
endif()

if(ENABLE_LIBWRAP)
  list(APPEND CLIENT_LIBS qtwrapper)
endif()
if(NOT MSVC)
  list(APPEND CLIENT_LIBS -lpthread)
  if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
    list(APPEND CLIENT_LIBS -lrt)
  endif()
endif()

foreach(MODULE ${QT_MODULES})
  list(APPEND QT_LIBS "Qt::${MODULE}")
endforeach()

if(MSVC)
  # Makes it a GUI executable instead of a console application
  set_target_properties(
    ${PROJECT_NAME}
    PROPERTIES
    WIN32_EXECUTABLE TRUE)

  list(APPEND CLIENT_LIBS
    ${LIBJAMI_LIB}
    ${GNUTLS_LIB}
    ${QT_LIBS}
    ${WINDOWS_SYS_LIBS})

  # specify output executable files
  set_target_properties(
    ${PROJECT_NAME}
    PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY_RELEASE
    "${JAMI_OUTPUT_DIRECTORY_RELEASE}")

  # executable icon
  target_sources(${PROJECT_NAME} PRIVATE ${PACKAGING_DIR}/wix/ico.rc)

  # translations
  if(Qt${QT_VERSION_MAJOR}LinguistTools_FOUND)
    message("Releasing and copying translation files")
    file(MAKE_DIRECTORY
      "${JAMI_OUTPUT_DIRECTORY_RELEASE}/share/jami/translations/")
    file(GLOB TS_FILES ${PROJECT_SOURCE_DIR}/translations/*.ts)
    set_source_files_properties(
      ${TS_FILES}
      PROPERTIES
      OUTPUT_LOCATION
      "${JAMI_OUTPUT_DIRECTORY_RELEASE}/share/jami/translations")

    qt_add_translation(QM_FILES ${TS_FILES})
    target_sources(${PROJECT_NAME} PRIVATE ${QM_FILES})
  endif()

  # executable name
  set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "Jami")
elseif (NOT APPLE)
  list(APPEND CLIENT_LIBS
    ${QT_LIBS}
    ${X11}
    ${LIBNM_LIBRARIES}
    ${LIBNOTIFY_LIBRARIES}
    ${LIBGDKPIXBUF_LIBRARIES}
    ${GLIB_LIBRARIES}
    ${GIO_LIBRARIES})

  install(
    TARGETS ${PROJECT_NAME}
    RUNTIME DESTINATION bin)

  # Install .desktop in XDG desktop direcory so that it is recognized
  # by the system.
  install(
    FILES ${DATA_DIR}/net.jami.Jami.desktop
    DESTINATION ${JAMI_DATA_PREFIX}/applications)

  # Install .desktop in the jami data directory, so that it can be
  # copied to the autostart directory by the client.
  install(
    FILES ${DATA_DIR}/net.jami.Jami.desktop
    DESTINATION "${JAMI_DATA_PREFIX}/${PROJECT_NAME}"
    PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) # 644

  # Adjust JAMI_DATA_PREFIX for snap package.
  #   (This must come after all 'install' commands that refer to
  #    JAMI_DATA_PREFIX; the following value is not meant to be used
  #    for any install destinations.)
  if(DEFINED ENV{SNAPCRAFT_PROJECT_NAME})
    set(JAMI_DATA_PREFIX
      "/snap/$ENV{SNAPCRAFT_PROJECT_NAME}/current/usr/share")
  endif()

  # (This must come after the above adjustment to JAMI_DATA_PREFIX.)
  target_compile_definitions(
    ${PROJECT_NAME}
    PRIVATE
    JAMI_INSTALL_PREFIX="${JAMI_DATA_PREFIX}")

  target_compile_definitions(
    ${PROJECT_NAME}
    PRIVATE
    HUNSPELL_INSTALL_DIR="${HUNSPELL_DICT_DIR}")

  # Logos
  install(
    FILES resources/images/net.jami.Jami.svg
    DESTINATION
    ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps)

  install(
    FILES resources/images/net.jami.Jami-48px.png
    DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/48x48/apps
    RENAME jami.png)

  install(
    FILES resources/images/jami-32px.xpm
    DESTINATION ${CMAKE_INSTALL_PREFIX}/share/pixmaps
    RENAME jami.xpm)

  # AppStream metadata
  #
  # MetaInfo file:
  install(
    FILES ${DATA_DIR}/net.jami.Jami.metainfo.xml
    DESTINATION ${CMAKE_INSTALL_PREFIX}/share/metainfo)
  # Catalog file:
  if(NOT DEFINED APPSTREAM_CATALOG_DIR)
    set(APPSTREAM_CATALOG_DIR /share/swcatalog/xml)
  endif()
  install(
    FILES ${DATA_DIR}/jami.xml
    DESTINATION ${CMAKE_INSTALL_PREFIX}${APPSTREAM_CATALOG_DIR})

  # man page
  install(
    FILES ${DOC_DIR}/jami.1
    DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man1)

  # Add a target to generate API documentation using Doxygen and
  # graphviz-dot.
  find_package(Doxygen)
  find_program(dot "dot")
  if(Doxygen_FOUND AND dot)
    configure_file(
      ${DOC_DIR}/Doxyfile.in
      ${DOC_DIR}/Doxyfile
      @ONLY)
    add_custom_target(
      doc
      COMMAND
      ${DOXYGEN_EXECUTABLE} ${DOC_DIR}/Doxyfile
      WORKING_DIRECTORY ${DOC_DIR}
      COMMENT "Generating API documentation with Doxygen"
      VERBATIM)

    # Create doc/README.md symlink to README since Doxygen doesn't
    # understand file with no extension.
    add_custom_command(
      TARGET doc
      PRE_BUILD
      COMMAND
      ln -sf ${CMAKE_CURRENT_SOURCE_DIR}/README ${DOC_DIR}/README.md)
  endif()

  # translations
  if(Qt${QT_VERSION_MAJOR}LinguistTools_FOUND)
    message("Releasing and copying translation files")
    file(MAKE_DIRECTORY
      "${CMAKE_BINARY_DIR}/share/jami/translations/")
    file(GLOB TS_FILES ${PROJECT_SOURCE_DIR}/translations/*.ts)
    set_source_files_properties(
      ${TS_FILES}
      PROPERTIES
      OUTPUT_LOCATION "share/jami/translations")

    qt_add_translation(QM_FILES ${TS_FILES})
    add_custom_target(translations ALL DEPENDS ${QM_FILES})
    install(DIRECTORY "${CMAKE_BINARY_DIR}/share/jami/translations/"
      DESTINATION ${CMAKE_INSTALL_PREFIX}/share/jami/translations)
  endif()

  # uninstall rule
  configure_file(
    "${EXTRAS_DIR}/build/cmake/cmake_uninstall.cmake.in"
    "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
    IMMEDIATE
    @ONLY)

  add_custom_target(
    uninstall
    COMMAND
    ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
else()
  set(resources
    ${CMAKE_CURRENT_SOURCE_DIR}/resources/images/jami.icns
    ${CMAKE_CURRENT_SOURCE_DIR}/resources/images/Assets.car)
  set(libs ${QT_LIBS} ${SYSTEM_CONFIGURATUION})
  if(ENABLE_SPARKLE)
    set(resources ${resources} ${SPARKLE_FRAMEWORK})
    set(libs ${libs} ${SPARKLE_FRAMEWORK})
  endif(ENABLE_SPARKLE)
  target_sources(${PROJECT_NAME} PRIVATE ${resources})
  list(APPEND CLIENT_LIBS ${libs})

  find_package(Iconv REQUIRED)
  list(APPEND CLIENT_LIBS Iconv::Iconv)
  list(APPEND CLIENT_LIBS
    "-framework AVFoundation"
    "-framework CoreAudio -framework CoreMedia -framework CoreVideo"
    "-framework VideoToolbox -framework AudioUnit -framework Carbon"
    "-framework Security"
      compression
      resolv
  )

  set(APP_CONTAINER "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.app/Contents")

  # ringtones. Copy the entire directory to the app bundle.
  # daemon/ringtones -> Jami.app/Contents/Resources/ringtones
  execute_process(
    COMMAND ${CMAKE_COMMAND} -E copy_directory
    ${DAEMON_DIR}/ringtones
    ${APP_CONTAINER}/Resources/ringtones
  )

  # translations
  if(Qt${QT_VERSION_MAJOR}LinguistTools_FOUND)
    file(GLOB TS_FILES ${PROJECT_SOURCE_DIR}/translations/*.ts)

    # Generate lproj folders.
    foreach(QM_FILE ${TS_FILES})
      # Extract language code from filename.
      get_filename_component(FILENAME ${QM_FILE} NAME_WE)
      string(REGEX REPLACE "^lrc_" "" LANG ${FILENAME})
      file(MAKE_DIRECTORY ${APP_CONTAINER}/Resources/${LANG}.lproj)
    endforeach()
    set_source_files_properties(
      ${TS_FILES}
      PROPERTIES OUTPUT_LOCATION
      "${APP_CONTAINER}/Resources/share/jami/translations")

    qt_add_translation(QM_FILES ${TS_FILES})
    target_sources(${PROJECT_NAME} PRIVATE ${QM_FILES})
  endif()

  set_target_properties(${PROJECT_NAME} PROPERTIES
          MACOSX_BUNDLE TRUE
          MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/resources/Info.plist"
          MACOSX_BUNDLE_EXECUTABLE_NAME "${PROJ_NAME}"
          MACOSX_BUNDLE_ICON_FILE "jami.icns"
          MACOSX_BUNDLE_GUI_IDENTIFIER "${BUNDLE_ID}"
          MACOSX_BUNDLE_SHORT_VERSION_STRING "${JAMI_VERSION}"
          MACOSX_BUNDLE_BUNDLE_VERSION "${JAMI_BUILD}"
          MACOSX_BUNDLE_COPYRIGHT "${PROJ_COPYRIGHT}")
  if(APPSTORE)
    message(STATUS "app store version")
    set_target_properties(${PROJECT_NAME} PROPERTIES
          XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/entitlements/appstore/Jami.entitlements")
  else()
    # Include container-migration.plist in the Resources directory of the app bundle
    set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/resources/container-migration.plist" PROPERTIES
      MACOSX_PACKAGE_LOCATION "Resources"
    )
    # Add container-migration.plist to the target
    target_sources(${PROJECT_NAME} PRIVATE
      "${CMAKE_CURRENT_SOURCE_DIR}/resources/container-migration.plist"
    )
    set_target_properties(${PROJECT_NAME} PROPERTIES
          SPARKLE_URL "${SPARKLE_URL}"
          SPARKLE_PUBLIC_KEY "${SPARKLE_PUBLIC_KEY}"
          XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/entitlements/Jami.entitlements"
          XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME TRUE)
  endif()
  if(DEPLOY)
    execute_process(COMMAND
      "${CMAKE_PREFIX_PATH}/bin/macdeployqt"
      "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.app"
      -qmldir=${QML_SRC_DIR})
    if(${ENABLE_SPARKLE} MATCHES true)
      file(COPY ${SPARKLE_FRAMEWORK} DESTINATION ${EXE_NAME}/Contents/Frameworks/)
    endif()
  endif()
endif()

target_include_directories(${PROJECT_NAME} PRIVATE ${CLIENT_INCLUDE_DIRS})
target_link_directories(${PROJECT_NAME} PRIVATE ${CLIENT_LINK_DIRS})
target_link_libraries(${PROJECT_NAME} PRIVATE ${CLIENT_LIBS})

qt_import_qml_plugins(${PROJECT_NAME})
qt_finalize_executable(${PROJECT_NAME})

# xrectsel.c (C) and macOS .mm (Objective-C++) files must not use the C++ PCH:
# the C compiler rejects C++-only headers and clang rejects a PCH built without
# Objective-C when compiling .mm sources.
set_source_files_properties(
  ${APP_SRC_DIR}/xrectsel.c
  ${APP_SRC_DIR}/os/macos/macutils.mm
  ${APP_SRC_DIR}/os/macos/connectivitymonitor.mm
  ${APP_SRC_DIR}/os/macos/appversionmanager.mm
  PROPERTIES SKIP_PRECOMPILE_HEADERS ON)

# Precompiled header — amortises the cost of parsing heavyweight Qt and STL
# headers across all translation units. Recompiled only when these headers
# themselves change, which is rare in practice.
target_precompile_headers(${PROJECT_NAME} PRIVATE
  # Qt Core
  <QtCore/QObject>
  <QtCore/QString>
  <QtCore/QStringList>
  <QtCore/QMap>
  <QtCore/QHash>
  <QtCore/QList>
  <QtCore/QVector>
  <QtCore/QVariant>
  <QtCore/QDir>
  <QtCore/QFile>
  <QtCore/QFileInfo>
  <QtCore/QTimer>
  <QtCore/QDebug>
  <QtCore/QUrl>
  <QtCore/QJsonObject>
  <QtCore/QJsonArray>
  <QtCore/QJsonDocument>
  <QtCore/QRegularExpression>
  # Qt GUI / Quick
  <QtGui/QImage>
  # STL
  <memory>
  <string>
  <vector>
  <map>
  <functional>
  <algorithm>
)

# tests
if(BUILD_TESTING)
  message("Add Jami tests")
  enable_testing()
  add_subdirectory(${TESTS_DIR})
endif()

find_program(CQFD_EXECUTABLE cqfd)
if(CQFD_EXECUTABLE)
  add_custom_target(cqfd-tests
    COMMAND ${CQFD_EXECUTABLE} run
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    USES_TERMINAL)
  add_custom_target(cqfd-qml-tests
    COMMAND ${CQFD_EXECUTABLE} -b qml run
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    USES_TERMINAL)
  add_custom_target(cqfd-unit-tests
    COMMAND ${CQFD_EXECUTABLE} -b unit run
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    USES_TERMINAL)
endif()
