/*
    SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
    SPDX-FileCopyrightText: 2016-2017 tezeb <tezeb+github@outoftheblue.pl>
    SPDX-FileCopyrightText: 2018 Lukas Vogel <lukedirtwalker@gmail.com>
    SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
    SPDX-FileCopyrightText: 2019 Maciej S. Szmigiero <mail@maciej.szmigiero.name>
    SPDX-FileCopyrightText: 2023 g10 Code GmbH
    SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>

    SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "mainwindow.h"

#include <gpgpass_version.h>

#include "clipboardhelper.h"
#include "conf/configuredialog.h"
#include "job/directoryreencryptjob.h"
#include "job/filedecryptjob.h"
#include "job/fileencryptjob.h"
#include "models/addfileinfoproxy.h"
#include "passentry.h"
#include "rootfoldersmanager.h"
#include "setupwidget.h"
#include "ui_mainwindow.h"
#include "usersdialog.h"
#include "util.h"
#include "widgets/passwordeditorwidget.h"
#include "widgets/passwordviewerwidget.h"

#include <KActionCollection>
#include <KLocalizedString>
#include <KMessageBox>
#include <KMessageWidget>
#include <KStandardAction>
#include <KStandardGuiItem>

#include <QCloseEvent>
#include <QComboBox>
#include <QDebug>
#include <QDirIterator>
#include <QFileInfo>
#include <QInputDialog>
#include <QMenu>
#include <QMessageBox>
#include <QShortcut>
#include <qstringliteral.h>

enum class StackLayer {
    WelcomePage = 0,
    PasswordViewer = 1,
    ConfigurationPage = 2,
    PasswordEditor = 3,
};

static QString directoryName(const QString &dirOrFile)
{
    QFileInfo fi{dirOrFile};
    if (fi.isDir()) {
        return fi.absoluteFilePath();
    } else {
        return fi.absolutePath();
    }
}

/**
 * @brief MainWindow::MainWindow handles all of the main functionality and also
 * the main window.
 * @param searchText for searching from cli
 * @param parent pointer
 */
MainWindow::MainWindow(QWidget *parent)
    : KXmlGuiWindow(parent)
    , ui(new Ui::MainWindow)
    , m_clipboardHelper(new ClipboardHelper(this))
    , m_passwordViewer(new PasswordViewerWidget(m_clipboardHelper, this))
    , m_passwordEditor(new PasswordEditorWidget(m_clipboardHelper, this))
    , m_rootFoldersManager(new RootFoldersManager(this))
{
    ui->setupUi(this);

    initToolBarButtons();
    setupGUI(ToolBar | Keys | Save | Create, QStringLiteral("gpgpassui.rc"));
    setCentralWidget(ui->centralWidget);

    ui->stackedWidget->addWidget(m_passwordViewer);
    auto setupWidget = new SetupWidget(this);
    ui->stackedWidget->addWidget(setupWidget);
    ui->stackedWidget->addWidget(m_passwordEditor);
    connect(setupWidget, &SetupWidget::setupComplete, this, &MainWindow::slotSetupFinished);

    connect(m_passwordEditor, &PasswordEditorWidget::editorClosed, this, [this]() {
        m_actionEdit->setChecked(false);
    });

    ui->separator->setFixedHeight(1);
    ui->separator->setFrameStyle(QFrame::HLine);
    ui->lineEditWrapper->setContentsMargins(style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
                                            style()->pixelMetric(QStyle::PM_LayoutTopMargin),
                                            style()->pixelMetric(QStyle::PM_LayoutRightMargin),
                                            style()->pixelMetric(QStyle::PM_LayoutBottomMargin));

    m_errorMessage = new KMessageWidget();
    m_errorMessage->setMessageType(KMessageWidget::Error);
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
    m_errorMessage->setPosition(KMessageWidget::Position::Header);
#endif
    m_errorMessage->hide();
    ui->messagesArea->addWidget(m_errorMessage);

    m_storeModel.setRootFoldersManager(m_rootFoldersManager);

    connect(&m_storeModel, &StoreModel::errorOccurred, this, [this](auto str) {
        m_errorMessage->setText(str);
        m_errorMessage->animatedShow();
        setUiElementsEnabled(true);
    });

    connect(&m_storeModel, &StoreModel::rootFoldersSizeChanged, this, &MainWindow::updateRootIndex);

    connect(m_passwordViewer, &PasswordViewerWidget::loaded, this, [this] {
        setUiElementsEnabled(true);
    });
}

