/*
    SPDX-License-Identifier: LGPL-2.1-or-later OR MIT
    SPDX-FileCopyrightText: 2021-2026 Andreas Cord-Landwehr <cordlandwehr@kde.org>
*/

#include "filtercriteriamodel.h"
#include "filtercriteriamodel_p.h"
#include "journaldhelper.h"
#include "kjournaldlib_log_general.h"
#include <KLocalizedString>
#include <QDebug>
#include <QDir>
#include <QRegularExpression>
#include <QString>
#include <memory>

QString FilterCriteriaModelPrivate::mapPriorityToString(qint32 priority)
{
    switch (priority) {
    case 0:
        return i18nc("Radio box option, log priority value", "Emergency");
    case 1:
        return i18nc("Radio box option, log priority value", "Alert");
    case 2:
        return i18nc("Radio box option, log priority value", "Critical");
    case 3:
        return i18nc("Radio box option, log priority value", "Error");
    case 4:
        return i18nc("Radio box option, log priority value", "Warning");
    case 5:
        return i18nc("Radio box option, log priority value", "Notice");
    case 6:
        return i18nc("Radio box option, log priority value", "Info");
    case 7:
        return i18nc("Radio box option, log priority value", "Debug");
    case -1:
        return i18nc("Radio box option, log priority value", "No Filter");
    }
    return QLatin1String("");
}

SelectionEntry::SelectionEntry(const QString &text,
                               const QVariant &data,
                               FilterCriteriaModel::Category category,
                               bool selected,
                               std::shared_ptr<SelectionEntry> parent)
    : mText(text)
    , mData(data)
    , mCategory(category)
    , mSelected(selected)
    , mParentItem(parent)
{
}

void SelectionEntry::appendChild(std::shared_ptr<SelectionEntry> item)
{
    mChildItems.push_back(item);
}

std::shared_ptr<SelectionEntry> SelectionEntry::child(int row)
{
    if (row < 0 || row >= mChildItems.size()) {
        return nullptr;
    }
    return mChildItems.at(row);
}

int SelectionEntry::childCount() const
{
    return mChildItems.size();
}

int SelectionEntry::row() const
{
    auto parent = mParentItem.lock();
    if (parent) {
        for (int i = 0; i < parent->mChildItems.size(); ++i) {
            if (parent->mChildItems.at(i).get() == this) {
                return i;
            }
        }
    }
    return 0;
}

int SelectionEntry::columnCount() const
{
    return 1;
}

QVariant SelectionEntry::data(FilterCriteriaModel::Roles role) const
{
    switch (role) {
    case FilterCriteriaModel::Roles::CATEGORY:
        return QVariant::fromValue(mCategory);
    case FilterCriteriaModel::Roles::TEXT:
        return QVariant::fromValue(mText); // TODO abbreviate
    case FilterCriteriaModel::Roles::DATA:
        return QVariant::fromValue(mData);
    case FilterCriteriaModel::Roles::LONGTEXT:
        return QVariant::fromValue(mText);
    case FilterCriteriaModel::Roles::SELECTED:
        return QVariant::fromValue(mSelected);
    case FilterCriteriaModel::Roles::HAS_CHILDREN:
        return childCount() > 0;
    }
    return QVariant();
}

bool SelectionEntry::setData(const QVariant &value, FilterCriteriaModel::Roles role)
{
    if (role == FilterCriteriaModel::Roles::SELECTED) {
        mSelected = value.toBool();
        return true;
    }
    qCWarning(KJOURNALDLIB_GENERAL) << "no settable role";
    return false;
}

std::shared_ptr<SelectionEntry> SelectionEntry::parentItem()
{
    return mParentItem.lock();
}

FilterCriteriaModelPrivate::FilterCriteriaModelPrivate() = default;

FilterCriteriaModelPrivate::~FilterCriteriaModelPrivate() = default;

bool FilterCriteriaModelPrivate::isRebuildModelPending() const
{
    return mBootFilter.has_value() && !mUniqueEntriesCache.contains(mBootFilter.value());
}

