#ifndef MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED
#define MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED

#include <QDialog>

namespace MOBase
{
class IPluginGame;
}
namespace Ui
{
class CreateInstanceDialog;
};
namespace cid
{
class Page;
}

class PluginContainer;
class Settings;

// this is a wizard for creating a new instance, it is made out of Page objects,
// see createinstancedialogpages.h
//
// each page can give back one or more pieces of information that is collected
// in creationInfo() and used by finish() to do the actual creation
//
// pages can be disabled if they return true in skip(), which happens globally
// for some (IntroPage has a setting in the registry), depending on context
// (NexusPage is skipped if the API key already exists) or explicitly (when
// only some info about the instance is missing on startup, such as a game
// variant)
//
class CreateInstanceDialog : public QDialog
{
  Q_OBJECT

public:
  enum class Actions
  {
    Find = 1
  };

  // instance type
  //
  enum Types
  {
    NoType = 0,
    Global,
    Portable
  };

  // all the paths required by the instance, some may be empty, such as
  // basically all of them except for `base` when the user doesn't use the
  // "Advanced" part of the paths page
  //
  struct Paths
  {
    QString base;
    QString downloads;
    QString mods;
    QString profiles;
    QString overwrite;
    QString ini;

    auto operator<=>(const Paths&) const = default;
  };

  struct ProfileSettings
  {
    bool localInis;
    bool localSaves;
    bool archiveInvalidation;

    auto operator<=>(const ProfileSettings&) const = default;
  };

  // all the info filled in the various pages
  //
  struct CreationInfo
  {
    Types type;
    MOBase::IPluginGame* game;
    QString gameLocation;
    QString gameVariant;
    QString instanceName;
    QString dataPath;
    QString iniPath;
    Paths paths;
    ProfileSettings profileSettings;
  };

  CreateInstanceDialog(const PluginContainer& pc, Settings* s,
                       QWidget* parent = nullptr);

  ~CreateInstanceDialog();

  Ui::CreateInstanceDialog* getUI();
  const PluginContainer& pluginContainer();
  Settings* settings();

  // disables all the pages except for the given one, used on startup when some
  // specific info is missing
  template <class Page>
  void setSinglePage(const QString& instanceName)
  {
    for (auto&& p : m_pages) {
      if (auto* tp = dynamic_cast<Page*>(p.get())) {
        tp->setSkip(false);
      } else {
        p->setSkip(true);
      }
    }

    setSinglePageImpl(instanceName);
  }

  // returns the page having the give path, or null
  //
  template <class Page>
  Page* getPage()
  {
    for (auto&& p : m_pages) {
      if (auto* tp = dynamic_cast<Page*>(p.get())) {
        return tp;
      }
    }

    return nullptr;
  }

  // moves to the next page; if `allowFinish` is true, calls finish() if
  // currently on the last page
  //
  void next(bool allowFinish = true);

  // moves to the previous page, if any
  //
  void back();

  // whether the current page reports that it is ready; if this is the last
  // page, next() would call finish()
  //
  bool canNext() const;

  // whether the current page is not the first one and there is an enabled page
  // prior
  //
  bool canBack() const;

  // selects the given page by index; this doesn't check if the page should be
  // skipped
  //
  void selectPage(std::size_t i);

  // moves by `d` pages, can be negative to move back
  //
  void changePage(int d);

  // creates the instance and closes the dialog
  //
  void finish();

  // updates the navigation buttons based on the current page
  //
  void updateNavigation();

  // whether this is the last enabled page
  //
  bool isOnLastPage() const;

  // returns whether the user has requested to switch to the new instance
  //
  bool switching() const;

  // gathers the info from all the pages as it appears, paths are not fixed;
  // see creationInfo()
  //
  CreationInfo rawCreationInfo() const;

  // gathers the info from all the pages: paths are converted to absolute and
  // the base dir variable is expanded everywhere; see rawCreationInfo()
  //
  CreationInfo creationInfo() const;

private:
  std::unique_ptr<Ui::CreateInstanceDialog> ui;
  const PluginContainer& m_pc;
  Settings* m_settings;
  std::vector<std::unique_ptr<cid::Page>> m_pages;
  QString m_originalNext;
  bool m_switching;
  bool m_singlePage;

  // creates a shortcut for the given sequence
  //
  void addShortcut(QKeySequence seq, std::function<void()> f);

  // creates a shortcut for the given sequence and executes the action when
  // activated
  //
  void addShortcutAction(QKeySequence seq, Actions a);

  // calls action() with the given action on the selected page, if any
  //
  void doAction(Actions a);

  // called from setSinglePage(), does whatever doesn't need the T
  //
  void setSinglePageImpl(const QString& instanceName);

  // adds a line to the creation log
  //
  void logCreation(const QString& s);
  void logCreation(const std::wstring& s);

  // calls the given member function on all pages until one returns an object
  // that's not empty; used by gatherInfo()
  //
  template <class MF, class... Args>
  auto getSelected(MF mf, Args&&... args) const
  {
    // return type
    using T = decltype((std::declval<cid::Page>().*mf)(std::forward<Args>(args)...));

    for (auto&& p : m_pages) {
      const auto t = (p.get()->*mf)(std::forward<Args>(args)...);
      if (t != T()) {
        return t;
      }
    }

    return T();
  }
};

#endif  // MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED