/*
 * Copyright (C) by Klaas Freitag <freitag@owncloud.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 */

#include <QtCore>
#include <QAbstractListModel>
#include <QDesktopServices>
#include <QWidget>
#include <QJsonObject>
#include <QJsonDocument>
#include <qloggingcategory.h>

#include "account.h"
#include "accountstate.h"
#include "accountmanager.h"
#include "conflictdialog.h"
#include "folderman.h"
#include "iconjob.h"
#include "accessmanager.h"
#include "owncloudgui.h"
#include "guiutility.h"
#include "invalidfilenamedialog.h"

#include "activitydata.h"
#include "activitylistmodel.h"
#include "systray.h"
#include "tray/usermodel.h"

#include "theme.h"

namespace OCC {

Q_LOGGING_CATEGORY(lcActivity, "nextcloud.gui.activity", QtInfoMsg)

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

ActivityListModel::ActivityListModel(AccountState *accountState,
    QObject *parent)
    : QAbstractListModel(parent)
    , _accountState(accountState)
{
}

QHash<int, QByteArray> ActivityListModel::roleNames() const
{
    auto roles = QAbstractListModel::roleNames();
    roles[DisplayPathRole] = "displayPath";
    roles[PathRole] = "path";
    roles[OpenablePathRole] = "openablePath";
    roles[DisplayLocationRole] = "displayLocation";
    roles[LinkRole] = "link";
    roles[MessageRole] = "message";
    roles[ActionRole] = "type";
    roles[DarkIconRole] = "darkIcon";
    roles[LightIconRole] = "lightIcon";
    roles[ActionTextRole] = "subject";
    roles[ActionsLinksRole] = "links";
    roles[ActionsLinksContextMenuRole] = "linksContextMenu";
    roles[ActionsLinksForActionButtonsRole] = "linksForActionButtons";
    roles[ActionTextColorRole] = "activityTextTitleColor";
    roles[ObjectTypeRole] = "objectType";
    roles[ObjectIdRole] = "objectId";
    roles[ObjectNameRole] = "objectName";
    roles[PointInTimeRole] = "dateTime";
    roles[DisplayActions] = "displayActions";
    roles[ShareableRole] = "isShareable";
    roles[IsCurrentUserFileActivityRole] = "isCurrentUserFileActivity";
    roles[IsCurrentUserFileActivityRole] = "isCurrentUserFileActivity";
    roles[ThumbnailRole] = "thumbnail";
    roles[TalkNotificationConversationTokenRole] = "conversationToken";
    roles[TalkNotificationMessageIdRole] = "messageId";
    roles[TalkNotificationMessageSentRole] = "messageSent";
    roles[TalkNotificationUserAvatarRole] = "userAvatar";

    return roles;
}

void ActivityListModel::setAccountState(AccountState *state)
{
    _accountState = state;
}

void ActivityListModel::setCurrentItem(const int currentItem)
{
    _currentItem = currentItem;
}

void ActivityListModel::setAndRefreshCurrentlyFetching(bool value)
{
    if (_currentlyFetching == value) {
        return;
    }
    _currentlyFetching = value;
    insertOrRemoveDummyFetchingActivity();
}

bool ActivityListModel::currentlyFetching() const
{
    return _currentlyFetching;
}

void ActivityListModel::setDoneFetching(bool value)
{
    _doneFetching = value;
}

void ActivityListModel::setHideOldActivities(bool value)
{
    _hideOldActivities = value;
}

void ActivityListModel::setDisplayActions(bool value)
{
    _displayActions = value;
}

QVariant ActivityListModel::data(const QModelIndex &index, int role) const
{
    Activity a;

    if (!index.isValid())
        return QVariant();

    a = _finalList.at(index.row());
    AccountStatePtr ast = AccountManager::instance()->account(a._accName);
    if (!ast && _accountState != ast.data())
        return QVariant();

    const auto getFilePath = [&]() {
        const auto fileName = a._fileAction == QStringLiteral("file_renamed") ? a._renamedFile : a._file;
        if (!fileName.isEmpty()) {
            const auto folder = FolderMan::instance()->folder(a._folder);

            const QString relPath = folder ? folder->remotePath() + fileName : fileName;

            const auto localFiles = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account());

            if (localFiles.isEmpty()) {
                return QString();
            }

            // If this is an E2EE file or folder, pretend we got no path, hiding the share button which is what we want
            if (folder) {
                SyncJournalFileRecord rec;
                folder->journalDb()->getFileRecord(fileName.mid(1), &rec);
                if (rec.isValid() && (rec._isE2eEncrypted || !rec._e2eMangledName.isEmpty())) {
                    return QString();
                }
            }

            return localFiles.constFirst();
        }
        return QString();
    };

    const auto getDisplayPath = [&a, &ast]() {
        if (!a._file.isEmpty()) {
            const auto folder = FolderMan::instance()->folder(a._folder);

            QString relPath = folder ? folder->remotePath() + a._file : a._file;

            const auto localFiles = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account());

            if (localFiles.count() > 0) {
                if (relPath.startsWith('/') || relPath.startsWith('\\')) {
                    return relPath.remove(0, 1);
                } else {
                    return relPath;
                }
            }
        }
        return QString();
    };

    const auto displayLocation = [&]() {
        const auto displayPath = QFileInfo(getDisplayPath()).path();
        return displayPath == "." || displayPath == "/" ? QString() : displayPath;
    };

    const auto generatePreviewMap = [](const PreviewData &preview) {
        return(QVariantMap {
            {QStringLiteral("source"), QStringLiteral("image://tray-image-provider/").append(preview._source)},
               {QStringLiteral("link"), preview._link},
               {QStringLiteral("mimeType"), preview._mimeType},
               {QStringLiteral("fileId"), preview._fileId},
               {QStringLiteral("view"), preview._view},
               {QStringLiteral("isMimeTypeIcon"), preview._isMimeTypeIcon},
               {QStringLiteral("filename"), preview._filename},
               {QStringLiteral("isUserAvatar"), false},
        });
    };

    const auto generateAvatarThumbnailMap = [](const QString &avatarThumbnailUrl) {
        return QVariantMap {
            {QStringLiteral("source"), avatarThumbnailUrl},
            {QStringLiteral("isMimeTypeIcon"), false},
            {QStringLiteral("isUserAvatar"), true},
        };
    };

    const auto generateIconPath = [&]() {
        auto colorIconPath = role == DarkIconRole ? QStringLiteral("qrc:///client/theme/white/") : QStringLiteral("qrc:///client/theme/black/");
        if (a._type == Activity::NotificationType && !a._talkNotificationData.userAvatar.isEmpty()) {
            return QStringLiteral("qrc:///client/theme/colored/talk-bordered.svg");
        } else if (a._type == Activity::SyncResultType) {
            colorIconPath.append("state-error.svg");
            return colorIconPath;
        } else if (a._type == Activity::SyncFileItemType) {
            if (a._status == SyncFileItem::NormalError
                || a._status == SyncFileItem::FatalError
                || a._status == SyncFileItem::DetailError
                || a._status == SyncFileItem::BlacklistedError) {
                colorIconPath.append("state-error.svg");
                return colorIconPath;
            } else if (a._status == SyncFileItem::SoftError
                || a._status == SyncFileItem::Conflict
                || a._status == SyncFileItem::Restoration
                || a._status == SyncFileItem::FileLocked
                || a._status == SyncFileItem::FileNameInvalid) {
                colorIconPath.append("state-warning.svg");
                return colorIconPath;
            } else if (a._status == SyncFileItem::FileIgnored) {
                colorIconPath.append("state-info.svg");
                return colorIconPath;
            } else {
                // File sync successful
                if (a._fileAction == "file_created") {
                    return a._previews.empty() ? QStringLiteral("qrc:///client/theme/colored/add.svg")
                                               : QStringLiteral("qrc:///client/theme/colored/add-bordered.svg");
                } else if (a._fileAction == "file_deleted") {
                    return a._previews.empty() ? QStringLiteral("qrc:///client/theme/colored/delete.svg")
                                               : QStringLiteral("qrc:///client/theme/colored/delete-bordered.svg");
                } else {
                    return a._previews.empty() ? colorIconPath % QStringLiteral("change.svg")
                                               : QStringLiteral("qrc:///client/theme/colored/change-bordered.svg");
                }
            }
        } else {
            // We have an activity
            if (a._icon.isEmpty()) {
                colorIconPath.append("activity.svg");
                return colorIconPath;
            }

            const QString basePath = QStringLiteral("image://tray-image-provider/") % a._icon % QStringLiteral("/");
            return role == DarkIconRole ? QString(basePath + QStringLiteral("white")) : QString(basePath + QStringLiteral("black"));
        }
    };

    switch (role) {
    case DisplayPathRole:
        return getDisplayPath();
    case PathRole:
        return QFileInfo(getFilePath()).path();
    case OpenablePathRole:
        return a._isMultiObjectActivity ? QFileInfo(getFilePath()).canonicalPath() : QFileInfo(getFilePath()).canonicalFilePath();
    case DisplayLocationRole:
        return displayLocation();
    case ActionsLinksRole: {
        QList<QVariant> customList;
        foreach (ActivityLink activityLink, a._links) {
            customList << QVariant::fromValue(activityLink);
        }
        return customList;
    }

    case ActionsLinksContextMenuRole: {
        return ActivityListModel::convertLinksToMenuEntries(a);
    }
    
    case ActionsLinksForActionButtonsRole: {
        return ActivityListModel::convertLinksToActionButtons(a);
    }

    case DarkIconRole:
    case LightIconRole:
        return generateIconPath();
    case ObjectTypeRole:
        return a._objectType;
    case ObjectIdRole:
        return a._objectId;
    case ObjectNameRole:
        return a._objectName;
    case ActionRole: {
        switch (a._type) {
        case Activity::ActivityType:
            return "Activity";
        case Activity::NotificationType:
            return "Notification";
        case Activity::SyncFileItemType:
            return "File";
        case Activity::SyncResultType:
            return "Sync";
        default:
            return QVariant();
        }
    }
    case ActionTextRole:
        if(a._subjectDisplay.isEmpty()) {
            return a._subject;
        }

        return a._subjectDisplay;
    case ActionTextColorRole:
        return a._id == -1 ? QLatin1String("#808080") : QLatin1String("#222");   // FIXME: This is a temporary workaround for _showMoreActivitiesAvailableEntry
    case MessageRole:
        return a._message;
    case LinkRole: {
        if (a._link.isEmpty()) {
            return "";
        } else {
            return a._link.toString();
        }
    }
    case AccountRole:
        return a._accName;
    case PointInTimeRole:
        //return a._id == -1 ? "" : QString("%1 - %2").arg(Utility::timeAgoInWords(a._dateTime.toLocalTime()), a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate));
        return a._id == -1 ? "" : Utility::timeAgoInWords(a._dateTime.toLocalTime());
    case AccountConnectedRole:
        return (ast && ast->isConnected());
    case DisplayActions:
        return _displayActions;
    case ShareableRole:
        return !data(index, PathRole).toString().isEmpty() && a._objectType == QStringLiteral("files") && _displayActions && a._fileAction != "file_deleted" && a._status != SyncFileItem::FileIgnored;
    case IsCurrentUserFileActivityRole:
        return a._isCurrentUserFileActivity;
    case ThumbnailRole: {
        if (a._type == Activity::NotificationType && !a._talkNotificationData.userAvatar.isEmpty()) {
            return generateAvatarThumbnailMap(a._talkNotificationData.userAvatar);
        }

        if(a._previews.empty()) {
            return {};
        }

        const auto preview = a._previews[0];
        return(generatePreviewMap(preview));
    }
    case TalkNotificationConversationTokenRole:
        return a._talkNotificationData.conversationToken;
    case TalkNotificationMessageIdRole:
        return a._talkNotificationData.messageId;
    case TalkNotificationMessageSentRole:
        return replyMessageSent(a);
    case TalkNotificationUserAvatarRole:
        return a._talkNotificationData.userAvatar;
    default:
        return QVariant();
    }
    return QVariant();
}

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

    return _finalList.count();
}