void FilterCriteriaModelPrivate::rebuildModel()
{
    Q_ASSERT(!mQmlEngineIncubationActive);
    qCDebug(KJOURNALDLIB_GENERAL) << "Rebuilding filter criteria model for boot-id:" << mBootFilter.value_or(QString());
    mIndexMap = {0, 0, 0, 0, 0};
    quint32 rootIndex{0};

    QMap<JournaldHelper::Field, QStringList> uniqueEntries;
    if (!mBootFilter.has_value() || !mUniqueEntriesCache.contains(mBootFilter.value_or(QString()))) {
        if (mJournal && JournaldHelper::queryUnique(mJournal->get(), JournaldHelper::Field::_BOOT_ID).contains(mBootFilter.value_or(QString()))) {
            auto iter = mUniqueEntriesCache.insert(
                mBootFilter.value_or(QString()),
                JournaldHelper::queryUnique(mJournal->get(),
                                            mBootFilter.value_or(QString()),
                                            {JournaldHelper::Field::_SYSTEMD_UNIT, JournaldHelper::Field::_SYSTEMD_USER_UNIT, JournaldHelper::Field::_EXE}));

            uniqueEntries = *iter;
        } else {
            qCWarning(KJOURNALDLIB_GENERAL) << "Skip filter criteria model build, boot-id not available:" << mBootFilter.value_or(QString());
        }
    } else {
        uniqueEntries = mUniqueEntriesCache.value(mBootFilter.value_or(QString()));
        qCDebug(KJOURNALDLIB_GENERAL) << "Populate filter criteria model from cache for boot-id:" << mBootFilter.value_or(QString());
    }

    mRootItem = std::make_unique<SelectionEntry>();
    mUniqueServiceUnitCache.clear();
    {
        auto parent = std::make_shared<SelectionEntry>(i18nc("Section title for log message source", "Transport"),
                                                       QVariant(),
                                                       FilterCriteriaModel::Category::TRANSPORT,
                                                       false,
                                                       mRootItem);
        mRootItem->appendChild(parent);
        mRootItem->child(rootIndex)->appendChild(std::move(std::make_unique<SelectionEntry>(i18nc("Checkbox option for kernel log messages", "Kernel"),
                                                                                            QLatin1String("kernel"),
                                                                                            FilterCriteriaModel::Category::TRANSPORT,
                                                                                            false,
                                                                                            parent)));
        mIndexMap[FilterCriteriaModel::Category::TRANSPORT] = rootIndex;
        ++rootIndex;
    }
    {
        auto parent = std::make_shared<SelectionEntry>(i18nc("Section title for log message priority", "Priority"),
                                                       QVariant(),
                                                       FilterCriteriaModel::Category::PRIORITY,
                                                       false,
                                                       mRootItem);
        mRootItem->appendChild(parent);
        for (int i = 0; i <= 7; ++i) {
            mRootItem->child(rootIndex)->appendChild(
                std::move(std::make_unique<SelectionEntry>(mapPriorityToString(i),
                                                           QString::number(i),
                                                           FilterCriteriaModel::Category::PRIORITY,
                                                           mPriorityLevel.has_value() && i == mPriorityLevel.value() ? true : false,
                                                           parent)));
            // magic index 8 means "unset priority"
        }
        // add "no filter" option at end
        mRootItem->child(rootIndex)->appendChild(std::move(std::make_unique<SelectionEntry>(mapPriorityToString(-1),
                                                                                            QString::number(-1),
                                                                                            FilterCriteriaModel::Category::PRIORITY,
                                                                                            !mPriorityLevel.has_value() || 8 == mPriorityLevel,
                                                                                            parent)));
        mIndexMap[FilterCriteriaModel::Category::PRIORITY] = rootIndex;
        ++rootIndex;
    }
    if (mLogViewMode == FilterCriteriaModel::LogViewMode::ALL_LOGS || mLogViewMode == FilterCriteriaModel::LogViewMode::ONLY_USER) {
        auto parent = std::make_shared<SelectionEntry>(i18nc("Section title for systemd user unit", "User Unit"),
                                                       QVariant(),
                                                       FilterCriteriaModel::Category::SYSTEMD_USER_UNIT,
                                                       false,
                                                       mRootItem);
        mRootItem->appendChild(parent);
        if (mJournal) {
            QStringList units = uniqueEntries.value(JournaldHelper::Field::_SYSTEMD_USER_UNIT);
            mUniqueServiceUnitCache.append(units);

            if (mGroupTemplatedSystemdUnits) {
                // systemd templates use '@' as delimiter between service name and the argument
                for (auto it = units.begin(); it != units.end(); it++) {
                    static const QRegularExpression templateArgumentExpr(QLatin1String("@.+\\.service"));
                    it->replace(templateArgumentExpr, FilterCriteriaModelPrivate::GROUPED_SERVICE_SUFFIX);
                }
            }

            units.erase(std::remove_if(std::begin(units),
                                       std::end(units),
                                       [](QStringView unit) {
                                           return unit.startsWith(QLatin1String("systemd-coredump@"))
                                               || unit.startsWith(QLatin1String("drkonqi-coredump-processor@"))
                                               || unit.startsWith(QLatin1String("drkonqi-coredump-launcher@"));
                                       }),
                        std::end(units));
            std::sort(std::begin(units), std::end(units), [](const QString &a, const QString &b) {
                return QString::compare(a, b, Qt::CaseInsensitive) <= 0;
            });
            // duplicates might come from template grouping
            units.erase(std::unique(std::begin(units), std::end(units)), std::end(units));

            for (const auto &unit : std::as_const(units)) {
                // skip any non-service units, because we expect users only be interested in filtering those
                if (!unit.endsWith(QLatin1String(".service"))) {
                    continue;
                }
                mRootItem->child(rootIndex)->appendChild(std::move(std::make_unique<SelectionEntry>(JournaldHelper::cleanupString(unit),
                                                                                                    unit,
                                                                                                    FilterCriteriaModel::Category::SYSTEMD_USER_UNIT,
                                                                                                    false,
                                                                                                    parent)));
            }
        }
        mIndexMap[FilterCriteriaModel::Category::SYSTEMD_USER_UNIT] = rootIndex;
        ++rootIndex;
    } else {
        mIndexMap[FilterCriteriaModel::Category::SYSTEMD_USER_UNIT] = -1;
    }
    if (mLogViewMode == FilterCriteriaModel::LogViewMode::ALL_LOGS || mLogViewMode == FilterCriteriaModel::LogViewMode::ONLY_SYSTEM) {
        auto parent = std::make_shared<SelectionEntry>(i18nc("Section title for systemd sytem unit", "System Unit"),
                                                       QVariant(),
                                                       FilterCriteriaModel::Category::SYSTEMD_SYSTEM_UNIT,
                                                       false,
                                                       mRootItem);
        mRootItem->appendChild(parent);
        if (mJournal) {
            QStringList units = uniqueEntries.value(JournaldHelper::Field::_SYSTEMD_UNIT);
            mUniqueServiceUnitCache.append(units);

            if (mGroupTemplatedSystemdUnits) {
                // systemd templates use '@' as delimiter between service name and the argument
                for (auto it = units.begin(); it != units.end(); it++) {
                    static const QRegularExpression templateArgumentExpr(QLatin1String("@.+\\.service"));
                    it->replace(templateArgumentExpr, FilterCriteriaModelPrivate::GROUPED_SERVICE_SUFFIX);
                }
            }

            units.erase(std::remove_if(std::begin(units),
                                       std::end(units),
                                       [](QStringView unit) {
                                           return unit.startsWith(QLatin1String("systemd-coredump@"))
                                               || unit.startsWith(QLatin1String("drkonqi-coredump-processor@"))
                                               || unit.startsWith(QLatin1String("drkonqi-coredump-launcher@"));
                                       }),
                        std::end(units));
            std::sort(std::begin(units), std::end(units), [](const QString &a, const QString &b) {
                return QString::compare(a, b, Qt::CaseInsensitive) <= 0;
            });
            // duplicates might come from template grouping
            units.erase(std::unique(std::begin(units), std::end(units)), std::end(units));

            for (const auto &unit : std::as_const(units)) {
                // skip any non-service units, because we expect users only be interested in filtering those
                if (!unit.endsWith(QLatin1String(".service"))) {
                    continue;
                }
                mRootItem->child(rootIndex)->appendChild(std::move(std::make_unique<SelectionEntry>(JournaldHelper::cleanupString(unit),
                                                                                                    unit,
                                                                                                    FilterCriteriaModel::Category::SYSTEMD_SYSTEM_UNIT,
                                                                                                    false,
                                                                                                    parent)));
            }
        }
        mIndexMap[FilterCriteriaModel::Category::SYSTEMD_SYSTEM_UNIT] = rootIndex;
        ++rootIndex;
    } else {
        mIndexMap[FilterCriteriaModel::Category::SYSTEMD_SYSTEM_UNIT] = -1;
    }
    {
        auto parent = std::make_shared<SelectionEntry>(i18nc("Section title for process list", "Process"),
                                                       QVariant(),
                                                       FilterCriteriaModel::Category::EXE,
                                                       false,
                                                       mRootItem);
        mRootItem->appendChild(parent);
        if (mJournal) {
            QStringList exes = uniqueEntries.value(JournaldHelper::Field::_EXE);
            std::sort(std::begin(exes), std::end(exes), [](const QString &a, const QString &b) {
                return QString::compare(a, b, Qt::CaseInsensitive) <= 0;
            });
            for (const auto &exe : std::as_const(exes)) {
                mRootItem->child(rootIndex)->appendChild(
                    std::move(std::make_unique<SelectionEntry>(JournaldHelper::cleanupString(exe), exe, FilterCriteriaModel::Category::EXE, false, parent)));
            }
        }
        mIndexMap[FilterCriteriaModel::Category::EXE] = rootIndex;
        ++rootIndex;
    }
}

