#include "keybind-settings-model.hpp"
#include "keyboard/keybind-manager.hpp"
#include "keyboard/keyboard.hpp"
#include "shortcut-conflict.hpp"
#include "fuzzy/fuzzy-searchable.hpp"
#include <algorithm>
#include <utility>

KeybindSettingsModel::KeybindSettingsModel(QObject *parent) : QAbstractListModel(parent) { rebuild({}); }

int KeybindSettingsModel::rowCount(const QModelIndex &) const { return static_cast<int>(m_entries.size()); }

QVariant KeybindSettingsModel::data(const QModelIndex &index, int role) const {
  if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(m_entries.size())) return {};
  const auto &e = m_entries[index.row()];
  switch (role) {
  case NameRole:
    return e.name;
  case IconRole:
    return e.icon;
  case DescriptionRole:
    return e.description;
  case ShortcutTokensRole:
    return e.shortcutTokens;
  case KeybindIdRole:
    return e.keybindId;
  default:
    return {};
  }
}

QHash<int, QByteArray> KeybindSettingsModel::roleNames() const {
  return {{NameRole, "name"},
          {IconRole, "icon"},
          {DescriptionRole, "description"},
          {ShortcutTokensRole, "shortcutTokens"},
          {KeybindIdRole, "keybindId"}};
}

QString KeybindSettingsModel::selectedName() const {
  return (m_selectedRow >= 0 && std::cmp_less(m_selectedRow, m_entries.size()))
             ? m_entries[m_selectedRow].name
             : QString();
}

QString KeybindSettingsModel::selectedDescription() const {
  return (m_selectedRow >= 0 && std::cmp_less(m_selectedRow, m_entries.size()))
             ? m_entries[m_selectedRow].description
             : QString();
}

QString KeybindSettingsModel::selectedIcon() const {
  return (m_selectedRow >= 0 && std::cmp_less(m_selectedRow, m_entries.size()))
             ? m_entries[m_selectedRow].icon
             : QString();
}

bool KeybindSettingsModel::hasSelection() const {
  return m_selectedRow >= 0 && std::cmp_less(m_selectedRow, m_entries.size());
}

void KeybindSettingsModel::setFilter(const QString &text) { rebuild(text); }

void KeybindSettingsModel::select(int row) {
  if (row == m_selectedRow) return;
  m_selectedRow = row;
  emit selectedChanged();
}

void KeybindSettingsModel::moveUp() {
  if (m_selectedRow > 0) select(m_selectedRow - 1);
}

void KeybindSettingsModel::moveDown() {
  if (m_selectedRow < rowCount() - 1) select(m_selectedRow + 1);
}

static QVariantList shortcutTokens(const Keyboard::Shortcut &shortcut) { return shortcut.toDisplayTokens(); }

QString KeybindSettingsModel::validateShortcut(int row, int key, int modifiers) const {
  Keyboard::Shortcut const shortcut(static_cast<Qt::Key>(key), static_cast<Qt::KeyboardModifiers>(modifiers));
  QString excludeId;
  if (row >= 0 && std::cmp_less(row, m_entries.size())) {
    excludeId = KeybindManager::instance()->idFor(static_cast<Keybind>(m_entries[row].keybindId));
  }
  return shortcut_conflict::validate(shortcut, excludeId);
}

void KeybindSettingsModel::setShortcut(int row, int key, int modifiers) {
  if (row < 0 || std::cmp_greater_equal(row, m_entries.size())) return;
  auto &e = m_entries[row];
  Keyboard::Shortcut const shortcut(static_cast<Qt::Key>(key), static_cast<Qt::KeyboardModifiers>(modifiers));
  KeybindManager::instance()->setKeybind(static_cast<Keybind>(e.keybindId), shortcut);
  e.shortcutTokens = shortcutTokens(shortcut);
  auto idx = index(row);
  emit dataChanged(idx, idx, {ShortcutTokensRole});
}

void KeybindSettingsModel::clearShortcut(int row) {
  if (row < 0 || std::cmp_greater_equal(row, m_entries.size())) return;
  auto &e = m_entries[row];
  KeybindManager::instance()->setKeybind(static_cast<Keybind>(e.keybindId), Keyboard::Shortcut());
  e.shortcutTokens.clear();
  auto idx = index(row);
  emit dataChanged(idx, idx, {ShortcutTokensRole});
}

QVariantList KeybindSettingsModel::shortcutDisplayTokens(int key, int modifiers) const {
  Keyboard::Shortcut const shortcut(static_cast<Qt::Key>(key), static_cast<Qt::KeyboardModifiers>(modifiers));
  return shortcut.toDisplayTokens();
}

void KeybindSettingsModel::rebuild(const QString &filter) {
  beginResetModel();
  m_entries.clear();

  auto query = filter.toStdString();
  fuzzy::Query const fuzzyQuery{query};
  std::vector<Scored<Entry>> scored;

  for (const auto &[id, info] : KeybindManager::instance()->orderedInfoList()) {
    int sc = 0;
    if (!query.empty()) {
      auto name = info->name.toStdString();
      auto desc = info->description.toStdString();
      auto const m = fuzzy::scoreWeighted({{name, 1.0}, {desc, 0.5}}, fuzzyQuery);
      if (!m.accepted()) continue;
      sc = m.score;
    }

    Entry e;
    e.keybindId = static_cast<int>(id);
    e.name = info->name;
    e.icon = info->icon;
    e.description = info->description;
    if (auto s = KeybindManager::instance()->resolve(id); s.isValid()) e.shortcutTokens = shortcutTokens(s);
    scored.push_back({.data = std::move(e), .score = sc});
  }

  if (!query.empty()) std::ranges::stable_sort(scored, std::greater{});

  m_entries.reserve(scored.size());
  for (auto &s : scored)
    m_entries.emplace_back(std::move(s.data));

  endResetModel();
  m_selectedRow = -1;
  select(m_entries.empty() ? -1 : 0);
}
