/*
 * This file is part of KMail.
 * SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
 *
 * Parts based on KMail code by:
 * Various authors.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "attachmentcontrollerbase.h"

#include "editor/attachment/attachmentclipboardjob.h"
#include "editor/attachment/attachmentcompressjob.h"
#include "editor/attachment/attachmentfrompublickeyjob.h"
#include "editor/attachment/attachmentfromurlbasejob.h"
#include "editor/attachment/attachmentfromurlutils.h"
#include "editor/attachment/attachmentjob.h"
#include "editor/attachment/attachmentmodel.h"
#include "editor/attachment/attachmentpropertiesdialog.h"
#include "editor/attachment/attachmentupdatejob.h"
#include "editor/job/composerjob.h"
#include "editor/part/globalpart.h"
#include "editor_debug.h"

#include <QAction>
#include <QActionGroup>
#include <QDesktopServices>
#include <QFileDialog>
#include <QIcon>
#include <QMenu>
#include <QMimeDatabase>
#include <QPointer>
#include <QPushButton>
#include <QSaveFile>
#include <QTemporaryFile>
#include <QTreeView>
#include <QUrlQuery>

#include <KActionCollection>
#include <KActionMenu>
#include <KJob>
#include <KLocalizedString>
#include <KMessageBox>
#include <KMime/Content>

#include <Libkleo/KeySelectionDialog>
#include <QGpgME/Protocol>

using namespace MessageComposer;
using namespace MessageCore;

class MessageComposer::AttachmentControllerBase::AttachmentControllerBasePrivate
{
public:
    AttachmentControllerBasePrivate(AttachmentControllerBase *qq);
    ~AttachmentControllerBasePrivate();

    void attachmentRemoved(const AttachmentPart::Ptr &part); // slot
    void compressJobResult(KJob *job); // slot
    void loadJobResult(KJob *job); // slot
    void openSelectedAttachments(); // slot
    void viewSelectedAttachments(); // slot
    void removeSelectedAttachments(); // slot
    void saveSelectedAttachmentAs(); // slot
    void selectedAttachmentProperties(); // slot
    void attachPublicKeyJobResult(KJob *job); // slot
    void slotAttachmentContentCreated(KJob *job); // slot
    void addAttachmentPart(AttachmentPart::Ptr part);
    void attachClipBoardElement(KJob *job);
    void selectedAllAttachment();
    void reloadAttachment();
    void updateJobResult(KJob *);

    AttachmentPart::List selectedParts;
    AttachmentControllerBase *const q;
    MessageComposer::AttachmentModel *model = nullptr;
    QWidget *wParent = nullptr;

    KActionCollection *mActionCollection = nullptr;
    QAction *attachPublicKeyAction = nullptr;
    QAction *attachMyPublicKeyAction = nullptr;
    QAction *openContextAction = nullptr;
    QAction *viewContextAction = nullptr;
    QAction *removeAction = nullptr;
    QAction *removeContextAction = nullptr;
    QAction *saveAsAction = nullptr;
    QAction *saveAsContextAction = nullptr;
    QAction *propertiesAction = nullptr;
    QAction *propertiesContextAction = nullptr;
    QAction *addAttachmentFileAction = nullptr;
    QAction *addAttachmentDirectoryAction = nullptr;
    QAction *addContextAction = nullptr;
    QAction *selectAllAction = nullptr;
    KActionMenu *attachmentMenu = nullptr;
    QAction *reloadAttachmentAction = nullptr;
    QAction *attachClipBoardAction = nullptr;
    // If part p is compressed, uncompressedParts[p] is the uncompressed part.
    QHash<AttachmentPart::Ptr, AttachmentPart::Ptr> uncompressedParts;
    bool encryptEnabled = false;
    bool signEnabled = false;
};

AttachmentControllerBase::AttachmentControllerBasePrivate::AttachmentControllerBasePrivate(AttachmentControllerBase *qq)
    : q(qq)
{
}

AttachmentControllerBase::AttachmentControllerBasePrivate::~AttachmentControllerBasePrivate() = default;

void AttachmentControllerBase::setSelectedParts(const AttachmentPart::List &selectedParts)
{
    d->selectedParts = selectedParts;
    const int selectedCount = selectedParts.count();

    d->openContextAction->setEnabled(selectedCount > 0);
    d->viewContextAction->setEnabled(selectedCount > 0);
    d->removeAction->setEnabled(selectedCount > 0);
    d->removeContextAction->setEnabled(selectedCount > 0);
    d->saveAsAction->setEnabled(selectedCount == 1);
    d->saveAsContextAction->setEnabled(selectedCount == 1);
    d->propertiesAction->setEnabled(selectedCount == 1);
    d->propertiesContextAction->setEnabled(selectedCount == 1);
}

void AttachmentControllerBase::AttachmentControllerBasePrivate::attachmentRemoved(const AttachmentPart::Ptr &part)
{
    uncompressedParts.remove(part);
}

void AttachmentControllerBase::AttachmentControllerBasePrivate::compressJobResult(KJob *job)
{
    if (job->error()) {
        KMessageBox::error(wParent, job->errorString(), i18nc("@title:window", "Failed to compress attachment"));
        return;
    }

    auto ajob = qobject_cast<AttachmentCompressJob *>(job);
    Q_ASSERT(ajob);
    AttachmentPart::Ptr originalPart = ajob->originalPart();
    AttachmentPart::Ptr compressedPart = ajob->compressedPart();

    if (ajob->isCompressedPartLarger()) {
        const int result = KMessageBox::questionTwoActions(wParent,
                                                           i18n("The compressed attachment is larger than the original. "
                                                                "Do you want to keep the original one?"),
                                                           QString(/*caption*/),
                                                           KGuiItem(i18nc("Do not compress", "Keep")),
                                                           KGuiItem(i18n("Compress")));
        if (result == KMessageBox::ButtonCode::PrimaryAction) {
            // The user has chosen to keep the uncompressed file.
            return;
        }
    }

    qCDebug(EDITOR_LOG) << "Replacing uncompressed part in model.";
    uncompressedParts[compressedPart] = originalPart;
    bool ok = model->replaceAttachment(originalPart, compressedPart);
    if (!ok) {
        // The attachment was removed from the model while we were compressing.
        qCDebug(EDITOR_LOG) << "Compressed a zombie.";
    }
}