void MainWindow::setVisible(bool visible)
{
    // This originated in the ctor, but we want this to happen after the first start wizard has been run
    // so for now, moved to show() on first call
    if (visible && firstShow) {
        firstShow = false;
        // register shortcut ctrl/cmd + Q to close the main window
        new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q), this, SLOT(close()));

        ui->treeView->setModel(&m_storeModel);
        ui->treeView->setHeaderHidden(true);
        ui->treeView->setIndentation(15);
        ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
        ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
        ui->treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
        ui->treeView->sortByColumn(0, Qt::AscendingOrder);
        connect(ui->treeView, &QWidget::customContextMenuRequested, this, &MainWindow::showContextMenu);
        connect(ui->treeView, &DeselectableTreeView::emptyClicked, this, &MainWindow::deselect);

        searchTimer.setInterval(350);
        searchTimer.setSingleShot(true);

        connect(&searchTimer, &QTimer::timeout, this, &MainWindow::onTimeoutSearch);

        ui->lineEdit->setClearButtonEnabled(true);

        setUiElementsEnabled(true);

        QTimer::singleShot(10, this, SLOT(focusInput()));
        verifyInitialized();

        updateRootIndex();
    }

    QMainWindow::setVisible(visible);
}

MainWindow::~MainWindow() = default;

void MainWindow::updateRootIndex()
{
    if (m_rootFoldersManager->rootFolders().count() == 1) {
        ui->treeView->setRootIndex(m_storeModel.index(0, 0));
    } else {
        ui->treeView->setRootIndex({});
    }
}

/**
 * @brief MainWindow::focusInput selects any text (if applicable) in the search
 * box and sets focus to it. Allows for easy searching, called at application
 * start and when receiving empty message in MainWindow::messageAvailable when
 * compiled with SINGLE_APP=1 (default).
 */
void MainWindow::focusInput()
{
    ui->lineEdit->selectAll();
    ui->lineEdit->setFocus();
}

/**
 * @brief MainWindow::changeEvent sets focus to the search box
 * @param event
 */
void MainWindow::changeEvent(QEvent *event)
{
    QWidget::changeEvent(event);
    if (event->type() == QEvent::ActivationChange) {
        if (isActiveWindow()) {
            focusInput();
        }
    }
}

/**
 * @brief MainWindow::initToolBarButtons init main ToolBar and connect actions
 */
void MainWindow::initToolBarButtons()
{
    m_actionAddEntry = new QAction(QIcon::fromTheme(QStringLiteral("entry-new")), i18nc("@action", "Add Entry"), this);
    m_actionAddEntry->setToolTip(i18nc("@info:tooltip", "Add Password Entry"));
    actionCollection()->addAction(QStringLiteral("add_entry"), m_actionAddEntry);
    connect(m_actionAddEntry, &QAction::triggered, this, &MainWindow::addPassword);

    m_actionAddFolder = new QAction(QIcon::fromTheme(QStringLiteral("folder-new-symbolic")), i18nc("@action", "Add Folder"), this);
    actionCollection()->addAction(QStringLiteral("add_folder"), m_actionAddFolder);
    connect(m_actionAddFolder, &QAction::triggered, this, &MainWindow::addFolder);

    m_actionDelete = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete-symbolic")), i18nc("@action", "Delete"), this);
    actionCollection()->addAction(QStringLiteral("gpgpass_delete"), m_actionDelete);
    connect(m_actionDelete, &QAction::triggered, this, &MainWindow::onDelete);

    m_actionEdit = new QAction(QIcon::fromTheme(QStringLiteral("document-properties-symbolic")), i18nc("@action", "Edit"), this);
    m_actionEdit->setCheckable(true);
    actionCollection()->addAction(QStringLiteral("gpgpass_edit"), m_actionEdit);
    connect(m_actionEdit, &QAction::toggled, this, &MainWindow::onEdit);

    m_actionUsers = new QAction(QIcon::fromTheme(QStringLiteral("x-office-address-book-symbolic")), i18nc("@action", "Users"), this);
    m_actionUsers->setToolTip(i18nc("@info:tooltip", "Manage who can read password in folder"));
    actionCollection()->addAction(QStringLiteral("gpgpass_users"), m_actionUsers);
    connect(m_actionUsers, &QAction::triggered, this, &MainWindow::onUsers);
    KStandardAction::quit(this, &MainWindow::close, actionCollection());

    m_actionConfig = KStandardAction::preferences(
        this,
        [this] {
            openConfig(ConfigureDialog::Page::None);
        },
        actionCollection());

    connect(ui->treeView, &QTreeView::clicked, this, &MainWindow::selectTreeItem);
    connect(ui->lineEdit, &QLineEdit::textChanged, this, &MainWindow::filterList);
    connect(ui->lineEdit, &QLineEdit::returnPressed, this, &MainWindow::selectFromSearch);
}

