############################################################################
#    Copyright (C) 2014-2017 by Ahmed Charles - acharles@outlook.com       #
#    Copyright (C) 2015-2018, 2020, 2023, 2025-2026 by Stephen Lyons       #
#                                                - slysven@virginmedia.com #
#    Copyright (C) 2018 by Huadong Qi - novload@outlook.com                #
#    Copyright (C) 2022 by Thiago Jung Bauermann - bauermann@kolabnow.com  #
#    Copyright (C) 2022 by Piotr Wilczynski - delwing@gmail.com            #
#                                                                          #
#    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 2 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.,                                       #
#    59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             #
############################################################################

set(EXE_MUDLET_NAME "mudlet")
set(EXE_MUDLET_TARGET "mudlet_executable")

set(mudlet_RCCS mudlet.qrc)
configure_file("${mudlet_SOURCE_DIR}/translations/translated/qm.qrc" "${mudlet_BINARY_DIR}/translations/translated/qm.qrc" COPYONLY)

if(USE_VARIABLE_SPLASH_SCREEN)
  list(APPEND mudlet_RCCS additional_splash_screens.qrc)
endif()

if(USE_FONTS)
  list(APPEND mudlet_RCCS mudlet_fonts_common.qrc)
  if(CMAKE_SYSTEM_NAME STREQUAL Linux OR CMAKE_SYSTEM_NAME STREQUAL FreeBSD)
    list(APPEND mudlet_RCCS mudlet_fonts_posix.qrc)
  endif()
endif()

# Add every .cpp source file that does not need Qt Widgets here, for mudlet_core, kept
# in alphabetical order (case-insensitive).
set(mudlet_SRCS
    ActionUnit.cpp
    AliasUnit.cpp
    CredentialManager.cpp
    ctelnet.cpp
    DioramaLayout.cpp
    discord.cpp
    EditorAddItemCommand.cpp
    EditorDeleteItemCommand.cpp
    EditorItemXMLHelpers.cpp
    EditorModifyPropertyCommand.cpp
    EditorMoveItemCommand.cpp
    EditorToggleActiveCommand.cpp
    EditorUndoStack.cpp
    EnvironmentKit.cpp
    EventLoopPump.cpp
    ExitAppearance.cpp
    FontManager.cpp
    GifTracker.cpp
    GMCPAuthenticator.cpp
    Host.cpp
    HostManager.cpp
    ircmessageformatter.cpp
    KeyUnit.cpp
    LuaInterface.cpp
    LuaLiteral.cpp
    mapInfoContributorManager.cpp
    MapDecals.cpp
    MapStyle.cpp
    MMCPClient.cpp
    MMCPServer.cpp
    MudletApp.cpp
    MudletInstanceCoordinator.cpp
    MudletMedia.cpp
    MudletReplay.cpp
    MxpTag.cpp
    OAuthClientFlow.cpp
    RoomAppearance.cpp
    ScriptUnit.cpp
    SecureStringUtils.cpp
    SentryWrapper.cpp
    SherpaRecognizer.cpp
    ShortcutsManager.cpp
    SignInStoreReconciler.cpp
    SpeechAudioCapture.cpp
    SpeechRecognizerFactory.cpp
    TAction.cpp
    TAlias.cpp
    TArea.cpp
    TAreaGraph.cpp
    TAreaGridIndex.cpp
    TAreaLodExitIndex.cpp
    TAreaSpanIndex.cpp
    TAreaZLevelIndex.cpp
    TAudioMixer.cpp
    TBuffer.cpp
    TConsoleModel.cpp
    TDebug.cpp
    TEncodingHelper.cpp
    TEncodingTable.cpp
    TEntityHandler.cpp
    TEntityResolver.cpp
    TForkedProcess.cpp
    TGlyphCache.cpp
    THyperlinkCompactManager.cpp
    THyperlinkSelectionManager.cpp
    THyperlinkVisibilityManager.cpp
    TimerUnit.cpp
    TIrcClient.cpp
    TKey.cpp
    TLabelModel.cpp
    TLinkStore.cpp
    TLuaInterpreter.cpp
    TLuaInterpreterDiscord.cpp
    TLuaInterpreterMapper.cpp
    TLuaInterpreterMedia.cpp
    TLuaInterpreterMMCP.cpp
    TLuaInterpreterMudletObjects.cpp
    TLuaInterpreterNetworking.cpp
    TLuaInterpreterTextToSpeech.cpp
    TLuaInterpreterSpeechToText.cpp
    TLuaInterpreterUI.cpp
    TMap.cpp
    TMapLabel.cpp
    TMedia.cpp
    TMediaVoice.cpp
    TMediaPlaylist.cpp
    TMxpBRTagHandler.cpp
    TMxpColorTagHandler.cpp
    TMxpCustomElementTagHandler.cpp
    TMxpDestTagHandler.cpp
    TMxpElementDefinitionHandler.cpp
    TMxpElementRegistry.cpp
    TMxpEntityTagHandler.cpp
    TMxpExpireTagHandler.cpp
    TMxpFontTagHandler.cpp
    TMxpFormattingTagsHandler.cpp
    TMxpFrameManager.cpp
    TMxpFrameTagHandler.cpp
    TMxpHRTagHandler.cpp
    TMxpImageTagHandler.cpp
    TMxpLinkTagHandler.cpp
    TMxpMudlet.cpp
    TMxpMusicTagHandler.cpp
    TMxpNodeBuilder.cpp
    TMxpProcessor.cpp
    TMxpSendTagHandler.cpp
    TMxpSoundTagHandler.cpp
    TMxpStatTagHandler.cpp
    TMxpSupportTagHandler.cpp
    TMxpTagHandler.cpp
    TMxpTagParser.cpp
    TMxpTagProcessor.cpp
    TMxpVarTagHandler.cpp
    TMxpVersionTagHandler.cpp
    TNullAppFrontend.cpp
    TNullConsoleFrontend.cpp
    TriggerMatchPool.cpp
    TriggerUnit.cpp
    TTriggerPrescan.cpp
    TRoom.cpp
    TRoomDB.cpp
    TScript.cpp
    TSpeechBridge.cpp
    TSpellChecker.cpp
    TStringUtils.cpp
    TTextCodec.cpp
    TTimer.cpp
    TTrigger.cpp
    TVar.cpp
    UntrustedText.cpp
    utils.cpp
    VarUnit.cpp
    VoskRecognizer.cpp
    XMLexport.cpp
    XMLimport.cpp)