void AttachmentControllerBase::AttachmentControllerBasePrivate::loadJobResult(KJob *job)
{
    if (job->error()) {
        KMessageBox::error(wParent, job->errorString(), i18n("Failed to attach file"));
        return;
    }

    auto ajob = qobject_cast<AttachmentLoadJob *>(job);
    Q_ASSERT(ajob);
    AttachmentPart::Ptr part = ajob->attachmentPart();
    q->addAttachment(part);
}

void AttachmentControllerBase::AttachmentControllerBasePrivate::openSelectedAttachments()
{
    Q_ASSERT(selectedParts.count() >= 1);
    for (const AttachmentPart::Ptr &part : std::as_const(selectedParts)) {
        q->openAttachment(part);
    }
}

void AttachmentControllerBase::AttachmentControllerBasePrivate::viewSelectedAttachments()
{
    Q_ASSERT(selectedParts.count() >= 1);
    for (const AttachmentPart::Ptr &part : std::as_const(selectedParts)) {
        q->viewAttachment(part);
    }
}

void AttachmentControllerBase::AttachmentControllerBasePrivate::removeSelectedAttachments()
{
    Q_ASSERT(selectedParts.count() >= 1);
    // We must store list, otherwise when we remove it changes selectedParts (as selection changed) => it will crash.
    const AttachmentPart::List toRemove = selectedParts;
    for (const AttachmentPart::Ptr &part : toRemove) {
        if (!model->removeAttachment(part)) {
            qCWarning(EDITOR_LOG) << "Impossible to remove attachment" << part->fileName();
        }
    }
}

void AttachmentControllerBase::AttachmentControllerBasePrivate::saveSelectedAttachmentAs()
{
    Q_ASSERT(selectedParts.count() == 1);
    q->saveAttachmentAs(selectedParts.constFirst());
}