const QModelIndex MainWindow::getCurrentTreeViewIndex()
{
    return ui->treeView->currentIndex();
}

void MainWindow::openConfig(ConfigureDialog::Page page)
{
    QScopedPointer<ConfigureDialog> dialog(new ConfigureDialog(m_rootFoldersManager, this));
    dialog->setModal(true);
    dialog->openPage(page);

    if (dialog->exec()) {
        if (dialog->result() == QDialog::Accepted) {
            show();

            m_passwordViewer->setPanelTimer();
            m_clipboardHelper->setClipboardTimer();
        }
    }
}

/**
 * @brief MainWindow::on_treeView_clicked read the selected password file
 * @param index
 */
void MainWindow::selectTreeItem(const QModelIndex &index)
{
    bool cleared = ui->treeView->currentIndex().flags() == Qt::NoItemFlags;
    m_clipboardHelper->clearClippedText();
    QString file = index.data(QFileSystemModel::FilePathRole).toString();
    m_actionEdit->setChecked(false);

    QFileInfo fileInfo(file);
    if (!file.isEmpty() && fileInfo.isFile() && !cleared) {
        m_selectedFile = file;
        const auto filePath = fileInfo.absoluteFilePath();
        if (m_passwordViewer->filePath() != filePath) {
            m_passwordViewer->setFilePath(ui->treeView->selectionModel()->currentIndex().data().toString(), filePath);
        }
        ui->stackedWidget->setCurrentIndex((int)StackLayer::PasswordViewer);
    } else {
        m_passwordViewer->clear();
        m_actionEdit->setEnabled(false);
        m_actionDelete->setEnabled(index.parent().isValid() || m_rootFoldersManager->rootFolders().count() == 1);
        ui->stackedWidget->setCurrentIndex((int)StackLayer::WelcomePage);
    }
}

/**
 * @brief MainWindow::on_treeView_doubleClicked when doubleclicked on
 * TreeViewItem, open the edit Window
 * @param index
 */
void MainWindow::editTreeItem(const QModelIndex &index)
{
    QFileInfo fileInfo{index.data(QFileSystemModel::Roles::FilePathRole).toString()};

    if (!fileInfo.isFile()) {
        return;
    }
    const auto filePath = fileInfo.absoluteFilePath();

    if (m_passwordViewer->filePath() == filePath && !m_passwordViewer->rawContent().isEmpty()) {
        switchToPasswordEditor(filePath, m_passwordViewer->rawContent());
    } else {
        auto decryptJob = new FileDecryptJob(filePath);
        connect(decryptJob, &FileDecryptJob::finished, this, [this, decryptJob](KJob *) {
            if (decryptJob->error() != KJob::NoError) {
                m_errorMessage->setText(decryptJob->errorText());
                m_errorMessage->animatedShow();
                return;
            }

            switchToPasswordEditor(decryptJob->filePath(), decryptJob->content());
        });
        decryptJob->start();
    }
}