# Every .cpp source file that needs Qt Widgets goes here instead, in mudlet_app, kept
# in alphabetical order (case-insensitive).
set(mudlet_app_SRCS
    ../3rdparty/qt-tags-widget/src/qtagedit.cpp
    CustomLineDrawContextMenuHandler.cpp
    CustomLineDrawHandler.cpp
    CustomLineEditContextMenuHandler.cpp
    CustomLineEditHandler.cpp
    CustomLineSession.cpp
    DarkTheme.cpp
    dlgAboutDialog.cpp
    dlgActionMainArea.cpp
    dlgAliasMainArea.cpp
    dlgColorTrigger.cpp
    dlgComposer.cpp
    dlgConnectionProfiles.cpp
    dlgIRC.cpp
    dlgKeysMainArea.cpp
    dlgMapLabel.cpp
    dlgMapper.cpp
    dlgModuleManager.cpp
    dlgNotepad.cpp
    dlgPackageExporter.cpp
    dlgPackageManager.cpp
    dlgProfilePreferences.cpp
    dlgRoomExits.cpp
    dlgRoomProperties.cpp
    dlgScriptsMainArea.cpp
    dlgSourceEditorArea.cpp
    dlgSourceEditorFindArea.cpp
    dlgSystemMessageArea.cpp
    dlgTimersMainArea.cpp
    dlgTriggerEditor.cpp
    dlgTriggerPatternEdit.cpp
    dlgTriggersMainArea.cpp
    dlgVarsMainArea.cpp
    EAction.cpp
    EditorAutoCompleteFocusHandler.cpp
    exitstreewidget.cpp
    FileOpenHandler.cpp
    HostDialogs.cpp
    LabelInteractionHandler.cpp
    MiddleMousePanHandler.cpp
    mudlet.cpp
    MudletProxyStyle.cpp
    PackageItemDelegate.cpp
    PanInteractionHandler.cpp
    RoomContextMenuHandler.cpp
    RoomMoveActivationHandler.cpp
    RoomMoveDragHandler.cpp
    SelectionRectangleHandler.cpp
    SingleLineTextEdit.cpp
    T2DMap.cpp
    TAccessibleLabel.cpp
    TAccessibleTextEdit.cpp
    TCommandLine.cpp
    TPasswordEntry.cpp
    TConsole.cpp
    TDebugFilterBar.cpp
    TDetachedWindow.cpp
    TDockWidget.cpp
    TEasyButtonBar.cpp
    TFeatureCallout.cpp
    TFlipButton.cpp
    TKeySequenceEdit.cpp
    TLabel.cpp
    TMainConsole.cpp
    TMapView.cpp
    TMapViewManager.cpp
    TMxpFrameWidgets.cpp
    TrailingWhitespaceMarker.cpp
    TriggerHighlighter.cpp
    TScrollBox.cpp
    TSplitter.cpp
    TSplitterHandle.cpp
    TTabBar.cpp
    TTextBox.cpp
    TTextEdit.cpp
    TToolBar.cpp
    TTreeWidget.cpp
    TUiTour.cpp
    WideComboBox.cpp
    widgetutils.cpp)

if(APPLE)
  # Sparkle updater for macOS
  if(USE_UPDATER)
    list(APPEND mudlet_app_SRCS sparkleupdater.mm)
    list(APPEND mudlet_app_HDRS sparkleupdater.h)
    set_source_files_properties(
      src/sparkleupdater.mm
      src/sparkleupdater.h
      PROPERTIES
      LANGUAGE OBJCXX
      COMPILE_OPTIONS "-fobjc-arc"
  )
  endif()

  # Native macOS microphone permission handling for speech recognition. Only
  # the .mm is appended here: appending the header would achieve nothing, since
  # the set(mudlet_HDRS ...) above already lists it and would overwrite this
  # anyway. Only the .mm is compiled, so only it carries the language settings.
  list(APPEND mudlet_SRCS MacMicrophonePermission.mm)
  set_source_files_properties(
    MacMicrophonePermission.mm
    PROPERTIES
    LANGUAGE OBJCXX
    COMPILE_OPTIONS "-fobjc-arc"
  )

  # The macOS speech recognition backend. Unlike MacMicrophonePermission its
  # class declares Q_OBJECT, so AUTOMOC must see the header - but only where
  # the .mm that defines the class is also built. Leaving it in the
  # unconditional mudlet_HDRS set moc'd it on every platform and left Linux and
  # Windows linking against a vtable nothing defined. The header is appended
  # after that set() instead, since this block runs before it.
  list(APPEND mudlet_SRCS AppleSpeechRecognizer.mm)
  set_source_files_properties(
    AppleSpeechRecognizer.mm
    PROPERTIES
    LANGUAGE OBJCXX
    COMPILE_OPTIONS "-fobjc-arc"
  )
endif()

# This is for compiled UI files, not those used at runtime
# Add every .ui file here, kept in alphabetical order (case-insensitive).
set(mudlet_UIS
    ui/about_dialog.ui
    ui/actions_main_area.ui
    ui/aliases_main_area.ui
    ui/color_trigger.ui
    ui/composer.ui
    ui/connection_profiles.ui
    ui/dlgPackageExporter.ui
    ui/irc.ui
    ui/keybindings_main_area.ui
    ui/main_window.ui
    ui/map_label.ui
    ui/mapper.ui
    ui/module_manager.ui
    ui/notes_editor.ui
    ui/package_manager.ui
    ui/profile_preferences.ui
    ui/room_exits.ui
    ui/room_properties.ui
    ui/scripts_main_area.ui
    ui/source_editor_area.ui
    ui/source_editor_find_area.ui
    ui/system_message_area.ui
    ui/timers_main_area.ui
    ui/trigger_editor.ui
    ui/trigger_pattern_edit.ui
    ui/triggers_main_area.ui
    ui/vars_main_area.ui)

# Add every .h header file that does not need Qt Widgets here, for mudlet_core, kept
# in alphabetical order (case-insensitive).
set(mudlet_HDRS
    ../3rdparty/discord/rpc/include/discord_register.h
    ../3rdparty/discord/rpc/include/discord_rpc.h
    ../3rdparty/kdtoolbox/singleshot_connect/singleshot_connect.h
    ActionUnit.h
    AliasUnit.h
    CredentialManager.h
    ctelnet.h
    DioramaLayout.h
    discord.h
    EditorAddItemCommand.h
    EditorCommand.h
    EditorDeleteItemCommand.h
    EditorItemXMLHelpers.h
    EditorModifyPropertyCommand.h
    EditorMoveItemCommand.h
    EditorToggleActiveCommand.h
    EditorUndoStack.h
    EnvironmentKit.h
    enums.h
    EventLoopPump.h
    ExitAppearance.h
    FontManager.h
    GifTracker.h
    GMCPAuthenticator.h
    Host.h
    HostManager.h
    ircmessageformatter.h
    KeyUnit.h
    LuaInterface.h
    LuaLiteral.h
    MacMicrophonePermission.h
    mapInfoContributorManager.h
    MapDecals.h
    MapStyle.h
    MMCP.h
    MMCPClient.h
    MMCPServer.h
    MudletApp.h
    MudletInstanceCoordinator.h
    MudletMedia.h
    MudletReplay.h
    MxpTag.h
    OAuthClientFlow.h
    RoomAppearance.h
    ScriptUnit.h
    SecureStringUtils.h
    SentryWrapper.h
    SherpaRecognizer.h
    ShortcutsManager.h
    SignInStoreReconciler.h
    SpeechAudioCapture.h
    SpeechRecognizer.h
    SpeechRecognizerFactory.h
    TAction.h
    TAlias.h
    TAppFrontend.h
    TArea.h
    TAreaGraph.h
    TAreaGridIndex.h
    TAreaLodExitIndex.h
    TAreaSpanIndex.h
    TAreaZLevelIndex.h
    TAstar.h
    TAudioMixer.h
    TBuffer.h
    TConsoleFrontend.h
    TConsoleModel.h
    TConsoleModelNotifier.h
    TDebug.h
    TEncodingHelper.h
    TEncodingTable.h
    TEntityHandler.h
    TEntityResolver.h
    TEvent.h
    TForkedProcess.h
    TGameDetails.h
    TGlyphCache.h
    THyperlinkCompactManager.h
    THyperlinkSelectionManager.h
    THyperlinkStyling.h
    THyperlinkVisibilityManager.h
    TimerUnit.h
    TIrcClient.h
    TKey.h
    TLabelModel.h
    TLinkStore.h
    TLuaInterpreter.h
    TMap.h
    TMapLabel.h
    TMapViewFrontend.h
    TMapViewsFrontend.h
    TMatchState.h
    TMedia.h
    TMediaVoice.h
    TMediaData.h
    TMediaPlaylist.h
    TMxpBRTagHandler.h
    TMxpClient.h
    TMxpColorTagHandler.h
    TMxpContext.h
    TMxpCustomElementTagHandler.h
    TMxpDestTagHandler.h
    TMxpElementDefinitionHandler.h
    TMxpElementRegistry.h
    TMxpEntityTagHandler.h
    TMxpEvent.h
    TMxpExpireTagHandler.h
    TMxpFontTagHandler.h
    TMxpFormattingTagsHandler.h
    TMxpFrameFrontend.h
    TMxpFrameManager.h
    TMxpFrameTagHandler.h
    TMxpHRTagHandler.h
    TMxpImageTagHandler.h
    TMxpLinkTagHandler.h
    TMxpMudlet.h
    TMxpMusicTagHandler.h
    TMxpNodeBuilder.h
    TMxpProcessor.h
    TMxpSendTagHandler.h
    TMxpSoundTagHandler.h
    TMxpStatTagHandler.h
    TMxpSupportTagHandler.h
    TMxpTagHandler.h
    TMxpTagHandlerResult.h
    TMxpTagParser.h
    TMxpTagProcessor.h
    TMxpVarTagHandler.h
    TMxpVersionTagHandler.h
    TNullAppFrontend.h
    TNullConsoleFrontend.h
    TPrintSink.h
    Tree.h
    TriggerMatchPool.h
    TriggerUnit.h
    TTriggerPrescan.h
    TRoom.h
    TRoomDB.h
    TScript.h
    TSpeechBridge.h
    TSpellChecker.h
    TStringUtils.h
    TTextCodec.h
    TTextProperties.h
    TTimer.h
    TTrigger.h
    TVar.h
    TWindowRegistry.h
    UntrustedText.h
    utils.h
    VarUnit.h
    VoskRecognizer.h
    widechar_width.h
    XMLexport.h
    XMLimport.h)

# Every .h header file that needs Qt Widgets goes here instead, in mudlet_app, kept
# in alphabetical order (case-insensitive).
set(mudlet_app_HDRS
    ../3rdparty/qt-tags-widget/include/QTagEdit/qtagedit.hpp
    CustomLineDrawContextMenuHandler.h
    CustomLineDrawHandler.h
    CustomLineEditContextMenuHandler.h
    CustomLineEditHandler.h
    CustomLineSession.h
    DarkTheme.h
    dlgAboutDialog.h
    dlgActionMainArea.h
    dlgAliasMainArea.h
    dlgColorTrigger.h
    dlgComposer.h
    dlgConnectionProfiles.h
    dlgIRC.h
    dlgKeysMainArea.h
    dlgMapLabel.h
    dlgMapper.h
    dlgModuleManager.h
    dlgNotepad.h
    dlgPackageExporter.h
    dlgPackageManager.h
    dlgProfilePreferences.h
    dlgRoomExits.h
    dlgRoomProperties.h
    dlgScriptsMainArea.h
    dlgSourceEditorArea.h
    dlgSourceEditorFindArea.h
    dlgSystemMessageArea.h
    dlgTimersMainArea.h
    dlgTriggerEditor.h
    dlgTriggerPatternEdit.h
    dlgTriggersMainArea.h
    dlgVarsMainArea.h
    EAction.h
    EditorAutoCompleteFocusHandler.h
    exitstreewidget.h
    FileOpenHandler.h
    HostDialogs.h
    LabelInteractionHandler.h
    MiddleMousePanHandler.h
    mudlet.h
    MudletProxyStyle.h
    PackageItemDelegate.h
    PanInteractionHandler.h
    RoomContextMenuHandler.h
    RoomMoveActivationHandler.h
    RoomMoveDragHandler.h
    SelectionRectangleHandler.h
    SingleLineTextEdit.h
    T2DMap.h
    TAccessibleConsole.h
    TAccessibleLabel.h
    TAccessibleTextEdit.h
    TCommandLine.h
    TPasswordEntry.h
    TConsole.h
    TDebugFilterBar.h
    TDetachedWindow.h
    TDockWidget.h
    TEasyButtonBar.h
    TFeatureCallout.h
    TFlipButton.h
    TKeySequenceEdit.h
    TLabel.h
    TMainConsole.h
    TMapView.h
    TMapViewManager.h
    TMxpFrameWidgets.h
    TrailingWhitespaceMarker.h
    TriggerHighlighter.h
    TScrollBox.h
    TSplitter.h
    TSplitterHandle.h
    TTabBar.h
    TTextBox.h
    TTextEdit.h
    TToolBar.h
    TTreeWidget.h
    TUiTour.h
    WideComboBox.h
    widgetutils.h)

if(APPLE)
  # Appended rather than listed above: AUTOMOC generates a moc file for every
  # Q_OBJECT header it is given, and AppleSpeechRecognizer is only compiled on
  # Apple.
  list(APPEND mudlet_HDRS AppleSpeechRecognizer.h)
endif()


if(USE_UPDATER)
  list(APPEND mudlet_app_SRCS updater.cpp)
  list(APPEND mudlet_app_HDRS updater.h)
  list(APPEND mudlet_SRCS updater/Feed.cpp)
  list(APPEND mudlet_HDRS updater/Feed.h)
  list(APPEND mudlet_SRCS updater/Release.cpp)
  list(APPEND mudlet_HDRS updater/Release.h)
  list(APPEND mudlet_SRCS updater/SemVer.cpp)
  list(APPEND mudlet_HDRS updater/SemVer.h)
  list(APPEND mudlet_app_SRCS updater/UpdateDialog.cpp)
  list(APPEND mudlet_app_HDRS updater/UpdateDialog.h)
  list(APPEND mudlet_UIS updater/update_dialog.ui)
endif(USE_UPDATER)