void AttachmentControllerBase::AttachmentControllerBasePrivate::selectedAttachmentProperties()
{
    Q_ASSERT(selectedParts.count() == 1);
    q->attachmentProperties(selectedParts.constFirst());
}

void AttachmentControllerBase::AttachmentControllerBasePrivate::reloadAttachment()
{
    Q_ASSERT(selectedParts.count() == 1);
    auto ajob = new AttachmentUpdateJob(selectedParts.constFirst(), q);
    connect(ajob, &AttachmentUpdateJob::result, q, [this](KJob *job) {
        updateJobResult(job);
    });
    ajob->start();
}

void AttachmentControllerBase::AttachmentControllerBasePrivate::updateJobResult(KJob *job)
{
    if (job->error()) {
        KMessageBox::error(wParent, job->errorString(), i18n("Failed to reload attachment"));
        return;
    }
    auto ajob = qobject_cast<AttachmentUpdateJob *>(job);
    Q_ASSERT(ajob);
    AttachmentPart::Ptr originalPart = ajob->originalPart();
    AttachmentPart::Ptr updatedPart = ajob->updatedPart();

    attachmentRemoved(originalPart);
    bool ok = model->replaceAttachment(originalPart, updatedPart);
    if (!ok) {
        // The attachment was removed from the model while we were compressing.
        qCDebug(EDITOR_LOG) << "Updated a zombie.";
    }
}

void AttachmentControllerBase::exportPublicKey(const GpgME::Key &key)
{
    if (key.isNull() || !QGpgME::openpgp()) {
        qCWarning(EDITOR_LOG) << "Tried to export key with empty fingerprint, or no OpenPGP.";
        return;
    }

    auto ajob = new MessageComposer::AttachmentFromPublicKeyJob(key, this);
    connect(ajob, &AttachmentFromPublicKeyJob::result, this, [this](KJob *job) {
        d->attachPublicKeyJobResult(job);
    });
    ajob->start();
}

void AttachmentControllerBase::AttachmentControllerBasePrivate::attachPublicKeyJobResult(KJob *job)
{
    // The only reason we can't use loadJobResult() and need a separate method
    // is that we want to show the proper caption ("public key" instead of "file")...

    if (job->error()) {
        KMessageBox::error(wParent, job->errorString(), i18n("Failed to attach public key"));
        return;
    }

    Q_ASSERT(dynamic_cast<MessageComposer::AttachmentFromPublicKeyJob *>(job));
    auto ajob = static_cast<MessageComposer::AttachmentFromPublicKeyJob *>(job);
    AttachmentPart::Ptr part = ajob->attachmentPart();
    q->addAttachment(part);
}

void AttachmentControllerBase::AttachmentControllerBasePrivate::attachClipBoardElement(KJob *job)
{
    if (job->error()) {
        qCDebug(EDITOR_LOG) << " Error during when get try to attach text from clipboard";
        KMessageBox::error(wParent, job->errorString(), i18n("Failed to attach text from clipboard"));
        return;
    }

    auto ajob = static_cast<MessageComposer::AttachmentClipBoardJob *>(job);
    AttachmentPart::Ptr part = ajob->attachmentPart();
    q->addAttachment(part);
}

static QTemporaryFile *dumpAttachmentToTempFile(const AttachmentPart::Ptr &part) // local
{
    auto file = new QTemporaryFile;
    if (!file->open()) {
        qCCritical(EDITOR_LOG) << "Could not open tempfile" << file->fileName();
        delete file;
        return nullptr;
    }
    if (file->write(part->data()) == -1) {
        qCCritical(EDITOR_LOG) << "Could not dump attachment to tempfile.";
        delete file;
        return nullptr;
    }
    file->flush();
    return file;
}

