#include "stream.h"

#include <util/generic/map.h>
#include <util/generic/variant.h>
#include <util/digest/numeric.h>

static NSv::TAction AppHostHasher(const YAML::Node& args, NSv::TAuxData&) {
    CHECK_NODE(args, args.IsMap(), "an argument is required");
    auto arg = args.begin()->second;
    CHECK_NODE(arg, arg.IsMap(), "must be a (node name -> hint description) map");
    using TPathItem = std::variant<size_t /* array index */, TString /* map key */>;
    using TPath = std::pair<size_t /* item */, TVector<TPathItem>>;
    TMap<TString, size_t /* path set id */> sourceMap;
    TMap<TString, size_t /* item id */> itemIndex;
    TMap<TVector<TPath>, size_t /* path set id */> pathIndex;
    for (const auto& kv : arg) {
        CHECK_NODE(kv.first, kv.first.IsScalar(), "must be a (scalar) node name");
        CHECK_NODE(kv.second, kv.second.IsSequence() && kv.second.size(), "must be a sequence of JSON paths");
        TVector<TPath> paths;
        for (const auto& path : kv.second) {
            CHECK_NODE(path, path.IsSequence() && path.size(), "must be a sequence of indices and keys");
            for (const auto& item : path) {
                CHECK_NODE(item, item.IsScalar(), "path items must be indices or keys");
            }
            auto it = path.begin();
            auto itemId = itemIndex.emplace(it->Scalar(), itemIndex.size()).first->second;
            paths.emplace_back(itemId, TVector<TPathItem>{});
            ui64 index;
            for (it++; it != path.end(); it++) {
                if (YAML::convert<ui64>::decode(*it, index)) {
                    paths.back().second.emplace_back(index);
                } else {
                    paths.back().second.emplace_back(TString(it->Scalar()));
                }
            }
        }
        std::sort(paths.begin(), paths.end());
        paths.erase(std::unique(paths.begin(), paths.end()), paths.end());
        auto pathId = pathIndex.emplace(paths, pathIndex.size()).first->second;
        CHECK_NODE(kv.first, sourceMap.emplace(kv.first.Scalar(), pathId).second, "duplicate node name");
    }

    TVector<TString> neededItems(itemIndex.size());
    TVector<TVector<TPath>> pathSets(pathIndex.size());
    TVector<std::pair<TString, size_t>> sources(sourceMap.begin(), sourceMap.end());
    for (const auto& [item, id] : itemIndex) {
        neededItems[id] = item;
    }
    for (const auto& [path, id] : pathIndex) {
        pathSets[id] = path;
    }

    return [=](NSv::NAppHost::TInterface ah) {
        auto req = ah.ReadAll();
        if (!req MUN_RETHROW) {
            return false;
        }

        TVector<NJson::TJsonValue> items(neededItems.size());
        for (size_t i = 0; i < items.size(); i++) {
            for (const auto& answer : req->GetAnswers()) {
                if (answer.GetType() == neededItems[i]) {
                    NSv::NAppHost::DecodeJson(answer, [&](NJson::TJsonValue&& v) {
                        items[i] = std::move(v);
                    });
                }
            }
        }
        TVector<TMaybe<ui64>> hints(pathSets.size());
        for (size_t i = 0; i < pathSets.size(); i++) {
            auto combine = [&](auto&& x) {
                ui64 h = THash<std::decay_t<decltype(x)>>{}(x);
                hints[i] = hints[i] ? CombineHashes(*hints[i], h) : h;
            };
            for (const auto& [item, path] : pathSets[i]) {
                NJson::TJsonValue* current = &items[item];
                for (const auto& elem : path) {
                    auto* index = std::get_if<size_t>(&elem);
                    current = index ? &(*current)[*index] : &(*current)[std::get<TString>(elem)];
                }
                if (current->IsString()) {
                    combine(current->GetStringSafe());
                } else if (current->IsBoolean()) {
                    combine(current->GetBooleanSafe());
                } else if (current->IsDouble()) {
                    combine(current->GetDoubleSafe());
                }
            }
        }

        NSv::NAppHost::TResponse rsp;
        for (const auto& [source, pathSetIdx] : sources) {
            if (const auto& hint = hints[pathSetIdx]) {
                auto& it = *rsp.AddHints();
                it.SetSourceName(source);
                it.SetValue(*hint);
            }
        }
        return ah.Write(std::move(rsp)) && ah.Close();
    };
}

SV_DEFINE_ACTION("apphost-hasher", AppHostHasher);
