/*
    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: 2019Maciej 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 "configdialog.h"
#include "filecontent.h"
#include "pass.h"
#include "passworddialog.h"
#include "qpushbuttonfactory.h"
#include "settings.h"
#include "ui_mainwindow.h"
#include "usersdialog.h"
#include "util.h"

#include <Prison/prison.h>
#include <KWidgetsAddons/KMessageWidget>
#include <KI18n/KLocalizedString>
#include <QCloseEvent>
#include <QFileInfo>
#include <QInputDialog>
#include <QLabel>
#include <QMenu>
#include <QMessageBox>
#include <QShortcut>
#include <QTimer>
#include <QComboBox>
#include <QDebug>

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)
    : QMainWindow(parent)
    , m_pass{std::make_unique<Pass>()}
    , ui(new Ui::MainWindow)
    , proxyModel{*m_pass}
{
#ifdef __APPLE__
    // extra treatment for mac os
    // see http://doc.qt.io/qt-5/qkeysequence.html#qt_set_sequence_auto_mnemonic
    qt_set_sequence_auto_mnemonic(true);
#endif
    ui->setupUi(this);

    m_clipboardHelper = new ClipboardHelper(this);

    m_errorMessage = new KMessageWidget();
    m_errorMessage->setMessageType(KMessageWidget::Error);
    m_errorMessage->hide();
    ui->messagesArea->addWidget(m_errorMessage);

    connect(m_pass.get(), &Pass::errorString, this, [this](auto str) {
        m_errorMessage->setText(str);
        m_errorMessage->animatedShow();
        setUiElementsEnabled(true);
    });
    connect(m_pass.get(), &Pass::critical, this, &MainWindow::critical);
    connect(m_pass.get(), &Pass::finishedShow, this, &MainWindow::passShowHandler);

    connect(m_pass.get(), &Pass::finishedInsert, this, [this]() {
        this->selectTreeItem(this->getCurrentTreeViewIndex());
    });
    connect(m_pass.get(), &Pass::startReencryptPath, this, &MainWindow::startReencryptPath);
    connect(m_pass.get(), &Pass::endReencryptPath, this, &MainWindow::endReencryptPath);

    {
        m_notInitialized = new KMessageWidget(i18n("Password store not initialized"));
        m_notInitialized->setCloseButtonVisible(false);
        QAction* action = new QAction(i18n("Initialize with users"));
        connect(action, &QAction::triggered, this, [this](){this->userDialog();});
        m_notInitialized->addAction(action);
        m_notInitialized->setMessageType(KMessageWidget::Error);
        ui->messagesArea->addWidget(m_notInitialized);
        m_notInitialized->hide();
    }
    {
        auto w = new QWidget();
        w->setLayout(new QHBoxLayout);
        auto l = new QLabel(i18n("Select profile"));
        auto sep = ui->toolBar->addSeparator();
        w->layout()->addWidget(l);
        m_profileBox = new QComboBox();
        w->layout()->addWidget(m_profileBox);
        m_profiles = ui->toolBar->addWidget(w);
        connect(m_profiles, &QAction::changed, sep, [this, sep]() { sep->setVisible(this->m_profiles->isVisible()); });
    }
}

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()));

        model.setNameFilters(QStringList() << QStringLiteral("*.gpg"));
        model.setNameFilterDisables(false);

        /*
         * I added this to solve Windows bug but now on GNU/Linux the main folder,
         * if hidden, disappear
         *
         * model.setFilter(QDir::NoDot);
         */

        QString passStore = Settings::getPassStore(Util::findPasswordStore());

        QModelIndex rootDir = model.setRootPath(passStore);
        model.fetchMore(rootDir);

        proxyModel.setSourceModel(&model);
        selectionModel.reset(new QItemSelectionModel(&proxyModel));

        ui->treeView->setModel(&proxyModel);
        ui->treeView->setRootIndex(proxyModel.mapFromSource(rootDir));
        ui->treeView->setColumnHidden(1, true);
        ui->treeView->setColumnHidden(2, true);
        ui->treeView->setColumnHidden(3, true);
        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);

        if (Settings::isNoLineWrapping()) {
            ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
        }
        ui->textBrowser->setOpenExternalLinks(true);
        ui->textBrowser->setContextMenuPolicy(Qt::DefaultContextMenu);

        updateProfileBox();

        clearPanelTimer.setInterval(1000 * Settings::getAutoclearPanelSeconds());
        clearPanelTimer.setSingleShot(true);
        connect(&clearPanelTimer, SIGNAL(timeout()), this, SLOT(clearPanel()));

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

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

        initToolBarButtons();
        initStatusBar();

        ui->lineEdit->setClearButtonEnabled(true);

        setUiElementsEnabled(true);

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

    QMainWindow::setVisible(visible);
}
MainWindow::~MainWindow() = default;

/**
 * @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()
{
    connect(ui->actionAddPassword, &QAction::triggered, this, &MainWindow::addPassword);
    connect(ui->actionAddFolder, &QAction::triggered, this, &MainWindow::addFolder);
    connect(ui->actionEdit, &QAction::triggered, this, &MainWindow::onEdit);
    connect(ui->actionDelete, &QAction::triggered, this, &MainWindow::onDelete);
    connect(ui->actionUsers, &QAction::triggered, this, &MainWindow::onUsers);
    connect(ui->actionConfig, &QAction::triggered, this, &MainWindow::onConfig);
    connect(ui->treeView, &QTreeView::clicked, this, &MainWindow::selectTreeItem);
    connect(ui->treeView, &QTreeView::doubleClicked, this, &MainWindow::editTreeItem);
    connect(m_profileBox, &QComboBox::currentTextChanged, this, &MainWindow::selectProfile);
    connect(ui->lineEdit, &QLineEdit::textChanged, this, &MainWindow::filterList);
    connect(ui->lineEdit, &QLineEdit::returnPressed, this, &MainWindow::selectFromSearch);

    ui->actionAddPassword->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
    ui->actionAddFolder->setIcon(QIcon::fromTheme(QStringLiteral("folder-new")));
    ui->actionEdit->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
    ui->actionDelete->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
    ui->actionUsers->setIcon(QIcon::fromTheme(QStringLiteral("x-office-address-book")));
    ui->actionConfig->setIcon(QIcon::fromTheme(QStringLiteral("applications-system")));
}

/**
 * @brief MainWindow::initStatusBar init statusBar with default message and logo
 */
void MainWindow::initStatusBar()
{
    ui->statusBar->showMessage(i18nc("placeholder is version number","Welcome to GnuPG Password Manager %1", QString::fromLocal8Bit(GPGPASS_VERSION_STRING)));

    QPixmap logo = QPixmap(QStringLiteral(":/artwork/32-gpgpass.png"));
    QLabel *logoApp = new QLabel(statusBar());
    logoApp->setPixmap(logo);
    statusBar()->addPermanentWidget(logoApp);
}

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

void MainWindow::showRemainingHtml(const QString &text)
{
    QString _text = text;
    if (!ui->textBrowser->toPlainText().isEmpty())
        _text = ui->textBrowser->toHtml() + _text;
    ui->textBrowser->setHtml(_text);
}

/**
 * @brief MainWindow::config pops up the configuration screen and handles all
 * inter-window communication
 */
void MainWindow::config()
{
    QScopedPointer<ConfigDialog> d(new ConfigDialog(this));
    d->setModal(true);

    if (d->exec()) {
        if (d->result() == QDialog::Accepted) {
            // Update the textBrowser line wrap mode
            if (Settings::isNoLineWrapping()) {
                ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
            } else {
                ui->textBrowser->setLineWrapMode(QTextBrowser::WidgetWidth);
            }

            this->show();

            updateProfileBox();
            ui->treeView->setRootIndex(proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore())));

            clearPanelTimer.setInterval(1000 * Settings::getAutoclearPanelSeconds());
            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;
    //    TODO(bezet): "Could not decrypt";
    m_clipboardHelper->clearClippedText();
    QString file = index.data(QFileSystemModel::FilePathRole).toString();
    ui->passwordName->setText(index.data().toString());
    if (!file.isEmpty() && QFileInfo(file).isFile() && !cleared) {
        m_pass->Show(file);
    } else {
        clearPanel();
        ui->actionEdit->setEnabled(false);
        ui->actionDelete->setEnabled(true);
    }
}

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

    if (fileOrFolder.isFile()) {
        editPassword(fileOrFolder.absoluteFilePath());
    }
}