bool ActivityListModel::canFetchMore(const QModelIndex &) const
{
    // We need to be connected to be able to fetch more
    if (_accountState && _accountState->isConnected() && Systray::instance()->isOpen()) {
        // If the fetching is reported to be done or we are currently fetching we can't fetch more
        if (!_doneFetching && !currentlyFetching()) {
            return true;
        }
    }

    return false;
}

void ActivityListModel::startFetchJob()
{
    if (!_accountState->isConnected() || currentlyFetching()) {
        return;
    }
    auto *job = new JsonApiJob(_accountState->account(), QLatin1String("ocs/v2.php/apps/activity/api/v2/activity"), this);
    QObject::connect(job, &JsonApiJob::jsonReceived,
        this, &ActivityListModel::activitiesReceived);

    QUrlQuery params;
    params.addQueryItem(QLatin1String("previews"), QLatin1String("true"));
    params.addQueryItem(QLatin1String("since"), QString::number(_currentItem));
    params.addQueryItem(QLatin1String("limit"), QString::number(50));
    job->addQueryParams(params);

    setAndRefreshCurrentlyFetching(true);
    qCInfo(lcActivity) << "Start fetching activities for " << _accountState->account()->displayName();
    job->start();
}

void ActivityListModel::setFinalList(const ActivityList &finalList)
{
    _finalList = finalList;
}

