#ifndef QT_QUICK_GUI_CONTROLLER_H
#define QT_QUICK_GUI_CONTROLLER_H

#include "../model/entryfiltermodel.h"
#include "../model/entrymodel.h"
#include "../model/fieldmodel.h"

#include <passwordfile/io/passwordfile.h>

#include <qtutilities/misc/desktoputils.h>

#include <QObject>
#include <QPersistentModelIndex>
#include <QVariant>

QT_FORWARD_DECLARE_CLASS(QGuiApplication)
QT_FORWARD_DECLARE_CLASS(QSettings)

#if defined(PASSWORD_MANAGER_GUI_QTWIDGETS) || defined(PASSWORD_MANAGER_ENABLE_UNDO_SUPPORT_FOR_QUICK_GUI)
#include "../gui/stacksupport.h"
#endif

namespace QtUtilities {
class QtSettings;
}

namespace QtGui {

class Controller : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString filePath READ filePath WRITE setFilePath NOTIFY filePathChanged)
    Q_PROPERTY(QString fileName READ fileName NOTIFY filePathChanged)
    Q_PROPERTY(QString password READ password WRITE setPassword RESET clearPassword NOTIFY passwordChanged)
    Q_PROPERTY(QString windowTitle READ windowTitle NOTIFY windowTitleChanged)
    Q_PROPERTY(bool fileOpen READ isFileOpen NOTIFY fileOpenChanged)
    Q_PROPERTY(bool passwordSet READ isPasswordSet NOTIFY passwordChanged)
    Q_PROPERTY(EntryModel *entryModel READ entryModel NOTIFY entryModelChanged)
    Q_PROPERTY(EntryFilterModel *entryFilterModel READ entryFilterModel NOTIFY entryFilterModelChanged)
    Q_PROPERTY(FieldModel *fieldModel READ fieldModel NOTIFY fieldModelChanged)
    //Q_PROPERTY(Io::AccountEntry *currentAccount READ currentAccount WRITE setCurrentAccount NOTIFY currentAccountChanged)
    Q_PROPERTY(QModelIndex currentAccountIndex READ currentAccountIndex WRITE setCurrentAccountIndex NOTIFY currentAccountChanged)
    Q_PROPERTY(QString currentAccountName READ currentAccountName NOTIFY currentAccountChanged)
    Q_PROPERTY(bool hasCurrentAccount READ hasCurrentAccount NOTIFY currentAccountChanged)
    //Q_PROPERTY(QList<QPersistentModelIndex> cutEntries READ cutEntries WRITE setCutEntries NOTIFY cutEntriesChanged)
    Q_PROPERTY(bool canPaste READ canPaste NOTIFY cutEntriesChanged)
    Q_PROPERTY(QStringList recentFiles READ recentFiles RESET clearRecentFiles NOTIFY recentFilesChanged)
    Q_PROPERTY(bool useNativeFileDialog READ useNativeFileDialog WRITE setUseNativeFileDialog NOTIFY useNativeFileDialogChanged)
    Q_PROPERTY(bool supportsNativeFileDialog READ supportsNativeFileDialog NOTIFY supportsNativeFileDialogChanged)
    Q_PROPERTY(QString entryFilter READ entryFilter WRITE setEntryFilter NOTIFY entryFilterChanged)
    Q_PROPERTY(bool hasEntryFilter READ hasEntryFilter NOTIFY hasEntryFilterChanged)
    Q_PROPERTY(bool filterAsDialog READ filterAsDialog NOTIFY filterAsDialogChanged)
    Q_PROPERTY(QUndoStack *undoStack READ undoStack NOTIFY undoStackChanged)
    Q_PROPERTY(QString undoText READ undoText NOTIFY undoTextChanged)
    Q_PROPERTY(QString redoText READ redoText NOTIFY redoTextChanged)
    Q_PROPERTY(bool darkModeEnabled READ isDarkModeEnabled WRITE setDarkModeEnabled NOTIFY darkModeEnabledChanged)