/**
 * @brief MainWindow::deselect clear the selection, password and copy buffer
 */
void MainWindow::deselect()
{
    m_clipboardHelper->clearClipboard();
    ui->treeView->clearSelection();
    ui->actionEdit->setEnabled(false);
    ui->actionDelete->setEnabled(false);
    ui->passwordName->setText(QString{});
    clearPanel();
}

void MainWindow::passShowHandler(const QString &p_output)
{
    QStringList templ = Settings::isUseTemplate() ? Settings::getPassTemplate().split(QStringLiteral("\n")) : QStringList();
    bool allFields = Settings::isUseTemplate() && Settings::isTemplateAllFields();
    FileContent fileContent = FileContent::parse(p_output, templ, allFields);
    QString output = p_output;
    QString password = fileContent.getPassword();

    // set clipped text
    m_clipboardHelper->setClippedText(password);

    // first clear the current view:
    clearPanel();

    // show what is needed:
    if (Settings::isHideContent()) {
        output = i18n("*** Content hidden ***");
    } else if (!Settings::isDisplayAsIs()) {
        if (!password.isEmpty()) {
            // set the password, it is hidden if needed in addToGridLayout
            addToGridLayout(0, i18n("Password"), password);
        }

        NamedValues namedValues = fileContent.getNamedValues();
        for (int j = 0; j < namedValues.length(); ++j) {
            NamedValue nv = namedValues.at(j);
            addToGridLayout(j + 1, nv.name, nv.value);
        }

        output = fileContent.getRemainingDataForDisplay();
    }

    if (Settings::isUseAutoclearPanel()) {
        clearPanelTimer.start();
    }

    output = output.toHtmlEscaped();
    output.replace(Util::protocolRegex(), QStringLiteral(R"(<a href="\1">\1</a>)"));
    output.replace(QStringLiteral("\n"), QStringLiteral("<br />"));
    showRemainingHtml(output);

    setUiElementsEnabled(true);
}

/**
 * @brief MainWindow::clearPanel hide the information from shoulder surfers
 */
void MainWindow::clearPanel()
{
    clearTemplateWidgets();
    ui->textBrowser->setHtml(QString{});
}

/**
 * @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);
    ui->actionAddPassword->setEnabled(state);
    ui->actionAddFolder->setEnabled(state);
    ui->actionUsers->setEnabled(state);
    ui->actionConfig->setEnabled(state);
    // is a file selected?
    state &= ui->treeView->currentIndex().isValid();
    ui->actionDelete->setEnabled(state);
    ui->actionEdit->setEnabled(state);
}

/**
 * @brief MainWindow::on_configButton_clicked run Mainwindow::config
 */
void MainWindow::onConfig()
{
    config();
}

/**
 * @brief Executes when the string in the search box changes, collapses the
 * TreeView
 * @param arg1
 */
void MainWindow::filterList(const QString &arg1)
{
    ui->statusBar->showMessage(i18n("Looking for: %1", arg1), 1000);
    ui->treeView->expandAll();
    clearPanel();
    ui->passwordName->setText(QString{});
    ui->actionEdit->setEnabled(false);
    ui->actionDelete->setEnabled(false);
    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);
    proxyModel.setFilterRegularExpression(regExp);
    ui->treeView->setRootIndex(proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore())));

    if (proxyModel.rowCount() > 0 && !query.isEmpty()) {
        selectFirstFile();
    } else {
        ui->actionEdit->setEnabled(false);
        ui->actionDelete->setEnabled(false);
    }
}

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

/**
 * @brief MainWindow::selectFirstFile select the first possible file in the
 * tree
 */
void MainWindow::selectFirstFile()
{
    QModelIndex index = proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore()));
    index = firstFile(index);
    ui->treeView->setCurrentIndex(index);
}

