# Copyright (C) 2004-2026 Robert Griebl
# SPDX-License-Identifier: GPL-3.0-only

cmake_minimum_required(VERSION 3.19.0)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_INCLUDE_CURRENT_DIR OFF)
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")

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

set(SENTRY_VERSION 0.12.2)
set(QCORO_VERSION  0.12.0)

set(QT_NO_PRIVATE_MODULE_WARNING ON)

# for QtCreator
set(QML_IMPORT_PATH ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/imports CACHE STRING "" FORCE)

# Supported languages
set(LANGUAGES en de fr es sv)

option(BACKEND_ONLY "Backend only" OFF)
option(FORCE_MOBILE "Force a mobile build on desktop" OFF)
option(SANITIZE     "Build with ASAN" OFF)
option(MODELTEST    "Build with modeltest" OFF)
option(SENTRY       "Build with sentry.io support" OFF)
option(VERBOSE_FETCH "Verbose output for 3rd party FetchContent" OFF)

set(NAME           "BrickStore")
set(DESCRIPTION    "${NAME} - an offline BrickLink inventory management tool")
set(COPYRIGHT      "2004-2026 Robert Griebl")
set(BRICKSTORE_URL "www.brickstore.dev")
set(GITHUB_URL     "github.com/rgriebl/brickstore")
set(BUILD_NUMBER   "$ENV{BUILD_NUMBER}")
if (NOT BUILD_NUMBER)
    set(BUILD_NUMBER   "custom")
endif()
if (WIN32)
    set(BUILD_USER     "$ENV{USERNAME}")
else()
    set(BUILD_USER     "$ENV{USER}")
endif()
cmake_host_system_information(RESULT BUILD_HOST QUERY HOSTNAME)

file(STRINGS "VERSION_NUMBER" VERSION)
string(REPLACE "." ";" VERSION_LIST ${VERSION})
list(GET VERSION_LIST 0 VERSION_MAJOR)
list(GET VERSION_LIST 1 VERSION_MINOR)
list(GET VERSION_LIST 2 VERSION_PATCH)

project(BrickStore
    VERSION ${VERSION}
    DESCRIPTION ${DESCRIPTION}
    HOMEPAGE_URL "https://${BRICKSTORE_URL}"
    LANGUAGES CXX C
)
find_package(Qt6 CONFIG QUIET COMPONENTS Core REQUIRED)
include(QtPlatformSupport) # load all the platform names

list(PREPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")

if (NOT (ANDROID OR APPLE))
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY bin)
endif()

# Android is the last supported 32bit platform
if (NOT ANDROID AND NOT WASM AND QT_32BIT)
    message(FATAL_ERROR "32bit builds for this platform are not supported.")
endif()

# important: set up sentry BEFORE enabling strict error reporting
set(BS_SENTRY OFF)
if (SENTRY)
    if (ANDROID)
        set(BS_SENTRY ON)
    else()
        set(BS_SENTRY ${SENTRY_VERSION})
        include(BuildSentry)
    endif()
endif()

include(BuildQCoro)

set(BS_WAYLAND_DECORATIONS OFF)
if (LINUX AND (NOT BACKEND_ONLY) AND (${Qt6_VERSION} VERSION_LESS "6.8.0") AND (${Qt6_VERSION} VERSION_GREATER_EQUAL "6.5.0"))
    # Qt < 6.8.0 draws very ugly window decorations on Gnome
    find_package(Wayland QUIET)
    if (Wayland_FOUND)
        if (${Wayland_VERSION} VERSION_GREATER_EQUAL "1.20.0")
            include(BuildQAdwaitaDecorations)
            set(BS_WAYLAND_DECORATIONS ON)
        endif()
    endif()
endif()

set(copts)
set(cxxopts)
if (MSVC)
    # need to disable C4127 due to 6.4.0 QStringBuilder
    # need to disable C4702 due to 6.4.0 QAnyStringView
    list(APPEND copts /W4 /WX /wd4127 /wd4702)
elseif (CLANG)
    if (NOT APPLE AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 17.0)
        list(APPEND cxxopts -fcoroutines-ts)
    endif()
    list(APPEND cxxopts -stdlib=libc++)
    list(APPEND copts -Wall -Wextra -Wpedantic -Werror)
    # clang bug, legal in C++20
    list(APPEND cxxopts -Wno-gnu-zero-variadic-macro-arguments -Wno-nested-anon-types)
elseif (GCC)
    list(APPEND cxxopts -fcoroutines)
    list(APPEND copts -Wall -Wextra -Wpedantic -Werror -Wno-expansion-to-defined)
endif()
add_compile_options("$<$<COMPILE_LANGUAGE:C>:${copts}>")
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:${copts}$<SEMICOLON>${cxxopts}>")

if (BACKEND_ONLY)
    qt_add_executable(${PROJECT_NAME})
else()
    qt_add_executable(${PROJECT_NAME} WIN32 MACOSX_BUNDLE)
endif()

if (NOT IOS) # somehow this tries to access the wrong build dir on iOS
    include(SeparateDebugInfo)
    enable_separate_debug_info(${PROJECT_NAME})
endif()

