// SPDX-FileCopyrightText: 2026 Marco Allegretti
// SPDX-License-Identifier: EUPL-1.2

#include "applicationusagemodel.h"

#include "applicationlistmodel.h"
#include "foliodelegate.h"
#include "foliosettings.h"
#include "homescreen.h"

#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>

namespace
{
constexpr qsizetype s_maxStoredEntries = 24;

QString normalizedStorageId(const QString &storageId)
{
    if (storageId.isEmpty()) {
        return {};
    }

    if (storageId.endsWith(QLatin1String(".desktop"))) {
        return storageId;
    }

    return storageId + QStringLiteral(".desktop");
}
}

ApplicationUsageStore::ApplicationUsageStore(HomeScreen *parent)
    : QObject{parent}
    , m_homeScreen{parent}
{
    load();
}

QList<ApplicationUsageEntry> ApplicationUsageStore::entries() const
{
    return m_entries.values();
}

void ApplicationUsageStore::recordUsage(const QString &storageId)
{
    const QString normalizedId = normalizedStorageId(storageId);
    if (normalizedId.isEmpty()) {
        return;
    }

    auto &entry = m_entries[normalizedId];
    entry.storageId = normalizedId;
    entry.launchCount = std::max(0, entry.launchCount) + 1;
    entry.lastUsed = QDateTime::currentDateTimeUtc();

    save();
    Q_EMIT usageChanged();
}

void ApplicationUsageStore::load()
{
    m_entries.clear();

    if (!m_homeScreen) {
        return;
    }

    const QJsonDocument doc = QJsonDocument::fromJson(m_homeScreen->folioSettings()->applicationUsage().toUtf8());
    const QJsonArray usageArray = doc.array();

    for (const QJsonValue &value : usageArray) {
        const QJsonObject object = value.toObject();
        const QString storageId = normalizedStorageId(object.value(QStringLiteral("storageId")).toString());
        if (storageId.isEmpty()) {
            continue;
        }

        ApplicationUsageEntry entry;
        entry.storageId = storageId;
        entry.launchCount = object.value(QStringLiteral("launchCount")).toInt();
        entry.lastUsed = QDateTime::fromString(object.value(QStringLiteral("lastUsed")).toString(), Qt::ISODateWithMs);

        if (entry.launchCount <= 0 || !entry.lastUsed.isValid()) {
            continue;
        }

        m_entries.insert(storageId, entry);
    }
}

void ApplicationUsageStore::save()
{
    if (!m_homeScreen) {
        return;
    }

    QList<ApplicationUsageEntry> entries = m_entries.values();
    std::sort(entries.begin(), entries.end(), [](const ApplicationUsageEntry &left, const ApplicationUsageEntry &right) {
        return left.lastUsed > right.lastUsed;
    });

    if (entries.size() > s_maxStoredEntries) {
        entries.resize(s_maxStoredEntries);
    }

    QJsonArray usageArray;
    for (const ApplicationUsageEntry &entry : std::as_const(entries)) {
        QJsonObject object;
        object.insert(QStringLiteral("storageId"), entry.storageId);
        object.insert(QStringLiteral("launchCount"), entry.launchCount);
        object.insert(QStringLiteral("lastUsed"), entry.lastUsed.toString(Qt::ISODateWithMs));
        usageArray.append(object);
    }

    m_homeScreen->folioSettings()->setApplicationUsage(QString::fromUtf8(QJsonDocument(usageArray).toJson(QJsonDocument::Compact)));
}

ApplicationUsageModel::ApplicationUsageModel(HomeScreen *homeScreen, ApplicationUsageStore *store, Mode mode)
    : QAbstractListModel{homeScreen}
    , m_homeScreen{homeScreen}
    , m_store{store}
    , m_mode{mode}
{
    if (m_store) {
        connect(m_store, &ApplicationUsageStore::usageChanged, this, &ApplicationUsageModel::rebuild);
    }

    if (m_homeScreen && m_homeScreen->applicationListModel()) {
        auto *applicationListModel = m_homeScreen->applicationListModel();
        connect(applicationListModel, &QAbstractItemModel::rowsInserted, this, &ApplicationUsageModel::rebuild);
        connect(applicationListModel, &QAbstractItemModel::rowsRemoved, this, &ApplicationUsageModel::rebuild);
        connect(applicationListModel, &QAbstractItemModel::modelReset, this, &ApplicationUsageModel::rebuild);
    }

    rebuild();
}

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

    return m_entries.size();
}

QVariant ApplicationUsageModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.size()) {
        return QVariant();
    }

    const ApplicationUsageEntry &entry = m_entries.at(index.row());
    switch (role) {
    case Qt::DisplayRole:
    case DelegateRole:
        return QVariant::fromValue(m_delegates.at(index.row()).get());
    case LaunchCountRole:
        return entry.launchCount;
    case LastUsedRole:
        return entry.lastUsed;
    default:
        return QVariant();
    }
}

QHash<int, QByteArray> ApplicationUsageModel::roleNames() const
{
    return {
        {DelegateRole, QByteArrayLiteral("delegate")},
        {LaunchCountRole, QByteArrayLiteral("launchCount")},
        {LastUsedRole, QByteArrayLiteral("lastUsed")},
    };
}

void ApplicationUsageModel::rebuild()
{
    QList<ApplicationUsageEntry> entries = m_store ? m_store->entries() : QList<ApplicationUsageEntry>{};

    std::sort(entries.begin(), entries.end(), [this](const ApplicationUsageEntry &left, const ApplicationUsageEntry &right) {
        if (m_mode == MostUsed && left.launchCount != right.launchCount) {
            return left.launchCount > right.launchCount;
        }

        return left.lastUsed > right.lastUsed;
    });

    QList<ApplicationUsageEntry> nextEntries;
    QList<std::shared_ptr<FolioDelegate>> nextDelegates;

    if (m_homeScreen && m_homeScreen->applicationListModel()) {
        for (const ApplicationUsageEntry &entry : std::as_const(entries)) {
            auto delegate = m_homeScreen->applicationListModel()->delegateForStorageId(entry.storageId);
            if (!delegate || !delegate->application()) {
                continue;
            }

            nextEntries.append(entry);
            nextDelegates.append(delegate);
        }
    }

    beginResetModel();
    m_entries = nextEntries;
    m_delegates = nextDelegates;
    endResetModel();
}