/**
 * @brief MainWindow::firstFile return location of first possible file
 * @param parentIndex
 * @return QModelIndex
 */
QModelIndex MainWindow::firstFile(QModelIndex parentIndex)
{
    QModelIndex index = parentIndex;
    int numRows = proxyModel.rowCount(parentIndex);
    for (int row = 0; row < numRows; ++row) {
        index = proxyModel.index(row, 0, parentIndex);
        if (model.fileInfo(proxyModel.mapToSource(index)).isFile())
            return index;
        if (proxyModel.hasChildren(index))
            return firstFile(index);
    }
    return index;
}

/**
 * @brief MainWindow::setPassword open passworddialog
 * @param file which pgp file
 * @param isNew insert (not update)
 */
void MainWindow::setPassword(QString file, bool isNew)
{
    PasswordDialog d(*m_pass, file, isNew, this);

    if (!d.exec()) {
        ui->treeView->setFocus();
    }
}

/**
 * @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 = Settings::getPassStore();
    }
    QString file = QInputDialog::getText(this, i18n("New file"), i18n("New password file: \n(Will be placed in %1 )", dir), QLineEdit::Normal, QString{}, &ok);
    if (!ok || file.isEmpty())
        return;
    file = QDir(dir).absoluteFilePath(file + QStringLiteral(".gpg"));
    setPassword(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 = i18nc("deleting a folder; placeholder is folder name","Are you sure you want to delete %1 and the whole content?", file);
        QDirIterator it(model.rootPath() + QLatin1Char('/') + 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 = i18nc("deleting a file; placeholder is file name","Are you sure you want to delete %1?", file);
    }

    if (QMessageBox::question(this,
                              isDir ? i18n("Delete folder?") : i18n("Delete password?"),
                              message,
                              QMessageBox::Yes | QMessageBox::No)
        != QMessageBox::Yes)
        return;

    m_pass->Remove(file, isDir);
}

/**
 * @brief MainWindow::onEdit try and edit (selected) password.
 */
void MainWindow::onEdit()
{
    QString file = ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString();
    if (!file.isEmpty()) {
        editPassword(file);
    }
}

/**
 * @brief MainWindow::userDialog see MainWindow::onUsers()
 * @param dir folder to edit users for.
 */
void MainWindow::userDialog(QString dir)
{
    if (dir.isEmpty()) {
        dir = Settings::getPassStore();
    }
    QFileInfo fi(dir);
    if (!fi.isDir()) {
        dir = fi.absolutePath();
    }
    UsersDialog d(dir, *m_pass, this);
    if (!d.exec()) {
        ui->treeView->setFocus();
    }
    verifyInitialized();
}

/**
 * @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 = Settings::getPassStore();
    } else {
        QFileInfo fi(dir);
        if (!fi.isDir()) {
            dir = fi.absolutePath();
        }
        dir = Util::normalizeFolderPath(dir);
    }
    userDialog(dir);
}

/**
 * @brief MainWindow::updateProfileBox update the list of profiles, optionally
 * select a more appropriate one to view too
 */
void MainWindow::updateProfileBox()
{
    QHash<QString, QString> profiles = Settings::getProfiles();

    if (profiles.isEmpty()) {
        m_profiles->setVisible(false);
    } else {
        m_profiles->setVisible(true);
        m_profileBox->setEnabled(profiles.size() > 1);
        m_profileBox->clear();
        QHashIterator<QString, QString> i(profiles);
        while (i.hasNext()) {
            i.next();
            if (!i.key().isEmpty())
                m_profileBox->addItem(i.key());
        }
        m_profileBox->model()->sort(0);
    }
    int index = m_profileBox->findText(Settings::getProfile());
    if (index != -1) // -1 for not found
        m_profileBox->setCurrentIndex(index);
}

/**
 * @brief MainWindow::on_profileBox_currentIndexChanged make sure we show the
 * correct "profile"
 * @param name
 */