if(USE_3DMAPPER)
  list(APPEND mudlet_app_SRCS glwidget.cpp)
  list(APPEND mudlet_app_HDRS glwidget.h)
  list(APPEND mudlet_app_SRCS modern_glwidget.cpp)
  list(APPEND mudlet_app_HDRS modern_glwidget.h)
  list(APPEND mudlet_app_SRCS MapSearchPanel.cpp)
  list(APPEND mudlet_app_HDRS MapSearchPanel.h)
  list(APPEND mudlet_SRCS BloomPass.cpp)
  list(APPEND mudlet_HDRS BloomPass.h)
  list(APPEND mudlet_SRCS CameraController.cpp)
  list(APPEND mudlet_HDRS CameraController.h)
  list(APPEND mudlet_SRCS ExitScene.cpp)
  list(APPEND mudlet_HDRS ExitScene.h)
  list(APPEND mudlet_SRCS GeometryManager.cpp)
  list(APPEND mudlet_HDRS GeometryManager.h)
  list(APPEND mudlet_SRCS LabelTextureCache.cpp)
  list(APPEND mudlet_HDRS LabelTextureCache.h)
  list(APPEND mudlet_SRCS MapAtlas.cpp)
  list(APPEND mudlet_HDRS MapAtlas.h)
  list(APPEND mudlet_SRCS MapRoute.cpp)
  list(APPEND mudlet_HDRS MapRoute.h)
  list(APPEND mudlet_SRCS MapSearch.cpp)
  list(APPEND mudlet_HDRS MapSearch.h)
  list(APPEND mudlet_SRCS MapStitch.cpp)
  list(APPEND mudlet_HDRS MapStitch.h)
  list(APPEND mudlet_SRCS RenderCommand.cpp)
  list(APPEND mudlet_HDRS RenderCommand.h)
  list(APPEND mudlet_SRCS RenderCommandQueue.cpp)
  list(APPEND mudlet_HDRS RenderCommandQueue.h)
  list(APPEND mudlet_SRCS ResourceManager.cpp)
  list(APPEND mudlet_HDRS ResourceManager.h)
  list(APPEND mudlet_SRCS RetainedMapScene.cpp)
  list(APPEND mudlet_HDRS RetainedMapScene.h)
  list(APPEND mudlet_SRCS ShaderManager.cpp)
  list(APPEND mudlet_HDRS ShaderManager.h)
  list(APPEND mudlet_app_HDRS glwidget_integration.h)
  list(APPEND mudlet_app_SRCS glwidget_integration.cpp)
endif(USE_3DMAPPER)

if(USE_MCPSERVER)
  list(APPEND mudlet_SRCS TMCPBridge.cpp)
  list(APPEND mudlet_SRCS TMCPServer.cpp)
  list(APPEND mudlet_SRCS TMCPLuaBridge.cpp)
  list(APPEND mudlet_HDRS TMCPBridge.h)
  list(APPEND mudlet_HDRS TMCPServer.h)
  list(APPEND mudlet_HDRS TMCPLuaBridge.h)
endif(USE_MCPSERVER)

if(NOT WIN32)
  include(cmake/EnableSanitizers.cmake)
else()
  message(STATUS "Sanitizers disabled on Windows")
endif()

find_package(Qt6 6.8.2 REQUIRED
    COMPONENTS Core
               Core5Compat
               Gui
               Multimedia
               MultimediaWidgets
               Network
               OpenGL
               OpenGLWidgets
               Svg
               UiTools
               Widgets
               Concurrent)

if(USE_MCPSERVER)
  find_package(Qt6 COMPONENTS HttpServer REQUIRED)
endif()

find_package(Qt6 COMPONENTS TextToSpeech QUIET)

message(STATUS "Qt version: ${Qt6Core_VERSION}")

set(QT_MAJOR_VERSION 6)
set(QT_LIBS Qt6::Concurrent
              Qt6::Core
              Qt6::Core5Compat
              Qt6::Gui
              Qt6::Network
              Qt6::Multimedia
              Qt6::OpenGL
              Qt6::Svg
              qt6keychain)
set(QT_APP_LIBS Qt6::MultimediaWidgets
                Qt6::OpenGLWidgets
                Qt6::UiTools
                Qt6::Widgets)

if(USE_MCPSERVER)
  list(APPEND QT_LIBS Qt6::HttpServer)
endif()

if(Qt6TextToSpeech_FOUND)
  list(APPEND QT_LIBS Qt6::TextToSpeech)
  message(STATUS "Using TextToSpeech module")
endif()

# MudletProxyStyle reads the AT-SPI screen reader status over D-Bus
if(UNIX AND NOT APPLE)
  find_package(Qt6 COMPONENTS DBus REQUIRED)
  list(APPEND QT_APP_LIBS Qt6::DBus)
endif()

message(STATUS "Using ${CMAKE_CXX_COMPILER_ID} compiler")

find_package(ZIP REQUIRED)
find_package(Lua51 REQUIRED)
find_package(ZLIB REQUIRED)
# 10.36 is the floor for PCRE2_MATCH_INVALID_UTF: it arrived in 10.34, and 10.36
# fixed it mis-optimising the start of a match in 8-bit mode with PCRE2_CASELESS
find_package(PCRE2 10.36 REQUIRED)
find_package(PUGIXML REQUIRED)
find_package(HUNSPELL REQUIRED)
find_package(Boost 1.44 CONFIG REQUIRED)
message(STATUS "Found Boost: ${Boost_DIR} (found version \"${Boost_VERSION}\")")


# FreeBSD's sysinfo() call is done in a package, libsysinfo.
# Find the library and include it appropriately.

if (CMAKE_SYSTEM_NAME STREQUAL FreeBSD)
  find_library(SYSINFO_LIBRARY NAMES sysinfo)
  find_path(SYSINFO_INCLUDE_DIR NAMES sys/sysinfo.h)
  find_library(KVM_LIBRARY NAMES kvm)
endif()

if(NOT USE_OWN_QTKEYCHAIN)
  find_package(Qt${QT_MAJOR_VERSION}Keychain REQUIRED)
  message(STATUS "Using system QtKeyChain (Qt${QT_MAJOR_VERSION}) library (found version \"${Qt${QT_MAJOR_VERSION}Keychain_VERSION}\").")
endif()

if(USE_3DMAPPER)
  if(POLICY CMP0072)
    cmake_policy(SET CMP0072 NEW)
  endif()
  find_package(OpenGL REQUIRED)
  find_package(assimp REQUIRED)
endif()

set(CMAKE_INCLUDE_CURRENT_DIR ON)

add_library(${LIB_MUDLET_TARGET} STATIC
    ${mudlet_SRCS}
    ${mudlet_RCCS}
    ${mudlet_HDRS}
)

