#include "include/EditorNS/editor.h"

#include "include/EditorNS/defer.h"
#include "include/notepadqq.h"
#include "include/nqqsettings.h"

#include <QDir>
#include <QEventLoop>
#include <QMessageBox>
#include <QTimer>
#include <QUrlQuery>
#include <QVBoxLayout>
#include <QWebChannel>
#include <QWebEngineSettings>

namespace EditorNS {

std::deque<std::unique_ptr<Editor>> Editor::m_editorBuffer;

Editor::Editor(QWidget* parent)
    : QWidget(parent)
{
    QString themeName = NqqSettings::getInstance().Appearance.getColorScheme();

    fullConstructor(themeFromName(themeName));
}

Editor::Editor(const Theme& theme, QWidget* parent)
    : QWidget(parent)
{ fullConstructor(theme); }

bool Editor::useMonaco()
{
    QString engine = NqqSettings::getInstance().General.getEditorEngine();
    if (engine == "monaco")
        return true;
    if (engine == "codemirror")
        return false;
#ifdef NQQ_USE_MONACO
    return true;
#else
    return false;
#endif
}

void Editor::fullConstructor(const Theme& theme)
{
    m_asyncRequestTracker = new AsyncRequestTracker(this, std::chrono::seconds(10));
    m_jsToCppProxy = new JsToCppProxy(this);
    connect(m_jsToCppProxy, &JsToCppProxy::messageReceived, this, &Editor::on_proxyMessageReceived);

    m_webView = new CustomQWebView(this);

    QUrlQuery query;
    query.addQueryItem("themePath", theme.path);
    query.addQueryItem("themeName", theme.name);

    const bool monaco = useMonaco();
    if (monaco) {
        query.addQueryItem("engine", "monaco");
    }

    QString htmlFile =
        monaco ? QFileInfo(Notepadqq::editorPath()).absolutePath() + "/index_monaco.html" : Notepadqq::editorPath();

    QUrl url = QUrl("file://" + htmlFile);
    url.setQuery(query);

    QWebChannel* channel = new QWebChannel(this);
    m_webView->page()->setWebChannel(channel);
    channel->registerObject(QStringLiteral("cpp_ui_driver"), m_jsToCppProxy);

    m_webView->page()->setBackgroundColor(qApp->palette().color(QPalette::Window));
    m_webView->setUrl(url);

    // To load the page in the background (http://stackoverflow.com/a/10520029):
    // (however, no noticeable improvement here on an i5, september 2014)
    // QString content = QString("<html><body onload='setTimeout(function() { window.location=\"%1\"; },
    // 1);'>Loading...</body></html>").arg("file://" + Notepadqq::editorPath());
    // m_webView->setContent(content.toUtf8());

    m_webView->pageAction(QWebEnginePage::InspectElement)->setVisible(false);

    // m_webView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);

    QWebEngineSettings* pageSettings = m_webView->page()->settings();
#ifdef QT_DEBUG
// pageSettings->setAttribute(QWebEngineSettings::DeveloperExtrasEnabled, true);
#endif
    pageSettings->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, true);

    m_layout = new QVBoxLayout(this);
    m_layout->setContentsMargins(0, 0, 0, 0);
    m_layout->setSpacing(0);
    m_layout->addWidget(m_webView, 1);
    setLayout(m_layout);

    connect(m_webView, &CustomQWebView::mouseWheel, this, &Editor::mouseWheel);
    connect(m_webView, &CustomQWebView::urlsDropped, this, &Editor::urlsDropped);
    connect(m_webView, &CustomQWebView::gotFocus, this, &Editor::gotFocus);
    setLanguage(nullptr);
    // TODO Display a message if a javascript error gets triggered.
    // Right now, if there's an error in the javascript code, we
    // get stuck waiting a J_EVT_READY that will never come.
}

Editor* Editor::takeNewEditor()
{
    static_assert(std::is_same_v<decltype(m_editorBuffer), std::deque<std::unique_ptr<Editor>>>,
        "The preload buffer must exclusively own unattached editors");
    if (m_editorBuffer.size() < 2)
        addEditorToBuffer(2 - m_editorBuffer.size());

    std::unique_ptr<Editor> editor = std::move(m_editorBuffer.front());
    m_editorBuffer.pop_front();
    return editor.release();
}

void Editor::addEditorToBuffer(const int howMany)
{
    for (int i = 0; i < howMany; i++)
        m_editorBuffer.push_back(std::make_unique<Editor>());
}