void MainWindow::selectProfile(QString name)
{
    if (name == Settings::getProfile())
        return;

    ui->lineEdit->clear();
    clearPanel();

    Settings::setProfile(name);

    Settings::setPassStore(Settings::getProfiles().value(name));
    ui->statusBar->showMessage(i18n("Profile changed to %1", name), 2000);

    ui->treeView->selectionModel()->clear();
    ui->treeView->setRootIndex(proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore())));

    verifyInitialized();

    ui->actionEdit->setEnabled(false);
    ui->actionDelete->setEnabled(false);
}

void MainWindow::verifyInitialized() {
    bool actionsEnabled;
    if (!QFile::exists(Settings::getPassStore() + QStringLiteral("/.gpg-id"))) {
        m_notInitialized->animatedShow();
        actionsEnabled = false;
    } else {
        m_notInitialized->animatedHide();
        actionsEnabled = true;
    }
    ui->actionAddFolder->setEnabled(actionsEnabled);
    ui->actionAddPassword->setEnabled(actionsEnabled);
    ui->actionDelete->setEnabled(ui->actionDelete->isEnabled() && actionsEnabled);
    ui->actionEdit->setEnabled(ui->actionEdit->isEnabled() && actionsEnabled);
}

/**
 * @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 (proxyModel.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();
        ui->actionDelete->setEnabled(false);
        ui->actionEdit->setEnabled(false);
        selected = false;
    }

    ui->treeView->setCurrentIndex(index);

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

    QFileInfo fileOrFolder = model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));

    QMenu contextMenu;
    if (!selected || fileOrFolder.isDir()) {
        const QAction *addFolderAction  = contextMenu.addAction(i18n("Add folder"));
        const QAction *addPasswordAction = contextMenu.addAction(i18n("Add password"));
        const QAction *usersAction = contextMenu.addAction(i18n("Users"));
        connect(addFolderAction, &QAction::triggered, this, &MainWindow::addFolder);
        connect(addPasswordAction, &QAction::triggered, this, &MainWindow::addPassword);
        connect(usersAction, &QAction::triggered, this, &MainWindow::onUsers);
    } else if (fileOrFolder.isFile()) {
        const QAction *edit = contextMenu.addAction(i18n("Edit"));
        connect(edit, &QAction::triggered, this, &MainWindow::onEdit);
    }
    if (selected) {
        contextMenu.addSeparator();
        if (fileOrFolder.isDir()) {
            const QAction *renameFolderAction = contextMenu.addAction(i18n("Rename folder"));
            connect(renameFolderAction, &QAction::triggered, this, &MainWindow::renameFolder);
        } else if (fileOrFolder.isFile()) {
            const QAction *renamePasswordAction = contextMenu.addAction(i18n("Rename password"));
            connect(renamePasswordAction, &QAction::triggered, this, &MainWindow::renamePassword);
        }
        const QAction *deleteItem = contextMenu.addAction(i18n("Delete"));
        connect(deleteItem, &QAction::triggered, this, &MainWindow::onDelete);
    }
    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 = Settings::getPassStore();
    }
    QString newdir = QInputDialog::getText(this, i18n("New file"), i18n("New Folder: \n(Will be placed in %1 )", 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, i18n("Rename file"), i18n("Rename Folder To: "), QLineEdit::Normal, srcDirName, &ok);
    if (!ok || newName.isEmpty())
        return;
    QString destDir = srcDir;
    destDir.replace(srcDir.lastIndexOf(srcDirName), srcDirName.length(), newName);
    m_pass->Move(srcDir, destDir);
}

/**
 * @brief MainWindow::editPassword read password and open edit window via
 * MainWindow::onEdit()
 */
void MainWindow::editPassword(const QString &file)
{
    if (!file.isEmpty()) {
        setPassword(file, false);
    }
}

/**
 * @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, i18n("Rename file"), i18n("Rename File To: "), QLineEdit::Normal, fileName, &ok);
    if (!ok || newName.isEmpty())
        return;
    QString newFile = QDir(filePath).filePath(newName);
    m_pass->Move(file, newFile);
}

/**
 * @brief MainWindow::clearTemplateWidgets empty the template widget fields in
 * the UI
 */
void MainWindow::clearTemplateWidgets()
{
    while (ui->gridLayout->count() > 0) {
        QLayoutItem *item = ui->gridLayout->takeAt(0);
        delete item->widget();
        delete item;
    }
}

