#include "torlinkc/ui/filter.hpp"

#include <algorithm>
#include <cctype>
#include <sstream>

namespace torlinkc::ui {

namespace {

std::string toLower(std::string s) {
  std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
  return s;
}

std::string trim(const std::string& s) {
  auto first = s.find_first_not_of(" \t\r\n");
  if (first == std::string::npos) return "";
  auto last = s.find_last_not_of(" \t\r\n");
  return s.substr(first, last - first + 1);
}

std::vector<std::string> splitWhitespace(const std::string& s) {
  std::istringstream iss(s);
  std::vector<std::string> out;
  std::string tok;
  while (iss >> tok) out.push_back(tok);
  return out;
}

}  // namespace

std::vector<TorrentResult> filterResults(const std::vector<TorrentResult>& list, bool hideDead,
                                          const std::unordered_map<std::string, Source>& sources,
                                          const std::string& textFilter) {
  std::vector<TorrentResult> filtered = list;

  if (hideDead) {
    std::vector<TorrentResult> next;
    for (const auto& r : filtered) {
      const auto it = sources.find(r.source);
      const bool reportsHealth = it == sources.end() || it->second.reportsHealth;
      if (r.seeders > 0 || !reportsHealth) next.push_back(r);
    }
    filtered = std::move(next);
  }

  const std::string text = toLower(trim(textFilter));
  if (text.empty()) return filtered;

  const auto tokens = splitWhitespace(text);
  std::string normalizedText;
  for (std::size_t i = 0; i < tokens.size(); ++i) {
    if (i) normalizedText += ' ';
    normalizedText += tokens[i];
  }

  std::vector<std::pair<int, TorrentResult>> scored;
  for (const auto& r : filtered) {
    const std::string name = toLower(r.name);
    bool matchesAll = true;
    for (const auto& token : tokens) {
      if (name.find(token) == std::string::npos) {
        matchesAll = false;
        break;
      }
    }
    if (!matchesAll) continue;

    int score = 10;  // base score for matching all tokens
    if (name.find(normalizedText) != std::string::npos) {
      score += 50;  // exact substring gets the highest boost
    } else {
      // Boost if tokens appear in the same order.
      long long lastIndex = -1;
      bool inOrder = true;
      for (const auto& token : tokens) {
        const auto idx = name.find(token, static_cast<std::size_t>(lastIndex + 1));
        if (idx == std::string::npos || static_cast<long long>(idx) < lastIndex) {
          inOrder = false;
          break;
        }
        lastIndex = static_cast<long long>(idx);
      }
      if (inOrder) score += 20;
    }
    scored.emplace_back(score, r);
  }

  std::stable_sort(scored.begin(), scored.end(),
                    [](const auto& a, const auto& b) { return a.first > b.first; });

  std::vector<TorrentResult> out;
  out.reserve(scored.size());
  for (auto& [score, r] : scored) out.push_back(std::move(r));
  return out;
}

}  // namespace torlinkc::ui