public:
    explicit Controller(QGuiApplication &app, QtUtilities::QtSettings &qtSettings, QSettings &settings, const QString &filePath = QString(),
        QObject *parent = nullptr);

    const QString &filePath() const;
    const QString &fileName() const;
    void setFilePath(const QString &filePath);
    const QString &password() const;
    void setPassword(const QString &password);
    Q_INVOKABLE void clearPassword();
    const QString &windowTitle() const;
    bool isFileOpen() const;
    bool isPasswordSet() const;
    EntryModel *entryModel();
    EntryFilterModel *entryFilterModel();
    FieldModel *fieldModel();
    Io::AccountEntry *currentAccount();
    void setCurrentAccount(Io::AccountEntry *entry);
    QModelIndex currentAccountIndex() const;
    void setCurrentAccountIndex(const QModelIndex &accountIndexMaybeFromFilterModel);
    bool hasCurrentAccount() const;
    const QList<QPersistentModelIndex> &cutEntries() const;
    void setCutEntries(const QList<QPersistentModelIndex> &cutEntries);
    QString currentAccountName() const;
    Q_INVOKABLE void cutEntry(const QModelIndex &entryIndexMaybeFromFilterModel);
    Q_INVOKABLE QStringList pasteEntries(const QModelIndex &destinationParent, int row = -1);
    Q_INVOKABLE bool copyToClipboard(const QString &text) const;
    Q_INVOKABLE QString getClipboardText() const;
    Q_INVOKABLE QString copyTOTP(const QString &url) const;
    Q_INVOKABLE QVariantMap computeTOTP(const QString &url) const;
    bool canPaste() const;
    const QStringList &recentFiles() const;
    Q_INVOKABLE void clearRecentFiles();
    bool useNativeFileDialog() const;
    void setUseNativeFileDialog(bool useNativeFileDialog);
    bool supportsNativeFileDialog() const;
    Q_INVOKABLE QModelIndex filterEntryIndex(const QModelIndex &entryIndex) const;
    QString entryFilter() const;
    void setEntryFilter(const QString &filter);
    bool hasEntryFilter() const;
    bool filterAsDialog() const;
    QUndoStack *undoStack();
    QString undoText() const;
    QString redoText() const;
    Io::PasswordFileSaveFlags prepareSaving();
    bool isDarkModeEnabled() const;
    void setDarkModeEnabled(bool darkModeEnabled);

public Q_SLOTS:
    void init();
    void load();
    void create();
    void close();
    void clear();
    void save();
    bool showNativeFileDialog(bool existing, bool createNew);
    bool showToast(const QString &message);
    bool performHapticFeedback();
    void handleFileSelectionAccepted(const QString &filePath, const QString &nativeUrl, bool existing, bool createNew);
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
    void handleFileSelectionAcceptedDescriptor(const QString &nativeUrl, const QString &fileName, int fileDescriptor, bool existing, bool createNew);
#endif
    void handleFileSelectionCanceled();
    void undo();
    void redo();
    QString computeFileSummary();
    void applyDarkmodeChange(const QPalette &palette);
    void applyDarkmodeChange(bool isDarkColorSchemeEnabled, bool isDarkPaletteEnabled);

Q_SIGNALS:
    void filePathChanged(const QString &newFilePath);
    void passwordChanged(const QString &newPassword);
    void passwordRequired(const QString &filePath);
    void windowTitleChanged(const QString &windowTitle);
    void fileOpenChanged(bool fileOpen);
    void fileError(const QString &errorMessage, const QString &retryAction);
    void fileSaved();
    void entryModelChanged();
    void entryFilterModelChanged();
    void fieldModelChanged();
    void currentAccountChanged();
    void cutEntriesChanged(const QList<QPersistentModelIndex> &cutEntries);
    void recentFilesChanged(const QStringList &recentFiles);
    void newNotification(const QString &message);
    void useNativeFileDialogChanged(bool useNativeFileDialog);
    void supportsNativeFileDialogChanged();
    void entryAboutToBeRemoved(const QModelIndex &removedIndex);
    void entryFilterChanged(const QString &newFilter);
    void hasEntryFilterChanged(bool hasEntryFilter);
    void filterAsDialogChanged(bool filterAsDialog);
    void undoStackChanged(QUndoStack *undoStack);
    void undoTextChanged(const QString &undoText);
    void redoTextChanged(const QString &redoText);
    void settingsError(const QString &errorMessage);
    void darkModeEnabledChanged(bool darkModeEnabled);
    void retranslate();

protected:
    bool eventFilter(QObject *object, QEvent *event) override;

private Q_SLOTS:
    void handleEntriesRemoved(const QModelIndex &parentIndex, int first, int last);
    void handleRecentFilesChanged();

private:
    void resetFileStatus();
    void updateWindowTitle();
    void setFileOpen(bool fileOpen);
    void emitFileError(const QString &when);
    QModelIndex ensureSourceEntryIndex(const QModelIndex &entryIndexMaybeFromFilterModel) const;

    QGuiApplication &m_app;
    QtUtilities::QtSettings &m_qtSettings;
    QSettings &m_settings;
    QString m_filePath;
    QString m_fileName;
    QString m_password;
    QString m_windowTitle;
    Io::PasswordFile m_file;
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
    QUndoStack m_undoStack;
#endif
    EntryModel m_entryModel;
    EntryFilterModel m_entryFilterModel;
    FieldModel m_fieldModel;
    QList<QPersistentModelIndex> m_cutEntries;
    QStringList m_recentFiles;
    QString m_nativeUrl;
    bool m_fileOpen;
    bool m_fileModified;
    bool m_useNativeFileDialog;
    bool m_filterAsDialog;
    bool m_darkModeEnabled;
    bool m_darkColorScheme;
    bool m_darkPalette;
};

inline QModelIndex Controller::ensureSourceEntryIndex(const QModelIndex &entryIndexMaybeFromFilterModel) const
{
    return entryIndexMaybeFromFilterModel.model() == &m_entryFilterModel ? m_entryFilterModel.mapToSource(entryIndexMaybeFromFilterModel)
                                                                         : entryIndexMaybeFromFilterModel;
}

