#include "general-settings-model.hpp"
#include "capabilities.hpp"
#include "config/config.hpp"
#include "view-utils.hpp"
#include "service-registry.hpp"
#include "theme.hpp"
#include "theme/theme-file.hpp"
#include "favicon/favicon-service.hpp"
#include "font-service.hpp"
#ifdef Q_OS_LINUX
#include "internal/icon-theme-db/icon-theme-db.hpp"
#endif
#include <QDirIterator>
#include <QGuiApplication>
#include <QIcon>
#include <QLocale>

GeneralSettingsModel::GeneralSettingsModel(QObject *parent) : QObject(parent) {
  m_windowMaterialModel.setSections(windowMaterialItems());
  m_fontModel.setSections(fontItems());
  m_faviconServiceModel.setSections(faviconServiceItems());
  m_keybindingSchemeModel.setSections(keybindingSchemeItems());
  m_languageModel.setSections(languageItems());
  refreshDynamicModels();

  connect(ServiceRegistry::instance()->config(), &config::Manager::configChanged, this, [this]() {
    refreshDynamicModels();
    emit configChanged();
  });
}

void GeneralSettingsModel::refreshDynamicModels() {
  if (auto items = themeItems(); items != m_themeItems) {
    m_themeItems = items;
    m_themeModel.setSections(items);
  }
  if (auto items = iconThemeItems(); items != m_iconThemeItems) {
    m_iconThemeItems = items;
    m_iconThemeModel.setSections(items);
  }
}

const config::ConfigValue &GeneralSettingsModel::cfg() const {
  return ServiceRegistry::instance()->config()->value();
}

config::Manager &GeneralSettingsModel::cfgManager() const { return *ServiceRegistry::instance()->config(); }

bool GeneralSettingsModel::searchFilesInRoot() const { return cfg().searchFilesInRoot; }
void GeneralSettingsModel::setSearchFilesInRoot(bool v) {
  cfgManager().mergeWithUser({.searchFilesInRoot = v});
}

bool GeneralSettingsModel::closeOnFocusLoss() const { return cfg().closeOnFocusLoss; }
void GeneralSettingsModel::setCloseOnFocusLoss(bool v) {
  cfgManager().mergeWithUser({.closeOnFocusLoss = v});
}

bool GeneralSettingsModel::closeOnEscape() const { return cfg().escapeKeyBehavior == "close_window"; }
void GeneralSettingsModel::setCloseOnEscape(bool v) {
  cfgManager().mergeWithUser({.escapeKeyBehavior = v ? std::string{"close_window"} : std::string{}});
}

bool GeneralSettingsModel::considerPreedit() const { return cfg().considerPreedit; }
void GeneralSettingsModel::setConsiderPreedit(bool v) { cfgManager().mergeWithUser({.considerPreedit = v}); }

bool GeneralSettingsModel::popToRootOnClose() const { return cfg().popToRootOnClose; }
void GeneralSettingsModel::setPopToRootOnClose(bool v) {
  cfgManager().mergeWithUser({.popToRootOnClose = v});
}

bool GeneralSettingsModel::popOnBackspace() const { return cfg().popOnBackspace; }
void GeneralSettingsModel::setPopOnBackspace(bool v) { cfgManager().mergeWithUser({.popOnBackspace = v}); }

bool GeneralSettingsModel::activateOnSingleClick() const { return cfg().activateOnSingleClick; }
void GeneralSettingsModel::setActivateOnSingleClick(bool v) {
  cfgManager().mergeWithUser({.activateOnSingleClick = v});
}

bool GeneralSettingsModel::wrapNavigation() const { return cfg().wrapNavigation; }
void GeneralSettingsModel::setWrapNavigation(bool v) { cfgManager().mergeWithUser({.wrapNavigation = v}); }

bool GeneralSettingsModel::encryptSensitiveData() const { return cfg().encryptSensitiveData; }
void GeneralSettingsModel::setEncryptSensitiveData(bool v) {
  cfgManager().mergeWithUser({.encryptSensitiveData = v});
}