const ActivityList &ActivityListModel::finalList() const
{
    return _finalList;
}

int ActivityListModel::currentItem() const
{
    return _currentItem;
}

void ActivityListModel::ingestActivities(const QJsonArray &activities)
{
    ActivityList list;

    QDateTime oldestDate = QDateTime::currentDateTime();
    oldestDate = oldestDate.addDays(static_cast<qint64>(_maxActivitiesDays) * -1);

    for (const auto &activ : activities) {
        const auto json = activ.toObject();

        auto a = Activity::fromActivityJson(json, _accountState->account());

        list.append(a);
        _currentItem = list.last()._id;

        _totalActivitiesFetched++;
        if (_totalActivitiesFetched == _maxActivities
            || (_hideOldActivities && a._dateTime < oldestDate)) {
            _showMoreActivitiesAvailableEntry = true;
            _doneFetching = true;
            break;
        }
    }

    _activityLists.append(list);

    if (list.size() > 0) {
        std::sort(list.begin(), list.end());
        beginInsertRows({}, _finalList.size(), _finalList.size() + list.size() - 1);
        _finalList.append(list);
        endInsertRows();

        appendMoreActivitiesAvailableEntry();
    }
}

void ActivityListModel::appendMoreActivitiesAvailableEntry()
{
    const QString moreActivitiesEntryObjectType = QLatin1String("activity_fetch_more_activities");
    if (_showMoreActivitiesAvailableEntry && !_finalList.isEmpty()
        && _finalList.last()._objectType != moreActivitiesEntryObjectType) {
        Activity a;
        a._type = Activity::ActivityType;
        a._accName = _accountState->account()->displayName();
        a._id = -1;
        a._objectType = moreActivitiesEntryObjectType;
        a._subject = tr("For more activities please open the Activity app.");
        a._dateTime = QDateTime::currentDateTime();

        if (const auto *app = _accountState->findApp(QLatin1String("activity"))) {
            a._link = app->url();
        }

        beginInsertRows({}, _finalList.size(), _finalList.size());
        _finalList.append(a);
        endInsertRows();
    }
}