inline const QString &Controller::filePath() const
{
    return m_filePath;
}

inline const QString &Controller::fileName() const
{
    return m_fileName;
}

inline const QString &Controller::password() const
{
    return m_password;
}

inline void Controller::clearPassword()
{
    setPassword(QString());
}

inline const QString &Controller::windowTitle() const
{
    return m_windowTitle;
}

inline bool Controller::isFileOpen() const
{
    return m_fileOpen;
}

inline bool Controller::isPasswordSet() const
{
    return !m_password.isEmpty();
}

inline EntryModel *Controller::entryModel()
{
    return &m_entryModel;
}

inline EntryFilterModel *Controller::entryFilterModel()
{
    return &m_entryFilterModel;
}

inline FieldModel *Controller::fieldModel()
{
    return &m_fieldModel;
}

inline Io::AccountEntry *Controller::currentAccount()
{
    return m_fieldModel.accountEntry();
}

inline void Controller::setCurrentAccount(Io::AccountEntry *entry)
{
    m_fieldModel.setAccountEntry(entry);
    emit currentAccountChanged();
}

inline QModelIndex Controller::currentAccountIndex() const
{
    return m_fieldModel.accountEntry() ? m_entryModel.index(const_cast<Io::AccountEntry *>(m_fieldModel.accountEntry())) : QModelIndex();
}

inline void Controller::setCurrentAccountIndex(const QModelIndex &accountIndexMaybeFromFilterModel)
{
    const auto accountIndex = ensureSourceEntryIndex(accountIndexMaybeFromFilterModel);
    m_fieldModel.setAccountEntry(m_entryModel.isNode(accountIndex) ? nullptr : static_cast<Io::AccountEntry *>(m_entryModel.entry(accountIndex)));
    emit currentAccountChanged();
}

inline bool Controller::hasCurrentAccount() const
{
    return m_fieldModel.accountEntry() != nullptr;
}

inline const QList<QPersistentModelIndex> &Controller::cutEntries() const
{
    return m_cutEntries;
}

inline void Controller::setCutEntries(const QList<QPersistentModelIndex> &cutEntries)
{
    cutEntriesChanged(m_cutEntries = cutEntries);
}

inline QString Controller::currentAccountName() const
{
    return m_fieldModel.accountEntry() ? QString::fromStdString(m_fieldModel.accountEntry()->label()) : QStringLiteral("?");
}

inline void Controller::cutEntry(const QModelIndex &entryIndex)
{
    cutEntriesChanged(m_cutEntries << QPersistentModelIndex(ensureSourceEntryIndex(entryIndex)));
}

inline bool Controller::canPaste() const
{
    return !m_cutEntries.isEmpty();
}

inline const QStringList &Controller::recentFiles() const
{
    return m_recentFiles;
}

inline bool Controller::useNativeFileDialog() const
{
    return m_useNativeFileDialog;
}

inline bool Controller::supportsNativeFileDialog() const
{
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
    return true;
#else
    return false;
#endif
}

inline QModelIndex Controller::filterEntryIndex(const QModelIndex &entryIndex) const
{
    return m_entryFilterModel.mapFromSource(entryIndex);
}

inline QString Controller::entryFilter() const
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
    return m_entryFilterModel.filterRegularExpression().pattern();
#else
    return m_entryFilterModel.filterRegExp().pattern();
#endif
}

inline bool Controller::hasEntryFilter() const
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
    return !m_entryFilterModel.filterRegularExpression().pattern().isEmpty();
#else
    return !m_entryFilterModel.filterRegExp().isEmpty();
#endif
}

inline bool Controller::filterAsDialog() const
{
    return m_filterAsDialog;
}

inline QUndoStack *Controller::undoStack()
{
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
    return &m_undoStack;
#else
    return nullptr;
#endif
}

inline QString Controller::undoText() const
{
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
    return m_undoStack.undoText();
#else
    return QString();
#endif
}

inline QString Controller::redoText() const
{
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
    return m_undoStack.redoText();
#else
    return QString();
#endif
}

inline bool Controller::isDarkModeEnabled() const
{
    return m_darkModeEnabled;
}

inline void Controller::setDarkModeEnabled(bool darkModeEnabled)
{
    if (darkModeEnabled != m_darkModeEnabled) {
        emit darkModeEnabledChanged(m_darkModeEnabled = darkModeEnabled);
    }
}

inline bool Controller::showToast(const QString &message)
{
    return QtUtilities::showToast(message);
}

inline bool Controller::performHapticFeedback()
{
    return QtUtilities::performHapticFeedback();
}

inline void Controller::undo()
{
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
    return m_undoStack.undo();
#endif
}

inline void Controller::redo()
{
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
    return m_undoStack.redo();
#endif
}

inline QString Controller::computeFileSummary()
{
    return QString::fromStdString(m_file.summary(prepareSaving()));
}

} // namespace QtGui

#endif // QT_QUICK_GUI_CONTROLLER_H
