#pragma once
#include "common/qt.hpp"
#include "common/entrypoint.hpp"
#include "argument.hpp"
#include "command-controller.hpp"
#include "command.hpp"
#include "common.hpp"
#include "ui/action-pannel/action.hpp"
#include "ui/action-pannel/action-panel-state.hpp"
#include "ui/dialog/dialog.hpp"
#include "ui/image/url.hpp"
#include <QString>
#include <chrono>
#include <cstdint>
#include <numeric>
#include <qevent.h>

class BaseView;
class DialogContentWidget;
class ActionPanelView;
class QWindow;

#define VALUE_OR(VALUE, FALLBACK) (VALUE ? VALUE : FALLBACK)

// matches raycast pop to root type policiy
// https://developers.raycast.com/api-reference/window-and-search-bar#poptoroottype
enum class PopToRootType : std::uint8_t { Default, Immediate, Suspended };

struct CloseWindowOptions {
  PopToRootType popToRootType = PopToRootType::Default;
  bool clearRootSearch = false; // has no effect if we do not pop to root
};

struct PopToRootOptions {
  bool clearSearch = true;
};

using ArgumentValues = std::vector<std::pair<QString, QString>>;

struct ActivateEntrypointOptions {
  LaunchProps props;
  bool toggleIfAlreadyActive = true;
};

struct CompleterState {
  ArgumentList args;
  ArgumentValues values;
  ImageURL icon;

  CompleterState(const ArgumentList &args, const ImageURL &icon) : args(args), icon(icon) {}
};

struct GoBackOptions {
  bool ignoreInstantDismiss = false;
};

class NavigationController : public QObject, NonCopyable {
  Q_OBJECT

public:
  struct CommandFrame {
    QObjectUniquePtr<CommandContext> context;
    std::unique_ptr<CommandController> controller;

    std::shared_ptr<AbstractCmd> command;
    LaunchProps launchProps;
    size_t viewCount;

    ~CommandFrame() {
      context->unload();
      qDebug() << "Unloading command" << QString::fromStdString(command->uniqueId());
    }
  };

  struct ViewState {
    BaseView *sender = nullptr;
    struct {
      QString title;
      ImageURL icon;
    } navigation;
    QString placeholderText;
    QString searchText;
    std::optional<CompleterState> completer;

    bool isLoading = false;
    bool supportsSearch = true;
    bool searchInteractive = true;
    bool needsTopBar = true;
    bool needsStatusBar = true;
    bool showBackButton = true;
    bool panelOpened = false;

    ~ViewState();
  };

signals:
  void currentViewStateChanged(const ViewState &state) const;
  void currentViewChanged(const ViewState &state) const;
  void viewPushed(const BaseView *view);
  void viewPoped(const BaseView *view);
  void viewReplaced();
  void actionPanelVisibilityChanged(bool visible);
  void activeActionPanelChanged();
  void windowVisiblityChanged(bool visible);
  void windowSizeRequested(QSize size);
  void searchTextSelected() const;
  void searchTextTampered(const QString &text) const;
  void searchPlaceholderTextChanged(const QString &text) const;
  void navigationStatusChanged(const QString &text, const ImageURL &icon) const;
  void navigationSuffixIconChanged(const std::optional<ImageURL> &icon) const;
  void confirmAlertRequested(DialogContentWidget *widget);
  void loadingChanged(bool value) const;
  void showHudRequested(const QString &title, const std::optional<ImageURL> &icon);

  void completionValuesChanged(const ArgumentValues &values) const;

  void invalidCompletionFired();

  void completionCreated(const CompleterState &completer) const;
  void completionDestroyed() const;

  void headerVisiblityChanged(bool value);
  void searchVisibilityChanged(bool value);
  void searchInteractiveChanged(bool value);
  void statusBarVisiblityChanged(bool value);
  void backButtonVisibilityChanged(bool visible);
  void windowActivationChanged(bool value) const;

public:
  Q_INVOKABLE void closeWindow(const CloseWindowOptions &settings = {});
  void closeWindow(const CloseWindowOptions &settings, std::chrono::milliseconds delay);
  Q_INVOKABLE void showWindow();
  Q_INVOKABLE void toggleWindow();
  bool isWindowOpened() const;
  void requestWindowSize(QSize size);

  bool windowActivated();
  void setWindowActivated(bool value = true);

  /**
   * Non-owning reference to the launcher window, registered by the QML window host.
   */
  void setWindow(QWindow *window) { m_window = window; }
  QWindow *window() const { return m_window; }

  void setPopToRootOnClose(bool value);

  bool hasCompleter() const;

  /**
   * If the instant dismiss flag is set to `true`, the next call to `goBack` or `closeWindow` will close the
   * window and pop to root regardless of the state of the navigation stack.
   *
   * This is typically set when opening a command using an external mechanism such as a deeplink.
   *
   * This flag automatically resets to false when consumed.
   */
  void setInstantDismiss(bool value = true);