AttachmentControllerBase::AttachmentControllerBase(MessageComposer::AttachmentModel *model, QWidget *wParent, KActionCollection *actionCollection)
    : QObject(wParent)
    , d(new AttachmentControllerBasePrivate(this))
{
    d->model = model;
    connect(model, &MessageComposer::AttachmentModel::attachUrlsRequested, this, &AttachmentControllerBase::addAttachments);
    connect(model, &MessageComposer::AttachmentModel::attachmentRemoved, this, [this](const MessageCore::AttachmentPart::Ptr &attr) {
        d->attachmentRemoved(attr);
    });
    connect(model, &AttachmentModel::attachmentCompressRequested, this, &AttachmentControllerBase::compressAttachment);
    connect(model, &MessageComposer::AttachmentModel::encryptEnabled, this, &AttachmentControllerBase::setEncryptEnabled);
    connect(model, &MessageComposer::AttachmentModel::signEnabled, this, &AttachmentControllerBase::setSignEnabled);

    d->wParent = wParent;
    d->mActionCollection = actionCollection;
}

AttachmentControllerBase::~AttachmentControllerBase() = default;

void AttachmentControllerBase::createActions()
{
    // Create the actions.
    d->attachPublicKeyAction = new QAction(i18n("Attach &Public Key..."), this);
    connect(d->attachPublicKeyAction, &QAction::triggered, this, &AttachmentControllerBase::showAttachPublicKeyDialog);

    d->attachMyPublicKeyAction = new QAction(i18n("Attach &My Public Key"), this);
    connect(d->attachMyPublicKeyAction, &QAction::triggered, this, &AttachmentControllerBase::attachMyPublicKey);

    d->attachmentMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("Attach"), this);
    connect(d->attachmentMenu, &QAction::triggered, this, &AttachmentControllerBase::showAddAttachmentFileDialog);
    d->attachmentMenu->setPopupMode(QToolButton::DelayedPopup);

    d->addAttachmentFileAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("&Attach File..."), this);
    d->addAttachmentFileAction->setIconText(i18n("Attach"));
    d->addContextAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("Add Attachment..."), this);
    connect(d->addAttachmentFileAction, &QAction::triggered, this, &AttachmentControllerBase::showAddAttachmentFileDialog);
    connect(d->addContextAction, &QAction::triggered, this, &AttachmentControllerBase::showAddAttachmentFileDialog);

    d->addAttachmentDirectoryAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("&Attach Directory..."), this);
    d->addAttachmentDirectoryAction->setIconText(i18n("Attach"));
    connect(d->addAttachmentDirectoryAction, &QAction::triggered, this, &AttachmentControllerBase::showAddAttachmentCompressedDirectoryDialog);

    d->attachClipBoardAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("&Attach Text From Clipboard..."), this);
    d->attachClipBoardAction->setIconText(i18n("Attach Text From Clipboard"));
    connect(d->attachClipBoardAction, &QAction::triggered, this, &AttachmentControllerBase::showAttachClipBoard);

    d->attachmentMenu->addAction(d->addAttachmentFileAction);
    d->attachmentMenu->addAction(d->addAttachmentDirectoryAction);
    d->attachmentMenu->addSeparator();
    d->attachmentMenu->addAction(d->attachClipBoardAction);

    d->removeAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("&Remove Attachment"), this);
    d->removeContextAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Remove"), this); // FIXME need two texts. is there a better way?
    connect(d->removeAction, &QAction::triggered, this, [this]() {
        d->removeSelectedAttachments();
    });
    connect(d->removeContextAction, &QAction::triggered, this, [this]() {
        d->removeSelectedAttachments();
    });

    d->openContextAction = new QAction(i18nc("to open", "Open"), this);
    connect(d->openContextAction, &QAction::triggered, this, [this]() {
        d->openSelectedAttachments();
    });

    d->viewContextAction = new QAction(i18nc("to view", "View"), this);
    connect(d->viewContextAction, &QAction::triggered, this, [this]() {
        d->viewSelectedAttachments();
    });

    d->saveAsAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("&Save Attachment As..."), this);
    d->saveAsContextAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("Save As..."), this);
    connect(d->saveAsAction, &QAction::triggered, this, [this]() {
        d->saveSelectedAttachmentAs();
    });
    connect(d->saveAsContextAction, &QAction::triggered, this, [this]() {
        d->saveSelectedAttachmentAs();
    });

    d->propertiesAction = new QAction(i18n("Attachment Pr&operties..."), this);
    d->propertiesContextAction = new QAction(i18n("Properties"), this);
    connect(d->propertiesAction, &QAction::triggered, this, [this]() {
        d->selectedAttachmentProperties();
    });
    connect(d->propertiesContextAction, &QAction::triggered, this, [this]() {
        d->selectedAttachmentProperties();
    });

    d->selectAllAction = new QAction(i18n("Select All"), this);
    connect(d->selectAllAction, &QAction::triggered, this, &AttachmentControllerBase::selectedAllAttachment);

    d->reloadAttachmentAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reload"), this);
    connect(d->reloadAttachmentAction, &QAction::triggered, this, [this]() {
        d->reloadAttachment();
    });

    // Insert the actions into the composer window's menu.
    KActionCollection *collection = d->mActionCollection;
    collection->addAction(QStringLiteral("attach_public_key"), d->attachPublicKeyAction);
    collection->addAction(QStringLiteral("attach_my_public_key"), d->attachMyPublicKeyAction);
    collection->addAction(QStringLiteral("attach"), d->addAttachmentFileAction);
    collection->setDefaultShortcut(d->addAttachmentFileAction, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_A));
    collection->addAction(QStringLiteral("attach_directory"), d->addAttachmentDirectoryAction);

    collection->addAction(QStringLiteral("remove"), d->removeAction);
    collection->addAction(QStringLiteral("attach_save"), d->saveAsAction);
    collection->addAction(QStringLiteral("attach_properties"), d->propertiesAction);
    collection->addAction(QStringLiteral("select_all_attachment"), d->selectAllAction);
    collection->addAction(QStringLiteral("attach_menu"), d->attachmentMenu);

    setSelectedParts(AttachmentPart::List());
    Q_EMIT actionsCreated();
}