FilterCriteriaModel::FilterCriteriaModel(QObject *parent)
    : QAbstractItemModel(parent)
    , d(new FilterCriteriaModelPrivate)
{
}

void FilterCriteriaModel::setJournalProvider(IJournalProvider *provider)
{
    d->mJournalProvider = provider;
    if (provider) {
        d->mJournal = provider->openJournal();
    } else {
        d->mJournal.reset();
    }
    Q_EMIT journalProviderChanged();
    // do not clear unique entry cache here, because boot-ids are unique across journald DBs
    if (!d->mQmlEngineIncubationActive && d->isRebuildModelPending()) {
        beginResetModel();
        d->rebuildModel();
        endResetModel();
    }
}

IJournalProvider *FilterCriteriaModel::journalProvider() const
{
    return d->mJournalProvider;
}

FilterCriteriaModel::~FilterCriteriaModel()
{
    qDebug(KJOURNALDLIB_GENERAL) << "deleting unique services for cached boots:" << d->mUniqueEntriesCache.size();
}

QHash<int, QByteArray> FilterCriteriaModel::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles[FilterCriteriaModel::TEXT] = "text";
    roles[FilterCriteriaModel::DATA] = "data";
    roles[FilterCriteriaModel::LONGTEXT] = "longtext";
    roles[FilterCriteriaModel::CATEGORY] = "category";
    roles[FilterCriteriaModel::SELECTED] = "selected";
    return roles;
}