# Use LTO, if available
include(CheckIPOSupported)
check_ipo_supported(RESULT HAS_LTO OUTPUT LTO_STATUS)
if (HAS_LTO)
    if (CMAKE_BUILD_TYPE STREQUAL "Debug")
        set(LTO_STATUS OFF)
    else()
        set_property(TARGET ${PROJECT_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
        set(LTO_STATUS ON)
    endif()
endif()

add_compile_definitions(
    QT_STRICT_ITERATORS
    QT_STRICT_QLIST_ITERATORS
    QT_NO_CAST_TO_ASCII
    QT_NO_CAST_FROM_ASCII
    QT_MESSAGELOGCONTEXT
    QT_USE_QSTRINGBUILDER
)

find_package(Qt6 CONFIG QUIET
    REQUIRED Core Gui Xml Network Concurrent Sql LinguistTools
    OPTIONAL_COMPONENTS ToolsTools
)

if (${Qt6_VERSION} VERSION_GREATER_EQUAL "6.8.0")
    add_compile_definitions(
        "QT_WARN_DEPRECATED_UP_TO=QT_VERSION_CHECK(6,7,0)"
    )
endif()

set(MIN_QT_VERSION 6.4.2)
set(BS_MCP_SERVER OFF)

if (ANDROID OR IOS OR FORCE_MOBILE)
    set(BS_MOBILE ON)
    set(BS_TYPE "Mobile")
    set(MIN_QT_VERSION 6.11.1)
    add_compile_definitions(BS_MOBILE)
    set(QUICK_CONTROLS "QtQuick.Controls.Material")
    find_package(Qt6 CONFIG QUIET REQUIRED Qml Quick Quick3D QuickControls2Impl QuickDialogs2 QuickTemplates2 QuickDialogs2Utils Svg Multimedia MultimediaQuickPrivate)
    find_package(Qt6 CONFIG QUIET REQUIRED QmlPrivate QuickPrivate QuickTemplates2Private QuickDialogs2Private QuickControls2ImplPrivate QuickDialogs2UtilsPrivate)

elseif (BACKEND_ONLY)
    set(BS_BACKEND ON)
    set(BS_TYPE "Backend")
    add_compile_definitions(BS_BACKEND)

    if (${Qt6_VERSION} VERSION_GREATER_EQUAL "6.10.0")
        find_package(Qt6 CONFIG QUIET REQUIRED GuiPrivate)
    endif()

else()
    set(BS_DESKTOP ON)
    set(BS_TYPE "Desktop")
    add_compile_definitions(BS_DESKTOP)
    find_package(Qt6 CONFIG QUIET REQUIRED Widgets PrintSupport Qml Quick Quick3D Multimedia QuickWidgets Svg)

    if (${Qt6_VERSION} VERSION_GREATER_EQUAL "6.10.0")
        find_package(Qt6 CONFIG QUIET REQUIRED GuiPrivate WidgetsPrivate QmlPrivate QuickPrivate)
        find_package(Qt6 CONFIG QUIET OPTIONAL_COMPONENTS HttpServer)
        if (TARGET Qt6::HttpServer)
             set(BS_MCP_SERVER ON)
             add_compile_definitions(BS_MCP_SERVER)
        endif()
    endif()
endif()

if (${Qt6_VERSION} VERSION_LESS "${MIN_QT_VERSION}")
    message(FATAL_ERROR "Qt ${MIN_QT_VERSION} or newer is required for building this variant")
endif()

set(PARALLEL_STL OFF)
if (WIN32)
    set(PARALLEL_STL ON)
    add_compile_definitions(
        BS_HAS_PARALLEL_STL
        NOMINMAX
    )
elseif (LINUX)
    # we need at least 2021.6 for the task_scheduler_handle shutdown mechanism
    find_package(TBB 2021.6 QUIET)
    if (TBB_FOUND)
        set(PARALLEL_STL ON)
        add_compile_definitions(
            BS_HAS_PARALLEL_STL
            BS_LIBSTDCPP_USES_TBB
            TBB_SUPPRESS_DEPRECATED_MESSAGES=1
        )
    endif()
endif()

if (WIN32)
    # MS broke backwards compatibility with the 17.10 MSVC2022 release:
    # without this workaround, every thread started via QThread::create crashes
    # in std::mutex.lock inside MSVCP140.dll
    # We DO deploy the correct MSVC runtime, but there are sentry reports where
    # Windows is loading the wrong (old) runtime from a OneDrive installation,
    # but there's no indication why that is happening.
    add_compile_definitions(_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
endif()

set(BS_LIBSECRET OFF)
if (LINUX AND (BS_DESKTOP OR BS_MOBILE))
    find_package(PkgConfig REQUIRED)
    pkg_check_modules(LIBSECRET QUIET IMPORTED_TARGET GLOBAL libsecret-1)
    if (LIBSECRET_FOUND)
        set(BS_LIBSECRET ON)
        add_compile_definitions(BS_HAS_LIBSECRET)
        target_link_libraries(${PROJECT_NAME} PRIVATE PkgConfig::LIBSECRET)
    endif()
endif()

if (SENTRY)
    add_compile_definitions(SENTRY_ENABLED)
endif()

if (MODELTEST)
    find_package(Qt6 REQUIRED Test)
    add_compile_definitions(MODELTEST)
endif()

if (NOT APPLE AND NOT WIN32 AND NOT ANDROID)
    set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "brickstore")
endif()

if (CLANG OR GCC)
    # cmake's RelWithDebInfo is hardcoded to O2, while Release has O3
    string(REGEX REPLACE "([\\/\\-]O)2" "\\13" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
endif()

if (SANITIZE)
    add_compile_definitions(SANITIZER_ENABLED)
    if (MSVC)
        set(ECM_ENABLE_SANITIZERS address)
    else()
        set(ECM_ENABLE_SANITIZERS address undefined)
    endif()
    include(${Qt6_DIR}/3rdparty/extra-cmake-modules/modules/ECMEnableSanitizers.cmake)
endif()

file(GLOB_RECURSE ICONS_QRC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} assets/icons/*)
file(GLOB_RECURSE FLAGS_QRC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} assets/flags/*)

set(qt_qm_base "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_TRANSLATIONS}")
foreach (LANG ${LANGUAGES})
    set(lts "translations/brickstore_${LANG}.ts")
    list(APPEND TS_FILES ${lts})
    set(qqm "${qt_qm_base}/qtbase_${LANG}.qm")
    if (EXISTS ${qqm})
        list(APPEND QT_QM_FILES ${qqm})
    else()
        set(qts "translations/qtbase_${LANG}.ts")
        if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${qts}")
            list(APPEND QT_TS_FILES ${qts})
        endif()
    endif()
endforeach()
target_sources(${PROJECT_NAME} PUBLIC ${TS_FILES} ${QT_TS_FILES})
set_source_files_properties(${TS_FILES} ${QT_TS_FILES} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/qm")

qt_add_lrelease(${PROJECT_NAME}
    TS_FILES ${TS_FILES} ${QT_TS_FILES}
    QM_FILES_OUTPUT_VARIABLE QM_FILES
)
# qt_add_lupdate is not flexible enough to scan all the required sources, so we better
# let lupdate itself scan recursively
add_custom_target(${PROJECT_NAME}_lupdate
    COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::lupdate "${CMAKE_SOURCE_DIR}/src"
            -locations relative -ts ${TS_FILES}
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    COMMAND_EXPAND_LISTS
    VERBATIM
)
if(NOT TARGET update_translations)
    add_custom_target(update_translations)
endif()
add_dependencies(update_translations ${PROJECT_NAME}_lupdate)
# end of custom qt_add_lupdate replacement

qt_add_resources(${PROJECT_NAME} qt_translations_qrc
    PREFIX "/translations"
    BASE "${qt_qm_base}"
    FILES ${QT_QM_FILES}
)
qt_add_resources(${PROJECT_NAME} bs_translations_qrc
    PREFIX "/translations"
    BASE "${CMAKE_CURRENT_BINARY_DIR}/qm"
    FILES ${QM_FILES}
)
qt_add_resources(${PROJECT_NAME} brickstore_qrc PREFIX / FILES
    translations/translations.json
    assets/generated-app-icons/brickstore.png
    assets/generated-app-icons/brickstore_doc.png
    ${ICONS_QRC}
    ${FLAGS_QRC}
    extensions/classic-print-script.bs.qml
    extensions/lego-pickabrick-export.bs.qml
)

#get_cmake_property(_variableNames VARIABLES)
#list (SORT _variableNames)
#foreach (_variableName ${_variableNames})
#    message(STATUS "${_variableName}=${${_variableName}}")
#endforeach()

include_directories(3rdparty)
add_subdirectory(3rdparty)

include_directories(src)
add_subdirectory(src)

# the generated app icons are not committed, so every platform that needs some
# needs the tool that scales them
include(BuildImageScale)

if (WIN32)
    # Windows resources: icons and file-version record
    set(GENERATED_ICONS_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/icons")
    set(GENERATED_ICONS
        "${GENERATED_ICONS_DIR}/brickstore.ico"
        "${GENERATED_ICONS_DIR}/brickstore_doc.ico"
    )
    # not an input to the resource compiler, so it is kept out of GENERATED_ICONS -
    # only the same script happens to produce it
    set(WIZARD_IMAGE "${GENERATED_ICONS_DIR}/windows-installer.png")
    add_custom_command(
        OUTPUT ${GENERATED_ICONS} "${WIZARD_IMAGE}"
        COMMAND "${CMAKE_COMMAND}" "-DIMAGESCALE=${IMAGESCALE_EXECUTABLE}"
                "-DASSET_DIR=${CMAKE_SOURCE_DIR}/assets"
                "-DOUT_DIR=${GENERATED_ICONS_DIR}"
                -P "${CMAKE_SOURCE_DIR}/windows/generate-icons.cmake"
        DEPENDS imagescale
                "${CMAKE_SOURCE_DIR}/assets/brickstore.png"
                "${CMAKE_SOURCE_DIR}/assets/generated-app-icons/brickstore_doc.png"
                "${CMAKE_SOURCE_DIR}/windows/generate-icons.cmake"
                "${CMAKE_SOURCE_DIR}/cmake/RunImageScale.cmake"
        COMMENT "Generating the Windows icons..."
        VERBATIM
    )

    # The icons are an input to the resource compiler, so rc.exe has to wait for
    # them. OBJECT_DEPENDS below says exactly that, but not every generator honors
    # it, hence the belt-and-braces target: a target-level dependency is respected
    # everywhere, and it completes before any source of BrickStore starts building.
    add_custom_target(windows-icons DEPENDS ${GENERATED_ICONS} "${WIZARD_IMAGE}")
    add_dependencies(${PROJECT_NAME} windows-icons)

    set(GENERATED_RC "${CMAKE_CURRENT_BINARY_DIR}/generated/brickstore.rc")
    configure_file(windows/brickstore.rc.in "${GENERATED_RC}" @ONLY)
    target_sources(${PROJECT_NAME} PRIVATE "${GENERATED_RC}")
    set_source_files_properties("${GENERATED_RC}" PROPERTIES
        GENERATED TRUE
        OBJECT_DEPENDS "${GENERATED_ICONS}"
    )

    # always link against widgets
    find_package(Qt6 REQUIRED Widgets)
    target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Widgets)

    target_link_libraries(${PROJECT_NAME} PRIVATE user32 advapi32 wininet)
endif()

if (LINUX AND TBB_FOUND)
    target_link_libraries(${PROJECT_NAME} PRIVATE TBB::tbb)
endif()

if (APPLE)
    set(EXECUTABLE ${PROJECT_NAME})
    if (CMAKE_OSX_DEPLOYMENT_TARGET)
        set(MACOSX_DEPLOYMENT_TARGET ${CMAKE_OSX_DEPLOYMENT_TARGET})
    endif()
    find_library(SECURITY_LIB Security)
    target_link_libraries(${PROJECT_NAME} PRIVATE ${SECURITY_LIB})
    if (LTO_STATUS)
        set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-Wl,-object_path_lto -Wl,lto.o")
    endif()
    set(QT_NO_SET_PLIST_LOCALIZATIONS, ON) # we handle this ourselves
    if (IOS)
        set_target_properties(${PROJECT_NAME} PROPERTIES
            MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/macos/Info.ios.plist"
            MACOSX_BUNDLE_GUI_IDENTIFIER "de.brickforge.brickstore"
            XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "de.brickforge.brickstore"
            XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
            XCODE_ATTRIBUTE_INSTALL_PATH "$(LOCAL_APPS_DIR)"
            XCODE_ATTRIBUTE_SKIP_INSTALL "NO"
            XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
            XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME AppIcon
            QT_IOS_LAUNCH_SCREEN "${CMAKE_SOURCE_DIR}/macos/LaunchScreen.storyboard"
        )
        set(APPICON_XCASSETS "${CMAKE_SOURCE_DIR}/assets/generated-app-icons/AppIcon.xcassets")
        target_sources(${PROJECT_NAME} PRIVATE ${APPICON_XCASSETS})
        set_source_files_properties(${APPICON_XCASSETS} PROPERTIES
            MACOSX_PACKAGE_LOCATION "Resources"
        )

        set(PRIVACY_INFO "${CMAKE_SOURCE_DIR}/macos/PrivacyInfo.xcprivacy")
        target_sources(${PROJECT_NAME} PRIVATE ${PRIVACY_INFO})
        set_source_files_properties(${PRIVACY_INFO} PROPERTIES
            MACOSX_PACKAGE_LOCATION "Resources"
        )

        # restrict the number of deployed plugins in static builds
        qt_import_plugins(${PROJECT_NAME}
            EXCLUDE_BY_TYPE qmltooling
            INCLUDE_BY_TYPE imageformats
                Qt::QGifPlugin
                Qt::QJpegPlugin
                Qt::QSvgPlugin
                Qt::QWebpPlugin
            INCLUDE_BY_TYPE sqldrivers
                Qt::QSQLiteDriverPlugin
        )
    else()
        set_target_properties(${PROJECT_NAME} PROPERTIES
            MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/macos/Info.macos.plist"
            MACOSX_BUNDLE_GUI_IDENTIFIER "de.brickforge.brickstore"
        )
        # CFBundleIconName needs a compiled asset catalog, and the Ninja generator
        # used here has no step for that - unlike the Xcode generator for iOS above.
        set(ASSETS_CAR "${CMAKE_CURRENT_BINARY_DIR}/macos/Assets.car")
        add_custom_command(
            OUTPUT "${ASSETS_CAR}"
            COMMAND "${CMAKE_COMMAND}" "-DIMAGESCALE=${IMAGESCALE_EXECUTABLE}"
                    "-DASSET_DIR=${CMAKE_SOURCE_DIR}/assets"
                    "-DOUT_DIR=${CMAKE_CURRENT_BINARY_DIR}/macos"
                    "-DDEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}"
                    -P "${CMAKE_SOURCE_DIR}/macos/generate-macos-icons.cmake"
            DEPENDS imagescale
                    "${CMAKE_SOURCE_DIR}/assets/brickstore.png"
                    "${CMAKE_SOURCE_DIR}/macos/generate-macos-icons.cmake"
                    "${CMAKE_SOURCE_DIR}/cmake/RunImageScale.cmake"
            COMMENT "Generating the macOS asset catalog..."
            VERBATIM
        )
        set_source_files_properties("${ASSETS_CAR}" PROPERTIES GENERATED TRUE)

        set(MACOS_RESOURCES
            assets/generated-app-icons/brickstore.icns
            assets/generated-app-icons/brickstore_doc.icns
            macos/macos.entitlements
            "${ASSETS_CAR}"
        )
        target_sources(${PROJECT_NAME} PUBLIC ${MACOS_RESOURCES})
        set_source_files_properties(${MACOS_RESOURCES} PROPERTIES
            MACOSX_PACKAGE_LOCATION "Resources"
        )
    endif()

    foreach (LANG ${LANGUAGES})
        set(locpath "locversions/${LANG}.lproj/locversion.plist")
        configure_file(macos/locversion.plist.in "${locpath}" @ONLY)
        target_sources(${PROJECT_NAME} PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/${locpath}")
        set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/${locpath} PROPERTIES
            MACOSX_PACKAGE_LOCATION "Resources/${LANG}.lproj"
        )
    endforeach()
endif()

if (ANDROID)
    if (BUILD_NUMBER STREQUAL "custom")
        set(VERSION_CODE "0")
    else()
        # the 1000000 offset is necessary because I screwed up the first PlayStore upload
        math(EXPR VERSION_CODE "1000000 + ${BUILD_NUMBER}")
    endif()
    # androiddeployqt copies one package source directory over its Gradle template
    # and offers no way to add generated files on the side, so the whole directory
    # is staged in the build tree and the launcher icons are generated into the
    # staged copy. The app target depends on it, and the apk target depends on the
    # app, so this is always in place before androiddeployqt runs.
    set(ANDROID_PACKAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/android-package")

    add_custom_target(android-package-source
        COMMAND "${CMAKE_COMMAND}" "-DIMAGESCALE=${IMAGESCALE_EXECUTABLE}"
                "-DASSET_DIR=${CMAKE_SOURCE_DIR}/assets"
                "-DSOURCE_DIR=${CMAKE_SOURCE_DIR}/android"
                "-DOUT_DIR=${ANDROID_PACKAGE_DIR}"
                -P "${CMAKE_SOURCE_DIR}/android/generate-package.cmake"
        COMMENT "Staging the Android package source..."
        DEPENDS imagescale
        VERBATIM
    )
    add_dependencies(${PROJECT_NAME} android-package-source)

    set_target_properties(${PROJECT_NAME} PROPERTIES
        QT_ANDROID_PACKAGE_SOURCE_DIR "${ANDROID_PACKAGE_DIR}"
        QT_ANDROID_PACKAGE_NAME "de.brickforge.brickstore"
        QT_ANDROID_VERSION_CODE ${VERSION_CODE}
        QT_ANDROID_VERSION_NAME ${VERSION}
        QT_ANDROID_MIN_SDK_VERSION 28 # starting with 6.8 this shouldn't be needed anymore
    )

    if (QT_ANDROID_SDK_BUILD_TOOLS_REVISION) # this has to be set as a target property to stick
        set_target_properties(${PROJECT_NAME} PROPERTIES
            QT_ANDROID_SDK_BUILD_TOOLS_REVISION "${QT_ANDROID_SDK_BUILD_TOOLS_REVISION}"
        )
    endif()

    if (QT_ANDROID_TARGET_SDK_VERSION) # this has to be set as a target property to stick
        set_target_properties(${PROJECT_NAME} PROPERTIES
            QT_ANDROID_TARGET_SDK_VERSION "${QT_ANDROID_TARGET_SDK_VERSION}"
        )
    endif()

    # androiddeployqt (Qt 6.11 / CMake >= 3.30) only deploys plugins listed in the
    # target's QT_PLUGIN_TARGETS. Modules linked PRIVATE deep inside our static
    # libraries never propagate their plugins here, and qt_import_plugins() can't
    # help (it only toggles plugins already in the list) — so add them directly:
    #   Qt6::Sql        -> QSQLiteDriverPlugin  (PRIVATE in bricklink_module)
    #   Qt6::Multimedia -> QtMultimedia backend (PRIVATE in scanner_module);
    #                      without a backend the camera scanner has no media I/O.
    set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY QT_PLUGIN_TARGETS
        Qt6::QSQLiteDriverPlugin
        Qt6::QFFmpegMediaPlugin
        Qt6::QAndroidMediaPlugin)

    include(FetchContent)

    FetchContent_Declare(
        android_openssl
        GIT_REPOSITORY https://github.com/KDAB/android_openssl.git
        GIT_TAG master
        GIT_SHALLOW YES
    )
    FetchContent_MakeAvailable(android_openssl)
    FetchContent_GetProperties(android_openssl)
    include(${android_openssl_SOURCE_DIR}/android_openssl.cmake)
    add_android_openssl_libraries(${PROJECT_NAME})

    # Mixing pre-NDK23 objects (e.g. Qt) and (post-)NDK23 objects will crash when unwinding:
    # https://android.googlesource.com/platform/ndk/+/master/docs/BuildSystemMaintainers.md#Unwinding
    if (ANDROID_NDK_MAJOR GREATER 22)
        target_link_libraries(${PROJECT_NAME} PRIVATE -lunwind)
    endif()
endif()

if (TARGET Qt6::qdoc)
    add_custom_target(extensions-doc
        WORKING_DIRECTORY
        COMMAND "${CMAKE_COMMAND}" -E rm -rf "${CMAKE_BINARY_DIR}/doc/html/extensions"
        COMMAND Qt6::qdoc "${CMAKE_SOURCE_DIR}/doc/extensions.qdocconf"
                --indexdir "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_DOCS}"
                --outputdir "${CMAKE_BINARY_DIR}/doc/html/extensions"
    )
endif()

add_custom_target("Other-Files" SOURCES
    .gitattributes
    .gitignore
    .github/workflows/build_cmake.yml
    .github/actions/install-qt/action.yml
    .github/actions/sentry-upload-dif/action.yml
    .github/actions/apple-signing/action.yml
    .github/FUNDING.yml
    .tag
    CHANGELOG.md
    LICENSE.GPL
    README.md
    VERSION_NUMBER
    BrickStoreXML.rnc
    configure
    android/AndroidManifest.xml
    android/build.gradle
    android/generate-package.cmake
    android/gradle.properties
    android/gradle/wrapper/gradle-wrapper.jar
    android/gradle/wrapper/gradle-wrapper.properties
    android/gradlew
    android/gradlew.bat
    android/res/values/colors.xml
    android/res/values/libs.xml
    android/res/values/splashscreentheme.xml
    android/res/xml/qtprovider_paths.xml
    android/src/de/brickforge/brickstore/ExtendedQtActivity.java
    assets/COPYING-ICONS
    assets/COPYING-FLAGS
    assets/generate-assets.sh
    cmake/BuildImageScale.cmake
    cmake/RunImageScale.cmake
    cmake/SeparateDebugInfo.cmake
    cmake/SeparateDebugInfo.Info.plist.in
    doc/extensions.css
    doc/extensions.qdoc
    doc/extensions.qdocconf
    doc/images/list_arrow.png
    doc/images/list_expand.png
    doc/images/favicon.ico
    docker/Dockerfile
    docker/entrypoint.sh
    docker/rebuild-db.sh
    extensions/README.md
    extensions/classic-print-script.bs.qml
    ldraw/create-bricklink-ldraw-id-mappings.py
    ldraw/mappings/wheels.yaml
    macos/dmg-background.png
    macos/dmg-ds_store
    macos/generate-ios-icons.cmake
    macos/generate-macos-icons.cmake
    macos/ExportOptions.ios.plist
    macos/Info.ios.plist
    macos/Info.macos.plist
    macos/LaunchScreen.storyboard
    macos/locversion.plist.in
    macos/PrivacyInfo.xcprivacy
    tools/git-release.sh
    translations/translations.json
    unix/dev.brickstore.BrickStore.desktop
    unix/dev.brickstore.BrickStore.mime.xml
    unix/dev.brickstore.BrickStore.metainfo.xml.in
    unix/flatpak.yaml.in
    unix/snapcraft.yaml.in
    windows/brickstore.iss
    windows/brickstore.rc.in
    windows/generate-icons.cmake
    windows/msix/AppxManifest.xml.in
    windows/msix/generate-assets.cmake
    windows/msix/generate-bundle.cmake
    windows/msix/generate-package.cmake
    windows/msix/priconfig.xml
)

## Installers, Bundles, etc.

if (MACOS)
    install(TARGETS ${PROJECT_NAME} BUNDLE DESTINATION .)

    set(DMG_DIR "${CMAKE_BINARY_DIR}/dmg")
    set(DMG_FILE "${CMAKE_BINARY_DIR}/${PROJECT_NAME}-${VERSION}.dmg")

    # deploy sentry dependencies
    set(COPY_SENTRY_COMMAND "")
    if (SENTRY)
        set(COPY_SENTRY_COMMAND COMMAND cp "$<TARGET_FILE:crashpad_handler>" "$<TARGET_FILE_DIR:${PROJECT_NAME}>")
    endif()
    set(MACOS_SIGN_ARGS "")
    if (MACOS_SIGNING_IDENTITY)
        set(MACOS_SIGN_ARGS "-sign-for-notarization=${MACOS_SIGNING_IDENTITY}")
    endif()

    # We cannot use CPack here: macdeployqt signs all binaries, but the installation step when
    # running CPack's DragNDrop generator would try to mess with the rpaths and in turn nullify
    # the code signatures.
    # Also: macdeployqt has to be run on the bundle in the build location. If we copy to the dmg
    # staging directory first, we get weird errors because the tool thinks that all Qt frameworks
    # are "outside of the bundle"

    add_custom_command(
        OUTPUT ${DMG_FILE}
        COMMENT "Creating macOS DMG. Please wait..."
        DEPENDS ${PROJECT_NAME}
        ${COPY_SENTRY_COMMAND}
        COMMAND "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_BINS}/macdeployqt" "$<TARGET_FILE_DIR:${PROJECT_NAME}>/../.."
                "-qmldir=${CMAKE_CURRENT_SOURCE_DIR}"
                "-verbose=2"
                ${MACOS_SIGN_ARGS}
        COMMAND rm -rf "${DMG_DIR}"
        COMMAND mkdir -p "${DMG_DIR}/.background"
        COMMAND cp "${CMAKE_SOURCE_DIR}/macos/dmg-ds_store" "${DMG_DIR}/.DS_Store"
        COMMAND cp "${CMAKE_SOURCE_DIR}/macos/dmg-background.png" "${DMG_DIR}/.background/background.png"
        COMMAND ln -s /Applications "${DMG_DIR}"
        COMMAND cp -a "$<TARGET_BUNDLE_DIR:${PROJECT_NAME}>" "${DMG_DIR}"
        COMMAND "${CMAKE_SOURCE_DIR}/macos/retry.sh" 5 hdiutil create ${DMG_FILE}
                               -volname "${PROJECT_NAME}-${VERSION}" -fs "HFS+" -format ULFO -ov
                               -srcdir ${DMG_DIR}
    )
    add_custom_target(dmg DEPENDS ${DMG_FILE})

elseif (WIN32)
    # deploy sentry dependencies
    set(COPY_SENTRY_COMMAND "")
    if (SENTRY)
        set(COPY_SENTRY_COMMAND COMMAND "${CMAKE_COMMAND}" -E copy
            "$<TARGET_FILE:crashpad_handler>" "${CMAKE_CURRENT_BINARY_DIR}/bin"
        )
    endif()

    set(INNO_ARCH ${CMAKE_SYSTEM_PROCESSOR})
    if (INNO_ARCH STREQUAL "AMD64")
        set(INNO_ARCH "x64")
    endif()

    if (QT_HOST_PATH)
        set(WINDEPLOYQT_PATH "${QT_HOST_PATH}/${QT6_HOST_INFO_BINDIR}")
        set(WINDEPLOYQT_EXTRA_ARGS "--qtpaths=${QT6_INSTALL_PREFIX}/${QT6_INSTALL_BINS}/qtpaths.bat")
    else()
        set(WINDEPLOYQT_PATH "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_BINS}")
    endif()

    if (${Qt6_VERSION} VERSION_LESS "6.5.0")
        list(APPEND WINDEPLOYQT_EXTRA_ARGS --no-qmltooling)
    elseif (${Qt6_VERSION} VERSION_GREATER "6.5.0")
        list(APPEND WINDEPLOYQT_EXTRA_ARGS --skip-plugin-types qmltooling)
    endif ()

    # deploy the Qt dependencies next to the executable. Split out of the installer
    # target, because the universal installer is built from the deployed trees of
    # both architectures, on a machine that has neither Qt nor a build directory.
    add_custom_target(deploy
        COMMAND "${WINDEPLOYQT_PATH}/windeployqt" $<TARGET_FILE:${PROJECT_NAME}>
                ${WINDEPLOYQT_EXTRA_ARGS}
                "--qmldir=${CMAKE_CURRENT_SOURCE_DIR}"
                "--no-opengl-sw"
                "--no-translations"
                "--verbose" "2"
        ${COPY_SENTRY_COMMAND}
        COMMENT "Deploying Qt dependencies. Please wait..."
        DEPENDS ${PROJECT_NAME}
    )

    # create an INNO setup installer for this architecture alone
    add_custom_target(installer
        COMMAND "iscc.exe" "/DSOURCE_DIR=${CMAKE_CURRENT_BINARY_DIR}/bin" "/DARCH=${INNO_ARCH}"
                "/O${CMAKE_CURRENT_BINARY_DIR}"
                "/F${PROJECT_NAME}-${VERSION}" "${CMAKE_SOURCE_DIR}/windows/brickstore.iss"
        COMMENT "Creating INNO Setup installer. Please wait..."
        DEPENDS deploy "${WIZARD_IMAGE}"
    )

    # The MSIX tile images are generated instead of committed: only the package
    # consumes them, and the scale matrix is ~20 files.
    #
    # imagescale is deliberately not in bin/: everything that lands there is
    # either swept up by the installer's *.exe glob or ends up in the deployed
    # tree, and this is build tooling, not payload.
    add_custom_target(msix-assets
        COMMAND "${CMAKE_COMMAND}" "-DIMAGESCALE=${IMAGESCALE_EXECUTABLE}"
                "-DASSET_DIR=${CMAKE_SOURCE_DIR}/assets"
                "-DOUT_DIR=${CMAKE_CURRENT_BINARY_DIR}/msix/Assets"
                -P "${CMAKE_SOURCE_DIR}/windows/msix/generate-assets.cmake"
        COMMENT "Generating MSIX assets..."
        DEPENDS imagescale
    )

elseif (ANDROID)
    # we cannot set the apk name directly, so we have to rename it after creation
    add_custom_target(final-apk
        COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_BINARY_DIR}/android-build/${PROJECT_NAME}.apk"
                                           "${CMAKE_BINARY_DIR}/${PROJECT_NAME}-${VERSION}.apk"
        COMMENT "Moving apk to final name and location..."
        DEPENDS apk
    )

    # gradle only knows "release" and "debug": androiddeployqt maps every release-ish
    # config onto the former, so we cannot just lowercase CMAKE_BUILD_TYPE here
    set(GRADLE_RELEASE_CONFIGS Release RelWithDebInfo MinSizeRel)
    if (CMAKE_BUILD_TYPE IN_LIST GRADLE_RELEASE_CONFIGS)
        set(GRADLE_VARIANT "release")
    else()
        set(GRADLE_VARIANT "debug")
    endif()

    add_custom_target(final-aab
        COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_BINARY_DIR}/android-build/build/outputs/bundle/${GRADLE_VARIANT}/android-build-${GRADLE_VARIANT}.aab"
                                           "${CMAKE_BINARY_DIR}/${PROJECT_NAME}-${VERSION}.aab"
        COMMENT "Moving aab to final name and location..."
        DEPENDS aab
    )

elseif (LINUX)
    install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin)
    install(FILES unix/dev.brickstore.BrickStore.desktop DESTINATION share/applications)
    install(FILES unix/dev.brickstore.BrickStore.mime.xml DESTINATION share/mime/packages)
    install(FILES assets/generated-app-icons/brickstore.png DESTINATION share/icons/hicolor/256x256/apps RENAME dev.brickstore.BrickStore.png)
    install(FILES assets/generated-app-icons/brickstore_doc.png DESTINATION share/icons/hicolor/128x128/mimetypes RENAME dev.brickstore.BrickStore.doc.png)

    string(TIMESTAMP APPSTREAM_RELEASE_DATE "%Y-%m-%d")
    configure_file(unix/dev.brickstore.BrickStore.metainfo.xml.in unix/dev.brickstore.BrickStore.metainfo.xml @ONLY)
    install(FILES ${CMAKE_BINARY_DIR}/unix/dev.brickstore.BrickStore.metainfo.xml DESTINATION share/metainfo)

    # Arch has Qt 6.2+ and is handled upstream via AUR
    # Ubuntu finally has a Qt 6.2+ starting with 22.04 LTS (jammy)
    # Debian will get a Qt 6.2+ with bookworm (currently testing, estimated release in summer 2023)

    set(DEBIAN_PACKAGE_DEPENDS
        libqt6sql6-sqlite
        qt6-qpa-plugins   # the TLS and basic image-format plugins are wrongly packaged into here
    )

    if (NOT BS_BACKEND)
        list(APPEND DEBIAN_PACKAGE_DEPENDS
            libqt6svg6
            qt6-gtk-platformtheme
            qt6-image-formats-plugins
            qml6-module-qtqml-workerscript
            qml6-module-qtquick
            qml6-module-qtquick-controls
            qml6-module-qt5compat-graphicaleffects
            qml6-module-quick3d
    )
    endif()
    string(REPLACE ";" "," CPACK_DEBIAN_PACKAGE_DEPENDS "${DEBIAN_PACKAGE_DEPENDS}")
    set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
    set(CPACK_PACKAGE_CONTACT "Robert Griebl <robert@griebl.org>")
    set(CPACK_DEBIAN_PACKAGE_SECTION x11)
    set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)

    add_custom_target(deb-package
        COMMAND "${CMAKE_CPACK_COMMAND}" "-C" "$<CONFIGURATION>" -G DEB
                "--config" "${CMAKE_BINARY_DIR}/BundleConfig.cmake"
        COMMENT "Running CPack. Please wait..."
        DEPENDS ${PROJECT_NAME}
    )

    set(SNAP_DIR "${CMAKE_BINARY_DIR}/snap")
    file(COPY "${CMAKE_SOURCE_DIR}/assets/generated-app-icons/brickstore.png" DESTINATION "${SNAP_DIR}/gui/")
    configure_file(unix/snapcraft.yaml.in "${SNAP_DIR}/snapcraft.yaml" @ONLY)
    configure_file(unix/flatpak.yaml.in flatpak.yaml @ONLY)

elseif (IOS)
    # the idea here comes from https://github.com/OlivierLDff/QtIosCMake/blob/master/AddQtIosApp.cmake

    add_custom_target(archive
        DEPENDS ${PROJECT_NAME}  ## we need to build twice! see QTBUG-105006
        VERBATIM
        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
        COMMAND xcodebuild
                -project ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.xcodeproj
                -configuration Release
                -scheme ${PROJECT_NAME}
                -archivePath ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.xcarchive
                -derivedDataPath ${CMAKE_BINARY_DIR}/derivedData
                archive
    )

    if (${CMAKE_OSX_SYSROOT} MATCHES ".*SIMULATOR.*")
        message(STATUS "Skipping IPA target generation for simulator builds")
    elseif (NOT CMAKE_XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER
            OR NOT CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY
            OR NOT CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM)
        message(STATUS "Skipping IPA generation because not all necessary variables are set")
    else()
        get_target_property(IOS_BUNDLE_IDENTIFIER ${PROJECT_NAME} XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER)

        set(EXPORT_OPTIONS_FILE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_ExportOptions.plist)
        configure_file(${CMAKE_SOURCE_DIR}/macos/ExportOptions.ios.plist ${EXPORT_OPTIONS_FILE})

        add_custom_target(ipa
            DEPENDS archive
            COMMAND xcodebuild
                    -exportArchive
                    -archivePath ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.xcarchive
                    -exportOptionsPlist ${EXPORT_OPTIONS_FILE}
                    -exportPath ${CMAKE_BINARY_DIR}/ipa-export
            COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_BINARY_DIR}/ipa-export/${PROJECT_NAME}.ipa"
                    "${CMAKE_BINARY_DIR}/${PROJECT_NAME}-${VERSION}.ipa"
        )
    endif()
endif()

# we don't want the standard 'package' target
set(CPACK_OUTPUT_CONFIG_FILE "${CMAKE_BINARY_DIR}/BundleConfig.cmake" )
include(CPack)

message(STATUS "")
message(STATUS "Configure Summary:")
message(STATUS "  Version ........ ${VERSION}")
message(STATUS "  Build number ... ${BUILD_NUMBER}")
message(STATUS "  Build type ..... ${CMAKE_BUILD_TYPE}")
message(STATUS "  Qt version ..... ${Qt6_VERSION}")
message(STATUS "  Qt location .... ${Qt6_DIR}")
message(STATUS "  Build type ..... ${BS_TYPE}")
message(STATUS "  Link-time opt. . ${LTO_STATUS}")
message(STATUS "  Use sentry.io .. ${BS_SENTRY}")
message(STATUS "  ASAN ........... ${SANITIZE}")
message(STATUS "  Qt Modeltest ... ${MODELTEST}")
message(STATUS "  Parallel STL ... ${PARALLEL_STL}")
message(STATUS "  Linux/libsecret  ${BS_LIBSECRET}")
message(STATUS "  Wayland deco ... ${BS_WAYLAND_DECORATIONS}")
message(STATUS "  MCP Server ..... ${BS_MCP_SERVER}")
message(STATUS "")