void Editor::invalidateEditorBuffer()
{ m_editorBuffer.clear(); }

void Editor::waitAsyncLoad()
{
    if (!m_loaded) {
        QEventLoop loop;
        connect(this, &Editor::editorReady, &loop, &QEventLoop::quit);
        // Block until a J_EVT_READY message is received
        loop.exec();
    }
}

void Editor::on_proxyMessageReceived(QString msg, QVariant data)
{
    deferToObject(this, [this, msg, data] {
        emit messageReceived(msg, data);

        if (msg.startsWith("[ASYNC_REPLY]")) {
            const auto reply = resolveAsyncReply(*m_asyncRequestTracker, msg, data);
            if (reply)
                emit asyncReplyReceived(reply->id, reply->message, data);

        } else if (msg == "J_EVT_READY") {
            m_loaded = true;
            emit editorReady();
        } else if (msg == "J_EVT_CONTENT_CHANGED")
            emit contentChanged();
        else if (msg == "J_EVT_CLEAN_CHANGED")
            emit cleanChanged(data.toBool());
        else if (msg == "J_EVT_CURSOR_ACTIVITY") {
            emit cursorActivity(data.toMap());
        } else if (msg == "J_EVT_DOCUMENT_INFO") {
            emit documentInfoRequested(data.toMap());
        }
    });
}

void Editor::setFocus()
{ m_webView->setFocus(); }

void Editor::clearFocus()
{ m_webView->clearFocus(); }

/**
 * Automatically converts local relative file names to absolute ones.
 */
void Editor::setFilePath(const QUrl& filename)
{
    QUrl old = m_filePath;
    QUrl newUrl = filename;

    if (newUrl.isLocalFile())
        newUrl = QUrl::fromLocalFile(QFileInfo(filename.toLocalFile()).absoluteFilePath());

    m_filePath = newUrl;
    emit fileNameChanged(old, newUrl);
}

/**
 * Always returns an absolute url.
 */
QUrl Editor::filePath() const
{ return m_filePath; }

QString Editor::tabName() const
{ return m_tabName; }

void Editor::setTabName(const QString& name)
{ m_tabName = name; }

QtPromise::QPromise<bool> Editor::isCleanP()
{
    return asyncSendMessageWithResultP("C_FUN_IS_CLEAN", QVariant(0)).then([](QVariant v) { return v.toBool(); });
}

bool Editor::isClean()
{
    QVariant data(0); // avoid crash on Mac OS X, see issue #702
    try {
        return asyncSendMessageWithResult("C_FUN_IS_CLEAN", data).get().toBool();
    } catch (const std::runtime_error& error) {
        // A not-yet-ready or failed editor must be retained rather than treated as safely discardable.
        qWarning() << "Unable to determine whether the editor is clean:" << error.what();
        return false;
    }
}

QtPromise::QPromise<void> Editor::markClean()
{
    return asyncSendMessageWithResultP("C_CMD_MARK_CLEAN").then([]() {}).wait(); // FIXME Remove
}

QtPromise::QPromise<void> Editor::markDirty()
{
    return asyncSendMessageWithResultP("C_CMD_MARK_DIRTY").then([]() {}).wait(); // FIXME Remove
}

QtPromise::QPromise<int> Editor::getHistoryGeneration()
{
    return asyncSendMessageWithResultP("C_FUN_GET_HISTORY_GENERATION").then([](QVariant v) { return v.toInt(); });
}

void Editor::setLanguage(const Language* lang)
{
    if (lang == nullptr) {
        lang = LanguageService::getInstance().lookupById("plaintext");
    }
    if (m_currentLanguage == lang) {
        return;
    }
    if (!m_customIndentationMode) {
        setIndentationMode(lang);
    }
    m_currentLanguage = lang;
    asyncSendMessageWithResultP("C_CMD_SET_LANGUAGE", lang->mime.isEmpty() ? lang->mode : lang->mime).then([=, this]() {
        emit currentLanguageChanged(m_currentLanguage->id, m_currentLanguage->name);
    });
}

void Editor::setLanguage(const QString& language)
{
    auto& cache = LanguageService::getInstance();
    auto lang = cache.lookupById(language);
    if (lang != nullptr) {
        setLanguage(lang);
    }
}

void Editor::setLanguageFromFilePath(const QString& filePath)
{
    auto name = QFileInfo(filePath).fileName();

    auto& cache = LanguageService::getInstance();
    auto lang = cache.lookupByFileName(name);
    if (lang != nullptr) {
        setLanguage(lang);
        return;
    }
    lang = cache.lookupByExtension(name);
    if (lang != nullptr) {
        setLanguage(lang);
    }
}

