#include "./syncthingapplet.h"
#include "./settingsdialog.h"

#include <syncthingconnector/syncthingservice.h>
#include <syncthingconnector/utils.h>

#include <syncthingmodel/syncthingicons.h>

#include <syncthingwidgets/misc/direrrorsdialog.h>
#include <syncthingwidgets/misc/internalerrorsdialog.h>
#include <syncthingwidgets/misc/otherdialogs.h>
#include <syncthingwidgets/misc/textviewdialog.h>
#include <syncthingwidgets/misc/utils.h>
#include <syncthingwidgets/settings/settings.h>
#include <syncthingwidgets/settings/settingsdialog.h>
#include <syncthingwidgets/settings/wizard.h>
#include <syncthingwidgets/webview/webviewdialog.h>
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
#include <syncthingwidgets/misc/syncthinglauncher.h>
#endif
#if defined(SYNCTHINGWIDGETS_GUI_QTQUICK_MODE_DESKTOP)
#include <syncthingwidgets/quick/helpers.h>
#endif

#include "resources/config.h"
#include "resources/qtconfig.h"

#include <qtforkawesome/utils.h>
#include <qtquickforkawesome/imageprovider.h>

#include <qtutilities/misc/desktoputils.h>
#include <qtutilities/misc/dialogutils.h>
#include <qtutilities/resources/resources.h>

#include <c++utilities/application/argumentparser.h>
#include <c++utilities/conversion/stringconversion.h>

#include <KConfigGroup>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <KPluginMetaData>
#endif

#include <Plasma/Theme>
#include <plasma/plasma_export.h>
#include <plasma_version.h>

#include <QClipboard>
#include <QDesktopServices>
#include <QGuiApplication>
#include <QMap>
#include <QMessageBox>
#include <QNetworkReply>
#include <QPalette>
#include <QQmlContext>
#include <QQmlEngine>
#include <QStringBuilder>

#include <iostream>

using namespace std;
using namespace Data;
using namespace Plasma;
using namespace CppUtilities;
using namespace QtUtilities;
using namespace QtGui;