mudlet_precompile_headers(${LIB_MUDLET_TARGET})
# Objective-C++ would need a precompiled header of its own for three files
set_source_files_properties(AppleSpeechRecognizer.mm MacMicrophonePermission.mm sparkleupdater.mm
  PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
if(USE_UPDATER)
  # Its own target, so it keeps plain ccache: the precompiled-header launcher
  # tells ccache to ignore __DATE__ and __TIME__, which this file embeds to order
  # test builds
  add_library(mudlet_build_time OBJECT updater/ReleaseBuildTime.cpp)
  set_target_properties(mudlet_build_time PROPERTIES AUTOMOC OFF)
  target_link_libraries(mudlet_build_time PRIVATE Qt6::Gui)
  target_sources(${LIB_MUDLET_TARGET} PRIVATE $<TARGET_OBJECTS:mudlet_build_time>)
endif()

########################### Debugging code inclusions ##########################
# Controls the include/selection of extra code to aide developers debug various
# features of Mudlet, generally speaking they will not be '#define'd in
# production builds - uncomment the
# 'target_compile_definitions(mudlet PRIVATE DEBUG__XXX)'
# line here to include the relevant code:
#
# Other DEFINES+=DEBUG_XXX expected here as a result of:
# https://github.com/Mudlet/Mudlet/issues/6314
#
# * Produce a timestamped message on the OS's command line when an autosave of
#   the map file gets requested and when such a request gets actioned (or
#   rejected because the profile is being loaded):
# target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC DEBUG_MAPAUTOSAVE)
#
# * Produce all the time the surprise that normally will only occur on the
#   first day of the fourth month of the Gregorian calendar year:
# target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC DEBUG_EASTER_EGGS)
#
# * Produce debugging messages about WILL/WONT/DO/DONT and other commands for
#   suboptions - change the value to 2 to get a bit more detail about the size
#   and nature of the command:
# target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC DEBUG_TELNET=1)
#
# * Produce qDebug() messages about the decoding of UTF-8 data when it is not
#   the single bytes of pure ASCII text:
# target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC DEBUG_UTF8_PROCESSING)
#
# * Produce qDebug() messages about the decoding of GB2312/GBK/GB18030 data when
#   it is not the single bytes of pure ASCII text:
# target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC DEBUG_GB_PROCESSING)
#
# * Produce qDebug() messages about the decoding of BIG5 data when it is not the
#   single bytes of pure ASCII text:
# target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC DEBUG_BIG5_PROCESSING)
#
# * Produce qDebug() messages about the decoding of EUC-KR data when it is not
#   the single bytes of pure ASCII text:
# target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC DEBUG_EUC_KR_PROCESSING)
#
# * Produce qDebug() messages about private/reserved ANSI CSI sequences that
#   Mudlet does not interpret, e.g. the `\x1b[?25l` a game may send around each
#   prompt to hide the cursor - which is frequent enough to be noisy:
# target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC DEBUG_CSI_PROCESSING)
#
# * Produce qDebug() messages about the decoding of ANSI SGR sequences:
#   target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC DEBUG_SGR_PROCESSING)
#
# * Produce qDebug() messages about the decoding of ANSI OSC sequences:
# target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC DEBUG_OSC_PROCESSING)
#
# * Produce qDebug() messages about the decoding of ANSI MXP sequences although
#   there is not much against this item at present {only an announcement of the
#   type (?) of an `\x1b[?z` received}:
# target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC DEBUG_MXP_PROCESSING)
#
# * Enable the features associated with reporting problems in processing Unicode
#   codepoints that cannot be displayed on screen in a `TConsole`:
# target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC DEBUG_CODEPOINT_PROBLEMS)
#
# * Produce qDebug() messages about window handling operations like dock widget
#   transfers, profile switching, and detached window management:
# target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC DEBUG_WINDOW_HANDLING)
#
# * Enable player icon adjustment controls in the 3D mapper for debugging and
#   alignment purposes - these are normally hidden in production builds:
# target_compile_definitions(mudlet PRIVATE DEBUG_PLAYER_ICON_CONTROLS)
#
# * Produce qDebug() messages about undo/redo operations in the trigger editor,
#   including command execution, stack operations, and edbee text undo integration:
# target_compile_definitions(${LIB_MUDLET_TARGET} PRIVATE DEBUG_UNDO_REDO)
#
# * Produce qDebug() messages about the GMCP Char.Login authentication flow:
# target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC DEBUG_GMCP_AUTHENTICATION)

# * Produce qDebug() messages about speech-to-text recognition, including
#   hallucination filtering, partial/final results, and audio level monitoring:
# target_compile_definitions(${LIB_MUDLET_TARGET} PRIVATE DEBUG_STT)

# target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC DEBUG_PLAYER_ICON_CONTROLS)

target_sources(${LIB_MUDLET_TARGET} PUBLIC "${mudlet_BINARY_DIR}/translations/translated/qm.qrc")

set_target_properties(${LIB_MUDLET_TARGET} PROPERTIES
  AUTOMOC ON AUTORCC ON POSITION_INDEPENDENT_CODE ON
  AUTOGEN_ORIGIN_DEPENDS OFF)

# AUTOMOC concatenates every moc_*.cpp into one mocs_compilation.cpp whose
# include order it controls. With GCC 16's -Wsfinae-incomplete that trips false
# positives: qmetatype.h's is_complete SFINAE trait is instantiated from one
# moc file while types defined in a later moc file are still incomplete, so
# defining them later warns. The generated TU is not hand-written code, so
# silence the warning there rather than rearranging includes across moc files.
# Scoped to GCC: -Wsfinae-incomplete is a GCC-only flag (GCC 15+), and GCC
# silently ignores unknown -Wno- options on older versions, but Clang/MSVC
# would diagnose the unknown flag.
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
  set_property(SOURCE "${CMAKE_CURRENT_BINARY_DIR}/${LIB_MUDLET_TARGET}_autogen/mocs_compilation.cpp"
               APPEND PROPERTY COMPILE_OPTIONS -Wno-sfinae-incomplete)
endif()

if(USE_FONTS)
  target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC INCLUDE_FONTS)
endif()

if(UNIX)
  set(LUA_DEFAULT_DIR "${CMAKE_INSTALL_PREFIX}/share/mudlet/lua")
endif(UNIX)

# APP_VERSION is PUBLIC because the functional tests link ${LIB_MUDLET_TARGET}
# and check the version identity it reports.
target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC APP_VERSION="${APP_VERSION}")

# Define a preprocessor symbol with the default fallback location from which to
# load installed mudlet lua files. Set LUA_DEFAULT_DIR to a platform-specific
# value. If LUA_DEFAULT_DIR is unset, the root directory will be used.
target_compile_definitions(${LIB_MUDLET_TARGET}
  PRIVATE
    APP_TARGET="${APP_TARGET}"
    LUA_DEFAULT_PATH="${LUA_DEFAULT_DIR}")
# Only on the file that reads it: ccache's base_dir cannot rewrite a path in a
# definition, so on every file it would stop worktrees sharing any cache hits.
set_property(SOURCE TLuaInterpreter.cpp APPEND PROPERTY
  COMPILE_DEFINITIONS LUA_SOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/mudlet-lua/lua")

if(USE_OWN_QTKEYCHAIN)
    target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC INCLUDE_OWN_QT6_KEYCHAIN QTKEYCHAIN_NO_EXPORT)
    target_include_directories(${LIB_MUDLET_TARGET} PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/qtkeychain)
endif()

if(DEFINED ENV{QT_NO_SSL})
  message(STATUS "Not including OpenSSL 3.x licence details as that library has been explicitly excluded from the build")
else()
  if (DEFINED ENV{CI} AND "$ENV{CI}" STREQUAL "true" )
    # This is ONLY used to include the Licence details for the OpenSSL 3
    # library - as such it only needs to/should be present for builds that
    # "package" Mudlet i.e. installer builds (for MacOS and Windows) and
    # AppImages (for Linux) in our CI procees all other builds will use a
    # "system-provided" libssl.so.3 library and we do not then NEED to mention
    # them in the "Third-party" libraries.
    target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC INCLUDE_OPENSSL3)
    message(STATUS "Including OpenSSL 3.x licence details for a package build")
  else()
    message(STATUS "Not including OpenSSL 3.x licence details as this seem to be a local build")
  endif()
endif()