void Editor::setLanguageFromFilePath()
{ setLanguageFromFilePath(filePath().toString()); }

QtPromise::QPromise<void> Editor::setIndentationMode(const Language* lang)
{
    const auto& s = NqqSettings::getInstance().Languages;
    const bool useDefaults = s.getUseDefaultSettings(lang->id);
    const auto& langId = useDefaults ? "default" : lang->id;

    return setIndentationMode(!s.getIndentWithSpaces(langId), s.getTabSize(langId));
}

QtPromise::QPromise<void> Editor::setIndentationMode(const bool useTabs, const int size)
{
    return asyncSendMessageWithResultP("C_CMD_SET_INDENTATION_MODE", QVariantMap{{"useTabs", useTabs}, {"size", size}})
        .then([]() {});
}

IndentationMode Editor::indentationMode()
{
    QVariantMap indent = asyncSendMessageWithResult("C_FUN_GET_INDENTATION_MODE").get().toMap();
    IndentationMode out;
    out.useTabs = indent.value("useTabs", true).toBool();
    out.size = indent.value("size", 4).toInt();
    return out;
}

QtPromise::QPromise<IndentationMode> Editor::indentationModeP()
{
    return asyncSendMessageWithResultP("C_FUN_GET_INDENTATION_MODE").then([](QVariant result) {
        QVariantMap indent = result.toMap();
        IndentationMode out;
        out.useTabs = indent.value("useTabs", true).toBool();
        out.size = indent.value("size", 4).toInt();
        return out;
    });
}

void Editor::setCustomIndentationMode(const bool useTabs, const int size)
{
    m_customIndentationMode = true;
    setIndentationMode(useTabs, size);
}

void Editor::setCustomIndentationMode(const bool useTabs)
{
    m_customIndentationMode = true;
    setIndentationMode(useTabs, 0);
}

void Editor::clearCustomIndentationMode()
{
    m_customIndentationMode = false;
    setIndentationMode(getLanguage());
}

bool Editor::isUsingCustomIndentationMode() const
{ return m_customIndentationMode; }

void Editor::setSmartIndent(bool enabled)
{ asyncSendMessageWithResultP("C_CMD_SET_SMART_INDENT", enabled); }

QtPromise::QPromise<void> Editor::setValue(const QString& value)
{
    auto lang = LanguageService::getInstance().lookupByContent(value);
    if (lang != nullptr) {
        setLanguage(lang);
    }
    return asyncSendMessageWithResultP("C_CMD_SET_VALUE", value).then([]() {}).wait(); // FIXME Remove
}

QString Editor::value()
{ return asyncSendMessageWithResult("C_FUN_GET_VALUE").get().toString(); }

bool Editor::fileOnDiskChanged() const
{ return m_fileOnDiskChanged; }

void Editor::setFileOnDiskChanged(bool fileOnDiskChanged)
{ m_fileOnDiskChanged = fileOnDiskChanged; }

void Editor::sendMessage(const QString msg, const QVariant data)
{
#ifdef QT_DEBUG
    qDebug() << "Legacy message " << msg << " sent.";
#endif
    waitAsyncLoad();

    emit m_jsToCppProxy->messageReceivedByJs(msg, data);
}

void Editor::sendMessage(const QString msg)
{ sendMessage(msg, 0); }

unsigned int messageIdentifier = 0;

QtPromise::QPromise<QVariant> Editor::asyncSendMessageWithResultP(const QString msg, const QVariant data)
{
    unsigned int currentMsgIdentifier = ++messageIdentifier;
    QString message_id = "[ASYNC_REQUEST]" + msg + "[ID=" + QString::number(currentMsgIdentifier) + "]";

    EmitAsyncRequest emitRequest = [this, message_id, data] {
        emit m_jsToCppProxy->messageReceivedByJs(message_id, data);
    };
    DeferAsyncRequest deferRequest = [this](EmitAsyncRequest request) {
        return QObject::connect(this, &Editor::editorReady, this, [this, request = std::move(request)]() mutable {
            m_loaded = true;
            request();
        });
    };

    return registerPromiseAndSend(
        *m_asyncRequestTracker, currentMsgIdentifier, msg, m_loaded, std::move(emitRequest), std::move(deferRequest));
}