void ActivityListModel::insertOrRemoveDummyFetchingActivity()
{
    const QString dummyFetchingActivityObjectType = QLatin1String("dummy_fetching_activity");
    if (_currentlyFetching && _finalList.isEmpty()) {
        Activity a;
        a._type = Activity::ActivityType;
        a._accName = _accountState->account()->displayName();
        a._id = -2;
        a._objectType = dummyFetchingActivityObjectType;
        a._subject = tr("Fetching activities …");
        a._dateTime = QDateTime::currentDateTime();
        a._icon = QLatin1String("qrc:///client/theme/colored/change-bordered.svg");

        beginInsertRows({}, 0, 0);
        _finalList.prepend(a);
        endInsertRows();
    } else if (!_finalList.isEmpty() && _finalList.first()._objectType == dummyFetchingActivityObjectType) {
        beginRemoveRows({}, 0, 0);
        _finalList.removeAt(0);
        endRemoveRows();
    }
}

void ActivityListModel::clearActivities()
{
    _activityLists.clear();
    if (!_finalList.isEmpty()) {
        const auto firstActivityIt = std::find_if(std::begin(_finalList), std::end(_finalList),
            [&](const Activity &activity) { return activity._type == Activity::ActivityType; });

        if (firstActivityIt != std::end(_finalList)) {
            const auto lastActivityItReverse = std::find_if(std::rbegin(_finalList), std::rend(_finalList),
                    [&](const Activity &activity) { return activity._type == Activity::ActivityType; });

            const auto lastActivityIt = (lastActivityItReverse + 1).base();

            if (lastActivityIt != std::end(_finalList)) {
                const int beginRemoveIndex = std::distance(std::begin(_finalList), firstActivityIt);
                const int endRemoveIndex = std::distance(std::begin(_finalList), lastActivityIt);
                beginRemoveRows({}, beginRemoveIndex, endRemoveIndex);
                _finalList.erase(firstActivityIt, std::end(_finalList));
                endRemoveRows();
            }
        }
    }
}

void ActivityListModel::activitiesReceived(const QJsonDocument &json, int statusCode)
{
    const auto activities = json.object().value(QStringLiteral("ocs")).toObject().value(QStringLiteral("data")).toArray();

    if (!_accountState) {
        return;
    }

    if (activities.empty()) {
        _doneFetching = true;
    }

    setAndRefreshCurrentlyFetching(false);

    ingestActivities(activities);

    emit activityJobStatusCode(statusCode);
}

void ActivityListModel::addErrorToActivityList(Activity activity)
{
    qCInfo(lcActivity) << "Error successfully added to the notification list: " << activity._subject;
    _notificationErrorsLists.prepend(activity);
    combineActivityLists();
}

void ActivityListModel::addIgnoredFileToList(Activity newActivity)
{
    qCInfo(lcActivity) << "First checking for duplicates then add file to the notification list of ignored files: " << newActivity._file;

    bool duplicate = false;
    if (_listOfIgnoredFiles.size() == 0) {
        _notificationIgnoredFiles = newActivity;
        _notificationIgnoredFiles._subject = tr("Files from the ignore list as well as symbolic links are not synced.");
        _listOfIgnoredFiles.append(newActivity);
        return;
    }

    foreach (Activity activity, _listOfIgnoredFiles) {
        if (activity._file == newActivity._file) {
            duplicate = true;
            break;
        }
    }

    if (!duplicate) {
        _notificationIgnoredFiles._message.append(", " + newActivity._file);
    }
}

void ActivityListModel::addNotificationToActivityList(Activity activity)
{
    qCInfo(lcActivity) << "Notification successfully added to the notification list: " << activity._subject;
    _notificationLists.prepend(activity);
    combineActivityLists();
}

void ActivityListModel::clearNotifications()
{
    qCInfo(lcActivity) << "Clear the notifications";
    _notificationLists.clear();
    combineActivityLists();
}