void AttachmentControllerBase::setEncryptEnabled(bool enabled)
{
    d->encryptEnabled = enabled;
}

void AttachmentControllerBase::setSignEnabled(bool enabled)
{
    d->signEnabled = enabled;
}

void AttachmentControllerBase::compressAttachment(const AttachmentPart::Ptr &part, bool compress)
{
    if (compress) {
        qCDebug(EDITOR_LOG) << "Compressing part.";

        auto ajob = new AttachmentCompressJob(part, this);
        connect(ajob, &AttachmentCompressJob::result, this, [this](KJob *job) {
            d->compressJobResult(job);
        });
        ajob->start();
    } else {
        qCDebug(EDITOR_LOG) << "Uncompressing part.";

        // Replace the compressed part with the original uncompressed part, and delete
        // the compressed part.
        AttachmentPart::Ptr originalPart = d->uncompressedParts.take(part);
        Q_ASSERT(originalPart); // Found in uncompressedParts.
        bool ok = d->model->replaceAttachment(part, originalPart);
        Q_ASSERT(ok);
        Q_UNUSED(ok)
    }
}

void AttachmentControllerBase::showContextMenu()
{
    Q_EMIT refreshSelection();

    const int numberOfParts(d->selectedParts.count());
    QMenu menu;

    if (numberOfParts > 0) {
        if (numberOfParts == 1) {
            const QString mimetype = QString::fromLatin1(d->selectedParts.first()->mimeType());
            QMimeDatabase mimeDb;
            auto mime = mimeDb.mimeTypeForName(mimetype);
            QStringList parentMimeType;
            if (mime.isValid()) {
                parentMimeType = mime.allAncestors();
            }
            if ((mimetype == QLatin1String("text/plain")) || (mimetype == QLatin1String("image/png")) || (mimetype == QLatin1String("image/jpeg"))
                || parentMimeType.contains(QLatin1String("text/plain")) || parentMimeType.contains(QLatin1String("image/png"))
                || parentMimeType.contains(QLatin1String("image/jpeg"))) {
                menu.addAction(d->viewContextAction);
            }
        }
        menu.addAction(d->openContextAction);
    }
    menu.addSeparator();
    if (numberOfParts == 1) {
        if (!d->selectedParts.first()->url().isEmpty()) {
            menu.addAction(d->reloadAttachmentAction);
        }
        menu.addAction(d->saveAsContextAction);
        menu.addSeparator();
        menu.addAction(d->propertiesContextAction);
        menu.addSeparator();
    }

    if (numberOfParts > 0) {
        menu.addAction(d->removeContextAction);
        menu.addSeparator();
    }
    const int nbAttachment = d->model->rowCount();
    if (nbAttachment != numberOfParts) {
        menu.addAction(d->selectAllAction);
        menu.addSeparator();
    }
    if (numberOfParts == 0) {
        menu.addAction(d->addContextAction);
    }

    menu.exec(QCursor::pos());
}