QtPromise::QPromise<QVariant> Editor::asyncSendMessageWithResultP(const QString msg)
{ return this->asyncSendMessageWithResultP(msg, 0); }

std::shared_future<QVariant> Editor::asyncSendMessageWithResult(
    const QString msg, const QVariant data, std::function<void(QVariant)> callback)
{
    unsigned int currentMsgIdentifier = ++messageIdentifier;
    QString message_id = "[ASYNC_REQUEST]" + msg + "[ID=" + QString::number(currentMsgIdentifier) + "]";
    EmitAsyncRequest emitRequest = [this, message_id, data] {
        emit m_jsToCppProxy->messageReceivedByJs(message_id, data);
    };
    DeferAsyncRequest deferRequest = [this](EmitAsyncRequest request) {
        return QObject::connect(this, &Editor::editorReady, this, [this, request = std::move(request)]() mutable {
            m_loaded = true;
            request();
        });
    };

    return trackLegacyAndWait(*m_asyncRequestTracker,
        currentMsgIdentifier,
        msg,
        std::move(callback),
        m_loaded,
        std::move(emitRequest),
        std::move(deferRequest));
}

std::shared_future<QVariant> Editor::asyncSendMessageWithResult(
    const QString msg, std::function<void(QVariant)> callback)
{ return this->asyncSendMessageWithResult(msg, 0, callback); }

void Editor::setZoomFactor(const qreal& factor)
{
    qreal normFact = factor;
    if (normFact > 14)
        normFact = 14;
    else if (normFact < 0.10)
        normFact = 0.10;

    m_webView->setZoomFactor(normFact);
}

qreal Editor::zoomFactor() const
{ return m_webView->zoomFactor(); }

void Editor::setSelectionsText(const QStringList& texts, SelectMode mode)
{
    QVariantMap data{{"text", texts}};
    switch (mode) {
    case SelectMode::After:
        data.insert("select", "after");
        break;
    case SelectMode::Before:
        data.insert("select", "before");
        break;
    default:
        data.insert("select", "selected");
        break;
    }
    sendMessage("C_CMD_SET_SELECTIONS_TEXT", data);
}

void Editor::setSelectionsText(const QStringList& texts)
{ setSelectionsText(texts, SelectMode::After); }

void Editor::insertBanner(QWidget* banner)
{ m_layout->insertWidget(0, banner); }

void Editor::removeBanner(QWidget* banner)
{
    if (banner != m_webView && m_layout->indexOf(banner) >= 0) {
        m_layout->removeWidget(banner);
        emit bannerRemoved(banner);
    }
}

void Editor::removeBanner(QString objectName)
{
    for (auto&& banner : findChildren<QWidget*>(objectName)) {
        removeBanner(banner);
    }
}

void Editor::setLineWrap(const bool wrap)
{ asyncSendMessageWithResultP("C_CMD_SET_LINE_WRAP", wrap); }

void Editor::setEOLVisible(const bool showeol)
{ asyncSendMessageWithResultP("C_CMD_SHOW_END_OF_LINE", showeol); }

void Editor::setWhitespaceVisible(const bool showspace)
{ asyncSendMessageWithResultP("C_CMD_SHOW_WHITESPACE", showspace); }

void Editor::setMathEnabled(const bool enabled)
{ asyncSendMessageWithResultP("C_CMD_ENABLE_MATH", enabled); }

QtPromise::QPromise<QPair<int, int>> Editor::cursorPositionP()
{
    return asyncSendMessageWithResultP("C_FUN_GET_CURSOR").then([](QVariant v) {
        QList<QVariant> cursor = v.toList();
        return QPair<int, int>(cursor[0].toInt(), cursor[1].toInt());
    });
}

void Editor::requestDocumentInfo()
{ asyncSendMessageWithResultP("C_CMD_GET_DOCUMENT_INFO"); }

QPair<int, int> Editor::cursorPosition()
{
    QList<QVariant> cursor = asyncSendMessageWithResult("C_FUN_GET_CURSOR").get().toList();
    return {cursor[0].toInt(), cursor[1].toInt()};
}

void Editor::setCursorPosition(const int line, const int column)
{ asyncSendMessageWithResultP("C_CMD_SET_CURSOR", QList<QVariant>{line, column}); }

void Editor::setCursorPosition(const QPair<int, int>& position)
{ setCursorPosition(position.first, position.second); }

void Editor::setCursorPosition(const Cursor& cursor)
{ setCursorPosition(cursor.line, cursor.column); }