bool GeneralSettingsModel::telemetrySystemInfo() const { return cfg().telemetry.systemInfo; }
void GeneralSettingsModel::setTelemetrySystemInfo(bool v) {
  cfgManager().mergeWithUser({.telemetry = config::Partial<config::TelemetryConfig>{.systemInfo = v}});
}

bool GeneralSettingsModel::layerShellEnabled() const { return cfg().launcherWindow.layerShell.enabled; }
void GeneralSettingsModel::setLayerShellEnabled(bool v) {
  cfgManager().mergeWithUser({.launcherWindow = config::Partial<config::WindowConfig>{
                                  .layerShell = config::Partial<config::LayerShellConfig>{.enabled = v}}});
}

bool GeneralSettingsModel::clientSideDecorations() const {
  return cfg().launcherWindow.clientSideDecorations.enabled;
}
void GeneralSettingsModel::setClientSideDecorations(bool v) {
  cfgManager().mergeWithUser(
      {.launcherWindow = config::Partial<config::WindowConfig>{
           .clientSideDecorations = config::Partial<config::WindowCSD>{.enabled = v}}});
}

QString GeneralSettingsModel::rounding() const {
  return QString::number(cfg().launcherWindow.effectiveRounding());
}
void GeneralSettingsModel::setRounding(const QString &v) {
  bool ok = false;
  int val = v.toInt(&ok);
  if (ok)
    cfgManager().mergeWithUser({.launcherWindow = config::Partial<config::WindowConfig>{.rounding = val}});
}

QString GeneralSettingsModel::csdBorderWidth() const {
  return QString::number(cfg().launcherWindow.clientSideDecorations.borderWidth);
}
void GeneralSettingsModel::setCsdBorderWidth(const QString &v) {
  bool ok = false;
  int val = v.toInt(&ok);
  if (ok)
    cfgManager().mergeWithUser(
        {.launcherWindow = config::Partial<config::WindowConfig>{
             .clientSideDecorations = config::Partial<config::WindowCSD>{.borderWidth = val}}});
}

QString GeneralSettingsModel::csdShadowSize() const {
  return QString::number(cfg().launcherWindow.clientSideDecorations.shadowSize);
}
void GeneralSettingsModel::setCsdShadowSize(const QString &v) {
  bool ok = false;
  int val = v.toInt(&ok);
  if (ok)
    cfgManager().mergeWithUser(
        {.launcherWindow = config::Partial<config::WindowConfig>{
             .clientSideDecorations = config::Partial<config::WindowCSD>{.shadowSize = val}}});
}

bool GeneralSettingsModel::compactMode() const { return cfg().launcherWindow.compactMode.enabled; }
void GeneralSettingsModel::setCompactMode(bool v) {
  cfgManager().mergeWithUser({.launcherWindow = config::Partial<config::WindowConfig>{
                                  .compactMode = config::Partial<config::WindowCompactMode>{.enabled = v}}});
}

bool GeneralSettingsModel::inputServerEnabled() const { return cfg().inputServer.enabled; }

void GeneralSettingsModel::setInputServerEnabled(bool v) {
  cfgManager().mergeWithUser({.inputServer = config::Partial<config::InputServer>{.enabled = v}});
}

QString GeneralSettingsModel::windowOpacity() const {
  return QString::number(
      cfg().launcherWindow.resolvedOpacity(platform::supports(platform::Capability::LiquidGlass),
                                           platform::supports(platform::Capability::WindowMaterial)));
}
void GeneralSettingsModel::setWindowOpacity(const QString &v) {
  bool ok = false;
  float val = v.toFloat(&ok);
  if (ok)
    cfgManager().mergeWithUser({.launcherWindow = config::Partial<config::WindowConfig>{.opacity = val}});
}

bool GeneralSettingsModel::nativeTextRendering() const { return cfg().font.rendering != "qt"; }
void GeneralSettingsModel::setNativeTextRendering(bool v) {
  cfgManager().mergeWithUser({.font = config::Partial<config::FontConfig>{.rendering = v ? "native" : "qt"}});
}