qint8 FilterCriteriaModel::priorityFilter() const
{
    return static_cast<qint8>(d->mPriorityLevel.value_or(-1));
}

void FilterCriteriaModel::setInitialPriorityFilter(qint8 priorityIndex)
{
    d->mPriorityLevel = priorityIndex;
}

QString FilterCriteriaModel::bootFilter() const
{
    return d->mBootFilter.value_or(QString());
}

void FilterCriteriaModel::setBootFilter(const QString &filter)
{
    if (d->mBootFilter.value_or(QString()) == filter) {
        return;
    }
    d->mBootFilter = filter;
    Q_EMIT bootFilterChanged(filter);
    if (!d->mQmlEngineIncubationActive) {
        beginResetModel();
        d->rebuildModel();
        endResetModel();
    }
}

void FilterCriteriaModel::resetBootFilter()
{
    if (!d->mBootFilter.has_value()) {
        return;
    }
    d->mBootFilter.reset();
    Q_EMIT bootFilterChanged(QString());
    if (!d->mQmlEngineIncubationActive) {
        beginResetModel();
        d->rebuildModel();
        endResetModel();
    }
}

void FilterCriteriaModel::setLogViewMode(LogViewMode mode)
{
    if (d->mLogViewMode == mode) {
        return;
    }
    d->mLogViewMode = mode;
    if (!d->mQmlEngineIncubationActive) {
        beginResetModel();
        d->rebuildModel();
        endResetModel();
        Q_EMIT logViewModeChanged();
    }
}