void MainWindow::switchToPasswordEditor(const QString &filePath, const QString &content)
{
    // Ensure we don't trigger the actionEdit
    disconnect(m_actionEdit, &QAction::toggled, this, &MainWindow::onEdit);
    m_actionEdit->setChecked(true);
    connect(m_actionEdit, &QAction::toggled, this, &MainWindow::onEdit);

    const QFileInfo fileInfo(filePath);
    const auto name = fileInfo.baseName();
    const auto absoluteFilePath = fileInfo.absoluteFilePath();
    PassEntry entry(name, content);
    m_passwordEditor->setPassEntry(entry);

    ui->stackedWidget->setCurrentIndex((int)StackLayer::PasswordEditor);

    disconnect(m_passwordEditor, &PasswordEditorWidget::save, this, nullptr);
    connect(m_passwordEditor, &PasswordEditorWidget::save, this, [this, name, fileInfo, absoluteFilePath](const QString &content) {
        const auto recipients = m_storeModel.recipientsForFile(fileInfo);

        if (recipients.isEmpty()) {
            m_errorMessage->setText(i18n("Could not read encryption key to use, .gpg-id file missing or invalid."));
            m_errorMessage->animatedShow();
            return;
        }

        auto encryptJob = new FileEncryptJob(absoluteFilePath, content.toUtf8(), recipients);
        connect(encryptJob, &FileEncryptJob::finished, this, [absoluteFilePath, name, encryptJob, content, this](KJob *) {
            if (encryptJob->error() != KJob::NoError) {
                m_errorMessage->setText(encryptJob->errorText());
                m_errorMessage->animatedShow();
                qWarning() << encryptJob->errorText();
                return;
            }

            ui->treeView->setFocus();

            // Ensure we don't trigger the actionEdit
            disconnect(m_actionEdit, &QAction::toggled, this, &MainWindow::onEdit);
            m_actionEdit->setChecked(false);
            connect(m_actionEdit, &QAction::toggled, this, &MainWindow::onEdit);

            if (m_passwordViewer->filePath() == absoluteFilePath) {
                m_passwordViewer->setRawContent(content);
            } else {
                // slower but ensure state is correct
                m_passwordViewer->setFilePath(name, absoluteFilePath);
            }

            ui->stackedWidget->setCurrentIndex((int)StackLayer::PasswordViewer);

            auto index = m_storeModel.indexForPath(absoluteFilePath);
            ui->treeView->setCurrentIndex(index);
            selectTreeItem(index);
        });
        encryptJob->start();
    });
}

/**
 * @brief MainWindow::deselect clear the selection, password and copy buffer
 */
void MainWindow::deselect()
{
    m_clipboardHelper->clearClipboard();
    ui->treeView->clearSelection();
    m_actionEdit->setEnabled(false);
    m_actionDelete->setEnabled(false);
    m_passwordViewer->clear();
    ui->stackedWidget->setCurrentIndex((int)StackLayer::WelcomePage);
}

/**
 * @brief MainWindow::setUiElementsEnabled enable or disable the relevant UI
 * elements
 * @param state
 */
void MainWindow::setUiElementsEnabled(bool state)
{
    ui->treeView->setEnabled(state);
    ui->lineEdit->setEnabled(state);
    ui->lineEdit->installEventFilter(this);
    m_actionAddEntry->setEnabled(state);
    m_actionAddFolder->setEnabled(state);
    m_actionUsers->setEnabled(state);
    m_actionConfig->setEnabled(state);
    // is a file selected?
    state &= ui->treeView->currentIndex().isValid();
    m_actionDelete->setEnabled(state);
    m_actionEdit->setEnabled(state);
}

/**
 * @brief Executes when the string in the search box changes, collapses the
 * TreeView
 * @param arg1
 */
void MainWindow::filterList(const QString &arg1)
{
    ui->treeView->expandAll();
    searchTimer.start();
}

/**
 * @brief MainWindow::onTimeoutSearch Fired when search is finished or too much
 * time from two keypresses is elapsed
 */