void ActivityListModel::removeActivityFromActivityList(int row)
{
    Activity activity = _finalList.at(row);
    removeActivityFromActivityList(activity);
    combineActivityLists();
}

void ActivityListModel::addSyncFileItemToActivityList(Activity activity)
{
    qCInfo(lcActivity) << "Successfully added to the activity list: " << activity._subject;
    _syncFileItemLists.prepend(activity);
    combineActivityLists();
}

void ActivityListModel::removeActivityFromActivityList(Activity activity)
{
    qCInfo(lcActivity) << "Activity/Notification/Error successfully dismissed: " << activity._subject;
    qCInfo(lcActivity) << "Trying to remove Activity/Notification/Error from view... ";

    int index = -1;
    if (activity._type == Activity::ActivityType) {
        index = _activityLists.indexOf(activity);
        if (index != -1) {
            _activityLists.removeAt(index);
            const auto indexInFinalList = _finalList.indexOf(activity);
            if (indexInFinalList != -1) {
                beginRemoveRows({}, indexInFinalList, indexInFinalList);
                _finalList.removeAt(indexInFinalList);
                endRemoveRows();
            }
        }
    } else if (activity._type == Activity::NotificationType) {
        index = _notificationLists.indexOf(activity);
        if (index != -1)
            _notificationLists.removeAt(index);
    } else {
        index = _notificationErrorsLists.indexOf(activity);
        if (index != -1)
            _notificationErrorsLists.removeAt(index);
    }

    if (index != -1) {
        qCInfo(lcActivity) << "Activity/Notification/Error successfully removed from the list.";
        qCInfo(lcActivity) << "Updating Activity/Notification/Error view.";
        combineActivityLists();
    }
}

void ActivityListModel::slotTriggerDefaultAction(const int activityIndex)
{
    if (activityIndex < 0 || activityIndex >= _finalList.size()) {
        qCWarning(lcActivity) << "Couldn't trigger default action at index" << activityIndex << "/ final list size:" << _finalList.size();
        return;
    }

    const auto modelIndex = index(activityIndex);
    const auto path = data(modelIndex, PathRole).toString();

    const auto activity = _finalList.at(activityIndex);
    if (activity._status == SyncFileItem::Conflict) {
        Q_ASSERT(!activity._file.isEmpty());
        Q_ASSERT(!activity._folder.isEmpty());
        Q_ASSERT(Utility::isConflictFile(activity._file));

        const auto folder = FolderMan::instance()->folder(activity._folder);

        const auto conflictedRelativePath = activity._file;
        const auto baseRelativePath = folder->journalDb()->conflictFileBaseName(conflictedRelativePath.toUtf8());

        const auto dir = QDir(folder->path());
        const auto conflictedPath = dir.filePath(conflictedRelativePath);
        const auto basePath = dir.filePath(baseRelativePath);

        const auto baseName = QFileInfo(basePath).fileName();

        if (!_currentConflictDialog.isNull()) {
            _currentConflictDialog->close();
        }
        _currentConflictDialog = new ConflictDialog;
        _currentConflictDialog->setBaseFilename(baseName);
        _currentConflictDialog->setLocalVersionFilename(conflictedPath);
        _currentConflictDialog->setRemoteVersionFilename(basePath);
        _currentConflictDialog->setAttribute(Qt::WA_DeleteOnClose);
        connect(_currentConflictDialog, &ConflictDialog::accepted, folder, [folder]() {
            folder->scheduleThisFolderSoon();
        });
        _currentConflictDialog->open();
        ownCloudGui::raiseDialog(_currentConflictDialog);
        return;
    } else if (activity._status == SyncFileItem::FileNameInvalid) {
        if (!_currentInvalidFilenameDialog.isNull()) {
            _currentInvalidFilenameDialog->close();
        }

        auto folder = FolderMan::instance()->folder(activity._folder);
        const auto folderDir = QDir(folder->path());
        _currentInvalidFilenameDialog = new InvalidFilenameDialog(_accountState->account(), folder,
            folderDir.filePath(activity._file));
        connect(_currentInvalidFilenameDialog, &InvalidFilenameDialog::accepted, folder, [folder]() {
            folder->scheduleThisFolderSoon();
        });
        connect(_currentInvalidFilenameDialog, &InvalidFilenameDialog::acceptedInvalidName, folder, [folder](const QString& filePath) {
            folder->acceptInvalidFileName(filePath);
            folder->scheduleThisFolderSoon();
        });
        _currentInvalidFilenameDialog->open();
        ownCloudGui::raiseDialog(_currentInvalidFilenameDialog);
        return;
    }

    if (!path.isEmpty()) {
        QDesktopServices::openUrl(QUrl::fromLocalFile(path));
    } else {
        const auto link = data(modelIndex, LinkRole).toUrl();
        Utility::openBrowser(link);
    }
}