void Editor::setSelection(int fromLine, int fromCol, int toLine, int toCol)
{
    QVariantList arg{fromLine, fromCol, toLine, toCol};
    asyncSendMessageWithResultP("C_CMD_SET_SELECTION", QVariant(arg));
}

QPair<int, int> Editor::scrollPosition()
{
    QVariantList scroll = asyncSendMessageWithResult("C_FUN_GET_SCROLL_POS").get().toList();
    return {scroll[0].toInt(), scroll[1].toInt()};
}

void Editor::setScrollPosition(const int left, const int top)
{ asyncSendMessageWithResultP("C_CMD_SET_SCROLL_POS", QVariantList{left, top}); }

void Editor::setScrollPosition(const QPair<int, int>& position)
{ setScrollPosition(position.first, position.second); }

QString Editor::endOfLineSequence() const
{ return m_endOfLineSequence; }

void Editor::setEndOfLineSequence(const QString& newLineSequence)
{ m_endOfLineSequence = newLineSequence; }

void Editor::setFont(QString fontFamily, int fontSize, double lineHeight)
{
    QMap<QString, QVariant> tmap;
    tmap.insert("family", fontFamily == nullptr ? "" : fontFamily);
    tmap.insert("size", QString::number(fontSize));
    tmap.insert("lineHeight", QString::number(lineHeight, 'f', 2));
    asyncSendMessageWithResultP("C_CMD_SET_FONT", tmap);
}

void Editor::setLineNumbersVisible(bool visible)
{ asyncSendMessageWithResultP("C_CMD_SET_LINE_NUMBERS_VISIBLE", visible); }

QTextCodec* Editor::codec() const
{ return m_codec; }

void Editor::setCodec(QTextCodec* codec)
{ m_codec = codec; }

bool Editor::bom() const
{ return m_bom; }

void Editor::setBom(bool bom)
{ m_bom = bom; }

Editor::Theme Editor::themeFromName(QString name)
{
    if (name == "default" || name.isEmpty())
        return Theme();

    if (useMonaco()) {
        return Theme(name, "");
    }

    QFileInfo editorPath(Notepadqq::editorPath());
    QDir bundledThemesDir(editorPath.absolutePath() + "/libs/codemirror/theme/");

    if (bundledThemesDir.exists(name + ".css"))
        return Theme(name, bundledThemesDir.filePath(name + ".css"));

    return Theme();
}

QList<Editor::Theme> Editor::themes()
{
    auto editorPath = QFileInfo(Notepadqq::editorPath());

    if (useMonaco()) {
        QList<Theme> out;
        out.append(Theme("vs", ""));
        out.append(Theme("vs-dark", ""));
        out.append(Theme("hc-black", ""));
        out.append(Theme("hc-light", ""));

        QDir addonThemesDir(editorPath.absolutePath() + "/libs/monaco-addons/themes/", "*.js");
        for (auto&& theme : addonThemesDir.entryInfoList()) {
            const QString name = theme.completeBaseName();
            if (name != "_all") {
                out.append(Theme(name, ""));
            }
        }
        return out;
    }

    QDir bundledThemesDir(editorPath.absolutePath() + "/libs/codemirror/theme/", "*.css");

    QList<Theme> out;
    for (auto&& theme : bundledThemesDir.entryInfoList()) {
        out.append(Theme(theme.completeBaseName(), theme.filePath()));
    }
    return out;
}

void Editor::setTheme(Theme theme)
{ sendMessage("C_CMD_SET_THEME", QVariantMap{{"name", theme.name}, {"path", theme.path}}); }

QList<Selection> Editor::selections()
{
    QList<Selection> out;

    QList<QVariant> sels = asyncSendMessageWithResult("C_FUN_GET_SELECTIONS").get().toList();
    for (int i = 0; i < sels.length(); i++) {
        QVariantMap selMap = sels[i].toMap();
        QVariantMap from = selMap.value("anchor").toMap();
        QVariantMap to = selMap.value("head").toMap();

        Selection sel;
        sel.from.line = from.value("line").toInt();
        sel.from.column = from.value("ch").toInt();
        sel.to.line = to.value("line").toInt();
        sel.to.column = to.value("ch").toInt();

        out.append(sel);
    }

    return out;
}

QtPromise::QPromise<QStringList> Editor::selectedTexts()
{
    return asyncSendMessageWithResultP("C_FUN_GET_SELECTIONS_TEXT").then([](QVariant text) {
        return text.toStringList();
    });
}