FilterCriteriaModel::LogViewMode FilterCriteriaModel::logViewMode() const
{
    return d->mLogViewMode;
}

QStringList FilterCriteriaModel::systemdUserUnitFilter() const
{
    if (d->mIndexMap.empty() || d->mIndexMap[FilterCriteriaModel::Category::SYSTEMD_USER_UNIT] < 0) {
        return {};
    }
    std::shared_ptr<SelectionEntry> parent = d->mRootItem->child(d->mIndexMap[FilterCriteriaModel::Category::SYSTEMD_USER_UNIT]);
    QStringList entries;
    for (int i = 0; i < parent->childCount(); ++i) {
        if (parent->child(i)->data(FilterCriteriaModel::SELECTED).toBool()) {
            const QString identifier = parent->child(i)->data(FilterCriteriaModel::DATA).toString();
            if (d->mGroupTemplatedSystemdUnits && identifier.endsWith(FilterCriteriaModelPrivate::GROUPED_SERVICE_SUFFIX)) {
                constexpr qsizetype suffixLength = FilterCriteriaModelPrivate::GROUPED_SERVICE_SUFFIX.size();
                const QString identifierBase = identifier.left(identifier.length() - suffixLength);
                for (const auto &unit : std::as_const(d->mUniqueServiceUnitCache)) {
                    if (unit.startsWith(identifierBase)) {
                        entries.append(unit);
                    }
                }
            } else {
                entries.append(identifier);
            }
        }
    }
    return entries;
}

QStringList FilterCriteriaModel::systemdSystemUnitFilter() const
{
    if (d->mIndexMap.empty() || d->mIndexMap[FilterCriteriaModel::Category::SYSTEMD_SYSTEM_UNIT] < 0) {
        return {};
    }
    std::shared_ptr<SelectionEntry> parent = d->mRootItem->child(d->mIndexMap[FilterCriteriaModel::Category::SYSTEMD_SYSTEM_UNIT]);
    QStringList entries;
    for (int i = 0; i < parent->childCount(); ++i) {
        if (parent->child(i)->data(FilterCriteriaModel::SELECTED).toBool()) {
            const QString identifier = parent->child(i)->data(FilterCriteriaModel::DATA).toString();
            if (d->mGroupTemplatedSystemdUnits && identifier.endsWith(FilterCriteriaModelPrivate::GROUPED_SERVICE_SUFFIX)) {
                constexpr qsizetype suffixLength = FilterCriteriaModelPrivate::GROUPED_SERVICE_SUFFIX.size();
                const QString identifierBase = identifier.left(identifier.length() - suffixLength);
                for (const auto &unit : std::as_const(d->mUniqueServiceUnitCache)) {
                    if (unit.startsWith(identifierBase)) {
                        entries.append(unit);
                    }
                }
            } else {
                entries.append(identifier);
            }
        }
    }
    return entries;
}

bool FilterCriteriaModel::groupTemplatedSystemdUnits() const
{
    return d->mGroupTemplatedSystemdUnits;
}

void FilterCriteriaModel::setGroupTemplatedSystemdUnits(bool enabled)
{
    if (enabled == d->mGroupTemplatedSystemdUnits) {
        return;
    }
    d->mGroupTemplatedSystemdUnits = enabled;
    Q_EMIT groupTemplatedSystemdUnitsChanged();
    if (!d->mQmlEngineIncubationActive) {
        beginResetModel();
        d->rebuildModel();
        endResetModel();
    }
}

QStringList FilterCriteriaModel::exeFilter() const
{
    if (d->mIndexMap.empty()) {
        return {};
    }
    std::shared_ptr<SelectionEntry> parent = d->mRootItem->child(d->mIndexMap[FilterCriteriaModel::Category::EXE]);
    QStringList entries;
    for (int i = 0; i < parent->childCount(); ++i) {
        if (parent->child(i)->data(FilterCriteriaModel::SELECTED).toBool()) {
            entries.append(parent->child(i)->data(FilterCriteriaModel::DATA).toString());
        }
    }
    return entries;
}

