/*
    SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
    SPDX-FileCopyrightText: 2007 Jeremy Whiting <jpwhiting@kde.org>
    SPDX-FileCopyrightText: 2016 Olivier Churlaud <olivier@churlaud.com>
    SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>
    SPDX-FileCopyrightText: 2019 David Redondo <kde@david-redondo.de>

    SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/

#include "themesmodel.h"

#include <QCollator>
#include <QDir>
#include <QStandardPaths>

#include <KColorScheme>
#include <KDesktopFile>
#include <KPluginMetaData>

#include <KConfigGroup>
#include <KSharedConfig>

#include <algorithm>

ThemesModel::ThemesModel(QObject *parent)
    : QAbstractListModel(parent)
{
}

ThemesModel::~ThemesModel() = default;

int ThemesModel::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid()) {
        return 0;
    }

    return m_data.count();
}

QVariant ThemesModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || index.row() >= m_data.count()) {
        return {};
    }

    const auto &item = m_data.at(index.row());

    switch (role) {
    case Qt::DisplayRole:
        return item.display;
    case PluginNameRole:
        return item.pluginName;
    case DescriptionRole:
        return item.description;
    case ColorTypeRole:
        return item.type;
    case IsLocalRole:
        return item.isLocal;
    case PendingDeletionRole:
        return item.pendingDeletion;
    }
    return {};
}

bool ThemesModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid() || index.row() >= m_data.count()) {
        return false;
    }

    if (role == PendingDeletionRole) {
        auto &item = m_data[index.row()];

        const bool pendingDeletion = value.toBool();

        if (item.pendingDeletion != pendingDeletion) {
            item.pendingDeletion = pendingDeletion;
            Q_EMIT dataChanged(index, index, {PendingDeletionRole});

            if (index.row() == selectedThemeIndex() && pendingDeletion) {
                // move to the next non-pending theme
                const auto nonPending = match(index, PendingDeletionRole, false);
                if (!nonPending.isEmpty()) {
                    setSelectedTheme(nonPending.first().data(PluginNameRole).toString());
                }
            }

            Q_EMIT pendingDeletionsChanged();
            return true;
        }
    }

    return false;
}

QHash<int, QByteArray> ThemesModel::roleNames() const
{
    return {
        {Qt::DisplayRole, QByteArrayLiteral("display")},
        {PluginNameRole, QByteArrayLiteral("pluginName")},
        {DescriptionRole, QByteArrayLiteral("description")},
        {ColorTypeRole, QByteArrayLiteral("colorType")},
        {IsLocalRole, QByteArrayLiteral("isLocal")},
        {PendingDeletionRole, QByteArrayLiteral("pendingDeletion")},
    };
}

QString ThemesModel::selectedTheme() const
{
    return m_selectedTheme;
}

void ThemesModel::setSelectedTheme(const QString &pluginName)
{
    if (m_selectedTheme == pluginName) {
        return;
    }

    m_selectedTheme = pluginName;

    Q_EMIT selectedThemeChanged(pluginName);

    Q_EMIT selectedThemeIndexChanged();
}

int ThemesModel::pluginIndex(const QString &pluginName) const
{
    const auto results = match(index(0, 0), PluginNameRole, pluginName, 1, Qt::MatchExactly);
    if (results.count() == 1) {
        return results.first().row();
    }

    return -1;
}

int ThemesModel::selectedThemeIndex() const
{
    return pluginIndex(m_selectedTheme);
}

void ThemesModel::load()
{
    beginResetModel();

    const int oldCount = m_data.count();

    m_data.clear();

    // Get all desktop themes
    QStringList themes;
    const QStringList packs =
        QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("plasma/desktoptheme"), QStandardPaths::LocateDirectory);
    for (const QString &ppath : packs) {
        const QDir cd(ppath);
        const QStringList &entries = cd.entryList(QDir::Dirs | QDir::Hidden | QDir::NoDotAndDotDot);
        for (const QString &pack : entries) {
            const QString prefix = QStringLiteral("%1%2%3%4metadata.").arg(ppath, QDir::separator(), pack, QDir::separator());

            QString _metadata = QStringLiteral("%1json").arg(prefix);
            if (QFile::exists(_metadata)) {
                themes << _metadata;
                continue;
            }

            _metadata = QStringLiteral("%1desktop").arg(prefix);
            if (QFile::exists(_metadata)) {
                themes << _metadata;
            }
        }
    }

    for (const QString &theme : std::as_const(themes)) {
        int themeSepIndex = theme.lastIndexOf(QLatin1Char('/'), -1);
        const QString themeRoot = theme.left(themeSepIndex);
        int themeNameSepIndex = themeRoot.lastIndexOf(QLatin1Char('/'), -1);
        const QString packageName = themeRoot.right(themeRoot.length() - themeNameSepIndex - 1);

        QString name;
        QString comment;

        if (theme.endsWith(QLatin1String(".json"))) {
            KPluginMetaData data = KPluginMetaData::fromJsonFile(theme);
            name = data.name();
            comment = data.description();
        } else {
            KDesktopFile df(theme);

            if (df.noDisplay()) {
                continue;
            }

            name = df.readName();
            if (name.isEmpty()) {
                name = packageName;
            }
            comment = df.readComment();
        }
        const bool isLocal = QFileInfo(theme).isWritable();
        bool hasPluginName = std::ranges::any_of(m_data, [&](const ThemesModelData &item) {
            return item.pluginName == packageName;
        });
        if (!hasPluginName) {
            // Plasma Theme creates a KColorScheme out of the "color" file and falls back to system colors if there is none
            const QString colorsPath = themeRoot + QStringLiteral("/colors");
            const bool followsSystemColors = !QFileInfo::exists(colorsPath);
            ColorType type = FollowsColorTheme;
            if (!followsSystemColors) {
                const KSharedConfig::Ptr config = KSharedConfig::openConfig(colorsPath);
                const QPalette palette = KColorScheme::createApplicationPalette(config);
                const int windowBackgroundGray = qGray(palette.window().color().rgb());
                if (windowBackgroundGray < 192) {
                    type = DarkTheme;
                } else {
                    type = LightTheme;
                }
            }
            ThemesModelData item{name, packageName, comment, type, isLocal, false};
            m_data.append(item);
        }
    }

    // Sort case-insensitively
    QCollator collator;
    collator.setCaseSensitivity(Qt::CaseInsensitive);
    std::ranges::sort(m_data, [&collator](const ThemesModelData &a, const ThemesModelData &b) {
        return collator.compare(a.display, b.display) < 0;
    });

    endResetModel();

    // an item might have been added before the currently selected one
    if (oldCount != m_data.count()) {
        Q_EMIT selectedThemeIndexChanged();
    }
}

QStringList ThemesModel::pendingDeletions() const
{
    QStringList pendingDeletions;

    for (const auto &item : std::as_const(m_data)) {
        if (item.pendingDeletion) {
            pendingDeletions.append(item.pluginName);
        }
    }

    return pendingDeletions;
}

void ThemesModel::removeRow(int row)
{
    beginRemoveRows(QModelIndex(), row, row);
    m_data.erase(m_data.begin() + row);
    endRemoveRows();
}

#include "moc_themesmodel.cpp"
