/*

  SPDX-FileCopyrightText: 2009-2024 Laurent Montel <montel@kde.org>

  SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "foldertreewidget.h"
#include "entitycollectionorderproxymodel.h"
#include "foldertreeview.h"
#include "hierarchicalfoldermatcher_p.h"
#include "kernel/mailkernel.h"

#include <PimCommon/PimUtil>
#include <PimCommonAkonadi/ImapAclAttribute>

#include <Akonadi/AttributeFactory>
#include <Akonadi/ChangeRecorder>
#include <Akonadi/EntityMimeTypeFilterModel>
#include <Akonadi/EntityTreeModel>
#include <Akonadi/ItemFetchScope>
#include <Akonadi/StatisticsProxyModel>

#include <Akonadi/ETMViewStateSaver>
#include <Akonadi/EntityTreeView>

#include <KMime/Message>

#include <MessageCore/MessageCoreSettings>

#include <KLocalizedString>

#include <QApplication>
#include <QFontDatabase>
#include <QHeaderView>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QPointer>
#include <QVBoxLayout>

using namespace MailCommon;

class Q_DECL_HIDDEN MailCommon::FolderTreeWidget::FolderTreeWidgetPrivate
{
public:
    QString filter;
    QString oldFilterStr;
    Akonadi::StatisticsProxyModel *filterModel = nullptr;
    FolderTreeView *folderTreeView = nullptr;
    FolderTreeWidgetProxyModel *readableproxy = nullptr;
    EntityCollectionOrderProxyModel *entityOrderProxy = nullptr;
    QLineEdit *filterFolderLineEdit = nullptr;
    QPointer<Akonadi::ETMViewStateSaver> saver;
    QStringList expandedItems;
    QString currentItem;
    QLabel *label = nullptr;
    bool dontKeyFilter = false;
};

FolderTreeWidget::FolderTreeWidget(QWidget *parent,
                                   KXMLGUIClient *xmlGuiClient,
                                   FolderTreeWidget::TreeViewOptions options,
                                   FolderTreeWidgetProxyModel::FolderTreeWidgetProxyModelOptions optReadableProxy)
    : QWidget(parent)
    , d(new FolderTreeWidgetPrivate())
{
    Akonadi::AttributeFactory::registerAttribute<PimCommon::ImapAclAttribute>();

    d->folderTreeView = new FolderTreeView(xmlGuiClient, this, options & ShowUnreadCount);
    d->folderTreeView->showStatisticAnimation(options & ShowCollectionStatisticAnimation);

    connect(d->folderTreeView, &FolderTreeView::manualSortingChanged, this, &FolderTreeWidget::slotManualSortingChanged);

    auto lay = new QVBoxLayout(this);
    lay->setContentsMargins({});

    d->label = new QLabel(i18n("You can start typing to filter the list of folders."), this);
    lay->addWidget(d->label);

    d->filterFolderLineEdit = new QLineEdit(this);

    d->filterFolderLineEdit->setClearButtonEnabled(true);
    d->filterFolderLineEdit->setPlaceholderText(i18nc("@info Displayed grayed-out inside the textbox, verb to search", "Search"));
    lay->addWidget(d->filterFolderLineEdit);

    if (!(options & HideStatistics)) {
        d->filterModel = new Akonadi::StatisticsProxyModel(this);
        d->filterModel->setSourceModel(KernelIf->collectionModel());
    }
    if (options & HideHeaderViewMenu) {
        d->folderTreeView->header()->setContextMenuPolicy(Qt::NoContextMenu);
    }

    d->readableproxy = new FolderTreeWidgetProxyModel(this, optReadableProxy);
    d->readableproxy->setSourceModel((options & HideStatistics) ? static_cast<QAbstractItemModel *>(KernelIf->collectionModel())
                                                                : static_cast<QAbstractItemModel *>(d->filterModel));
    d->readableproxy->addContentMimeTypeInclusionFilter(KMime::Message::mimeType());

    connect(d->folderTreeView, &FolderTreeView::changeTooltipsPolicy, this, &FolderTreeWidget::slotChangeTooltipsPolicy);

    d->folderTreeView->setSelectionMode(QAbstractItemView::SingleSelection);
    d->folderTreeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
    d->folderTreeView->installEventFilter(this);

    // Order proxy
    d->entityOrderProxy = new EntityCollectionOrderProxyModel(this);
    d->entityOrderProxy->setSourceModel(d->readableproxy);
    d->entityOrderProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
    KConfigGroup grp(KernelIf->config(), QStringLiteral("CollectionTreeOrder"));
    d->entityOrderProxy->setOrderConfig(grp);
    d->folderTreeView->setModel(d->entityOrderProxy);

    if (options & UseDistinctSelectionModel) {
        d->folderTreeView->setSelectionModel(new QItemSelectionModel(d->entityOrderProxy, this));
    }

    lay->addWidget(d->folderTreeView);

    d->dontKeyFilter = (options & DontKeyFilter);

    if ((options & UseLineEditForFiltering)) {
        connect(d->filterFolderLineEdit, &QLineEdit::textChanged, this, &FolderTreeWidget::slotFilterFixedString);
        d->label->hide();
    } else {
        d->filterFolderLineEdit->hide();
        setAttribute(Qt::WA_InputMethodEnabled);
    }
}

FolderTreeWidget::~FolderTreeWidget() = default;

void FolderTreeWidget::slotFilterFixedString(const QString &text)
{
    delete d->saver;
    if (d->oldFilterStr.isEmpty()) {
        // Save it.
        Akonadi::ETMViewStateSaver saver;
        saver.setView(folderTreeView());
        d->expandedItems = saver.expansionKeys();
        d->currentItem = saver.currentIndexKey();
    } else if (text.isEmpty()) {
        d->saver = new Akonadi::ETMViewStateSaver;
        d->saver->setView(folderTreeView());
        QString currentIndex = d->saver->currentIndexKey();
        if (d->saver->selectionKeys().isEmpty()) {
            currentIndex = d->currentItem;
        } else if (!currentIndex.isEmpty()) {
            d->expandedItems << currentIndex;
        }
        d->saver->restoreExpanded(d->expandedItems);
        d->saver->restoreCurrentItem(currentIndex);
    } else {
        d->folderTreeView->expandAll();
    }
    d->oldFilterStr = text;
    d->entityOrderProxy->setFilterWildcard(text);
}

void FolderTreeWidget::disableContextMenuAndExtraColumn()
{
    d->folderTreeView->disableContextMenuAndExtraColumn();
}

void FolderTreeWidget::selectCollectionFolder(const Akonadi::Collection &collection, bool expand)
{
    const QModelIndex index = Akonadi::EntityTreeModel::modelIndexForCollection(d->folderTreeView->model(), collection);

    d->folderTreeView->setCurrentIndex(index);
    if (expand) {
        d->folderTreeView->setExpanded(index, true);
    }
    d->folderTreeView->scrollTo(index);
}

void FolderTreeWidget::setSelectionMode(QAbstractItemView::SelectionMode mode)
{
    d->folderTreeView->setSelectionMode(mode);
}

QAbstractItemView::SelectionMode FolderTreeWidget::selectionMode() const
{
    return d->folderTreeView->selectionMode();
}

QItemSelectionModel *FolderTreeWidget::selectionModel() const
{
    return d->folderTreeView->selectionModel();
}

QModelIndex FolderTreeWidget::currentIndex() const
{
    return d->folderTreeView->currentIndex();
}

Akonadi::Collection FolderTreeWidget::selectedCollection() const
{
    if (d->folderTreeView->selectionMode() == QAbstractItemView::SingleSelection) {
        Akonadi::Collection::List lstCollection = selectedCollections();
        if (lstCollection.isEmpty()) {
            return {};
        } else {
            return lstCollection.at(0);
        }
    }

    return {};
}

Akonadi::Collection::List FolderTreeWidget::selectedCollections() const
{
    Akonadi::Collection::List collections;
    const QItemSelectionModel *selectionModel = d->folderTreeView->selectionModel();
    const QModelIndexList selectedIndexes = selectionModel->selectedIndexes();
    for (const QModelIndex &index : selectedIndexes) {
        if (index.isValid()) {
            const auto collection = index.model()->data(index, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
            if (collection.isValid()) {
                collections.append(collection);
            }
        }
    }

    return collections;
}

FolderTreeView *FolderTreeWidget::folderTreeView() const
{
    return d->folderTreeView;
}

void FolderTreeWidget::slotGeneralFontChanged()
{
    // Custom/System font support
    if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) {
        setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
    }
}

void FolderTreeWidget::slotGeneralPaletteChanged()
{
    d->readableproxy->updatePalette();
    d->folderTreeView->updatePalette();
}

void FolderTreeWidget::readConfig()
{
    setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));

    d->folderTreeView->readConfig();
    d->folderTreeView->setDropActionMenuEnabled(SettingsIf->showPopupAfterDnD());
    d->readableproxy->setWarningThreshold(SettingsIf->closeToQuotaThreshold());
    d->readableproxy->readConfig();

    KConfigGroup readerConfig(KernelIf->config(), QStringLiteral("AccountOrder"));
    QStringList listOrder;
    if (readerConfig.readEntry("EnableAccountOrder", true)) {
        listOrder = readerConfig.readEntry("order", QStringList());
    }
    d->entityOrderProxy->setTopLevelOrder(listOrder);
}

void FolderTreeWidget::restoreHeaderState(const QByteArray &data)
{
    d->folderTreeView->restoreHeaderState(data);
}

void FolderTreeWidget::slotChangeTooltipsPolicy(FolderTreeWidget::ToolTipDisplayPolicy policy)
{
    changeToolTipsPolicyConfig(policy);
}

void FolderTreeWidget::changeToolTipsPolicyConfig(ToolTipDisplayPolicy policy)
{
    switch (policy) {
    case DisplayAlways:
    case DisplayWhenTextElided: // Need to implement in the future
        if (d->filterModel) {
            d->filterModel->setToolTipEnabled(true);
        }
        break;
    case DisplayNever:
        if (d->filterModel) {
            d->filterModel->setToolTipEnabled(false);
        }
    }
    d->folderTreeView->setTooltipsPolicy(policy);
}

Akonadi::StatisticsProxyModel *FolderTreeWidget::statisticsProxyModel() const
{
    return d->filterModel;
}

FolderTreeWidgetProxyModel *FolderTreeWidget::folderTreeWidgetProxyModel() const
{
    return d->readableproxy;
}

EntityCollectionOrderProxyModel *FolderTreeWidget::entityOrderProxy() const
{
    return d->entityOrderProxy;
}

QLineEdit *FolderTreeWidget::filterFolderLineEdit() const
{
    return d->filterFolderLineEdit;
}

void FolderTreeWidget::applyFilter(const QString &filter)
{
    d->label->setText(filter.isEmpty() ? i18n("You can start typing to filter the list of folders.") : i18n("Path: (%1)", filter));

    HierarchicalFolderMatcher matcher;
    matcher.setFilter(filter, d->entityOrderProxy->filterCaseSensitivity());
    d->entityOrderProxy->setFolderMatcher(matcher);
    d->folderTreeView->expandAll();
    const QAbstractItemModel *const model = d->folderTreeView->model();
    const QModelIndex current = d->folderTreeView->currentIndex();
    const QModelIndex start = current.isValid() ? current : model->index(0, 0);
    const QModelIndex firstMatch = matcher.findFirstMatch(model, start);
    if (firstMatch.isValid()) {
        d->folderTreeView->setCurrentIndex(firstMatch);
        d->folderTreeView->scrollTo(firstMatch);
    }
}

void FolderTreeWidget::clearFilter()
{
    d->filter.clear();
    applyFilter(d->filter);
    const QModelIndexList lst = d->folderTreeView->selectionModel()->selectedIndexes();
    if (!lst.isEmpty()) {
        d->folderTreeView->scrollTo(lst.first());
    }
}

void FolderTreeWidget::slotManualSortingChanged(bool active)
{
    d->entityOrderProxy->setManualSortingActive(active);
    d->folderTreeView->setManualSortingActive(active);
}

bool FolderTreeWidget::eventFilter(QObject *o, QEvent *e)
{
    Q_UNUSED(o)
    if (d->dontKeyFilter) {
        return false;
    }

    if (e->type() == QEvent::KeyPress) {
        const QKeyEvent *const ke = static_cast<QKeyEvent *>(e);
        switch (ke->key()) {
        case Qt::Key_Backspace: {
            const int filterLength(d->filter.length());
            if (filterLength > 0) {
                d->filter.truncate(filterLength - 1);
                applyFilter(d->filter);
            }
            return false;
        }
        case Qt::Key_Delete:
            d->filter.clear();
            applyFilter(d->filter);
            return false;
        default: {
            const QString s = ke->text();
            if (!s.isEmpty() && s.at(0).isPrint()) {
                d->filter += s;
                applyFilter(d->filter);
                return false;
            }
            break;
        }
        }
    } else if (e->type() == QEvent::InputMethod) {
        const QInputMethodEvent *const ime = static_cast<QInputMethodEvent *>(e);
        d->filter += ime->commitString();
        applyFilter(d->filter);
        return false;
    }
    return false;
}

bool FolderTreeWidget::event(QEvent *e)
{
    if (e->type() == QEvent::ApplicationPaletteChange) {
        slotGeneralPaletteChanged();
    }
    return QWidget::event(e);
}

#include "moc_foldertreewidget.cpp"