QString GeneralSettingsModel::fontSize() const { return QString::number(cfg().font.normal.size); }
void GeneralSettingsModel::setFontSize(const QString &v) {
  bool ok = false;
  float val = v.toFloat(&ok);
  if (ok) cfgManager().mergeWithUser({.font = config::Partial<config::FontConfig>{.normal{.size = val}}});
}

using qml::makeDropdownItem;

static QVariantList wrapSection(const QString &title, const QVariantList &items) {
  QVariantMap section;
  section[QStringLiteral("title")] = title;
  section[QStringLiteral("items")] = items;
  return {section};
}

QVariantList GeneralSettingsModel::windowMaterialItems() const {
  QVariantList items;
  items.append(makeDropdownItem(QStringLiteral("none"), tr("None")));
  items.append(makeDropdownItem(QStringLiteral("blur"), tr("Blurred")));
  if (platform::supports(platform::Capability::LiquidGlass))
    items.append(makeDropdownItem(QStringLiteral("liquid_glass"), tr("Liquid Glass")));
  return wrapSection(tr("Window material"), items);
}

QVariant GeneralSettingsModel::currentWindowMaterial() const {
  auto id = QString::fromStdString(
      cfg().launcherWindow.resolvedMaterial(platform::supports(platform::Capability::LiquidGlass),
                                            platform::supports(platform::Capability::WindowMaterial)));
  QString name = id == "liquid_glass" ? tr("Liquid Glass") : id == "none" ? tr("None") : tr("Blurred");
  return makeDropdownItem(id, name);
}

void GeneralSettingsModel::selectWindowMaterial(const QString &id) {
  cfgManager().mergeWithUser(
      {.launcherWindow = config::Partial<config::WindowConfig>{.material = id.toStdString()}});
}

QVariantList GeneralSettingsModel::themeItems() const {
  QVariantList items;
  for (const auto &theme : ThemeService::instance().themes()) {
    auto iconUrl = theme->icon() ? ImageURL::local(QString::fromStdString(theme->icon()->string()))
                                 : ImageURL::builtin(BuiltinIcon::Vicinae);
    items.append(makeDropdownItem(theme->id(), theme->name(), qml::imageSourceFor(iconUrl)));
  }
  return wrapSection(tr("Themes"), items);
}

QVariant GeneralSettingsModel::currentTheme() const {
  auto id = QString::fromStdString(cfg().systemTheme().name);
  auto *theme = ThemeService::instance().findTheme(id);
  if (!theme) return makeDropdownItem(id, id);
  auto iconUrl = theme->icon() ? ImageURL::local(QString::fromStdString(theme->icon()->string()))
                               : ImageURL::builtin(BuiltinIcon::Vicinae);
  return makeDropdownItem(id, theme->name(), qml::imageSourceFor(iconUrl));
}

QVariantList GeneralSettingsModel::fontItems() const {
  QVariantList items;
  for (const auto &family : ServiceRegistry::instance()->fontService()->families()) {
    items.append(makeDropdownItem(family, family));
  }
  return wrapSection(tr("Fonts"), items);
}

QVariant GeneralSettingsModel::currentFont() const {
  auto &family = cfg().font.normal.family;
  auto name = family == "auto" ? QGuiApplication::font().family() : QString::fromStdString(family);
  return makeDropdownItem(name, name);
}

QVariantList GeneralSettingsModel::iconThemeItems() const {
  QVariantList items;
#ifdef Q_OS_LINUX
  IconThemeDatabase const db;
  for (const auto &theme : db.themes()) {
    items.append(makeDropdownItem(theme.name, theme.name));
  }
#endif
  return wrapSection(tr("Icon Themes"), items);
}

QVariant GeneralSettingsModel::currentIconTheme() const {
  auto &ith = cfg().systemTheme().iconTheme;
  auto name = ith == "auto" ? QIcon::themeName() : QString::fromStdString(ith);
  return makeDropdownItem(name, name);
}

