#include "command-list-model.hpp"

#include <utility>

int CommandListModel::rowCount(const QModelIndex &parent) const {
  if (parent.isValid()) return 0;
  return static_cast<int>(m_flat.size());
}

QVariant CommandListModel::data(const QModelIndex &index, int role) const {
  if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(m_flat.size())) return {};

  const auto &flat = m_flat[index.row()];

  if (flat.kind == FlatItem::SectionHeader) {
    switch (role) {
    case IsSection:
      return true;
    case IsSelectable:
      return false;
    case SectionName:
      return m_sectionInfos[flat.sectionIdx].name;
    case Title:
      return QString();
    case Subtitle:
      return QString();
    case IconSource:
      return QString();
    case Accessory:
      return {};
    default:
      return {};
    }
  }

  switch (role) {
  case IsSection:
    return false;
  case IsSelectable:
    return true;
  case SectionName:
    return QString();
  case Title:
    return itemTitle(flat.sectionIdx, flat.itemIdx);
  case Subtitle:
    return itemSubtitle(flat.sectionIdx, flat.itemIdx);
  case IconSource:
    return itemIconSource(flat.sectionIdx, flat.itemIdx);
  case Accessory:
    return itemAccessory(flat.sectionIdx, flat.itemIdx);
  default:
    return {};
  }
}

QHash<int, QByteArray> CommandListModel::roleNames() const {
  return {
      {IsSection, "isSection"},     {IsSelectable, "isSelectable"},
      {SectionName, "sectionName"}, {Title, "title"},
      {Subtitle, "subtitle"},       {IconSource, "iconSource"},
      {Accessory, "itemAccessory"},
  };
}

void CommandListModel::setSelectedIndex(int index) {
  bool const changed = (m_selectedIndex != index);
  m_selectedIndex = index;
  if (changed) emit selectedIndexChanged();

  if (index < 0 || std::cmp_greater_equal(index, m_flat.size())) {
    m_lastSelectedItemId.clear();
    onSelectionCleared();
    return;
  }

  const auto &flat = m_flat[index];
  if (flat.kind == FlatItem::SectionHeader) {
    m_lastSelectedItemId.clear();
    onSelectionCleared();
    return;
  }

  auto id = itemId(flat.sectionIdx, flat.itemIdx);
  bool const sameItem = (id == m_lastSelectedItemId);
  m_lastSelectedItemId = id;

  if (!sameItem) { onItemSelected(flat.sectionIdx, flat.itemIdx); }

  auto panel = createActionPanel(flat.sectionIdx, flat.itemIdx);
  if (panel) {
    m_scope.setActions(std::move(panel));
  } else {
    m_scope.clearActions();
  }
}

void CommandListModel::onSelectionCleared() { m_scope.clearActions(); }

void CommandListModel::refreshActionPanel() { setSelectedIndex(m_selectedIndex); }

void CommandListModel::activateSelected() { m_scope.executePrimaryAction(); }

int CommandListModel::nextSelectableIndex(int from, int direction) const {
  int const count = static_cast<int>(m_flat.size());
  if (count == 0) return from;

  int idx = from + direction;
  if (idx < 0)
    idx = count - 1;
  else if (idx >= count)
    idx = 0;

  while (idx != from) {
    if (m_flat[idx].kind != FlatItem::SectionHeader) return idx;
    idx += direction;
    if (idx < 0)
      idx = count - 1;
    else if (idx >= count)
      idx = 0;
  }

  return from;
}

int CommandListModel::nextSectionIndex(int from, int direction) const {
  int const count = static_cast<int>(m_flat.size());
  if (count == 0) return from;

  int currentSection = -1;
  if (from >= 0 && from < count) { currentSection = m_flat[from].sectionIdx; }

  // Helper: return the first DataItem at or after `idx`.
  auto firstDataItemFrom = [&](int idx) -> int {
    while (idx < count && m_flat[idx].kind != FlatItem::DataItem)
      ++idx;
    return (idx < count) ? idx : from;
  };

  if (direction > 0) {
    // Jump directly to first item of next section.
    for (int idx = from + 1; idx < count; ++idx) {
      if (m_flat[idx].kind == FlatItem::DataItem && m_flat[idx].sectionIdx != currentSection) { return idx; }
      if (m_flat[idx].kind == FlatItem::SectionHeader && m_flat[idx].sectionIdx != currentSection) {
        return firstDataItemFrom(idx + 1);
      }
    }
    return nextSelectableIndex(-1, 1);
  }

  // Going up: find start of current section, then go to previous.
  int currentStart = from;
  for (int idx = from - 1; idx >= 0; --idx) {
    if (m_flat[idx].kind == FlatItem::DataItem && m_flat[idx].sectionIdx == currentSection) {
      currentStart = idx;
    } else if (m_flat[idx].sectionIdx != currentSection || m_flat[idx].kind == FlatItem::SectionHeader) {
      break;
    }
  }

  // If not already at the first item of current section, jump there.
  if (currentStart < from) { return currentStart; }

  // Find the previous section's first item.
  for (int idx = currentStart - 1; idx >= 0; --idx) {
    if (m_flat[idx].kind == FlatItem::SectionHeader || m_flat[idx].sectionIdx != currentSection) {
      // Walk backward to find the start of that section.
      int prevSection = -1;
      for (int j = idx; j >= 0; --j) {
        if (m_flat[j].kind == FlatItem::DataItem) {
          prevSection = m_flat[j].sectionIdx;
          break;
        }
      }
      if (prevSection < 0) break;
      // Find the first DataItem of prevSection.
      for (int j = 0; j < count; ++j) {
        if (m_flat[j].kind == FlatItem::DataItem && m_flat[j].sectionIdx == prevSection) return j;
      }
      break;
    }
  }

  // Wrap: go to last section's first item.
  int lastSection = -1;
  for (int i = count - 1; i >= 0; --i) {
    if (m_flat[i].kind == FlatItem::DataItem) {
      lastSection = m_flat[i].sectionIdx;
      break;
    }
  }
  if (lastSection >= 0) {
    for (int i = 0; i < count; ++i) {
      if (m_flat[i].kind == FlatItem::DataItem && m_flat[i].sectionIdx == lastSection) return i;
    }
  }
  return from;
}

int CommandListModel::scrollTargetIndex(int index, int direction) const {
  if (direction < 0 && index > 0 && std::cmp_less(index, m_flat.size())) {
    if (m_flat[index - 1].kind == FlatItem::SectionHeader) return index - 1;
  }
  return index;
}

void CommandListModel::setSections(const std::vector<SectionInfo> &sections) {
  if (m_awaitingData) {
    m_awaitingData = false;
    emit awaitingDataChanged();
  }

  auto newFlat = buildFlatList(sections);
  int const oldCount = static_cast<int>(m_flat.size());
  int const newCount = static_cast<int>(newFlat.size());

  if (m_selectFirstOnReset) {
    beginResetModel();
    m_sectionInfos = sections;
    m_flat = std::move(newFlat);
    m_selectedIndex = -1;
    m_lastSelectedItemId.clear();
    endResetModel();
    return;
  }

  // Incremental update — preserves ListView scroll position and selection
  if (newCount < oldCount) {
    beginRemoveRows({}, newCount, oldCount - 1);
    m_sectionInfos = sections;
    m_flat = std::move(newFlat);
    endRemoveRows();
  } else if (newCount > oldCount) {
    beginInsertRows({}, oldCount, newCount - 1);
    m_sectionInfos = sections;
    m_flat = std::move(newFlat);
    endInsertRows();
  } else {
    m_sectionInfos = sections;
    m_flat = std::move(newFlat);
  }

  int const overlap = std::min(oldCount, newCount);
  if (overlap > 0) emit dataChanged(index(0), index(overlap - 1));

  int const prevSelected = m_selectedIndex;

  if (newCount == 0) {
    m_selectedIndex = -1;
  } else if (m_selectedIndex >= newCount) {
    m_selectedIndex = nextSelectableIndex(newCount, -1);
  } else if (m_selectedIndex >= 0 && m_flat[m_selectedIndex].kind == FlatItem::SectionHeader) {
    m_selectedIndex = nextSelectableIndex(m_selectedIndex, 1);
  }

  if (m_selectedIndex != prevSelected) emit selectedIndexChanged();
}

bool CommandListModel::dataItemAt(int row, int &section, int &item) const {
  if (row < 0 || std::cmp_greater_equal(row, m_flat.size())) return false;
  const auto &flat = m_flat[row];
  if (flat.kind == FlatItem::SectionHeader) return false;
  section = flat.sectionIdx;
  item = flat.itemIdx;
  return true;
}

std::vector<CommandListModel::FlatItem>
CommandListModel::buildFlatList(const std::vector<SectionInfo> &sections) {
  std::vector<FlatItem> flat;

  for (int s = 0; std::cmp_less(s, sections.size()); ++s) {
    const auto &sec = sections[s];
    if (sec.count == 0) continue;

    if (!sec.name.isEmpty())
      flat.push_back({.kind = FlatItem::SectionHeader, .sectionIdx = s, .itemIdx = -1});

    for (int i = 0; i < sec.count; ++i) {
      flat.push_back({.kind = FlatItem::DataItem, .sectionIdx = s, .itemIdx = i});
    }
  }

  return flat;
}