bool FilterCriteriaModel::isKernelFilterEnabled() const
{
    if (d->mIndexMap.empty()) {
        return {};
    }
    std::shared_ptr<SelectionEntry> parent = d->mRootItem->child(d->mIndexMap[FilterCriteriaModel::Category::TRANSPORT]);
    for (int i = 0; i < parent->childCount(); ++i) {
        if (parent->child(i)->data(FilterCriteriaModel::DATA) == QLatin1String("kernel") && parent->child(i)->data(FilterCriteriaModel::SELECTED).toBool()) {
            return true;
        }
    }
    return false;
}

QModelIndex FilterCriteriaModel::index(int row, int column, const QModelIndex &parent) const
{
    if (!hasIndex(row, column, parent)) {
        return QModelIndex();
    }

    SelectionEntry *parentItem;

    if (!parent.isValid()) {
        parentItem = d->mRootItem.get();
    } else {
        parentItem = static_cast<SelectionEntry *>(parent.internalPointer());
    }

    std::shared_ptr<SelectionEntry> childItem = parentItem->child(row);
    if (childItem) {
        return createIndex(row, column, childItem.get());
    }
    return QModelIndex();
}

QModelIndex FilterCriteriaModel::parent(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return QModelIndex();
    }

    SelectionEntry *childItem = static_cast<SelectionEntry *>(index.internalPointer());
    std::shared_ptr<SelectionEntry> parentItem = childItem->parentItem();

    if (parentItem.get() == d->mRootItem.get()) {
        return QModelIndex();
    }

    return createIndex(parentItem->row(), 0, parentItem.get());
}

int FilterCriteriaModel::rowCount(const QModelIndex &parent) const
{
    if (!d->mRootItem) {
        return 0;
    } else if (!parent.isValid()) {
        return d->mRootItem->childCount();
    } else {
        auto parentItem = static_cast<SelectionEntry *>(parent.internalPointer());
        return parentItem->childCount();
    }
    return 0;
}

int FilterCriteriaModel::columnCount(const QModelIndex &parent) const
{
    return 1;
}

QVariant FilterCriteriaModel::data(const QModelIndex &index, int role) const
{
    if (!index.parent().isValid()) {
        if (index.row() < 0 || index.row() >= d->mRootItem->childCount()) {
            qCCritical(KJOURNALDLIB_GENERAL) << "Index out of range" << index;
            return QVariant();
        }
        return d->mRootItem->child(index.row())->data(static_cast<FilterCriteriaModel::Roles>(role));
    } else {
        auto entry = static_cast<SelectionEntry *>(index.internalPointer());
        if (nullptr != entry) {
            return entry->data(static_cast<FilterCriteriaModel::Roles>(role));
        }
    }
    return QVariant();
}