void MainWindow::onTimeoutSearch()
{
    QString query = ui->lineEdit->text();

    if (query.isEmpty()) {
        ui->treeView->collapseAll();
        deselect();
    }

    query.replace(QStringLiteral(" "), QStringLiteral(".*"));
    QRegularExpression regExp(query, QRegularExpression::CaseInsensitiveOption);
    m_storeModel.setFilterRegularExpression(regExp);

    if (m_storeModel.rowCount() > 0 && !query.isEmpty()) {
        selectFirstFile();
    } else {
        m_actionEdit->setEnabled(false);
        m_actionDelete->setEnabled(false);
    }
}

/**
 * @brief MainWindow::on_lineEdit_returnPressed get searching
 *
 * Select the first possible file in the tree
 */
void MainWindow::selectFromSearch()
{
    if (m_storeModel.rowCount() > 0) {
        selectFirstFile();
        selectTreeItem(ui->treeView->currentIndex());
    }
}

/**
 * @brief MainWindow::selectFirstFile select the first possible file in the
 * tree
 */
void MainWindow::selectFirstFile()
{
    auto model = ui->treeView->model();
    auto index = firstFile(model->index(0, 0));
    ui->treeView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
}

/**
 * @brief MainWindow::firstFile return location of first possible file
 * @param parentIndex
 * @return QModelIndex
 */
QModelIndex MainWindow::firstFile(QModelIndex parentIndex)
{
    auto model = parentIndex.model();
    int numRows = model->rowCount(parentIndex);
    for (int row = 0; row < numRows; ++row) {
        auto index = model->index(row, 0, parentIndex);
        if (index.data(AddFileInfoProxy::FileInfoRole).value<QFileInfo>().isFile())
            return index;
        if (model->hasChildren(index))
            return firstFile(index);
    }
    return parentIndex;
}

QString MainWindow::fallbackStore()
{
    const auto rootFolders = m_rootFoldersManager->rootFolders();
    if (rootFolders.isEmpty()) {
        QMessageBox::critical(this, i18nc("@title:dialog", "No Password Store Found"), i18nc("@info", "Please add a password store first."));
        return {};
    }
    return rootFolders[0]->path();
}

/**
 * @brief MainWindow::addPassword add a new password by showing a
 * number of dialogs.
 */
void MainWindow::addPassword()
{
    bool ok;

    QString dir = directoryName(ui->treeView->currentIndex().data(QFileSystemModel::Roles::FilePathRole).toString());
    if (dir.isEmpty()) {
        dir = fallbackStore();
        if (dir.isEmpty()) {
            return;
        }
    }
    QString file = QInputDialog::getText(this,
                                         i18nc("@title:dialog", "New File"),
                                         xi18nc("@info", "<para>New password file:</para><para>(Will be placed in <filename>%1</filename>)</para>", dir),
                                         QLineEdit::Normal,
                                         QString{},
                                         &ok);
    if (!ok || file.isEmpty())
        return;
    file = QDir(dir).absoluteFilePath(file + QStringLiteral(".gpg"));
    switchToPasswordEditor(file);
}

/**
 * @brief MainWindow::onDelete remove password, if you are
 * sure.
 */
void MainWindow::onDelete()
{
    QModelIndex currentIndex = ui->treeView->currentIndex();
    if (!currentIndex.isValid()) {
        // If not valid, we might end up passing empty string
        // to delete, and taht might delete unexpected things on disk
        return;
    }

    QFileInfo fileOrFolder{currentIndex.data(QFileSystemModel::FilePathRole).toString()};
    bool isDir = fileOrFolder.isDir();
    QString file = fileOrFolder.absoluteFilePath();

    QString message;
    if (isDir) {
        message = xi18nc("@info deleting a folder; placeholder is folder name",
                         "Are you sure you want to remove <filename>%1</filename> and the whole content?",
                         file);
        QDirIterator it(file, QDirIterator::Subdirectories);
        while (it.hasNext()) {
            it.next();
            if (auto fi = it.fileInfo(); fi.isFile()) {
                if (fi.suffix() != QStringLiteral("gpg")) {
                    message += QStringLiteral("<br><strong>")
                        + i18nc("extra warning during certain folder deletions",
                                "Attention: "
                                "there are unexpected files in the given folder, "
                                "check them before continue")
                        + QStringLiteral("</strong>");
                    break;
                }
            }
        }
    } else {
        message = xi18nc("@info deleting a file; placeholder is file name", "Are you sure you want to remove <filename>%1</filename>?", file);
    }

    if (KMessageBox::warningTwoActions(this, message, i18nc("@title:dialog", "Confirm Deletion"), KStandardGuiItem::remove(), KStandardGuiItem::cancel())
        != KMessageBox::PrimaryAction)
        return;

    if (!isDir) {
        QFile(file).remove();
    } else {
        QDir dir(file);
        dir.removeRecursively();
    }
}

