/*
  This file is part of KMail, the KDE mail client.
  SPDX-FileCopyrightText: 1997 Markus Wuebben <markus.wuebben@kde.org>
  SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
  SPDX-FileCopyrightText: 2009 Andras Mantia <andras@kdab.net>
  SPDX-FileCopyrightText: 2013-2026 Laurent Montel <montel@kde.org>

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

// define this to copy all html that is written to the readerwindow to
// filehtmlwriter.out in the current working directory
// #define KMAIL_READER_HTML_DEBUG 1

#include "viewer.h"
#include "config-messageviewer.h"
#include "csshelper.h"
#include "dkim-verify/dkimwidgetinfo.h"
#include "messageviewer_viewer_format_debug.h"
#include "viewer/mimeparttree/mimeparttreeview.h"
#include "viewer/mimeparttree/mimetreemodel.h"
#include "viewer/webengine/mailwebengineview.h"
#include "viewer_p.h"
#include "widgets/zoomactionmenu.h"
#include <Akonadi/ItemFetchJob>
#include <Akonadi/ItemFetchScope>
#include <Akonadi/MessageParts>
#include <QUrl>
#include <TextAddonsWidgets/OpenSavedFileFolderWidget>
#include <WebEngineViewer/WebEngineManageScript>
#include <WebEngineViewer/WebHitTestResult>

#include <Akonadi/ErrorAttribute>

#include <KLocalizedString>

#include <QAction>

#include <Akonadi/MDNStateAttribute>

namespace MessageViewer
{
class AbstractMessageLoadedHandler::AbstractMessageLoadedHandlerPrivate
{
public:
    Akonadi::Session *mSession = nullptr;
};

AbstractMessageLoadedHandler::AbstractMessageLoadedHandler()
    : d(new AbstractMessageLoadedHandlerPrivate)
{
}

AbstractMessageLoadedHandler::~AbstractMessageLoadedHandler() = default;

void AbstractMessageLoadedHandler::setSession(Akonadi::Session *session)
{
    d->mSession = session;
}

Akonadi::Session *AbstractMessageLoadedHandler::session() const
{
    return d->mSession;
}

Viewer::Viewer(QWidget *aParent, QWidget *mainWindow, KActionCollection *actionCollection)
    : QWidget(aParent)
    , d_ptr(new ViewerPrivate(this, mainWindow, actionCollection))
{
    initialize();
}

// the d_ptr is automatically deleted
Viewer::~Viewer() = default;

void Viewer::initialize()
{
    connect(d_ptr, &ViewerPrivate::displayPopupMenu, this, &Viewer::displayPopupMenu);
    connect(d_ptr, &ViewerPrivate::popupMenu, this, &Viewer::popupMenu);
    connect(d_ptr, &ViewerPrivate::urlClicked, this, [this](const Akonadi::Item &item, const QUrl &url) {
        Q_EMIT urlClicked(item, url);
    });
    connect(d_ptr, &ViewerPrivate::requestConfigSync, this, &Viewer::requestConfigSync);
    connect(d_ptr, &ViewerPrivate::makeResourceOnline, this, &Viewer::makeResourceOnline);
    connect(d_ptr, &ViewerPrivate::showReader, this, &Viewer::showReader);
    connect(d_ptr, &ViewerPrivate::showMessage, this, &Viewer::showMessage);
    connect(d_ptr, &ViewerPrivate::replyMessageTo, this, &Viewer::replyMessageTo);
    connect(d_ptr, &ViewerPrivate::showStatusBarMessage, this, &Viewer::showStatusBarMessage);
    connect(d_ptr, &ViewerPrivate::itemRemoved, this, &Viewer::itemRemoved);
    connect(d_ptr, &ViewerPrivate::changeDisplayMail, this, &Viewer::slotChangeDisplayMail);
    connect(d_ptr, &ViewerPrivate::moveMessageToTrash, this, &Viewer::moveMessageToTrash);
    connect(d_ptr, &ViewerPrivate::pageIsScrolledToBottom, this, &Viewer::pageIsScrolledToBottom);
    connect(d_ptr, &ViewerPrivate::printingFinished, this, &Viewer::printingFinished);
    connect(d_ptr, &ViewerPrivate::zoomChanged, this, &Viewer::zoomChanged);

    connect(d_ptr, &ViewerPrivate::showNextMessage, this, &Viewer::showNextMessage);
    connect(d_ptr, &ViewerPrivate::showPreviousMessage, this, &Viewer::showPreviousMessage);
    connect(d_ptr, &ViewerPrivate::sendResponse, this, &Viewer::sendResponse);

    setMessage(std::shared_ptr<KMime::Message>(), MimeTreeParser::Delayed);
    qGuiApp->installEventFilter(this);
    slotGeneralPaletteChanged();
}

bool Viewer::eventFilter(QObject *obj, QEvent *event)
{
    Q_UNUSED(obj);
    if (event->type() == QEvent::ApplicationPaletteChange) {
        slotGeneralPaletteChanged();
    }
    return false;
}

void Viewer::slotGeneralPaletteChanged()
{
    Q_D(Viewer);
    d->updatePalette();
}

void Viewer::changeEvent(QEvent *event)
{
    Q_D(Viewer);
    if (event->type() == QEvent::FontChange) {
        d->slotGeneralFontChanged();
    }
    QWidget::changeEvent(event);
}

void Viewer::setMessage(const std::shared_ptr<KMime::Message> &message, MimeTreeParser::UpdateMode updateMode)
{
    Q_D(Viewer);
    if (message == d->message()) {
        return;
    }
    d->setMessage(message, updateMode);
}

void Viewer::setMessageItem(const Akonadi::Item &item, MimeTreeParser::UpdateMode updateMode)
{
    Q_D(Viewer);
    if (d->messageItem() == item) {
        return;
    }
    if (!item.isValid() || item.loadedPayloadParts().contains(Akonadi::MessagePart::Body)) {
        d->setMessageItem(item, updateMode);
    } else {
        Akonadi::ItemFetchJob *job = createFetchJob(item);
        connect(job, &Akonadi::ItemFetchJob::result, this, [d](KJob *job) {
            d->itemFetchResult(job);
        });
        d->displaySplashPage(i18n("Loading message..."));
    }
}

QString Viewer::messagePath() const
{
    Q_D(const Viewer);
    return d->mMessagePath;
}

void Viewer::setMessagePath(const QString &path)
{
    Q_D(Viewer);
    d->mMessagePath = path;
}

void Viewer::displaySplashPage(const QString &templateName, const QVariantHash &data, const QByteArray &domain)
{
    Q_D(Viewer);
    d->displaySplashPage(templateName, data, domain);
}

void Viewer::enableMessageDisplay()
{
    Q_D(Viewer);
    d->enableMessageDisplay();
}

void Viewer::printMessage(const Akonadi::Item &msg)
{
    Q_D(Viewer);
    d->printMessage(msg);
}

void Viewer::printPreviewMessage(const Akonadi::Item &message)
{
    Q_D(Viewer);
    d->printPreviewMessage(message);
}

void Viewer::printPreview()
{
    Q_D(Viewer);
    d->slotPrintPreview();
}

void Viewer::print()
{
    Q_D(Viewer);
    d->slotPrintMessage();
}

void Viewer::resizeEvent(QResizeEvent *)
{
    Q_D(Viewer);
    if (!d->mResizeTimer.isActive()) {
        //
        // Combine all resize operations that are requested as long a
        // the timer runs.
        //
        d->mResizeTimer.start(100);
    }
}

void Viewer::closeEvent(QCloseEvent *e)
{
    Q_D(Viewer);
    QWidget::closeEvent(e);
    d->writeConfig();
}

void Viewer::slotAttachmentSaveAs()
{
    Q_D(Viewer);
    d->slotAttachmentSaveAs();
}

void Viewer::slotAttachmentSaveAll()
{
    Q_D(Viewer);
    d->slotAttachmentSaveAll();
}

void Viewer::slotSaveMessage()
{
    Q_D(Viewer);
    d->slotSaveMessage();
}

void Viewer::slotScrollUp()
{
    Q_D(Viewer);
    d->mViewer->scrollUp(10);
}

void Viewer::slotScrollDown()
{
    Q_D(Viewer);
    d->mViewer->scrollDown(10);
}

void Viewer::atBottom()
{
    Q_D(const Viewer);
    d->mViewer->isScrolledToBottom();
}

void Viewer::slotJumpDown()
{
    Q_D(Viewer);
    d->mViewer->scrollPageDown(100);
}

void Viewer::slotScrollPrior()
{
    Q_D(Viewer);
    d->mViewer->scrollPageUp(80);
}

void Viewer::slotScrollNext()
{
    Q_D(Viewer);
    d->mViewer->scrollPageDown(80);
}

QString Viewer::selectedText() const
{
    Q_D(const Viewer);
    return d->mViewer->selectedText();
}

Viewer::DisplayFormatMessage Viewer::displayFormatMessageOverwrite() const
{
    Q_D(const Viewer);
    return d->displayFormatMessageOverwrite();
}

void Viewer::setDisplayFormatMessageOverwrite(Viewer::DisplayFormatMessage format)
{
    Q_D(Viewer);
    d->setDisplayFormatMessageOverwrite(format);
}

void Viewer::setHtmlLoadExtDefault(bool loadExtDefault)
{
    Q_D(Viewer);
    d->setHtmlLoadExtDefault(loadExtDefault);
}

void Viewer::setHtmlLoadExtOverride(bool loadExtOverride)
{
    Q_D(Viewer);
    d->setHtmlLoadExtOverride(loadExtOverride);
}

bool Viewer::htmlLoadExtOverride() const
{
    Q_D(const Viewer);
    return d->htmlLoadExtOverride();
}

bool Viewer::htmlMail() const
{
    Q_D(const Viewer);
    return d->htmlMail();
}

bool Viewer::htmlLoadExternal() const
{
    Q_D(const Viewer);
    return d->htmlLoadExternal();
}

bool Viewer::isFixedFont() const
{
    Q_D(const Viewer);
    return d->mHtmlHeadSettings.fixedFont;
}

void Viewer::setUseFixedFont(bool useFixedFont)
{
    Q_D(Viewer);
    d->setUseFixedFont(useFixedFont);
}

QWidget *Viewer::mainWindow()
{
    Q_D(Viewer);
    return d->mMainWindow;
}

void Viewer::setDecryptMessageOverwrite(bool overwrite)
{
    Q_D(Viewer);
    d->setDecryptMessageOverwrite(overwrite);
}

std::shared_ptr<KMime::Message> Viewer::message() const
{
    Q_D(const Viewer);
    return d->mMessage;
}

Akonadi::Item Viewer::messageItem() const
{
    Q_D(const Viewer);
    return d->mMessageItem;
}

bool Viewer::event(QEvent *e)
{
    Q_D(Viewer);
    if (e->type() == QEvent::PaletteChange) {
        d->recreateCssHelper();
        d->update(MimeTreeParser::Force);
        e->accept();
        return true;
    }

    return QWidget::event(e);
}

void Viewer::slotFind()
{
    Q_D(Viewer);
    d->slotFind();
}

const AttachmentStrategy *Viewer::attachmentStrategy() const
{
    Q_D(const Viewer);
    return d->attachmentStrategy();
}

void Viewer::setAttachmentStrategy(const AttachmentStrategy *strategy)
{
    Q_D(Viewer);
    d->setAttachmentStrategy(strategy);
}

QString Viewer::overrideEncoding() const
{
    Q_D(const Viewer);
    return d->overrideEncoding();
}

void Viewer::setOverrideEncoding(const QString &encoding)
{
    Q_D(Viewer);
    d->setOverrideEncoding(encoding);
}

CSSHelper *Viewer::cssHelper() const
{
    Q_D(const Viewer);
    return d->cssHelper();
}

KToggleAction *Viewer::toggleFixFontAction() const
{
    Q_D(const Viewer);
    return d->mToggleFixFontAction;
}

bool Viewer::mimePartTreeIsEmpty() const
{
    Q_D(const Viewer);
    return d->mimePartTreeIsEmpty();
}

KToggleAction *Viewer::toggleMimePartTreeAction() const
{
    Q_D(const Viewer);
    return d->mToggleMimePartTreeAction;
}

QAction *Viewer::selectAllAction() const
{
    Q_D(const Viewer);
    return d->mSelectAllAction;
}

QAction *Viewer::viewSourceAction() const
{
    Q_D(const Viewer);
    return d->mViewSourceAction;
}

QAction *Viewer::copyURLAction() const
{
    Q_D(const Viewer);
    return d->mCopyURLAction;
}

QAction *Viewer::copyAction() const
{
    Q_D(const Viewer);
    return d->mCopyAction;
}

QAction *Viewer::speakTextAction() const
{
    Q_D(const Viewer);
    return d->mSpeakTextAction;
}

QAction *Viewer::copyImageLocation() const
{
    Q_D(const Viewer);
    return d->mCopyImageLocation;
}

QAction *Viewer::saveAsAction() const
{
    Q_D(const Viewer);
    return d->mSaveMessageAction;
}

QAction *Viewer::urlOpenAction() const
{
    Q_D(const Viewer);
    return d->mUrlOpenAction;
}

bool Viewer::printingMode() const
{
    Q_D(const Viewer);
    return d->printingMode();
}

void Viewer::setPrinting(bool enable)
{
    Q_D(Viewer);
    d->setPrinting(enable);
}

void Viewer::writeConfig(bool force)
{
    Q_D(Viewer);
    d->writeConfig(force);
}

QUrl Viewer::urlClicked() const
{
    Q_D(const Viewer);
    return d->mClickedUrl;
}

QUrl Viewer::imageUrlClicked() const
{
    Q_D(const Viewer);
    return d->imageUrl();
}

void Viewer::update(MimeTreeParser::UpdateMode updateMode)
{
    Q_D(Viewer);
    d->update(updateMode);
}

void Viewer::setMessagePart(KMime::Content *aMsgPart)
{
    Q_D(Viewer);
    d->setMessagePart(aMsgPart);
}

void Viewer::clear(MimeTreeParser::UpdateMode updateMode)
{
    setMessage(std::shared_ptr<KMime::Message>(), updateMode);
}

void Viewer::slotShowMessageSource()
{
    Q_D(Viewer);
    d->slotShowMessageSource();
}

void Viewer::readConfig()
{
    Q_D(Viewer);
    d->readConfig();
}

QAbstractItemModel *Viewer::messageTreeModel() const
{
    return d_func()->mMimePartTree->mimePartModel();
}

Akonadi::ItemFetchJob *Viewer::createFetchJob(const Akonadi::Item &item)
{
    Q_D(Viewer);
    auto job = new Akonadi::ItemFetchJob(item, d->mSession);
    job->fetchScope().fetchAllAttributes();
    job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
    job->fetchScope().fetchFullPayload(true);
    job->fetchScope().fetchAttribute<Akonadi::ErrorAttribute>();
    job->fetchScope().fetchAttribute<Akonadi::MDNStateAttribute>();
    return job;
}

void Viewer::addMessageLoadedHandler(AbstractMessageLoadedHandler *handler)
{
    Q_D(Viewer);

    if (!handler) {
        return;
    }

    handler->setSession(d->mSession);
    d->mMessageLoadedHandlers.insert(handler);
}

void Viewer::removeMessageLoadedHandler(AbstractMessageLoadedHandler *handler)
{
    Q_D(Viewer);

    d->mMessageLoadedHandlers.remove(handler);
}

void Viewer::deleteMessage()
{
    Q_D(Viewer);
    Q_EMIT deleteMessage(d->messageItem());
}

void Viewer::selectAll()
{
    Q_D(Viewer);
    d->selectAll();
}

void Viewer::copySelectionToClipboard()
{
    Q_D(Viewer);
    d->slotCopySelectedText();
}

void Viewer::setZoomFactor(qreal zoomFactor)
{
    Q_D(Viewer);
    d->mZoomActionMenu->setZoomFactor(zoomFactor);
}

void Viewer::slotZoomReset()
{
    Q_D(Viewer);
    d->mZoomActionMenu->slotZoomReset();
}

void Viewer::slotZoomIn()
{
    Q_D(Viewer);
    d->mZoomActionMenu->slotZoomIn();
}

void Viewer::slotZoomOut()
{
    Q_D(Viewer);
    d->mZoomActionMenu->slotZoomOut();
}

QAction *Viewer::findInMessageAction() const
{
    Q_D(const Viewer);
    return d->mFindInMessageAction;
}

QAction *Viewer::shareTextAction() const
{
    Q_D(const Viewer);
    return d->mShareTextAction;
}

void Viewer::slotChangeDisplayMail(Viewer::DisplayFormatMessage mode, bool loadExternal)
{
    qCDebug(MESSAGEVIEWER_VIEWER_FORMAT_LOG) << "DisplayFormatMessage: " << mode << " loadExternal " << loadExternal;

    if ((htmlLoadExtOverride() != loadExternal) || (displayFormatMessageOverwrite() != mode)) {
        qCDebug(MESSAGEVIEWER_VIEWER_FORMAT_LOG) << "We will change format";
        setHtmlLoadExtOverride(loadExternal);
        setDisplayFormatMessageOverwrite(mode);
        update(MimeTreeParser::Force);
    }
}

QAction *Viewer::saveMessageDisplayFormatAction() const
{
    Q_D(const Viewer);
    return d->mSaveMessageDisplayFormat;
}

QAction *Viewer::resetMessageDisplayFormatAction() const
{
    Q_D(const Viewer);
    return d->mResetMessageDisplayFormat;
}

QAction *Viewer::developmentToolsAction() const
{
    Q_D(const Viewer);
    return d->mDevelopmentToolsAction;
}

KToggleAction *Viewer::disableEmoticonAction() const
{
    Q_D(const Viewer);
    return d->mDisableEmoticonAction;
}

void Viewer::saveMainFrameScreenshotInFile(const QString &filename)
{
    Q_D(Viewer);
    d->saveMainFrameScreenshotInFile(filename);
}

KActionMenu *Viewer::shareServiceUrlMenu() const
{
    Q_D(const Viewer);
    return d->mShareServiceUrlMenu;
}

HeaderStylePlugin *Viewer::headerStylePlugin() const
{
    Q_D(const Viewer);
    return d->mHeaderStylePlugin;
}

DKIMViewerMenu *Viewer::dkimViewerMenu()
{
    Q_D(Viewer);
    return d->dkimViewerMenu();
}

RemoteContentMenu *Viewer::remoteContentMenu() const
{
    Q_D(const Viewer);
    return d->remoteContentMenu();
}

void Viewer::showMdnInformations(const QString &message, bool canDeny)
{
    Q_D(Viewer);
    d->showMdnInformations(message, canDeny);
}

void Viewer::mdnWarningAnimatedHide()
{
    Q_D(Viewer);
    d->mdnWarningAnimatedHide();
}

void Viewer::setPluginName(const QString &pluginName)
{
    Q_D(Viewer);
    d->setPluginName(pluginName);
}

void Viewer::showOpenAttachmentFolderWidget(const QList<QUrl> &urls)
{
    Q_D(Viewer);
    d->showSavedFileFolderWidget(urls, TextAddonsWidgets::OpenSavedFileFolderWidget::FileType::Attachment);
}

QList<QAction *> Viewer::viewerPluginActionList(ViewerPluginInterface::SpecificFeatureTypes features)
{
    Q_D(Viewer);
    return d->viewerPluginActionList(features);
}

QList<QAction *> Viewer::interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const
{
    Q_D(const Viewer);
    return d->interceptorUrlActions(result);
}

void Viewer::runJavaScript(const QString &code)
{
    Q_D(Viewer);
    d->mViewer->page()->runJavaScript(code, WebEngineViewer::WebEngineManageScript::scriptWordId());
}

void Viewer::setPrintElementBackground(bool printElementBackground)
{
    Q_D(Viewer);
    d->mViewer->setPrintElementBackground(printElementBackground);
}

bool Viewer::showSignatureDetails() const
{
    Q_D(const Viewer);
    return d->showSignatureDetails();
}

void Viewer::setShowSignatureDetails(bool showDetails)
{
    Q_D(Viewer);
    d->setShowSignatureDetails(showDetails);
}

bool Viewer::showEncryptionDetails() const
{
    Q_D(const Viewer);
    return d->showEncryptionDetails();
}

void Viewer::hasMultiMessages(bool messages)
{
    Q_D(Viewer);
    d->hasMultiMessages(messages);
}

void Viewer::updateShowMultiMessagesButton(bool enablePreviousButton, bool enableNextButton)
{
    Q_D(Viewer);
    d->updateShowMultiMessagesButton(enablePreviousButton, enableNextButton);
}

void Viewer::setShowEncryptionDetails(bool showDetails)
{
    Q_D(Viewer);
    d->setShowEncryptionDetails(showDetails);
}

qreal Viewer::webViewZoomFactor() const
{
    Q_D(const Viewer);
    return d->webViewZoomFactor();
}

void Viewer::setWebViewZoomFactor(qreal factor)
{
    Q_D(Viewer);
    d->setWebViewZoomFactor(factor);
}

MessageViewer::DKIMWidgetInfo *Viewer::dkimWidgetInfo()
{
    Q_D(Viewer);
    return d->mDkimWidgetInfo;
}

void Viewer::exportToPdf(const QString &fileName)
{
    Q_D(Viewer);
    d->exportToPdf(fileName);
}

void Viewer::showDevelopmentTools()
{
    Q_D(Viewer);
    d->slotShowDevelopmentTools();
}

void Viewer::setIdentityManager(KIdentityManagementCore::IdentityManager *ident)
{
    Q_D(Viewer);
    d->setIdentityManager(ident);
}

void Viewer::setFolderIdentity(uint folderIdentity)
{
    Q_D(Viewer);
    d->setFolderIdentity(folderIdentity);
}

}

#include "moc_viewer.cpp"