  void setSearchPlaceholderText(const QString &text, const BaseView *caller = nullptr);
  void broadcastSearchText(const QString &text, const BaseView *caller = nullptr);
  void setSearchText(const QString &text, const BaseView *caller = nullptr);
  void setBackButtonVisibility(bool value, const BaseView *caller = nullptr);

  void setLoading(bool value, const BaseView *caller = nullptr);
  bool isLoading(const BaseView *caller = nullptr) const;

  void popToRoot(const PopToRootOptions &opts = {});

  QString searchText(const BaseView *caller = nullptr) const;
  QString navigationTitle(const BaseView *caller = nullptr) const;
  void searchPlaceholderText(const QString &text);

  void setDialog(DialogContentWidget *dialog);
  void confirmAlert(const QString &title, const QString &description, const std::function<void()> &onConfirm);

  void createCompletion(const ArgumentList &args, const ImageURL &icon);
  void destroyCurrentCompletion();

  ArgumentValues completionValues() const;
  std::vector<QString> unnamedCompletionValues() const;
  void setCompletionValues(const ArgumentValues &values);

  void selectSearchText() const;

  void openActionPanel();
  void closeActionPanel();
  void toggleActionPanel();

  void setActions(std::unique_ptr<ActionPanelState> state, const BaseView *caller = nullptr);
  void setActions(ActionPanelView *view, const BaseView *caller = nullptr);
  void clearActions(const BaseView *caller = nullptr);
  void notifyActionPanelChanged(const BaseView *caller = nullptr);

  void clearSearchText();
  void setNavigationTitle(const QString &navigationTitle, const BaseView *caller = nullptr);
  void setNavigationIcon(const ImageURL &icon);
  void setNavigationSuffixIcon(const std::optional<ImageURL> &icon);

  bool executePrimaryAction();
  void executeAction(AbstractAction *action);

private:
  void executeActionNow(AbstractAction *action);

public:
  void setHeaderVisiblity(bool value, const BaseView *caller = nullptr);
  void setSearchVisibility(bool value, const BaseView *caller = nullptr);
  void setSearchInteractive(bool value, const BaseView *caller = nullptr);
  void setStatusBarVisibility(bool value, const BaseView *caller = nullptr);

  /**
   * Unlike clear accessory, this only alters the visiblity: doesn't delete anything.
   * We need to rework the search accessory system anyways, as the ownership rules
   * are beyond confusing.
   */
  void showHud(const QString &title, const std::optional<ImageURL> &icon = std::nullopt);

  void launch(const std::shared_ptr<AbstractCmd> &cmd);
  void launch(const std::shared_ptr<AbstractCmd> &cmd, const ArgumentValues &arguments);
  void launch(const std::shared_ptr<AbstractCmd> &cmd, const LaunchProps &props);
  bool activateEntrypoint(const EntrypointId &id, const ActivateEntrypointOptions &options = {});

  const AbstractCmd *activeCommand() const;
  CommandFrame *activeFrame() const { return m_frames.back().get(); }
  bool reloadActiveCommand();
  void unloadActiveCommand();

  void setCloseOnFocusLoss(bool value);

  /**
   * Go back to previous navigation state.
   * If the `instantDismiss` flag is set, this will close the window and pop to root
   * no matter what and then clear the flag.
   * The flag can be completely bypassed by passing `ignoreInstantDismiss`.
   * See `setInstantDismiss`
   */
  void goBack(const GoBackOptions &opts = {});

  void popCurrentView();
  template <typename T> void pushView() { pushView(new T); }
  void pushView(BaseView *view);

  /**
   * Replace the current view without unloading the current command.
   * Can be useful to display introduction views before pushing the actual view.
   */
  void replaceView(BaseView *view);
  template <typename T> void replaceView() { replaceView(new T); }

  size_t viewStackSize() const;
  bool isRootSearch() const;
  const ViewState *topState() const;
  ViewState *topState();

  NavigationController(ApplicationContext &ctx);

private:
  std::unique_ptr<ViewState> createViewState(BaseView *view) const;
  void activateView(const ViewState &state);

  ApplicationContext &m_ctx;
  std::vector<std::unique_ptr<CommandFrame>> m_frames;

  struct PendingPopToRoot {
    PopToRootType type = PopToRootType::Immediate;
    bool clearSearch = false;
  };

  void applyPopToRoot(const PendingPopToRoot &popToRoot);

  ViewState *findViewState(const BaseView *view);
  const ViewState *findViewState(const BaseView *view) const;
  const BaseView *topView() const;

  bool m_windowOpened = false;
  bool m_windowActivated = false;
  bool m_isPanelOpened = false;
  bool m_popToRootOnClose = false;
  bool m_instantDismiss = false;
  bool m_closeOnFocusLoss = false;
  QWindow *m_window = nullptr;
  std::vector<std::unique_ptr<ViewState>> m_views;
  std::optional<PendingPopToRoot> m_pendingPopToRoot;
};