/**
 * @brief MainWindow::onEdit try and edit (selected) password.
 */
void MainWindow::onEdit(bool edit)
{
    if (edit) {
        editTreeItem(ui->treeView->currentIndex());
    } else {
        selectTreeItem(getCurrentTreeViewIndex());
        m_actionEdit->setChecked(false);
    }
}

/**
 * @brief MainWindow::userDialog see MainWindow::onUsers()
 * @param dir folder to edit users for.
 */
void MainWindow::userDialog(QString dir)
{
    if (dir.isEmpty()) {
        dir = fallbackStore();
        if (dir.isEmpty()) {
            return;
        }
    }
    QFileInfo fi(dir);
    if (!fi.isDir()) {
        dir = fi.absolutePath();
    }
    const auto recipients = m_storeModel.recipientsForFile(QFileInfo(dir));
    auto usersDialog = new UsersDialog(recipients, this);
    usersDialog->setAttribute(Qt::WA_DeleteOnClose);
    connect(usersDialog, &UsersDialog::save, this, [dir, this, usersDialog](const QList<QByteArray> &recipients) {
        auto reencryptJob = new DirectoryReencryptJob(m_storeModel, recipients, dir);
        connect(reencryptJob, &DirectoryReencryptJob::result, this, [this, usersDialog](KJob *job) {
            if (job->error() != KJob::NoError) {
                usersDialog->showError(job->errorText());
                return;
            }

            setUiElementsEnabled(true);
            ui->treeView->setFocus();
            verifyInitialized();
            usersDialog->deleteLater();

        });

        // statusBar()->showMessage(i18n("Re-encrypting folders"), 3000);
        setUiElementsEnabled(false);
        ui->treeView->setDisabled(true);
        reencryptJob->start();
    });
    usersDialog->show();
}

/**
 * @brief MainWindow::onUsers edit users for the current
 * folder,
 * gets lists and opens UserDialog.
 */
void MainWindow::onUsers()
{
    QString dir = ui->treeView->currentIndex().data(QFileSystemModel::Roles::FilePathRole).toString();
    if (dir.isEmpty()) {
        dir = fallbackStore();
        if (dir.isEmpty()) {
            return;
        }
    } else {
        QFileInfo fi(dir);
        if (!fi.isDir()) {
            dir = fi.absolutePath();
        }
        dir = Util::normalizeFolderPath(dir);
    }
    userDialog(dir);
}

void MainWindow::slotSetupFinished(const QString &location, const QByteArray &keyId)
{
    const QString gpgIdFile = location + QStringLiteral(".gpg-id");
    QFile gpgId(gpgIdFile);
    if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
        KMessageBox::error(this, xi18nc("@info", "Unable to write user configuration at <filename>%1</filename>. Error: %2", gpgIdFile, gpgId.errorString()));
        return;
    }
    gpgId.write(keyId);
    gpgId.close();

    m_rootFoldersManager->addRootFolder(i18nc("Default store name", "Local Store"), location);
    m_rootFoldersManager->save();

    ui->lineEdit->clear();
    m_passwordViewer->clear();
    ui->treeView->selectionModel()->clear();

    m_actionEdit->setEnabled(false);
    m_actionDelete->setEnabled(false);

    ui->stackedWidget->setCurrentIndex((int)StackLayer::WelcomePage);
    verifyInitialized();
}