void AttachmentControllerBase::openAttachment(const AttachmentPart::Ptr &part)
{
    QTemporaryFile *tempFile = dumpAttachmentToTempFile(part);
    if (!tempFile) {
        KMessageBox::error(d->wParent,
                           i18n("KMail was unable to write the attachment to a temporary file."),
                           i18nc("@title:window", "Unable to open attachment"));
        return;
    }
    tempFile->setPermissions(QFile::ReadUser);
    if (!QDesktopServices::openUrl(QUrl::fromLocalFile(tempFile->fileName()))) {
        qWarning() << "Unable to open" << QUrl::fromLocalFile(tempFile->fileName());
    }
    tempFile->deleteLater();
}

void AttachmentControllerBase::viewAttachment(const AttachmentPart::Ptr &part)
{
    auto composer = new MessageComposer::ComposerJob;
    auto attachmentJob = new MessageComposer::AttachmentJob(part, composer);
    connect(attachmentJob, &AttachmentJob::result, this, [this](KJob *job) {
        d->slotAttachmentContentCreated(job);
    });
    attachmentJob->start();
}

void AttachmentControllerBase::AttachmentControllerBasePrivate::slotAttachmentContentCreated(KJob *job)
{
    if (!job->error()) {
        const MessageComposer::AttachmentJob *const attachmentJob = qobject_cast<MessageComposer::AttachmentJob *>(job);
        Q_ASSERT(attachmentJob);
        if (attachmentJob) {
            Q_EMIT q->showAttachment(attachmentJob->content());
        }
    } else {
        // TODO: show warning to the user
        qCWarning(EDITOR_LOG) << "Error creating KMime::Content for attachment:" << job->errorText();
    }
}

void AttachmentControllerBase::saveAttachmentAs(const AttachmentPart::Ptr &part)
{
    QString pname = part->name();
    if (pname.isEmpty()) {
        pname = i18n("unnamed");
    }

    const QString fileName = QFileDialog::getSaveFileName(d->wParent, i18n("Save Attachment As"), pname);

    if (fileName.isEmpty()) {
        qCDebug(EDITOR_LOG) << "Save Attachment As dialog canceled.";
        return;
    }

    QSaveFile file(fileName);
    if (!file.open(QIODevice::WriteOnly)) {
        KMessageBox::error(d->wParent, i18n("File %1 could not be created.", fileName), i18n("Error saving message"));
        return;
    }
    file.write(part->data());
    file.commit();
}

void AttachmentControllerBase::attachmentProperties(const AttachmentPart::Ptr &part)
{
    QPointer<AttachmentPropertiesDialog> dialog = new AttachmentPropertiesDialog(part, false, d->wParent);

    dialog->setEncryptEnabled(d->encryptEnabled);
    dialog->setSignEnabled(d->signEnabled);

    if (dialog->exec() && dialog) {
        d->model->updateAttachment(part);
    }
    delete dialog;
}

void AttachmentControllerBase::attachDirectory(const QUrl &url)
{
    const int rc = KMessageBox::warningTwoActions(d->wParent,
                                                  i18n("Do you really want to attach this directory \"%1\"?", url.toLocalFile()),
                                                  i18nc("@title:window", "Attach directory"),
                                                  KGuiItem(i18nc("@action:button", "Attach")),
                                                  KStandardGuiItem::cancel());
    if (rc == KMessageBox::ButtonCode::PrimaryAction) {
        addAttachment(url);
    }
}

void AttachmentControllerBase::showAttachClipBoard()
{
    auto job = new MessageComposer::AttachmentClipBoardJob(this);
    connect(job, &AttachmentClipBoardJob::result, this, [this](KJob *job) {
        d->attachClipBoardElement(job);
    });
    job->start();
}

