#include "strong_connectivity_checks_common.h"
#include "component_report.h"

#include <yandex/maps/wiki/common/string_utils.h>
#include <maps/libs/log8/include/log8.h>

#include <optional>
#include <string>

namespace maps::wiki::validator::checks {

using categories::RD_EL;
using categories::RD_JC;

namespace {

void reportStronglyConnectedComponent(
    CheckContext* context,
    const GraphComponent& component,
    Severity baseSeverity,
    const std::string& descrSuffix,
    MessageKeyGenerator keyGenerator,
    MessageComparator messageComparator)
{
    if (component.onAoiBorder) {
        return;
    }
    if (component.isolated) {
         context->error(
            "isolated-scc" + descrSuffix,
            component.geom,
            component.representatives,
            keyGenerator,
            messageComparator);
        return;
    }
    context->report(
        baseSeverity,
        "extraneous-scc" + descrSuffix,
        component.geom,
        component.representatives,
        keyGenerator,
        messageComparator);
}

void
logOutEdges(
    graph::NodeID nodeId,
    const graph::Edges& outEdges,
    const std::string& descrSuffix)
{
    DEBUG()
        << "SCC edges: "
        << (descrSuffix.empty() ? "" : "suffix = " + descrSuffix + "; ")
        << "rd_el_id = " << nodeId << "; "
        << "out rd_el_ids = {"
        << common::join(outEdges,
            [](const graph::Edge& edge) {
                return edge.endNodeId();
            }, ',')
        << "}";
}

} // namespace

void
checkStrongConnectivity(
    CheckContext* context,
    const RoadNetwork& roadNetwork,
    Severity errorSeverity,
    const std::string& descrSuffix,
    MessageKeyGenerator keyGenerator,
    MessageComparator messageComparator)
{
    graph::StronglyConnectedComponentsFinder sccFinder;
    context->objects<RD_EL>().visit(
        [&](const RoadElement* rdEl) {
            if (!roadNetwork.filterRdEl(rdEl)) {
                return;
            }
            sccFinder.exploreNode(rdEl->id(),
                [&](graph::NodeID nodeId) {
                    auto outEdges = roadNetwork.outEdges(nodeId);
                    logOutEdges(nodeId, outEdges, descrSuffix);
                    return outEdges;
                });
        });

    const auto& components = sccFinder.stronglyConnectedComponents();
    if (components.empty()) {
        context->warning(
            "empty-roadset" + descrSuffix,
            boost::none,
            {});
        return;
    }
    StronglyConnectedComponentReport componentReport(
        context,
        components,
        [&](const RoadElement* rdEl){ return roadNetwork.filterRdEl(rdEl); });

    for (const auto& component : componentReport.secondaries()) {
        reportStronglyConnectedComponent(
            context,
            component,
            errorSeverity,
            descrSuffix,
            keyGenerator,
            messageComparator);
    }
}

} // namespace maps::wiki::validator::checks
