#pragma once
#include "common.hpp"
#include "common/types.hpp"
#include "services/clipboard/clipboard-content.hpp"
#include "services/clipboard/clipboard-db.hpp"
#include "services/clipboard/clipboard-encrypter.hpp"
#include "services/clipboard/clipboard-server.hpp"
#include <QString>
#include <expected>
#include <filesystem>
#include <QJsonObject>
#include <qcontainerfwd.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qfuture.h>
#include <qfuturewatcher.h>
#include <qimage.h>
#include <qjsonobject.h>
#include <qmimedata.h>
#include <qmimedatabase.h>
#include <qstringview.h>
#include <QTimer>

class ClipboardService : public QObject, public NonCopyable {
  Q_OBJECT

signals:
  void allSelectionsRemoved() const;
  void itemCopied(const InsertClipboardHistoryLine &item) const;
  void itemInserted(const ClipboardHistoryEntry &entry) const;
  void selectionPinStatusChanged(const QString &id, bool pinned) const;
  void selectionRemoved(const QString &id) const;
  /**
   * When a selection is copied, its update time is modified which makes it appear on top
   * of the list.
   */
  void selectionUpdated() const;
  void monitoringChanged(bool value) const;

public:
  enum class OfferDecryptionError {
    DecryptionRequired, // if encryption is disabled and data was previous encrypted
    DecryptionFailed,
    DataUnavailable,
  };

  ClipboardService(const std::filesystem::path &path, std::optional<db::EncryptionKey> key = std::nullopt);

  static QString readText();
  static Clipboard::ReadContent readContent();

  bool removeAllSelections();

  std::optional<QString> retrieveKeywords(const QString &id);
  bool setKeywords(const QString &id, const QString &keywords);

  std::expected<QByteArray, OfferDecryptionError> getMainOfferData(const QString &selectionId) const;
  AbstractClipboardServer *clipboardServer() const;
  bool removeSelection(const QString &id);
  bool setPinned(const QString &id, bool pinned);
  QFuture<PaginatedResponse<ClipboardHistoryEntry>> listAll(int limit = 100, int offset = 0,
                                                            const ClipboardListSettings &opts = {}) const;
  static constexpr int CLIPBOARD_RESTORE_DELAY_MS = 800;

  bool copyText(const QString &text, const Clipboard::CopyOptions &options = {.concealed = true});
  void scheduleClipboardRestore(int delayMs = CLIPBOARD_RESTORE_DELAY_MS);
  bool copyHtml(const Clipboard::Html &data, const Clipboard::CopyOptions &options = {.concealed = false});
  bool copyFile(const std::filesystem::path &path,
                const Clipboard::CopyOptions &options = {.concealed = false});
  bool copyUrls(const std::vector<QUrl> &urls, const Clipboard::CopyOptions &options = {.concealed = false});
  bool copyContent(Clipboard::Content content, const Clipboard::CopyOptions &options = {.concealed = false});
  void setRecordAllOffers(bool value);
  bool clear();
  void saveSelection(ClipboardSelection selection);
  ClipboardSelection retrieveSelection(int offset = 0);
  std::optional<ClipboardSelection> retrieveSelectionById(const QString &id);
  bool copySelectionRecord(const QString &id, const Clipboard::CopyOptions &options);
  std::unique_ptr<QMimeData> mimeDataFromSelection(const ClipboardSelection &selection) const;
  std::unique_ptr<QMimeData> dragMimeDataForSelection(const ClipboardSelection &selection) const;
  bool copySelection(const ClipboardSelection &selection, const Clipboard::CopyOptions &options);
  bool copyQMimeData(QMimeData *data, const Clipboard::CopyOptions &options = {});

  /**
   * Whether we have a working clipboard server implementation to use.
   * This does not take into account the current monitoring preference, only
   * whether we are able to monitor.
   */
  bool supportsMonitoring() const;
  bool monitoring() const;
  void setMonitoring(bool value);
  void setEncryptionKey(std::optional<db::EncryptionKey> key);
  void setIgnorePasswords(bool value);
  bool isEncryptionReady() const;

private:
  ClipboardDatabase openDatabase() const { return ClipboardDatabase(m_dbKey); }

  std::unique_ptr<ClipboardEncrypter> m_encrypter;

  QMimeDatabase _mimeDb;
  std::filesystem::path m_dataDir;
  std::optional<db::EncryptionKey> m_dbKey;
  std::unique_ptr<AbstractClipboardServer> m_clipboardServer;

  static QString getSelectionPreferredMimeType(const ClipboardSelection &selection);
  static QString getOfferTextPreview(const ClipboardDataOffer &offer);

  /**
   * Unique selection hash obtained by hashing all the data offer hashes together.
   * This is used to prevent reinserting the exact same selection multiple times.
   */
  QByteArray computeSelectionHash(const ClipboardSelection &selection) const;
  bool isClearSelection(const ClipboardSelection &selection) const;

  /**
   * Sanitize the passed selection by removing duplicate offers.
   * The selection is sanitized in place, no copy is made.
   */
  static ClipboardSelection &sanitizeSelection(ClipboardSelection &selection);

  std::expected<QByteArray, ClipboardService::OfferDecryptionError>
  decryptOffer(const QByteArray &data, ClipboardEncryptionType type) const;

  static ClipboardOfferKind getKind(const ClipboardDataOffer &offer);

  void restoreClipboard();

  bool m_recordAllOffers = true;
  bool m_monitoring = false;
  bool m_ignorePasswords = true;
  std::optional<ClipboardSelection> m_lastSelection;
  QTimer m_restoreTimer;
  QFutureWatcher<std::expected<ClipboardHistoryEntry, QString>> m_indexingSelection;
};