void Editor::setOverwrite(bool overwrite)
{ asyncSendMessageWithResultP("C_CMD_SET_OVERWRITE", overwrite); }

void Editor::setTabsVisible(bool visible)
{ asyncSendMessageWithResultP("C_CMD_SET_TABS_VISIBLE", visible); }

QtPromise::QPromise<std::pair<IndentationMode, bool>> Editor::detectDocumentIndentation()
{
    return asyncSendMessageWithResultP("C_FUN_DETECT_INDENTATION_MODE").then([](QVariant result) {
        QVariantMap indent = result.toMap();
        IndentationMode out;

        bool found = indent.value("found", false).toBool();

        if (found) {
            out.useTabs = indent.value("useTabs", true).toBool();
            out.size = indent.value("size", 4).toInt();
        }

        return std::make_pair(out, found);
    });
}

void Editor::print(std::shared_ptr<QPrinter> printer)
{
    // 1. Set theme to default because dark themes would force the printer to color the entire
    //    document in the background color. Default theme has white background.
    // 2. Set WebView's bg-color to white to prevent visual artifacts when printing less than one page.
    // 3. Set C_CMD_DISPLAY_PRINT_STYLE to hide UI elements like the gutter.

#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QColor prevBackgroundColor = m_webView->page()->backgroundColor();
    QString prevStylesheet = m_webView->styleSheet();

    this->setLineWrap(true);
    setTheme(themeFromName("default"));
    m_webView->page()->setBackgroundColor(Qt::transparent);
    m_webView->setStyleSheet("background-color: white");
    sendMessage("C_CMD_DISPLAY_PRINT_STYLE");
    m_webView->page()->print(printer.get(), [=, this](bool /*success*/) {
        // Note: it is important to capture "printer" in order to keep the shared_ptr alive.
        sendMessage("C_CMD_DISPLAY_NORMAL_STYLE");
        m_webView->setStyleSheet(prevStylesheet);
        m_webView->page()->setBackgroundColor(prevBackgroundColor);
        setTheme(themeFromName(NqqSettings::getInstance().Appearance.getColorScheme()));
        this->setLineWrap(NqqSettings::getInstance().General.getWordWrap());
    });
#else
    Q_UNUSED(printer);
#endif
}

QtPromise::QPromise<QByteArray> Editor::printToPdf(const QPageLayout& pageLayout)
{
    // 1. Set theme to default because dark themes would force the printer to color the entire
    //    document in the background color. Default theme has white background.
    // 2. Set WebView's bg-color to white to prevent visual artifacts when printing less than one page.
    // 3. Set C_CMD_DISPLAY_PRINT_STYLE to hide UI elements like the gutter.

    return QtPromise::QPromise<QByteArray>([&](const QtPromise::QPromiseResolve<QByteArray>& resolve,
                                               const QtPromise::QPromiseReject<QByteArray>& reject) {

#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
        QColor prevBackgroundColor = m_webView->page()->backgroundColor();
        QString prevStylesheet = m_webView->styleSheet();

        this->setLineWrap(true);
        setTheme(themeFromName("default"));
        m_webView->page()->setBackgroundColor(Qt::transparent);
        m_webView->setStyleSheet("background-color: white");
        asyncSendMessageWithResultP("C_CMD_DISPLAY_PRINT_STYLE").wait();

        m_webView->page()->printToPdf(
            [=, this](const QByteArray& data) {
                QTimer::singleShot(0, this, [=, this]() {
                    asyncSendMessageWithResultP("C_CMD_DISPLAY_NORMAL_STYLE").wait();
                    m_webView->setStyleSheet(prevStylesheet);
                    m_webView->page()->setBackgroundColor(prevBackgroundColor);
                    setTheme(themeFromName(NqqSettings::getInstance().Appearance.getColorScheme()));
                    this->setLineWrap(NqqSettings::getInstance().General.getWordWrap());
                });

                if (data.isEmpty() || data.isNull()) {
                    reject(QByteArray());
                } else {
                    resolve(data);
                }
            },
            pageLayout);

#else
        reject(QByteArray());
#endif
    });
}

QtPromise::QPromise<QString> Editor::getCurrentWord()
{
    return asyncSendMessageWithResultP("C_FUN_GET_CURRENT_WORD").then([](QVariant v) { return v.toString(); });
}

QtPromise::QPromise<int> Editor::lineCount()
{
    return asyncSendMessageWithResultP("C_FUN_GET_LINE_COUNT").then([](QVariant v) { return v.toInt(); });
}
} // namespace EditorNS