void MainWindow::verifyInitialized()
{
    auto alreadyConfigured = !m_rootFoldersManager->rootFolders().isEmpty();
    ui->sidebar->setVisible(alreadyConfigured);
    ui->stackedWidget->setCurrentIndex(alreadyConfigured ? (int)StackLayer::WelcomePage : (int)StackLayer::ConfigurationPage);

    m_actionAddFolder->setEnabled(alreadyConfigured);
    m_actionAddEntry->setEnabled(alreadyConfigured);
    m_actionDelete->setEnabled(m_actionDelete->isEnabled() && alreadyConfigured);
    m_actionEdit->setEnabled(m_actionEdit->isEnabled() && alreadyConfigured);
}

/**
 * @brief MainWindow::closeEvent hide or quit
 * @param event
 */
void MainWindow::closeEvent(QCloseEvent *event)
{
    m_clipboardHelper->clearClipboard();
    event->accept();
}

/**
 * @brief MainWindow::eventFilter filter out some events and focus the
 * treeview
 * @param obj
 * @param event
 * @return
 */
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
    if (obj == ui->lineEdit && event->type() == QEvent::KeyPress) {
        auto *key = dynamic_cast<QKeyEvent *>(event);
        if (key != nullptr && key->key() == Qt::Key_Down) {
            ui->treeView->setFocus();
        }
    }
    return QObject::eventFilter(obj, event);
}

/**
 * @brief MainWindow::keyPressEvent did anyone press return, enter or escape?
 * @param event
 */
void MainWindow::keyPressEvent(QKeyEvent *event)
{
    switch (event->key()) {
    case Qt::Key_Delete:
        onDelete();
        break;
    case Qt::Key_Return:
    case Qt::Key_Enter:
        if (m_storeModel.rowCount() > 0)
            selectTreeItem(ui->treeView->currentIndex());
        break;
    case Qt::Key_Escape:
        ui->lineEdit->clear();
        break;
    default:
        break;
    }
}

/**
 * @brief MainWindow::showContextMenu show us the (file or folder) context
 * menu
 * @param pos
 */
void MainWindow::showContextMenu(const QPoint &pos)
{
    QModelIndex index = ui->treeView->indexAt(pos);
    bool selected = true;
    if (!index.isValid()) {
        ui->treeView->clearSelection();
        m_actionDelete->setEnabled(false);
        m_actionEdit->setEnabled(false);
        selected = false;
    }

    ui->treeView->setCurrentIndex(index);

    QPoint globalPos = ui->treeView->viewport()->mapToGlobal(pos);

    QFileInfo fileOrFolder = ui->treeView->currentIndex().data(AddFileInfoProxy::FileInfoRole).value<QFileInfo>();

    QMenu contextMenu;

    if (!selected || fileOrFolder.isDir()) {
        QAction *addFolderAction = contextMenu.addAction(i18nc("@action:inmenu", "Add Folder"));
        addFolderAction->setIcon(QIcon::fromTheme(QStringLiteral("folder-add-symbolic")));
        connect(addFolderAction, &QAction::triggered, this, &MainWindow::addFolder);

        QAction *addPasswordAction = contextMenu.addAction(i18nc("@action:inmenu", "Add Entry"));
        addPasswordAction->setIcon(QIcon::fromTheme(QStringLiteral("lock-symbolic")));
        connect(addPasswordAction, &QAction::triggered, this, &MainWindow::addPassword);

        QAction *usersAction = contextMenu.addAction(i18nc("@action:inmenu", "Configure Users"));
        usersAction->setIcon(QIcon::fromTheme(QStringLiteral("system-users-symbolic")));
        connect(usersAction, &QAction::triggered, this, &MainWindow::onUsers);
    } else if (fileOrFolder.isFile()) {
        QAction *edit = contextMenu.addAction(i18nc("@action:inmenu", "Edit Entry"));
        edit->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
        connect(edit, &QAction::triggered, this, [this] {
            onEdit(true);
        });
    }
    if (selected && (index.parent().isValid() || m_rootFoldersManager->rootFolders().count() == 1)) {
        contextMenu.addSeparator();
        if (fileOrFolder.isDir()) {
            QAction *renameFolderAction = contextMenu.addAction(i18nc("@action:inmenu", "Rename Folder"));
            connect(renameFolderAction, &QAction::triggered, this, &MainWindow::renameFolder);
            renameFolderAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename-symbolic")));
        } else if (fileOrFolder.isFile()) {
            QAction *renamePasswordAction = contextMenu.addAction(i18nc("@action:inmenu", "Rename Entry"));
            renamePasswordAction->setToolTip(i18nc("@info:tooltip", "Rename Password Entry"));
            renamePasswordAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename-symbolic")));
            connect(renamePasswordAction, &QAction::triggered, this, &MainWindow::renamePassword);
        }

        QAction *deleteItem = contextMenu.addAction(fileOrFolder.isFile() ? i18nc("@action:inmenu", "Delete Entry") : i18nc("@action:inmenu", "Delete Folder"));
        deleteItem->setIcon(QIcon::fromTheme(QStringLiteral("delete-symbolic")));
        connect(deleteItem, &QAction::triggered, this, &MainWindow::onDelete);
    }

    if (!index.parent().isValid() && m_rootFoldersManager->rootFolders().count() != 1) {
        contextMenu.addSeparator();

        const auto configureAction = contextMenu.addAction(i18nc("@action:inmenu", "Configure Password Stores"));
        configureAction->setIcon(QIcon::fromTheme(QStringLiteral("configure-symbolic")));
        connect(configureAction, &QAction::triggered, this, [this] {
            openConfig(ConfigureDialog::Page::PasswordsStore);
        });
    }

    contextMenu.exec(globalPos);
}

