#pragma once

#include <maps/wikimap/mapspro/services/mrc/libs/common/include/algorithm/collection.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/threadpool_wrapper.h>

#include <algorithm>
#include <functional>
#include <locale>
#include <span>
#include <sstream>
#include <type_traits>
#include <unordered_map>
#include <vector>

namespace maps::mrc::excess_features_eraser {

constexpr auto THREADS_NUMBER = 4;

std::string concat(const auto&... args)
{
    auto os = std::ostringstream{};
    os.imbue(std::locale(""));
    ((os << args), ...);
    return os.str();
}

void eraseFrom(auto&& from, auto&& range)
{
    for (const auto& item : range) {
        from.erase(item);
    }
}

auto makeFlatSet(auto&& range)
{
    auto result = std::vector(std::begin(range), std::end(range));
    common::sortUnique(result);
    return result;
}

template <class Range, class KeyFunction, class MappedFunction>
auto makeMap(Range&& range, KeyFunction keyFn, MappedFunction mappedFn)
{
    using T = decltype(*std::begin(range));
    using Key = std::remove_cvref_t<std::invoke_result_t<KeyFunction, T>>;
    using Mapped = std::remove_cvref_t<std::invoke_result_t<MappedFunction, T>>;
    auto result = std::unordered_map<Key, Mapped>{};
    for (const auto& item : range) {
        result.insert({std::invoke(keyFn, item), std::invoke(mappedFn, item)});
    }
    return result;
}

auto makeDifference(auto&& lhs, auto&& rhs)
{
    auto lhsSet = makeFlatSet(lhs);
    auto rhsSet = makeFlatSet(rhs);
    auto result = std::vector<typename decltype(lhsSet)::value_type>{};
    std::set_difference(std::begin(lhsSet),
                        std::end(lhsSet),
                        std::begin(rhsSet),
                        std::end(rhsSet),
                        std::back_inserter(result));
    return result;
}

template <class Range, class Function>
auto invokeForEach(Range&& range, Function fn)
{
    using From = decltype(*std::begin(range));
    using To = std::remove_cvref_t<std::invoke_result_t<Function, From>>;
    auto result = std::vector<To>{};
    for (const auto& item : range) {
        result.push_back(std::invoke(fn, item));
    }
    return result;
}

template <size_t ThreadsNumber, size_t BatchSize, class Range, class Function>
void parallelForEachBatch(Range&& range, Function fn)
{
    auto threadPool = common::ThreadpoolWrapper{ThreadsNumber};
    auto first = std::begin(range);
    for (size_t pos = 0, count = std::size(range); pos < count;) {
        auto prev = pos;
        pos += std::min<size_t>(count - pos, BatchSize);
        auto batch = std::span(first + prev, first + pos);
        threadPool->add([&fn, batch] { std::invoke(fn, batch); });
    }
    threadPool->drain();
    threadPool.checkExceptions();
}

template <class Enum>
void advance(Enum& item, int n)
{
    item = Enum(static_cast<std::underlying_type_t<Enum>>(item) + n);
}

template <class Enum>
std::vector<Enum> allOf()
{
    auto result = std::vector<Enum>{};
    for (auto item = Enum::Min; item <= Enum::Max; advance(item, 1)) {
        result.push_back(item);
    }
    return result;
}

}  // namespace maps::mrc::excess_features_eraser