bool FilterCriteriaModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    auto entry = static_cast<SelectionEntry *>(index.internalPointer());
    if (nullptr == entry) {
        return QAbstractItemModel::setData(index, value, role);
    }
    if (value == entry->data(static_cast<FilterCriteriaModel::Roles>(role))) {
        return false; // nothing to do
    }

    // clear operations for top-level categories
    const bool result = entry->setData(value, static_cast<FilterCriteriaModel::Roles>(role));
    const auto category = entry->data(FilterCriteriaModel::Roles::CATEGORY).value<FilterCriteriaModel::Category>();
    Q_EMIT dataChanged(index, index, {role});

    if (result && category == FilterCriteriaModel::Category::PRIORITY && static_cast<FilterCriteriaModel::Roles>(role) == SELECTED) {
        // only listen on changes that set entry data to true, because this is considered a selector in the list
        std::shared_ptr<SelectionEntry> parent = d->mRootItem->child(d->mIndexMap[FilterCriteriaModel::Category::PRIORITY]);
        for (int i = 0; i < parent->childCount(); ++i) {
            const bool selectedValue = (i == index.row());
            parent->child(i)->setData(selectedValue, FilterCriteriaModel::SELECTED);
            static_cast<SelectionEntry *>(FilterCriteriaModel::index(i, 0, index.parent()).internalPointer())
                ->setData(selectedValue, static_cast<FilterCriteriaModel::Roles>(role));
        }
        Q_EMIT dataChanged(FilterCriteriaModel::index(0, 0, index.parent()), FilterCriteriaModel::index(parent->childCount() - 1, 0, index.parent()), {role});
        Q_ASSERT(index.row() >= 0);
        if (parent->child(index.row())->data(FilterCriteriaModel::DATA).toInt() >= 0) {
            d->mPriorityLevel = parent->child(index.row())->data(FilterCriteriaModel::DATA).toInt();
        } else {
            d->mPriorityLevel = std::nullopt;
        }
        qCDebug(KJOURNALDLIB_GENERAL) << "set priority level to:" << static_cast<qint8>(d->mPriorityLevel.value_or(-1));
        Q_EMIT priorityFilterChanged(index.row());
        Q_EMIT priorityFilterUserChanged(index.row());
    } else if (result && category == FilterCriteriaModel::Category::SYSTEMD_USER_UNIT) {
        // for checkable entries update parent's selected state
        if (value.toBool() == true) {
            setData(index.parent(), true, FilterCriteriaModel::Roles::SELECTED);
        } else {
            const auto parent = static_cast<SelectionEntry *>(index.parent().internalPointer());
            if (parent) {
                bool hasSelectedSibling{false};
                for (int i = 0; i < parent->childCount(); ++i) {
                    hasSelectedSibling = hasSelectedSibling || parent->child(i)->data(SELECTED).toBool();
                }
                setData(index.parent(), hasSelectedSibling, FilterCriteriaModel::Roles::SELECTED);
            }
        }
        Q_EMIT systemdUserUnitFilterChanged();
    } else if (result && category == FilterCriteriaModel::Category::SYSTEMD_SYSTEM_UNIT) {
        // for checkable entries update parent's selected state
        if (value.toBool() == true) {
            setData(index.parent(), true, FilterCriteriaModel::Roles::SELECTED);
        } else {
            const auto parent = static_cast<SelectionEntry *>(index.parent().internalPointer());
            if (parent) {
                bool hasSelectedSibling{false};
                for (int i = 0; i < parent->childCount(); ++i) {
                    hasSelectedSibling = hasSelectedSibling || parent->child(i)->data(SELECTED).toBool();
                }
                setData(index.parent(), hasSelectedSibling, FilterCriteriaModel::Roles::SELECTED);
            }
        }
        Q_EMIT systemdSystemUnitFilterChanged();
    } else if (result && category == FilterCriteriaModel::Category::EXE) {
        // for checkable entries update parent's selected state
        if (value.toBool() == true) {
            setData(index.parent(), true, FilterCriteriaModel::Roles::SELECTED);
        } else {
            const auto parent = static_cast<SelectionEntry *>(index.parent().internalPointer());
            if (parent) {
                bool hasSelectedSibling{false};
                for (int i = 0; i < parent->childCount(); ++i) {
                    hasSelectedSibling = hasSelectedSibling || parent->child(i)->data(SELECTED).toBool();
                }
                setData(index.parent(), hasSelectedSibling, FilterCriteriaModel::Roles::SELECTED);
            }
        }
        Q_EMIT exeFilterChanged();
    } else if (result && category == FilterCriteriaModel::Category::TRANSPORT) {
        Q_EMIT kernelFilterChanged();
    }
    return result;
}

QVector<std::pair<QString, bool>> FilterCriteriaModel::entries(FilterCriteriaModel::Category category) const
{
    QVector<std::pair<QString, bool>> values;

    for (int i = 0; i < d->mRootItem->child(static_cast<int>(category))->childCount(); ++i) {
        values.append(
            std::make_pair<QString, bool>(d->mRootItem->child(static_cast<int>(category))->child(i)->data(FilterCriteriaModel::DATA).toString(), false));
    }
    return values;
}

void FilterCriteriaModel::classBegin()
{
    d->mQmlEngineIncubationActive = true;
}

void FilterCriteriaModel::componentComplete()
{
    d->mQmlEngineIncubationActive = false;
    beginResetModel();
    d->rebuildModel();
    endResetModel();
}

#include "moc_filtercriteriamodel.cpp"