# Surface the linked qtkeychain version for diagnostics - the QTKEYCHAIN_VERSION macro in
# qtkeychain's own header is stale (hardcoded to 0x000100), so the CMake package version is
# the only reliable signal. Relevant since qtkeychain 0.17.0 changed the Windows credential
# naming scheme (see the "pre-0.17 qtkeychain naming" stage in CredentialManager::retrievePassword).
if(USE_OWN_QTKEYCHAIN)
  get_directory_property(QTKEYCHAIN_LINKED_VERSION
    DIRECTORY ${CMAKE_SOURCE_DIR}/3rdparty/qtkeychain
    DEFINITION QTKEYCHAIN_VERSION)
else()
  set(QTKEYCHAIN_LINKED_VERSION ${Qt${QT_MAJOR_VERSION}Keychain_VERSION})
endif()
if(QTKEYCHAIN_LINKED_VERSION)
  target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC QTKEYCHAIN_LINKED_VERSION="${QTKEYCHAIN_LINKED_VERSION}")
endif()

# Define shader hot-reloading if enabled
if(USE_SHADER_HOT_RELOAD AND USE_3DMAPPER)
  target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC MUDLET_SHADER_HOT_RELOAD=1)
endif()

# Define memory tracking if enabled
if(USE_MEMORY_TRACKING)
  target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC MUDLET_MEMORY_TRACKING)
endif()

# Used to force an include of winsock2.h BEFORE Qt tries to include winsock.h
# from windows.h - only needed on Windows builds but we cannot use Q_OS_WINDOWS
# for an #ifdef because we need a symbol that is defined BEFORE we include
# any Qt header file!
if(WIN32)
    target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC INCLUDE_WINSOCK2)
endif()

# One of IRC_STATIC or IRC_SHARED must be defined for the communi IRC library
target_compile_definitions(communi PUBLIC IRC_STATIC)

target_link_libraries(
        ${LIB_MUDLET_TARGET}
        communi
        Boost::boost
        HUNSPELL::HUNSPELL
        LUA51::LUA51
        PCRE2::PCRE2
        PUGIXML::PUGIXML
        ZIP::ZIP
        ZLIB::ZLIB
# PLACEMARKER: sample benchmarking code
#       nanobench
        ${QT_LIBS})

target_include_directories(${LIB_MUDLET_TARGET} INTERFACE
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${CMAKE_CURRENT_BINARY_DIR}/${LIB_MUDLET_TARGET}_autogen/include
)

if (SYSINFO_LIBRARY)
  target_link_libraries(${LIB_MUDLET_TARGET} ${SYSINFO_LIBRARY} ${KVM_LIBRARY})
endif (SYSINFO_LIBRARY)

if (SYSINFO_INCLUDE_DIR)
  target_include_directories(${LIB_MUDLET_TARGET} INTERFACE ${SYSINFO_INCLUDE_DIR})
endif (SYSINFO_INCLUDE_DIR)

if(USE_UPDATER)
  target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC INCLUDE_UPDATER)
endif(USE_UPDATER)

# Embed the LeakSanitizer suppression list into the binary so that
# sanitizer-enabled builds (PTBs, testing AppImages) filter third-party noise
# out of leak reports without needing LSAN_OPTIONS set at runtime; picked up
# by __lsan_default_suppressions() in LsanHooks.cpp
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${mudlet_SOURCE_DIR}/asan-suppressions.txt")
file(READ "${mudlet_SOURCE_DIR}/asan-suppressions.txt" MUDLET_LSAN_SUPPRESSIONS_CONTENT)
string(REPLACE "\\" "\\\\" MUDLET_LSAN_SUPPRESSIONS_CONTENT "${MUDLET_LSAN_SUPPRESSIONS_CONTENT}")
string(REPLACE "\"" "\\\"" MUDLET_LSAN_SUPPRESSIONS_CONTENT "${MUDLET_LSAN_SUPPRESSIONS_CONTENT}")
string(REPLACE "\n" "\\n" MUDLET_LSAN_SUPPRESSIONS_CONTENT "${MUDLET_LSAN_SUPPRESSIONS_CONTENT}")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/LsanSuppressions.h.in" "${CMAKE_CURRENT_BINARY_DIR}/LsanSuppressions.h" @ONLY)
target_include_directories(${LIB_MUDLET_TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})

# LeakSanitizer resolves its hooks as weak symbols and a weak reference never
# pulls a member out of a static archive, so they cannot live in
# ${LIB_MUDLET_TARGET}: binaries that bring their own main() (the Qt Test ones,
# via QTEST_MAIN) would link no suppressions at all. An OBJECT library puts the
# definitions on the link line of every target that links it.
add_library(mudlet_lsan_hooks OBJECT LsanHooks.cpp)
target_include_directories(mudlet_lsan_hooks PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
set_target_properties(mudlet_lsan_hooks PROPERTIES AUTOMOC OFF POSITION_INDEPENDENT_CODE ON)

if(USE_3DMAPPER)
  target_link_libraries(${LIB_MUDLET_TARGET} OpenGL::GLU)
  target_link_libraries(${LIB_MUDLET_TARGET} assimp::assimp)
  target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC INCLUDE_3DMAPPER)
endif()

if(USE_MCPSERVER)
  target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC INCLUDE_MCPSERVER)
endif()

if(USE_VARIABLE_SPLASH_SCREEN)
  target_compile_definitions(${LIB_MUDLET_TARGET} PUBLIC INCLUDE_VARIABLE_SPLASH_SCREEN)
endif()

target_compile_options(${LIB_MUDLET_TARGET} PUBLIC -Wno-deprecated)

# The widgets: main window, dialogs, consoles, map views and editors.
# ${LIB_MUDLET_TARGET} must not depend on anything here, nor link Qt Widgets.
add_library(${LIB_MUDLET_APP_TARGET} STATIC
    ${mudlet_app_SRCS}
    ${mudlet_app_HDRS}
    ${mudlet_UIS}
)
mudlet_precompile_headers(${LIB_MUDLET_APP_TARGET} ${MUDLET_PRECOMPILED_WIDGET_HEADERS})
set_target_properties(${LIB_MUDLET_APP_TARGET} PROPERTIES
  AUTOUIC ON AUTOUIC_SEARCH_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/ui"
  AUTOMOC ON AUTORCC ON POSITION_INDEPENDENT_CODE ON
  AUTOGEN_ORIGIN_DEPENDS OFF)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
  set_property(SOURCE "${CMAKE_CURRENT_BINARY_DIR}/${LIB_MUDLET_APP_TARGET}_autogen/mocs_compilation.cpp"
               APPEND PROPERTY COMPILE_OPTIONS -Wno-sfinae-incomplete)
endif()
# The ui_*.h headers, for the tests that reach into a dialog's widgets
target_include_directories(${LIB_MUDLET_APP_TARGET} INTERFACE
    ${CMAKE_CURRENT_BINARY_DIR}/${LIB_MUDLET_APP_TARGET}_autogen/include
)
# Qt Widgets ahead of ${LIB_MUDLET_TARGET}, so the linker records Qt's own
# libraries before a distribution's qt6keychain: that one needs Qt6DBus, which
# would otherwise be found in the system Qt rather than beside Qt6Gui.
target_link_libraries(${LIB_MUDLET_APP_TARGET} PUBLIC
    edbee-lib
    ${QT_APP_LIBS}
    ${LIB_MUDLET_TARGET})