void AttachmentControllerBase::showAddAttachmentCompressedDirectoryDialog()
{
    const QUrl url = QFileDialog::getExistingDirectoryUrl(d->wParent, i18nc("@title:window", "Attach Directory"));
    if (url.isValid()) {
        attachDirectory(url);
    }
}

void AttachmentControllerBase::showAddAttachmentFileDialog()
{
    const auto urls = QFileDialog::getOpenFileUrls(d->wParent, i18nc("@title:window", "Attach File"));
    if (!urls.isEmpty()) {
        for (const auto &url : urls) {
            QMimeDatabase mimeDb;
            const auto mimeType = mimeDb.mimeTypeForUrl(url);
            if (mimeType.name() == QLatin1String("inode/directory")) {
                const int rc = KMessageBox::warningTwoActions(d->wParent,
                                                              i18n("Do you really want to attach this directory \"%1\"?", url.toLocalFile()),
                                                              i18nc("@title:window", "Attach directory"),
                                                              KGuiItem(i18nc("@action:button", "Attach")),
                                                              KStandardGuiItem::cancel());
                if (rc == KMessageBox::ButtonCode::PrimaryAction) {
                    addAttachment(url);
                }
            } else {
                addAttachment(url);
            }
        }
    }
}

void AttachmentControllerBase::addAttachment(const AttachmentPart::Ptr &part)
{
    part->setEncrypted(d->model->isEncryptSelected());
    part->setSigned(d->model->isSignSelected());
    d->model->addAttachment(part);

    Q_EMIT fileAttached();
}

void AttachmentControllerBase::addAttachmentUrlSync(const QUrl &url)
{
    MessageCore::AttachmentFromUrlBaseJob *ajob = MessageCore::AttachmentFromUrlUtils::createAttachmentJob(url, this);
    if (ajob->exec()) {
        AttachmentPart::Ptr part = ajob->attachmentPart();
        addAttachment(part);
    } else {
        if (ajob->error()) {
            KMessageBox::error(d->wParent, ajob->errorString(), i18nc("@title:window", "Failed to attach file"));
        }
    }
}

void AttachmentControllerBase::addAttachment(const QUrl &url)
{
    MessageCore::AttachmentFromUrlBaseJob *ajob = MessageCore::AttachmentFromUrlUtils::createAttachmentJob(url, this);
    connect(ajob, &AttachmentFromUrlBaseJob::result, this, [this](KJob *job) {
        d->loadJobResult(job);
    });
    ajob->start();
}

void AttachmentControllerBase::addAttachments(const QList<QUrl> &urls)
{
    for (const QUrl &url : urls) {
        addAttachment(url);
    }
}

void AttachmentControllerBase::showAttachPublicKeyDialog()
{
    using Kleo::KeySelectionDialog;
    QPointer<KeySelectionDialog> dialog = new KeySelectionDialog(i18n("Attach Public OpenPGP Key"),
                                                                 i18n("Select the public key which should be attached."),
                                                                 std::vector<GpgME::Key>(),
                                                                 KeySelectionDialog::PublicKeys | KeySelectionDialog::OpenPGPKeys,
                                                                 false /* no multi selection */,
                                                                 false /* no remember choice box */,
                                                                 d->wParent);

    if (dialog->exec() == QDialog::Accepted) {
        exportPublicKey(dialog->selectedKey());
    }
    delete dialog;
}

void AttachmentControllerBase::attachMyPublicKey()
{
}

void AttachmentControllerBase::enableAttachPublicKey(bool enable)
{
    d->attachPublicKeyAction->setEnabled(enable);
}

void AttachmentControllerBase::enableAttachMyPublicKey(bool enable)
{
    d->attachMyPublicKeyAction->setEnabled(enable);
}

void AttachmentControllerBase::clear()
{
    const auto parts = d->model->attachments();
    for (const auto &attachmentPart : parts) {
        if (!d->model->removeAttachment(attachmentPart)) {
            qCWarning(EDITOR_LOG) << "Impossible to remove attachment" << attachmentPart->fileName();
        }
    }
}

#include "moc_attachmentcontrollerbase.cpp"
