/*
 *   SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
 *   SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
 *
 *   SPDX-License-Identifier: GPL-2.0-or-later
 */

#include <KAuthorized>
#include <KCModuleData>
#include <KFileUtils>
#include <KPluginFactory>
#include <KPluginMetaData>
#include <KRuntimePlatform>
#include <KService>

#include <QGuiApplication>
#include <QStandardPaths>

#include <ranges>
#include <set>

#include "../systemsettings_app_debug.h"

enum MetaDataSource {
    SystemSettings = 1,
    KInfoCenter = 2,
    All = SystemSettings | KInfoCenter,
};

inline QList<KPluginMetaData> findExternalKCMModules(MetaDataSource source)
{
    const auto findExternalModulesInFilesystem = [](const QString &sourceNamespace) {
        const QString sourceNamespaceDirName = QStringLiteral("plasma/%1/externalmodules").arg(sourceNamespace);
        const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, sourceNamespaceDirName, QStandardPaths::LocateDirectory);
        const QStringList files = KFileUtils::findAllUniqueFiles(dirs, QStringList{QStringLiteral("*.desktop")});

        QList<KPluginMetaData> metaDataList;
        for (const QString &file : files) {
            KService service(file);
            QJsonObject kplugin;
            kplugin.insert(QLatin1String("Name"), service.name());
            kplugin.insert(QLatin1String("Icon"), service.icon());
            kplugin.insert(QLatin1String("Description"), service.comment());

            QJsonObject root;
            root.insert(QLatin1String("KPlugin"), kplugin);
            root.insert(QLatin1String("X-KDE-Weight"), service.property<int>(QStringLiteral("X-KDE-Weight")));
            root.insert(QLatin1String("X-KDE-KInfoCenter-Category"), service.property<QString>(QStringLiteral("X-KDE-KInfoCenter-Category")));
            root.insert(QLatin1String("X-KDE-System-Settings-Category"), service.property<QString>(QStringLiteral("X-KDE-System-Settings-Category")));
            root.insert(QLatin1String("X-KDE-System-Settings-Parent-Category"),
                        service.property<QString>(QStringLiteral("X-KDE-System-Settings-Parent-Category")));
            root.insert(QLatin1String("X-KDE-System-Settings-Parent-Category-V2"),
                        service.property<QString>(QStringLiteral("X-KDE-System-Settings-Parent-Category-V2")));
            root.insert(QLatin1String("IsExternalApp"), true);

            const KPluginMetaData metaData(root, file);

            if (!KAuthorized::authorizeControlModule(metaData.pluginId())) {
                continue;
            }

            metaDataList << metaData;
        }
        return metaDataList;
    };

    QList<KPluginMetaData> metaDataList;
    if (source & SystemSettings) {
        metaDataList << findExternalModulesInFilesystem(QStringLiteral("systemsettings"));
    }

    if (source & KInfoCenter) {
        metaDataList << findExternalModulesInFilesystem(QStringLiteral("kinfocenter"));
    }

    return metaDataList;
}

inline QList<KPluginMetaData> findKCMsMetaData(MetaDataSource source)
{
    QList<KPluginMetaData> modules;
    std::set<QString> uniquePluginIds;

    auto filter = [](const KPluginMetaData &data) {
        const auto supportedPlatforms = data.value(QStringLiteral("X-KDE-OnlyShowOnQtPlatforms"), QStringList());
        const auto qtPlatformStartsWith = [](const auto &platform) {
            return qGuiApp->platformName().startsWith(platform);
        };
        if (!supportedPlatforms.isEmpty() && !std::ranges::any_of(supportedPlatforms, qtPlatformStartsWith)) {
            return false;
        }

        auto kRuntimePlatforms = KRuntimePlatform::runtimePlatform();

        // HACK: currently on desktop no form factors are specified
        if (kRuntimePlatforms.empty()) {
            kRuntimePlatforms.append(QStringLiteral("desktop"));
        }

        // Filter out if the form factor does not match the current runtime platform.
        // If the KCM defines "all" or has no defined form factor, don't filter.
        for (const auto &formFactor : data.formFactors()) {
            if (formFactor == QStringLiteral("all")) {
                return true;
            }
            if (kRuntimePlatforms.contains(formFactor)) {
                return true;
            }
        }
        return data.formFactors().empty();
    };

    // We need the exist calls because otherwise the trader language aborts if the property doesn't exist and the second part of the or is not evaluated
    QList<KPluginMetaData> metaDataList = KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms"), filter);
    if (source & SystemSettings) {
        metaDataList << KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms/systemsettings"), filter);
        metaDataList << KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms/systemsettings_qwidgets"), filter);
    }
    if (source & KInfoCenter) {
        metaDataList << KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms/kinfocenter"), filter);
    }
    for (const auto &m : std::as_const(metaDataList)) {
        if (!KAuthorized::authorizeControlModule(m.pluginId())) {
            continue;
        }
        modules << m;
        const bool inserted = uniquePluginIds.insert(m.pluginId()).second;
        if (!inserted) {
            qWarning() << "the plugin" << m.pluginId() << " was found in multiple namespaces";
        }
    }
    std::stable_sort(modules.begin(), modules.end(), [](const KPluginMetaData &m1, const KPluginMetaData &m2) {
        return QString::compare(m1.pluginId(), m2.pluginId(), Qt::CaseInsensitive) < 0;
    });
    return modules;
}

inline bool isKinfoCenterKcm(const KPluginMetaData &data)
{
    // external module or a KCM in the namespace
    return data.fileName().contains(QLatin1String("/kinfocenter/"));
}

inline KCModuleData *loadModuleData(const KPluginMetaData &data)
{
    if (auto factory = KPluginFactory::loadFactory(data).plugin) {
        return factory->create<KCModuleData>();
    }
    return nullptr;
}