QVariantList GeneralSettingsModel::faviconServiceItems() const {
  QVariantList items;
  for (const auto &svc : FaviconService::providers()) {
    auto iconSource = qml::imageSourceFor(svc.icon);
    items.append(makeDropdownItem(svc.id, svc.name, iconSource));
  }
  return wrapSection(tr("Favicon Services"), items);
}

QVariant GeneralSettingsModel::currentFaviconService() const {
  auto id = QString::fromStdString(cfg().faviconService);
  for (const auto &svc : FaviconService::providers()) {
    if (svc.id == id) return makeDropdownItem(id, svc.name);
  }
  return makeDropdownItem(id, id);
}

QVariantList GeneralSettingsModel::keybindingSchemeItems() const {
  QVariantList items;
  items.append(makeDropdownItem(QStringLiteral("default"), tr("Default")));
  items.append(makeDropdownItem(QStringLiteral("vim"), QStringLiteral("Vim")));
  items.append(makeDropdownItem(QStringLiteral("emacs"), QStringLiteral("Emacs")));
  return wrapSection(tr("Keybinding Schemes"), items);
}

QVariant GeneralSettingsModel::currentKeybindingScheme() const {
  auto id = QString::fromStdString(cfg().keybinding);
  if (id == "vim") return makeDropdownItem(id, QStringLiteral("Vim"));
  if (id == "emacs") return makeDropdownItem(id, QStringLiteral("Emacs"));
  return makeDropdownItem(id, tr("Default"));
}

void GeneralSettingsModel::selectTheme(const QString &id) {
  cfgManager().mergeThemeConfig({.name = id.toStdString()});
}

void GeneralSettingsModel::selectFont(const QString &id) {
  cfgManager().mergeWithUser(
      {.font = config::Partial<config::FontConfig>{.normal = {.family = id.toStdString()}}});
}

void GeneralSettingsModel::selectIconTheme(const QString &id) {
  cfgManager().mergeThemeConfig({.iconTheme = id.toStdString()});
}

void GeneralSettingsModel::selectFaviconService(const QString &id) {
  cfgManager().mergeWithUser({.faviconService = id.toStdString()});
}

void GeneralSettingsModel::selectKeybindingScheme(const QString &id) {
  cfgManager().mergeWithUser({.keybinding = id.toStdString()});
}

static QString languageDisplayName(const QString &code) {
  QString name = QLocale(code).nativeLanguageName();
  if (name.isEmpty()) return code;
  name[0] = name.at(0).toUpper();
  return name;
}

QVariantList GeneralSettingsModel::languageItems() const {
  QStringList codes;
  QDirIterator it(QStringLiteral(":/i18n"), {QStringLiteral("vicinae_*.qm")});
  while (it.hasNext()) {
    it.next();
    codes.append(it.fileInfo().baseName().sliced(QStringLiteral("vicinae_").size()));
  }
  codes.sort();

  QVariantList items;
  items.append(makeDropdownItem(QStringLiteral("system"), tr("System default")));
  for (const auto &code : codes) {
    items.append(makeDropdownItem(code, languageDisplayName(code)));
  }
  return wrapSection(tr("Languages"), items);
}

QVariant GeneralSettingsModel::currentLanguage() const {
  const auto &language = cfg().language;
  if (!language || *language == "system") {
    return makeDropdownItem(QStringLiteral("system"), tr("System default"));
  }
  auto code = QString::fromStdString(*language);
  return makeDropdownItem(code, languageDisplayName(code));
}

void GeneralSettingsModel::selectLanguage(const QString &id) {
  cfgManager().updateUser([&](config::Partial<config::ConfigValue> &value) {
    if (id == QStringLiteral("system")) {
      value.language = "system";
    } else {
      value.language = id.toStdString();
    }
  });
}

QString GeneralSettingsModel::toggleShortcut() const {
  return QString::fromStdString(cfg().globalShortcuts.toggle.value_or(""));
}

void GeneralSettingsModel::setToggleShortcut(const QString &shortcut) {
  cfgManager().mergeWithUser(
      {.globalShortcuts = config::Partial<config::GlobalShortcuts>{.toggle = shortcut.toStdString()}});
}