# qt-tags-widget is listed in mudlet_app_SRCS/HDRS, not add_subdirectory()'d, as its own
# project has install() rules; a file upstream adds or renames has to be listed there too.
set_property(SOURCE ../3rdparty/qt-tags-widget/src/qtagedit.cpp APPEND PROPERTY
  INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/qt-tags-widget/include/QTagEdit)

# main() stays out of the libraries: a test that links them brings its own, and
# the linker can pull main.cpp's object in for any inline function it defines.
add_executable(${EXE_MUDLET_TARGET} main.cpp)
target_compile_definitions(${EXE_MUDLET_TARGET} PRIVATE APP_TARGET="${APP_TARGET}")
set_target_properties(${EXE_MUDLET_TARGET} PROPERTIES OUTPUT_NAME ${EXE_MUDLET_NAME})
target_link_libraries(${EXE_MUDLET_TARGET} ${LIB_MUDLET_APP_TARGET} mudlet_lsan_hooks)

# Enable symbol exports from the main executable so that dynamically loaded Lua modules
# can find and use Lua API symbols (like lua_gettop) at runtime. Without this, modules
# will fail to load with "undefined symbol" errors.
set_target_properties(${EXE_MUDLET_TARGET} PROPERTIES ENABLE_EXPORTS ON)

if (WITH_SENTRY)
  include(cmake/WithSentry.cmake)
  add_subdirectory(crash_reporter)
endif()

if(WIN32)
  target_link_libraries(${EXE_MUDLET_TARGET} ws2_32)
  set_target_properties(${EXE_MUDLET_TARGET} PROPERTIES WIN32_EXECUTABLE ON)
  if(MINGW)
    # Qt's entry point renames main() to qMain(), but its WinMain library drops off the link line
    # because Qt6::Core sits in Sentry's LINK_GROUP; MinGW's own startup code calls main() fine
    set_target_properties(${EXE_MUDLET_TARGET} PROPERTIES qt_no_entrypoint ON)
  endif()
  # Windows reserves only 1 MiB of stack by default, and Qt recurses once per overlapping sibling widget
  # when repainting, so a few thousand labels overflowed it; 8 MiB matches the Linux and macOS main thread
  if(MSVC)
    target_link_options(${EXE_MUDLET_TARGET} PRIVATE "/STACK:8388608")
  else()
    target_link_options(${EXE_MUDLET_TARGET} PRIVATE "LINKER:--stack,8388608")
  endif()
  # The UNICODE and _UNICODE definitions are needed to make
  # (QString) mudlet::getShortPathName(const QString& name) work in Windows:
  target_compile_definitions(${EXE_MUDLET_TARGET} PRIVATE UNICODE _UNICODE)

  # Select Windows executable icon based on build type
  if(APP_BUILD MATCHES "-ptb.+")
    set(MUDLET_ICON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/icons/mudlet_ptb.ico")
  elseif((APP_BUILD MATCHES "-dev.+") OR (APP_BUILD MATCHES "-test.+"))
    set(MUDLET_ICON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/icons/mudlet_dev.ico")
  else()
    set(MUDLET_ICON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/icons/mudlet.ico")
  endif()

  # Configure and add the Windows resource file for the executable icon
  configure_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/mudlet.rc.in"
    "${CMAKE_CURRENT_BINARY_DIR}/mudlet.rc"
    @ONLY
  )
  target_sources(${EXE_MUDLET_TARGET} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/mudlet.rc")
endif()

if(APPLE)
  target_link_libraries(${EXE_MUDLET_TARGET} "-framework AppKit")
  target_link_libraries(${EXE_MUDLET_TARGET} "-framework AVFoundation")
  # A normal link, not a weak one: Speech.framework has shipped with every
  # macOS Mudlet supports
  target_link_libraries(${EXE_MUDLET_TARGET} "-framework Speech")

  # Microphone permission and speech recognition sources are already added via
  # mudlet_SRCS/mudlet_HDRS
  target_link_libraries(${LIB_MUDLET_TARGET} "-framework AVFoundation")
  target_link_libraries(${LIB_MUDLET_TARGET} "-framework Speech")

  if(USE_UPDATER)
    # need to specifically call out Objective-C sources so that automoc builds them.
    target_sources(${LIB_MUDLET_APP_TARGET} PRIVATE
    "${CMAKE_CURRENT_SOURCE_DIR}/sparkleupdater.h"
    "${CMAKE_CURRENT_SOURCE_DIR}/sparkleupdater.mm")
    find_package(Sparkle)
    target_link_libraries(${LIB_MUDLET_APP_TARGET} PUBLIC "-framework AppKit" Sparkle::Sparkle)
  endif(USE_UPDATER)

  set_target_properties(${EXE_MUDLET_TARGET} PROPERTIES
    MACOSX_BUNDLE ON
    MACOSX_BUNDLE_BUNDLE_NAME Mudlet
    MACOSX_BUNDLE_BUNDLE_VERSION ${APP_VERSION}${APP_BUILD}
    MACOSX_BUNDLE_GUI_IDENTIFIER org.mudlet.mudlet
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${APP_VERSION}
    MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/MacOSXBundleInfo.plist.in"
  )

  if(${APP_BUILD} MATCHES "-ptb.+")
    set_target_properties(${EXE_MUDLET_TARGET} PROPERTIES
      MACOSX_BUNDLE_ICON_FILE mudlet_ptb.icns
      MACOSX_BUNDLE_LONG_VERSION_STRING ${APP_VERSION}${APP_BUILD}
    )
    file(GLOB ICON_FILE "icons/mudlet_ptb.icns")
  elseif((${APP_BUILD} MATCHES "-dev.+") OR (${APP_BUILD} MATCHES "-test.+"))
    set_target_properties(${EXE_MUDLET_TARGET} PROPERTIES
      MACOSX_BUNDLE_ICON_FILE mudlet_dev.icns
      MACOSX_BUNDLE_LONG_VERSION_STRING ${APP_VERSION}${APP_BUILD}
    )
    file(GLOB ICON_FILE "icons/mudlet_dev.icns")
  else()
    set_target_properties(${EXE_MUDLET_TARGET} PROPERTIES
      MACOSX_BUNDLE_ICON_FILE mudlet.icns
      MACOSX_BUNDLE_LONG_VERSION_STRING ${APP_VERSION}
    )
    file(GLOB ICON_FILE "icons/mudlet.icns")
  endif()

  # Set Sparkle feed URL in Info.plist for PTB and release builds
  if(USE_UPDATER)
    if(APP_BUILD MATCHES "-ptb.+")
      set(SPARKLE_CHANNEL "ptb")
    elseif(NOT (APP_BUILD MATCHES "-dev.+" OR APP_BUILD MATCHES "-test.+"))
      set(SPARKLE_CHANNEL "release")
    endif()

    if(SPARKLE_CHANNEL)
      set(SPARKLE_FEED_URL "https://www.mudlet.org/wp-content/files/appcast/${SPARKLE_CHANNEL}-${CMAKE_SYSTEM_PROCESSOR}.xml")
      add_custom_command(TARGET ${EXE_MUDLET_TARGET} POST_BUILD
        COMMAND bash -c
          "/usr/libexec/PlistBuddy -c 'Delete :SUFeedURL' '$<TARGET_BUNDLE_DIR:${EXE_MUDLET_TARGET}>/Contents/Info.plist' 2>/dev/null || true && /usr/libexec/PlistBuddy -c 'Add :SUFeedURL string ${SPARKLE_FEED_URL}' '$<TARGET_BUNDLE_DIR:${EXE_MUDLET_TARGET}>/Contents/Info.plist'"
        VERBATIM
        COMMENT "Setting Sparkle feed URL for ${SPARKLE_CHANNEL}-${CMAKE_SYSTEM_PROCESSOR}"
      )
    endif()
  endif()

  # LIST_DIRECTORIES must stay false: globbing directories too makes CMake emit a
  # copy_directory rule that races the per-file bundling rules under Ninja,
  # failing with "Error copying ...: No such file or directory"
  file(GLOB_RECURSE MUDLET_LUA_FILES LIST_DIRECTORIES false "mudlet-lua/*")
  file(GLOB_RECURSE LUA_TRANSLATIONS LIST_DIRECTORIES false "../translations/lua/*")
  file(GLOB DIC_FILES "*.dic")
  file(GLOB AFF_FILES "*.aff")
  target_sources(${EXE_MUDLET_TARGET} PUBLIC ${MUDLET_LUA_FILES} ${LUA_TRANSLATIONS} ${DIC_FILES} ${AFF_FILES} ${ICON_FILE})

  set_source_files_properties(${DIC_FILES} ${AFF_FILES} ${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
  foreach(FILE ${MUDLET_LUA_FILES})
    string(REPLACE ${CMAKE_CURRENT_SOURCE_DIR} "" PATH_IN_BUNDLE ${FILE} )
    get_filename_component(PATH_IN_BUNDLE "${PATH_IN_BUNDLE}" DIRECTORY)
    set_source_files_properties(${FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources/${PATH_IN_BUNDLE})
  endforeach()
  foreach(FILE ${LUA_TRANSLATIONS})
    # convert relative paths to absolute ones, necessary for the ../translations
    file(REAL_PATH ${FILE} FILE)
    string(REPLACE ${CMAKE_SOURCE_DIR}/translations "" PATH_IN_BUNDLE ${FILE} )
    get_filename_component(PATH_IN_BUNDLE "${PATH_IN_BUNDLE}" DIRECTORY)
    set_source_files_properties(${FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources/translations/${PATH_IN_BUNDLE})
  endforeach()
endif()

# *nix "make install" provision:
if(UNIX AND NOT APPLE)
  # CMAKE_INSTALL_PREFIX is automagically set on Unix to DESTDIR environment
  # variable and is prefixed onto relative DESTINATION values
  install(
	DIRECTORY "../translations/lua/"
	DESTINATION "share/mudlet/lua/translations"
	FILES_MATCHING PATTERN "*.json"
	PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
  install(
    DIRECTORY "mudlet-lua/lua"
    DESTINATION "share/mudlet"
    FILES_MATCHING
    PATTERN "*.lua" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
  install(
    DIRECTORY "mudlet-lua/tests"
    DESTINATION "share/mudlet"
    FILES_MATCHING
    PATTERN "*.lua" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
    # the package specs install these fixtures, so they have to travel with them
    PATTERN "*.mpackage" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ
                                     WORLD_READ
    PATTERN "*.xml" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
    PATTERN "*.txt" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
    # the pathfinding specs' map fixture, zipped because unpacked it adds ~5MiB to every package
    PATTERN "*.zip" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
    # binary map fixtures
    PATTERN "*.dat" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
  install(
    DIRECTORY "../3rdparty/lcf"
    DESTINATION "share/mudlet/lua"
    FILES_MATCHING
    PATTERN "*.lua" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
    PATTERN "LICENSE*" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
    PATTERN "COPYING*" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
    PATTERN ".*" EXCLUDE)
  # FILES_MATCHING still creates every directory it walks, so a directory holding no Lua would ship
  # empty; EXCLUDE_EMPTY_DIRECTORIES would do this but needs CMake 3.31
  install(
    CODE [[
    file(GLOB_RECURSE lcfEntries LIST_DIRECTORIES true
         "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/share/mudlet/lua/lcf/*")
    # deepest first, so a parent left empty by removing its children goes too
    list(SORT lcfEntries ORDER DESCENDING)
    foreach(lcfEntry IN LISTS lcfEntries)
      if(IS_DIRECTORY "${lcfEntry}")
        file(GLOB lcfChildren "${lcfEntry}/*")
        if(NOT lcfChildren)
          file(REMOVE_RECURSE "${lcfEntry}")
        endif()
      endif()
    endforeach()
  ]])

  install(
    FILES "../mudlet.desktop"
    DESTINATION "share/applications" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ
      WORLD_READ)
  install(
    FILES "../mudlet.svg"
    DESTINATION "share/icons/hicolor/scalable/apps" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ
    WORLD_READ)
  install(
    FILES "../mudlet.png"
    DESTINATION "share/icons/hicolor/512x512/apps" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ
    WORLD_READ)

  install(
    TARGETS ${EXE_MUDLET_TARGET}
    RUNTIME
      DESTINATION "bin"
      PERMISSIONS
        OWNER_READ
        OWNER_WRITE
        OWNER_EXECUTE
        GROUP_READ
        GROUP_EXECUTE
        WORLD_READ
        WORLD_EXECUTE)
endif()

# `cmake --build <dir> --target run-mudlet` - build if needed, then launch.
#
# On macOS this goes through the bundle rather than the binary inside it. macOS
# attributes a permission request to the responsible process, which for a binary
# started from a shell is the application owning the terminal, not Mudlet: the
# built-in speech backend then asks for speech recognition, the terminal's
# application is required to carry the usage description, and TCC kills Mudlet
# when it does not. See AppleSpeechRecognizer::doStartListening().
#
# `open` detaches, so qDebug() and qWarning() would go nowhere - hence the two
# flags, which put both streams in a file beside the build.
#
# A sanitizer build is the exception, and runs the binary directly: ASAN_OPTIONS
# and its siblings are read from the environment of the process, and `open`
# hands the request to launchd rather than passing this shell's environment on,
# so a sanitized run launched that way would silently ignore every option set
# for it - and report to a terminal nothing is reading. That backend declines to
# ask for permission rather than dying when it finds itself started this way, so
# the trade is a message instead of a crash.
if(APPLE AND NOT USE_SANITIZER)
  add_custom_target(run-mudlet
    # -n so a rebuild is actually what starts, rather than the running copy
    # being brought to the front
    COMMAND open -n "$<TARGET_BUNDLE_DIR:${EXE_MUDLET_TARGET}>"
            --stdout "${CMAKE_BINARY_DIR}/mudlet-run.log"
            --stderr "${CMAKE_BINARY_DIR}/mudlet-run.log"
    COMMAND ${CMAKE_COMMAND} -E echo "Mudlet started; its output is in ${CMAKE_BINARY_DIR}/mudlet-run.log"
    DEPENDS ${EXE_MUDLET_TARGET}
    USES_TERMINAL
    COMMENT "Launching Mudlet as an application")
else()
  add_custom_target(run-mudlet
    COMMAND "$<TARGET_FILE:${EXE_MUDLET_TARGET}>"
    DEPENDS ${EXE_MUDLET_TARGET}
    USES_TERMINAL
    COMMENT "Launching Mudlet")
endif()