void ActivityListModel::slotTriggerAction(const int activityIndex, const int actionIndex)
{
    if (activityIndex < 0 || activityIndex >= _finalList.size()) {
        qCWarning(lcActivity) << "Couldn't trigger action on activity at index" << activityIndex << "/ final list size:" << _finalList.size();
        return;
    }

    const auto activity = _finalList[activityIndex];

    if (actionIndex < 0 || actionIndex >= activity._links.size()) {
        qCWarning(lcActivity) << "Couldn't trigger action at index" << actionIndex << "/ actions list size:" << activity._links.size();
        return;
    }

    const auto action = activity._links[actionIndex];

    if (action._verb == "WEB") {
        Utility::openBrowser(QUrl(action._link));
        return;
    }

    emit sendNotificationRequest(activity._accName, action._link, action._verb, activityIndex);
}

void ActivityListModel::slotTriggerDismiss(const int activityIndex)
{
    if (activityIndex < 0 || activityIndex >= _finalList.size()) {
        qCWarning(lcActivity) << "Couldn't trigger action on activity at index" << activityIndex << "/ final list size:" << _finalList.size();
        return;
    }

    const auto activityLinks = _finalList[activityIndex]._links;

    const auto foundActivityLinkIt = std::find_if(std::cbegin(activityLinks), std::cend(activityLinks), [](const ActivityLink &link) {
        return link._verb == QStringLiteral("DELETE");
    });

    if (foundActivityLinkIt == std::cend(activityLinks)) {
        qCWarning(lcActivity) << "Couldn't find dismiss action in activity at index" << activityIndex
                              << " links.size() " << activityLinks.size();
        return;
    }

    const auto actionIndex = static_cast<int>(std::distance(activityLinks.begin(), foundActivityLinkIt));

    if (actionIndex < 0 || actionIndex > activityLinks.size()) {
        qCWarning(lcActivity) << "Couldn't find dismiss action in activity at index" << activityIndex
                              << " actionIndex found " << actionIndex;
        return;
    }

    slotTriggerAction(activityIndex, actionIndex);
}

AccountState *ActivityListModel::accountState() const
{
    return _accountState;
}

QVariantList ActivityListModel::convertLinksToActionButtons(const Activity &activity)
{
    QVariantList customList;

    if (activity._links.size() == 1) {
        return customList;
    }

    if (static_cast<quint32>(activity._links.size()) > maxActionButtons()) {
        customList << ActivityListModel::convertLinkToActionButton(activity._links.first());
        return customList;
    }

    for (const auto &activityLink : activity._links) {
        if (activityLink._primary
            || activityLink._verb == QStringLiteral("DELETE")
            || activityLink._verb == QStringLiteral("WEB")) {
            customList << ActivityListModel::convertLinkToActionButton(activityLink);
        }
    }

    return customList;
}

