#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 <maps/wikimap/mapspro/services/mrc/libs/db/include/common.h>

#include <span>
#include <unordered_map>
#include <utility>
#include <vector>

namespace maps::mrc::signs_export_gen {

template <typename Range, typename Transform>
auto transform(Range&& range, Transform transform)
{
    using T = decltype(*std::begin(range));
    using ResultT = std::remove_cvref_t<std::invoke_result_t<Transform, T>>;
    auto result = std::vector<ResultT>{};
    for (const auto& item : range) {
        result.push_back(std::invoke(transform, item));
    }
    return result;
}

template <typename Map>
std::vector<typename Map::key_type>
keys(const Map& map)
{
    using Key = typename Map::key_type;
    std::vector<Key> result;
    result.reserve(map.size());

    for (const auto& [key, _] : map) {
        result.push_back(key);
    }
    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;
}

template <typename Range, class KeyFunction>
auto toMapByKey(Range&& range, KeyFunction keyFn)
{
    using T = std::remove_cvref_t<decltype(*std::begin(range))>;
    std::unordered_map<db::TId, T> result;
    for (auto&& item : range) {
        auto id = std::invoke(keyFn, item);
        result.emplace(id, std::forward<T>(item));
    }
    return result;
}

auto join(auto&& range1, auto&& range2)
{
    using T = std::remove_cvref_t<decltype(*std::begin(range1))>;
    std::vector<T> result;
    result.reserve(range1.size() + range2.size());
    std::move(range1.begin(), range1.end(), std::back_inserter(result));
    std::move(range2.begin(), range2.end(), std::back_inserter(result));
    return result;
}

auto joinUnique(auto&& range1, auto&& range2)
{
    auto result = join(std::move(range1), std::move(range2));
    common::sortUnique(result);
    return result;
}

template <typename Map>
std::optional<typename Map::mapped_type>
findOptional(const Map& map, typename Map::key_type key)
{
    auto itr = map.find(key);
    if (itr != map.end()) {
        return itr->second;
    }
    return std::nullopt;
}

template<typename Range, typename T>
bool contains(const Range& range, const T& value)
{
    return std::find(begin(range), end(range), value) != end(range);
}

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();
}

}  // namespace maps::mrc::signs_export_gen