/**
 * @brief MainWindow::addFolder add a new folder to store passwords in
 */
void MainWindow::addFolder()
{
    bool ok;
    QString dir = directoryName(ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString());

    if (dir.isEmpty()) {
        dir = fallbackStore();
        if (dir.isEmpty()) {
            return;
        }
    }
    QString newdir = QInputDialog::getText(this,
                                           i18nc("@title:dialog", "New File"),
                                           xi18nc("@info", "<para>New Folder:</para><para>(Will be placed in <filename>%1</filename>)</para>", dir),
                                           QLineEdit::Normal,
                                           QString{},
                                           &ok);
    if (!ok || newdir.isEmpty())
        return;
    QDir(dir).mkdir(newdir);
}

/**
 * @brief MainWindow::renameFolder rename an existing folder
 */
void MainWindow::renameFolder()
{
    bool ok;
    QString srcDir = QDir::cleanPath(directoryName(ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString()));
    if (srcDir.isEmpty()) {
        return;
    }
    QString srcDirName = QDir(srcDir).dirName();
    QString newName =
        QInputDialog::getText(this, i18nc("@title:dialog", "Rename Folder"), i18nc("@label:textbox", "Rename folder to:"), QLineEdit::Normal, srcDirName, &ok);
    if (!ok || newName.isEmpty())
        return;
    QString destDir = srcDir;
    destDir.replace(srcDir.lastIndexOf(srcDirName), srcDirName.length(), newName);
    m_storeModel.move(srcDir, destDir);
}

/**
 * @brief MainWindow::renamePassword rename an existing password
 */
void MainWindow::renamePassword()
{
    bool ok;
    QString file = ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString();
    QString filePath = QFileInfo(file).path();
    QString fileName = QFileInfo(file).fileName();
    if (fileName.endsWith(QStringLiteral(".gpg"), Qt::CaseInsensitive))
        fileName.chop(4);

    QString newName =
        QInputDialog::getText(this, i18nc("@title:dialog", "Rename File"), i18nc("@label:textbox", "Rename file to: "), QLineEdit::Normal, fileName, &ok);
    if (!ok || newName.isEmpty())
        return;
    QString newFile = QDir(filePath).filePath(newName);
    m_storeModel.move(file, newFile);
}