namespace Plasmoid {

static inline QPalette paletteFromTheme(const Plasma::Theme &theme)
{
#if PLASMA_VERSION_MAJOR >= 5 && PLASMA_VERSION_MINOR >= 68
    return theme.palette();
#else
    auto p = QPalette();
    p.setColor(QPalette::Normal, QPalette::Text, theme.color(Plasma::Theme::TextColor));
    p.setColor(QPalette::Normal, QPalette::WindowText, theme.color(Plasma::Theme::TextColor));
    p.setColor(QPalette::Normal, QPalette::Window, theme.color(Plasma::Theme::BackgroundColor));
    return p;
#endif
}

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
SyncthingApplet::SyncthingApplet(QObject *parent, const KPluginMetaData &data, const QVariantList &args)
    : Applet(parent, data, args)
#else
SyncthingApplet::SyncthingApplet(QObject *parent, const QVariantList &data)
    : Applet(parent, data)
#endif
    , m_faUrl(QStringLiteral("image://fa/"))
    , m_iconManager(IconManager::instance(&m_palette))
    , m_aboutDlg(nullptr)
    , m_data(nullptr)
    , m_models(m_data)
    , m_downloadModel(*m_data.connection())
    , m_settingsDlg(nullptr)
    , m_wizard(nullptr)
    , m_imageProvider(nullptr)
    , m_webViewDlg(nullptr)
    , m_notificationsDlg(nullptr)
    , m_hasInternalErrors(false)
    , m_initialized(false)
    , m_showTabTexts(false)
    , m_showDownloads(false)
    , m_showSyncthingIcons(true)
    , m_applyingSettingsForWizard(false)
{
    // restore settings
    auto &settings = Settings::values();
    settings.isPlasmoid = true;
    Settings::restore();

    // load config
    const auto &c = config();
    if (c.readEntry<>("preferIconsFromTheme", false)) {
        Data::setForkAwesomeThemeOverrides();
    }
    m_currentConnectionConfig = c.readEntry<int>("selectedConfig", 0);
    m_defaultTab = c.readEntry<>("defaultTab", 0);
    m_lastTab = c.readEntry<>("lastTab", 0);
    m_showSyncthingIcons = c.readEntry<>("showStIcons", m_showSyncthingIcons);
    m_defaultTab = m_defaultTab < 0 ? m_lastTab : m_defaultTab;

    // configure connection
    m_data.connection()->setInsecure(Settings::values().connection.insecure);

#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
    m_data.notifier()->setService(&m_service);
#endif
}

SyncthingApplet::~SyncthingApplet()
{
#if defined(SYNCTHINGWIDGETS_GUI_QTQUICK_MODE_DESKTOP)
    m_quickUI.reset();
    emit quickUIChanged();
#endif
    delete m_settingsDlg;
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
    delete m_webViewDlg;
#endif
    config().writeEntry("lastTab", m_lastTab);
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
    SyncthingService::setMainInstance(nullptr);
#endif
}

#ifdef SYNCTHINGWIDGETS_GUI_QTQUICK
SyncthingApplet *Plasmoid::SyncthingApplet::create(QQmlEngine *qmlEngine, QJSEngine *engine)
{
    return dataObjectFromProperty<SyncthingApplet>(qmlEngine, engine);
}
#endif

void showErrorIfSet(const QString &errorMessage)
{
    if (errorMessage.isEmpty()) {
        return;
    }
    auto msgBox = new QMessageBox();
    msgBox->setWindowTitle(QStringLiteral(APP_NAME));
    msgBox->setAttribute(Qt::WA_DeleteOnClose, true);
    msgBox->setText(errorMessage);
    msgBox->setIcon(QMessageBox::Critical);
    msgBox->show();
}

void SyncthingApplet::init()
{
    LOAD_QT_TRANSLATIONS;
    setupCommonQtApplicationAttributes();

    Applet::init();
    std::cerr << "Initializing " APP_NAME ", version " APP_VERSION "\n";
    std::cerr << "Plugin ID: " << pluginMetaData().pluginId().toStdString() << '\n';

    // connect signals and slots
    auto *const connection = m_data.connection();
    auto *const notifier = m_data.notifier();
    connect(notifier, &SyncthingNotifier::statusChanged, this, &SyncthingApplet::handleConnectionStatusChanged);
    connect(notifier, &SyncthingNotifier::syncComplete, &m_dbusNotifier, &DBusStatusNotifier::showSyncComplete);
    connect(notifier, &SyncthingNotifier::disconnected, &m_dbusNotifier, &DBusStatusNotifier::showDisconnect);
    connect(connection, &SyncthingConnection::autoReconnectIntervalChanged, this, &SyncthingApplet::updateStatusIconAndTooltip);
    connect(connection, &SyncthingConnection::hasOutOfSyncDirsChanged, this, &SyncthingApplet::updateStatusIconAndTooltip);
    connect(connection, &SyncthingConnection::newDevices, this, &SyncthingApplet::handleDevicesChanged);
    connect(connection, &SyncthingConnection::devStatusChanged, this, &SyncthingApplet::handleDevicesChanged);
    connect(connection, &SyncthingConnection::error, this, &SyncthingApplet::handleInternalError);
    connect(connection, &SyncthingConnection::trafficChanged, this, &SyncthingApplet::trafficChanged);
    connect(connection, &SyncthingConnection::dirStatisticsChanged, this, &SyncthingApplet::handleDirStatisticsChanged);
    connect(connection, &SyncthingConnection::newNotification, this, &SyncthingApplet::handleNewNotification);
    connect(connection, &SyncthingConnection::newErrors, this, &SyncthingApplet::updateStatusIconAndTooltip);
    connect(notifier, &SyncthingNotifier::newDevice, &m_dbusNotifier, &DBusStatusNotifier::showNewDev);
    connect(notifier, &SyncthingNotifier::newDir, &m_dbusNotifier, &DBusStatusNotifier::showNewDir);
    connect(&m_dbusNotifier, &DBusStatusNotifier::connectRequested, connection,
        static_cast<void (SyncthingConnection::*)(void)>(&SyncthingConnection::connect));
    connect(&m_dbusNotifier, &DBusStatusNotifier::dismissNotificationsRequested, connection, &SyncthingConnection::requestClearingErrors);
    connect(&m_dbusNotifier, &DBusStatusNotifier::showNotificationsRequested, this, &SyncthingApplet::showNotificationsDialog);
    connect(&m_dbusNotifier, &DBusStatusNotifier::errorDetailsRequested, this, &SyncthingApplet::showInternalErrorsDialog);
    connect(&m_dbusNotifier, &DBusStatusNotifier::webUiRequested, this, &SyncthingApplet::showWebUI);
    connect(&m_iconManager, &IconManager::statusIconsChanged, this, &SyncthingApplet::connectionStatusChanged);
    connect(&m_theme, &Plasma::Theme::themeChanged, this, &SyncthingApplet::handleThemeChanged);

    // initialize systemd service support
    auto &settings = Settings::values();
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
    SyncthingService::setMainInstance(&m_service);
    settings.systemd.setupService(m_service);
    connect(&m_service, &SyncthingService::systemdAvailableChanged, this, &SyncthingApplet::handleSystemdStatusChanged);
    connect(&m_service, &SyncthingService::stateChanged, this, &SyncthingApplet::handleSystemdStatusChanged);
    connect(&m_service, &SyncthingService::errorOccurred, this, &SyncthingApplet::handleSystemdServiceError);
#endif

    // apply settings and connect according to settings
    const auto palette = paletteFromTheme(m_theme);
    setBrightColors(isPaletteDark(palette));
    m_iconManager.setPalette(palette);
    handleSettingsChanged();

    // show wizard on first launch
    if (settings.firstLaunch || settings.fakeFirstLaunch) {
        showWizard();
        settings.firstLaunch = false;
        saveSettings();
    }

    // show error when settings cannot be restored
    showErrorIfSet(settings.error);

    m_initialized = true;
}

void SyncthingApplet::initEngine(QObject *object)
{
    const auto engine = qmlEngine(object);
    if (!engine) {
        return;
    }
    const auto color = m_theme.color(Plasma::Theme::TextColor);
    if (!(m_imageProvider = qobject_cast<QtForkAwesome::QuickImageProvider *>(engine->imageProvider(QStringLiteral("fa"))))) {
        m_imageProvider
            = new QtForkAwesome::QuickImageProvider(QtForkAwesome::Renderer::global(), color); // using global renderer for system icons override
        engine->addImageProvider(QStringLiteral("fa"), m_imageProvider);
        connect(engine, &QObject::destroyed, this, &SyncthingApplet::handleEngineDestroyed);
    }

#if defined(SYNCTHINGWIDGETS_GUI_QTQUICK_MODE_DESKTOP)
    if (auto &settings = Settings::values(); !m_quickUI) {
        // use separate engine for showing regular Qt Quick UI as part of the Plasmoid so each instance of the Plasmoid can have its own regular Qt Quick UI with its own data/models
        // notes: - Within this separate engine, this SyncthingApplet instance is exposed as TrayWidget singleton so QML code from the `syncthingwidgets` library can access it in
        //          consistency with how it accesses the TrayWidget class from stand-alone tray app.
        //        - Using a separate engine requires a 2nd image provider which is already taken care of by the QuickUI class.
        //        - Using just one engine was not possible because then only one set of singletons could exist. This would also lead to lifetime issues as the engine might outlive
        //          the SyncthingApplet instance so one needed to unregister the TrayWidget singleton.
        //        - The QML code of the Plasmoid can therefore also not use the singletons. (So it has to use e.g. `plasmoid.data.connection` instead of `SyncthingData.connection`
        //          to access the SyncthingConnection object of the current Plasmoid instance.)
        auto &quickUI = m_quickUI.emplace(qGuiApp, settings.qt);
        dataObjectToProperty(&m_quickUI->engine, &m_data);
        dataObjectToProperty(&m_quickUI->engine, &m_models);
        dataObjectToProperty(&m_quickUI->engine, &quickUI.ui);
        dataObjectToProperty(&m_quickUI->engine, this);
        connect(&m_quickUI->ui, &QuickUI::changesWindowVisibleChanged, this, &SyncthingApplet::handleChangesWindowVisibleChanged);
        emit quickUIChanged();
    }
#endif
}

QString SyncthingApplet::connectButtonState() const
{
    switch (m_data.connection()->status()) {
    case Data::SyncthingStatus::Disconnected:
        return m_data.connection()->isConnecting() ? QStringLiteral("connecting") : QStringLiteral("disconnected");
    case Data::SyncthingStatus::Reconnecting:
        return QStringLiteral("connecting");
    case Data::SyncthingStatus::Paused:
        return QStringLiteral("paused");
    default:
        return QStringLiteral("idle");
    }
}

QString SyncthingApplet::incomingTraffic() const
{
    return m_data.formatIncomingTraffic();
}

bool SyncthingApplet::hasIncomingTraffic() const
{
    return m_data.connection()->totalIncomingRate() > 0.0;
}

QString SyncthingApplet::outgoingTraffic() const
{
    return m_data.formatOutgoingTraffic();
}

bool SyncthingApplet::hasOutgoingTraffic() const
{
    return m_data.connection()->totalOutgoingRate() > 0.0;
}

SyncthingStatistics SyncthingApplet::globalStatistics() const
{
    return m_overallStats.global;
}

SyncthingStatistics SyncthingApplet::localStatistics() const
{
    return m_overallStats.local;
}

QStringList SyncthingApplet::connectionConfigNames() const
{
    const auto &settings = Settings::values().connection;
    QStringList names;
    names.reserve(static_cast<int>(settings.secondary.size() + 1));
    names << settings.primary.label;
    for (const auto &setting : settings.secondary) {
        names << setting.label;
    }
    return names;
}

QString SyncthingApplet::currentConnectionConfigName() const
{
    const auto &settings = Settings::values().connection;
    if (m_currentConnectionConfig == 0) {
        return settings.primary.label;
    } else if (m_currentConnectionConfig > 0 && static_cast<unsigned>(m_currentConnectionConfig) <= settings.secondary.size()) {
        return settings.secondary[static_cast<unsigned>(m_currentConnectionConfig) - 1].label;
    }
    return QString();
}

Data::SyncthingConnectionSettings *SyncthingApplet::connectionConfig(int index)
{
    auto &connectionSettings = Settings::values().connection;
    if (index >= 0 && static_cast<unsigned>(index) <= connectionSettings.secondary.size()) {
        return index == 0 ? &connectionSettings.primary : &connectionSettings.secondary[static_cast<unsigned>(index) - 1];
    }
    return nullptr;
}

void SyncthingApplet::setCurrentConnectionConfigIndex(int index)
{
    auto &settings = Settings::values();
    bool reconnectRequired = false;
    if (index != m_currentConnectionConfig && index >= 0 && static_cast<unsigned>(index) <= settings.connection.secondary.size()) {
        auto &selectedConfig = index == 0 ? settings.connection.primary : settings.connection.secondary[static_cast<unsigned>(index) - 1];
        reconnectRequired = m_data.connection()->applySettings(selectedConfig);
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
        if (m_webViewDlg) {
            m_webViewDlg->applySettings(selectedConfig, false, m_data.connection());
        }
#endif
        config().writeEntry<int>("selectedConfig", index);
        emit currentConnectionConfigIndexChanged(m_currentConnectionConfig = index);
    }

    // apply systemd settings, reconnect if required and possible
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
    const auto systemdConsideredForReconnect
        = settings.systemd.apply(*m_data.connection(), currentConnectionConfig(), reconnectRequired).consideredForReconnect;
#else
    const auto systemdConsideredForReconnect = false;
#endif
    if (!systemdConsideredForReconnect && (reconnectRequired || !m_data.connection()->isConnected())) {
        m_data.connection()->reconnect();
    } else {
        concludeWizard();
    }
}

bool SyncthingApplet::isStartStopEnabled() const
{
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
    return Settings::values().systemd.showButton;
#else
    return false;
#endif
}

bool SyncthingApplet::hasInternalErrors() const
{
    return m_hasInternalErrors;
}

void SyncthingApplet::setPassiveStates(const QList<QtUtilities::ChecklistItem> &passiveStates)
{
    m_passiveSelectionModel.setItems(passiveStates);
    const auto currentState = static_cast<int>(m_data.connection()->status());
    setPassive(currentState >= 0 && currentState < passiveStates.size() && passiveStates.at(currentState).isChecked());
}

void SyncthingApplet::updateStatusIconAndTooltip()
{
    m_data.updateStatusInfo();
    m_data.updateDeviceInfo();
    emit m_data.statusInfo()->statusInfoChanged();
    emit connectionStatusChanged();
}

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void SyncthingApplet::handleRelevantControlsChanged(bool visible, int tabIndex)
{
    m_lastTab = tabIndex;
    CppUtilities::modFlagEnum(m_visibleControls, VisibleControls::TrayWidget, visible);
    QtGui::handleRelevantControlsChanged(m_visibleControls, tabIndex, *m_data.connection());
}

#if defined(GUI_QTQUICK) && defined(SYNCTHINGWIDGETS_GUI_QTQUICK_MODE_DESKTOP)
void SyncthingApplet::handleMainWindowVisibleChanged(bool visible)
{
    CppUtilities::modFlagEnum(m_visibleControls, VisibleControls::MainWindow, visible);
    QtGui::handleRelevantControlsChanged(m_visibleControls, m_lastTab, *m_data.connection());
}

void SyncthingApplet::handleChangesWindowVisibleChanged(bool visible)
{
    CppUtilities::modFlagEnum(m_visibleControls, VisibleControls::RecentChangesWindow, visible);
    QtGui::handleRelevantControlsChanged(m_visibleControls, m_lastTab, *m_data.connection());
}
#endif
#endif

void SyncthingApplet::saveSettings()
{
    Settings::save();
    showErrorIfSet(Settings::values().error);
}

QIcon SyncthingApplet::loadForkAwesomeIcon(const QString &name, int size) const
{
    const auto icon = QtForkAwesome::iconFromId(name);
    return QtForkAwesome::isIconValid(icon)
        ? QIcon(QtForkAwesome::Renderer::global().pixmap(icon, QSize(size, size), QGuiApplication::palette().color(QPalette::WindowText)))
        : QIcon();
}

bool SyncthingApplet::areWipFeaturesEnabled() const
{
    return Settings::values().enableWipFeatures;
}

void SyncthingApplet::showSettingsDialog()
{
    if (!m_settingsDlg) {
        m_settingsDlg = new SettingsDialog(*this);
        // create a launcher as its monitoring of whether the network is metered is required by the connection settings
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
        if (!SyncthingLauncher::mainInstance()) {
            SyncthingLauncher::setMainInstance(new SyncthingLauncher(m_settingsDlg));
        }
#endif
        // show wizard when requested
        connect(m_settingsDlg, &SettingsDialog::wizardRequested, this, &SyncthingApplet::showWizard);
        // ensure settings take effect when applied
        connect(m_settingsDlg, &SettingsDialog::applied, this, &SyncthingApplet::handleSettingsChanged);
        // save plasmoid specific settings to disk when applied
        connect(m_settingsDlg, &SettingsDialog::applied, this, &SyncthingApplet::configChanged);
        // save global/general settings to disk when applied
        connect(m_settingsDlg, &SettingsDialog::applied, this, &SyncthingApplet::saveSettings);
    }
    centerWidget(m_settingsDlg);
    m_settingsDlg->show();
    m_settingsDlg->activateWindow();
}

void SyncthingApplet::showWizard()
{
    if (!m_wizard) {
        m_wizard = Wizard::instance();
        connect(m_wizard, &Wizard::destroyed, this, [this] { m_wizard = nullptr; });
        connect(m_wizard, &Wizard::settingsDialogRequested, this, &SyncthingApplet::showSettingsDialog);
        connect(m_wizard, &Wizard::openSyncthingRequested, this, &SyncthingApplet::showWebUI);
        connect(m_wizard, &Wizard::settingsChanged, this, &SyncthingApplet::applySettingsChangesFromWizard);
    }
    centerWidget(m_wizard);
    m_wizard->show();
    m_wizard->activateWindow();
}

void SyncthingApplet::applySettingsChangesFromWizard()
{
    // reset possibly opened settings dialog to be consistent with new configuration
    if (m_settingsDlg) {
        m_settingsDlg->reset();
    }

    // ensure first connection is selected as this is the connection the wizard configures
    m_applyingSettingsForWizard = true;
    applySettings(0);
}

void SyncthingApplet::concludeWizard(const QString &errorMessage)
{
    if (!m_applyingSettingsForWizard) {
        return;
    }
    m_applyingSettingsForWizard = false;
    if (m_wizard) {
        m_wizard->handleConfigurationApplied(errorMessage, m_data.connection());
    }
}

void SyncthingApplet::showSyncthingUI(bool noQuickUI)
{
    auto *const dlg
        = QtGui::showWebUI(m_data.connection()->syncthingUrl(), currentConnectionConfig(), m_webViewDlg, this, m_data.connection(), noQuickUI);
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
    if (!dlg) {
        return;
    }
    if (!m_webViewDlg) {
        m_webViewDlg = dlg;
        connect(m_webViewDlg, &WebViewDialog::destroyed, this, &SyncthingApplet::handleWebViewDeleted);
    }
    m_webViewDlg->show();
    m_webViewDlg->activateWindow();
#else
    Q_UNUSED(dlg)
#endif
}

void SyncthingApplet::showWebUI()
{
    showSyncthingUI();
}

#if defined(GUI_QTQUICK) && defined(SYNCTHINGWIDGETS_GUI_QTQUICK_MODE_DESKTOP)
void SyncthingApplet::showQtQuickGui()
{
    if (m_quickUI.has_value()) {
        m_quickUI->ui.showMainWindow();
    }
}
#endif

void SyncthingApplet::showLog()
{
    auto *const dlg = TextViewDialog::forLogEntries(*m_data.connection());
    dlg->setAttribute(Qt::WA_DeleteOnClose, true);
    centerWidget(dlg);
    dlg->show();
}

void SyncthingApplet::showOwnDeviceId()
{
    auto *const dlg = ownDeviceIdDialog(*m_data.connection());
    dlg->setAttribute(Qt::WA_DeleteOnClose, true);
    centerWidget(dlg);
    dlg->show();
}

void SyncthingApplet::showAboutDialog()
{
    if (!m_aboutDlg) {
        m_aboutDlg = new AboutDialog(nullptr, QStringLiteral(APP_NAME), aboutDialogAttribution(), QStringLiteral(APP_VERSION),
            CppUtilities::applicationInfo.dependencyVersions, QStringLiteral(APP_URL), QStringLiteral(APP_DESCRIPTION), aboutDialogImage());
        m_aboutDlg->setWindowTitle(tr("About") + QStringLiteral(" - " APP_NAME));
        m_aboutDlg->setWindowIcon(QIcon::fromTheme(QStringLiteral("syncthingtray")));
        m_aboutDlg->setAttribute(Qt::WA_DeleteOnClose);
        connect(m_aboutDlg, &QObject::destroyed, this, &SyncthingApplet::handleAboutDialogDeleted);
    }
    centerWidget(m_aboutDlg);
    m_aboutDlg->show();
    m_aboutDlg->activateWindow();
}

void SyncthingApplet::showNotificationsDialog()
{
    if (!m_notificationsDlg) {
        m_notificationsDlg = errorNotificationsDialog(*m_data.connection());
        m_notificationsDlg->setAttribute(Qt::WA_DeleteOnClose, true);
        connect(m_notificationsDlg, &QDialog::destroyed, this, &SyncthingApplet::handleNotificationsDialogDeleted);
    }
    m_notificationsDlg->show();
    m_notificationsDlg->activateWindow();
}

void SyncthingApplet::showInternalErrorsDialog()
{
    auto *const errorViewDlg = InternalErrorsDialog::instance();
    connect(errorViewDlg, &InternalErrorsDialog::errorsCleared, this, &SyncthingApplet::handleErrorsCleared);
    centerWidget(errorViewDlg);
    errorViewDlg->show();
}

void SyncthingApplet::showDirectoryErrors(const QString &dirId)
{
    auto row = 0;
    auto *const dir = m_data.connection()->findDirInfo(dirId, row);
    if (!dir) {
        return;
    }
    m_data.connection()->requestDirPullErrors(dirId);
    auto *const dlg = new DirectoryErrorsDialog(*m_data.connection(), *dir);
    dlg->setAttribute(Qt::WA_DeleteOnClose, true);
    centerWidget(dlg);
    dlg->show();
}

bool SyncthingApplet::showFileBrowser(const QString &dirId)
{
    if (auto row = 0; auto *const dir = m_data.connection()->findDirInfo(dirId, row)) {
        showCenteredDialog(QtGui::browseRemoteFilesDialog(*m_data.connection(), *dir));
    }
    return true;
}

void SyncthingApplet::showIgnorePatterns(const QString &dirId)
{
    if (auto row = 0; auto *const dir = m_data.connection()->findDirInfo(dirId, row)) {
        showCenteredDialog(QtGui::ignorePatternsDialog(*m_data.connection(), *dir));
    }
}

void SyncthingApplet::showCenteredDialog(QWidget *dlg)
{
    dlg->resize(600, 500);
    centerWidget(dlg);
    dlg->show();
}

void SyncthingApplet::copyToClipboard(const QString &text)
{
    QGuiApplication::clipboard()->setText(text);
}

void SyncthingApplet::copyToClipboard(const QString &dirId, const QString &relativePath)
{
    if (const auto fullPath = m_data.connection()->fullPath(dirId, relativePath); !fullPath.isEmpty()) {
        QGuiApplication::clipboard()->setText(fullPath);
    } else {
        QGuiApplication::clipboard()->setText(relativePath);
    }
}

void SyncthingApplet::openLocalFileOrDir(const QString &dirId, const QString &relativePath)
{
    if (const auto fullPath = m_data.connection()->fullPath(dirId, relativePath); !fullPath.isEmpty()) {
        QtUtilities::openLocalFileOrDir(fullPath);
    } else {
        QMessageBox::warning(nullptr, QStringLiteral(APP_NAME), tr("Associated directory does not exist."));
    }
}

/*!
 * \brief Ensures settings take effect when applied via the settings dialog.
 * \remarks Does not save the settings to disk. This is done in Settings::save() and Applet::configChanged().
 */
void SyncthingApplet::handleSettingsChanged()
{
    applySettings();
}

void SyncthingApplet::handleConnectionStatusChanged(Data::SyncthingStatus previousStatus, Data::SyncthingStatus newStatus)
{
    Q_UNUSED(previousStatus)
    if (!m_initialized) {
        return;
    }

    setPassive(static_cast<int>(newStatus) < passiveStates().size() && passiveStates().at(static_cast<int>(newStatus)).isChecked());
    updateStatusIconAndTooltip();

    if (m_applyingSettingsForWizard) {
        switch (newStatus) {
        case SyncthingStatus::Disconnected:
            concludeWizard(tr("Unable to establish connection to Syncthing."));
            break;
        case SyncthingStatus::Reconnecting:
            break;
        default:
            concludeWizard();
        }
    }
}

void SyncthingApplet::handleDevicesChanged()
{
    m_data.updateDeviceInfo();
    emit m_data.statusInfo()->statusInfoChanged();
    emit connectionStatusChanged();
}

void SyncthingApplet::handleInternalError(
    const QString &errorMsg, SyncthingErrorCategory category, int networkError, const QNetworkRequest &request, const QByteArray &response)
{
    if (!InternalError::isRelevant(*m_data.connection(), category, errorMsg, networkError)) {
        return;
    }
    auto error = InternalError(errorMsg, request.url(), response);
    if (Settings::values().notifyOn.internalErrors) {
        m_dbusNotifier.showInternalError(error);
    }
    InternalErrorsDialog::addError(std::move(error));
    if (!m_hasInternalErrors) {
        emit hasInternalErrorsChanged(m_hasInternalErrors = true);
    }
}

void SyncthingApplet::handleDirStatisticsChanged()
{
    m_overallStats = m_data.connection()->computeOverallDirStatistics();
    emit statisticsChanged();
}

void SyncthingApplet::handleErrorsCleared()
{
    emit hasInternalErrorsChanged(m_hasInternalErrors = false);
}

void SyncthingApplet::handleAboutDialogDeleted()
{
    m_aboutDlg = nullptr;
}

void SyncthingApplet::handleWebViewDeleted()
{
    m_webViewDlg = nullptr;
}

void SyncthingApplet::handleNotificationsDialogDeleted()
{
    m_notificationsDlg = nullptr;
}

void SyncthingApplet::handleNewNotification(DateTime when, const QString &msg)
{
    if (Settings::values().notifyOn.syncthingErrors) {
        m_dbusNotifier.showSyncthingNotification(when, msg);
    }
}

void SyncthingApplet::handleSystemdServiceError(const QString &context, const QString &name, const QString &message)
{
    handleInternalError(tr("D-Bus error - unable to ") % context % QChar('\n') % name % QChar(':') % message, SyncthingErrorCategory::SpecificRequest,
        QNetworkReply::NoError, QNetworkRequest(), QByteArray());
}

void Plasmoid::SyncthingApplet::handleEngineDestroyed()
{
    m_imageProvider = nullptr; // engine has ownership over image provider
}

void SyncthingApplet::handleThemeChanged()
{
    // unset the fa-URL to provoke Qt Quick to reload the images
    emit faUrlChanged(m_faUrl = QString());

    // return to the event loop before setting the new theme color; otherwise Qt Quick does not update the images
    QTimer::singleShot(0, this, [this] {
        const auto palette = paletteFromTheme(m_theme);
        m_iconManager.setPalette(palette);
        setBrightColors(isPaletteDark(palette));
        if (m_imageProvider) {
            m_imageProvider->setDefaultColor(m_theme.color(Plasma::Theme::TextColor));
        }
        emit faUrlChanged(m_faUrl = QStringLiteral("image://fa/"));
    });
}

void SyncthingApplet::setBrightColors(bool brightColors)
{
    m_models.setBrightColors(brightColors);
    m_downloadModel.setBrightColors(brightColors);
}

void SyncthingApplet::applySettings(int changeConnectionIndex)
{
    const KConfigGroup config = this->config();
    const auto &settings = Settings::values();

    // apply notification settings
    settings.apply(*m_data.notifier());

    // apply appearance settings
    setSize(config.readEntry<QSize>("size", QSize(25, 25)));
    setShowingTabTexts(config.readEntry<bool>("showTabTexts", false));
    setShowingDownloads(config.readEntry<bool>("showDownloads", false));
    setShowingSyncthingIcons(config.readEntry<bool>("showStIcons", true));
    m_iconManager.applySettings(&settings.icons.status, nullptr, settings.icons.usePaletteForStatus, false);
    m_defaultTab = config.readEntry<>("defaultTab", 0);
#if defined(GUI_QTQUICK) && defined(SYNCTHINGWIDGETS_GUI_QTQUICK_MODE_DESKTOP)
    if (m_quickUI.has_value()) {
        m_quickUI->ui.setSyncthingIconsVisible(m_showSyncthingIcons);
    }
#endif

    // restore selected states
    // note: The settings dialog writes this to the Plasmoid's config like the other settings. However, it
    //       is simpler and more efficient to assign the states directly. Of course this is only possible if
    //       the dialog has already been shown.
    if (m_settingsDlg) {
        setPassiveStates(m_settingsDlg->appearanceOptionPage()->passiveStatusSelection()->items());
    } else {
        m_passiveSelectionModel.applyVariantList(config.readEntry("passiveStates", QVariantList()));
    }

    // apply connection config
    const int newConfig = changeConnectionIndex < 0 ? m_currentConnectionConfig : changeConnectionIndex;
    m_currentConnectionConfig = -1; // force update
    setCurrentConnectionConfigIndex(newConfig);

    // update status icons and tooltip because the reconnect interval might have changed
    updateStatusIconAndTooltip();

    emit settingsChanged();
}

#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
void SyncthingApplet::handleSystemdStatusChanged()
{
    Settings::values().systemd.apply(*m_data.connection(), currentConnectionConfig());
}
#endif

} // namespace Plasmoid

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
namespace Plasmoid {
K_PLUGIN_CLASS(SyncthingApplet)
}
#else
K_EXPORT_PLASMA_APPLET_WITH_JSON(syncthing, Plasmoid::SyncthingApplet, "metadata.json")
#endif

#include "syncthingapplet.moc"