/**
 * @brief MainWindow::addToGridLayout add a field to the template grid
 * @param position
 * @param field
 * @param value
 */
void MainWindow::addToGridLayout(int position, const QString &field, const QString &value)
{
    QString trimmedField = field.trimmed();
    QString trimmedValue = value.trimmed();

    // Combine the Copy button and the line edit in one widget
    QFrame *frame = new QFrame();
    QLayout *ly = new QHBoxLayout();
    ly->setContentsMargins(5, 2, 2, 2);
    ly->setSpacing(0);
    frame->setLayout(ly);
    auto fieldLabel = createPushButton(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy to clipboard"), m_clipboardHelper, [this, trimmedValue] {
        m_clipboardHelper->copyTextToClipboard(trimmedValue);
    });

    frame->layout()->addWidget(fieldLabel.release());

    auto qrButton = createPushButton(QIcon::fromTheme(QStringLiteral("view-barcode-qr")), i18n("View QR Code"), m_clipboardHelper, [this, trimmedValue]() {
        auto barcode = Prison::createBarcode(Prison::QRCode);
        if (!barcode) {
            return;
        }
        barcode->setData(trimmedValue);
        auto image = barcode->toImage(barcode->preferredSize(window()->devicePixelRatioF()));

        QDialog popup(nullptr, Qt::Popup | Qt::FramelessWindowHint);
        QVBoxLayout *layout = new QVBoxLayout;
        QLabel *popupLabel = new QLabel();
        layout->addWidget(popupLabel);
        popupLabel->setPixmap(QPixmap::fromImage(image));
        popupLabel->setScaledContents(true);
        popupLabel->show();
        popup.setLayout(layout);
        popup.move(QCursor::pos());
        popup.exec();
    });
    frame->layout()->addWidget(qrButton.release());

    if (trimmedField == i18n("Password")) {
        auto *line = new QLineEdit();
        line->setObjectName(trimmedField);
        line->setText(trimmedValue);
        line->setReadOnly(true);
        line->setContentsMargins(0, 0, 0, 0);
        line->setEchoMode(QLineEdit::Password);
        auto icon = QIcon::fromTheme(QStringLiteral("password-show-on"));
        icon.addFile(QStringLiteral("password-show-off"), QSize(), QIcon::Normal, QIcon::Off);
        auto showButton = createPushButton(icon, i18n("Toggle password visibility"), line, [line]() {
            if (line->echoMode() == QLineEdit::Password) {
                line->setEchoMode(QLineEdit::Normal);
            } else {
                line->setEchoMode(QLineEdit::Password);
            }
        });
        showButton->setCheckable(true);
        showButton->setContentsMargins(0, 0, 0, 0);
        frame->layout()->addWidget(showButton.release());
        frame->layout()->addWidget(line);
    } else {
        auto *line = new QLabel();
        line->setOpenExternalLinks(true);
        line->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard);
        line->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
        line->setObjectName(trimmedField);
        trimmedValue.replace(Util::protocolRegex(), QStringLiteral(R"(<a href="\1">\1</a>)"));
        line->setText(trimmedValue);
        line->setContentsMargins(5, 0, 0, 0);
        frame->layout()->addWidget(line);
    }

    // set into the layout
    ui->gridLayout->addWidget(new QLabel(trimmedField), position, 0);
    ui->gridLayout->addWidget(frame, position, 1);
}

/**
 * @brief MainWindow::startReencryptPath disable ui elements and treeview
 */
void MainWindow::startReencryptPath()
{
    statusBar()->showMessage(i18n("Re-encrypting folders"), 3000);
    setUiElementsEnabled(false);
    ui->treeView->setDisabled(true);
}

/**
 * @brief MainWindow::endReencryptPath re-enable ui elements
 */
void MainWindow::endReencryptPath()
{
    setUiElementsEnabled(true);
}

/**
 * @brief MainWindow::critical critical message popup wrapper.
 * @param title
 * @param msg
 */
void MainWindow::critical(const QString& title, const QString& msg)
{
    QMessageBox::critical(this, title, msg);
}