QVariant ActivityListModel::convertLinkToActionButton(const OCC::ActivityLink &activityLink)
{
    auto activityLinkCopy = activityLink;

    const auto isReplyIconApplicable = activityLink._verb == QStringLiteral("REPLY");

    const QString replyButtonPath = QStringLiteral("image://svgimage-custom-color/reply.svg");

    if (isReplyIconApplicable) {
        activityLinkCopy._imageSource = QString(replyButtonPath + "/");
        activityLinkCopy._imageSourceHovered = QString(replyButtonPath + "/");
    }

    return QVariant::fromValue(activityLinkCopy);
}

QVariantList ActivityListModel::convertLinksToMenuEntries(const Activity &activity)
{
    QVariantList customList;

    if (static_cast<quint32>(activity._links.size()) > maxActionButtons()) {
        for (int i = 0; i < activity._links.size(); ++i) {
            const auto &activityLink = activity._links[i];
            if (!activityLink._primary) {
                customList << QVariantMap{
                    {QStringLiteral("actionIndex"), i}, {QStringLiteral("label"), activityLink._label}};
            }
        }
    }

    return customList;
}

void ActivityListModel::combineActivityLists()
{
    ActivityList resultList;

    if (_notificationErrorsLists.count() > 0) {
        std::sort(_notificationErrorsLists.begin(), _notificationErrorsLists.end());
        resultList.append(_notificationErrorsLists);
    }
    if (_listOfIgnoredFiles.size() > 0)
        resultList.append(_notificationIgnoredFiles);

    if (_notificationLists.count() > 0) {
        std::sort(_notificationLists.begin(), _notificationLists.end());
        resultList.append(_notificationLists);
    }

    if (_syncFileItemLists.count() > 0) {
        std::sort(_syncFileItemLists.begin(), _syncFileItemLists.end());
        resultList.append(_syncFileItemLists);
    }

    if (_activityLists.count() > 0) {
        std::sort(_activityLists.begin(), _activityLists.end());
        resultList.append(_activityLists);
    }

    if (_finalList.isEmpty() && !resultList.isEmpty()) {
        beginInsertRows({}, 0, resultList.size() - 1);
        _finalList = resultList;
        endInsertRows();
    } else if (!_finalList.isEmpty()) {
        beginResetModel();
        _finalList = resultList;
        endResetModel();
    }

    if (_activityLists.size() > 0) {
        appendMoreActivitiesAvailableEntry();
    }
}

bool ActivityListModel::canFetchActivities() const
{
    return _accountState->isConnected() && _accountState->account()->capabilities().hasActivities();
}

void ActivityListModel::fetchMore(const QModelIndex &)
{
    if (canFetchActivities()) {
        startFetchJob();
    }
}

void ActivityListModel::slotRefreshActivity()
{
    clearActivities();
    _doneFetching = false;
    _currentItem = 0;
    _totalActivitiesFetched = 0;
    _showMoreActivitiesAvailableEntry = false;

    if (canFetchActivities()) {
        startFetchJob();
    } else {
        _doneFetching = true;
        combineActivityLists();
    }
}

void ActivityListModel::slotRefreshActivityInitial()
{
    if (_activityLists.isEmpty() && !currentlyFetching()) {
        slotRefreshActivity();
    }
}

void ActivityListModel::slotRemoveAccount()
{
    _finalList.clear();
    _activityLists.clear();
    setAndRefreshCurrentlyFetching(false);
    _doneFetching = false;
    _currentItem = 0;
    _totalActivitiesFetched = 0;
    _showMoreActivitiesAvailableEntry = false;
}

void ActivityListModel::setReplyMessageSent(const int activityIndex, const QString &message)
{
    if (activityIndex < 0 || activityIndex >= _finalList.size()) {
        qCWarning(lcActivity) << "Couldn't trigger action on activity at index" << activityIndex << "/ final list size:" << _finalList.size();
        return;
    }

    _finalList[activityIndex]._talkNotificationData.messageSent = message;

    emit dataChanged(index(activityIndex, 0), index(activityIndex, 0), {ActivityListModel::TalkNotificationMessageSentRole});
}

QString ActivityListModel::replyMessageSent(const Activity &activity) const
{
    return activity._talkNotificationData.messageSent;
}
}
