#include "rd.h"
#include "calc.h"
#include "generic.h"
#include "maps/wikimap/mapspro/services/editor/src/objects/attr_object.h"
#include "maps/wikimap/mapspro/services/editor/src/objects/object.h"
#include "maps/wikimap/mapspro/services/editor/src/objects/areal_object.h"
#include "maps/wikimap/mapspro/services/editor/src/objects/junction.h"
#include "maps/wikimap/mapspro/services/editor/src/objects/linear_element.h"
#include "maps/wikimap/mapspro/services/editor/src/objects/point_object.h"
#include "maps/wikimap/mapspro/services/editor/src/objects/complex_object.h"
#include "maps/wikimap/mapspro/services/editor/src/objects/relation_object.h"
#include "maps/wikimap/mapspro/services/editor/src/edit_options.h"
#include "maps/wikimap/mapspro/services/editor/src/validator.h"
#include "registry.h"
#include "maps/wikimap/mapspro/services/editor/src/magic_strings.h"
#include "maps/wikimap/mapspro/services/editor/src/configs/categories_strings.h"

#include <yandex/maps/wiki/configs/editor/categories.h>
#include <yandex/maps/wiki/common/string_utils.h>
#include <yandex/maps/wiki/common/rd/lane.h>
#include <yandex/maps/wiki/common/rd/access_id.h>

#include <unordered_set>

namespace maps::wiki::srv_attrs {
namespace
{
const std::string TOPOLOGY = "topology";
const std::string TANKER_KEYSET_LANE_DIRECTIONS = "attr-values:cond_lane-lane__";
const std::string TANKER_KEY_BIKES = "attr-values:cond-access_id__16";
const std::string TANKER_KEY_START = "{{";
const std::string TANKER_KEY_END = "}}";

std::string
srvCondInvalid(const ComplexObject* cond, ObjectsCache& cache)
{
    return isComplexObjectTopologyValid(cond, cache)
        ? s_emptyString
        : TOPOLOGY;
}

const std::unordered_set<std::string> ALL_ATTR_VEHICLE_RESTRICTIONS {
    ATTR_VEHICLE_RESTRICTION_WEIGHT_LIMIT,
    ATTR_VEHICLE_RESTRICTION_AXLE_WEIGHT_LIMIT,
    ATTR_VEHICLE_RESTRICTION_MAX_WEIGHT_LIMIT,
    ATTR_VEHICLE_RESTRICTION_HEIGHT_LIMIT,
    ATTR_VEHICLE_RESTRICTION_WIDTH_LIMIT,
    ATTR_VEHICLE_RESTRICTION_LENGTH_LIMIT,
    ATTR_VEHICLE_RESTRICTION_PAYLOAD_LIMIT,
    ATTR_VEHICLE_RESTRICTION_MIN_ECO_CLASS,
    ATTR_VEHICLE_RESTRICTION_TRAILER_NOT_ALLOWED
};

bool
condHasVehicleRestrictions(const GeoObject* cond)
{
    return
        cond->categoryId() == CATEGORY_COND &&
        std::any_of(ALL_ATTR_VEHICLE_RESTRICTIONS.begin(), ALL_ATTR_VEHICLE_RESTRICTIONS.end(),
            [&](const auto& attrId) {
                return !cond->attributes().value(attrId).empty();
            });
}

common::AccessId
accessId(const GeoObject* obj)
{
    const AttributeDef* accessIdAttrDef = nullptr;
    for (const auto& attrDef : obj->attributes().definitions()) {
        if (attrDef->id().ends_with(ATTR_ACCESS_ID_SUFFIX)) {
            accessIdAttrDef = attrDef.get();
        }
    }
    if (!accessIdAttrDef) {
        return common::AccessId::None;
    }
    const auto& accessIdValue = obj->attributes().value(accessIdAttrDef->id());
    if (accessIdValue.empty() ||
        !std::all_of(accessIdValue.begin(), accessIdValue.end(), ::isdigit))
    {
        return common::AccessId::None;
    }
    const auto result = static_cast<common::AccessId>(
        boost::lexical_cast<uint32_t>(accessIdValue));
    if (!common::isValid(result)) {
        return common::AccessId::None;
    }
    return result;
}

bool
isRestrictedCond(const GeoObject* cond)
{
    return cond->categoryId() == CATEGORY_COND &&
        cond->attributes().value(ATTR_COND_COND_TYPE) == "1";
}

bool
isPassCond(const GeoObject* cond)
{
    return cond->categoryId() == CATEGORY_COND &&
        cond->attributes().value(ATTR_COND_COND_TYPE) == "5";
}
} // namespace

class RD_COND_LANE : public ServiceAttributesRegistry::Registrar
{
public:
    RD_COND_LANE(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_COND_LANE)
    {
        registerAttr(SRV_INVALID, CallbackWrapper<ComplexObject>(srvCondInvalid))
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_TO, CATEGORY_RD_EL}}
        })
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_FROM, CATEGORY_RD_EL}}
        })
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_VIA, CATEGORY_RD_JC}}
        });
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<ComplexObject>(screenLabel))
            .depends(
        {
            {Aspect::Type::Attribute, ATTR_RD_EL_T_LANE},
            {{RelationType::Slave, ROLE_FROM, CATEGORY_RD_EL}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, ATTR_RD_EL_F_LANE},
            {{RelationType::Slave, ROLE_FROM, CATEGORY_RD_EL}}
        });
    }

public:
    static std::string screenLabel(const ComplexObject* condLane, ObjectsCache&)
    {
        auto fromRange = condLane->slaveRelations().range(ROLE_FROM);
        REQUIRE(fromRange.size() == 1,
            "Wrong number of 'from' elements in " << condLane->id());
        auto fromElement = fromRange.begin()->relative();
        auto viaRange = condLane->slaveRelations().range(ROLE_VIA);
        REQUIRE(viaRange.size() == 1,
            "Wrong number of 'via' junctions in " << condLane->id());
        auto viaJunction = viaRange.begin()->relative();
        const auto& attrName =
            viaJunction->id() == as<LinearElement>(fromElement)->junctionAId()
            ? ATTR_RD_EL_F_LANE
            : ATTR_RD_EL_T_LANE;
        auto lanes = common::lanesFromString(fromElement->attributes().value(attrName));
        if (lanes.empty()) {
            return s_emptyString;
        }
        auto minLane  = boost::lexical_cast<size_t>(condLane->attributes().value(ATTR_COND_LANE_LANE_MIN));
        auto maxLane  = boost::lexical_cast<size_t>(condLane->attributes().value(ATTR_COND_LANE_LANE_MAX));
        std::string lanesLabel;
        if (minLane > lanes.size() - 1 ||
            maxLane > lanes.size() - 1) {
            lanesLabel = s_emptyString;
        } else if (minLane == maxLane
            && lanes[minLane].kind() == common::LaneKind::Bike) {
            lanesLabel  = " (" +
                TANKER_KEY_START + TANKER_KEY_BIKES + TANKER_KEY_END
                + ")";
        } else {
            size_t minLaneNoBike = 1;
            size_t maxLaneNoBike = 1;
            for (size_t laneNum = lanes.size() - 1; laneNum > 0 ; --laneNum) {
                if (lanes[laneNum].kind() == common::LaneKind::Bike) {
                    continue;
                }
                if (minLane < laneNum) {
                    ++minLaneNoBike;
                }
                if (maxLane < laneNum) {
                    ++maxLaneNoBike;
                }
            }
            lanesLabel =
                " (" +
                std::to_string(minLaneNoBike)
                + (minLaneNoBike == maxLaneNoBike
                    ? s_emptyString
                    : NICE_DASH + std::to_string(maxLaneNoBike))
                + ")";
        }
        return TANKER_KEY_START
            + TANKER_KEYSET_LANE_DIRECTIONS
            + condLane->attributes().value(ATTR_COND_LANE_LANE)
            + TANKER_KEY_END
            + lanesLabel;
    }
};

class RD_JC : public ServiceAttributesRegistry::Registrar
{
public:
    RD_JC(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_RD_JC)
    {
        registerAttr(
           CATEGORY_RD_JC + SUFFIX_CAN_DELETE,
           CallbackWrapper<Junction>(canDelete))
            .depends(
        {
            {Aspect::Type::Attribute, s_emptyString},
            {{RelationType::Master, ROLE_START, CATEGORY_RD_EL}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, s_emptyString},
            {{RelationType::Master, ROLE_END, CATEGORY_RD_EL}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, s_emptyString},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, s_emptyString},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND_DS}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, s_emptyString},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND_ANNOTATION}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, s_emptyString},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND_CAM}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, s_emptyString},
            {{RelationType::Master, ROLE_PLACED_AT, CATEGORY_COND_TRAFFIC_LIGHT}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, s_emptyString},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND_LANE}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, s_emptyString},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND_TOLL}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, s_emptyString},
            {{RelationType::Master, ROLE_LOCATED_AT, CATEGORY_COND_RAILWAY_CROSSING}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, s_emptyString},
            {{RelationType::Master, ROLE_LOCATED_AT, CATEGORY_COND_SPEED_BUMP}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, s_emptyString},
            {{RelationType::Master, ROLE_LOCATED_AT, CATEGORY_COND_BORDER_CHECKPOINT}}
        });

        registerAttr(SRV_VALENCY, CallbackWrapper<Junction>(valency))
            .depends(
        {
            {Aspect::Type::Relations, STR_TO_ATTR_OWNER},
            {{RelationType::Master, ROLE_START, CATEGORY_RD_EL}}
        })
            .depends(
        {
            {Aspect::Type::Relations, STR_TO_ATTR_OWNER},
            {{RelationType::Master, ROLE_END, CATEGORY_RD_EL}}
        });

        registerAttr(SRV_HOTSPOT_LABEL, CallbackWrapper<Junction>(hotSpotLabel))
            .depends(
        {
            {Aspect::Type::Attribute, ATTR_COND_COND_TYPE},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, ATTR_COND_CAM_COND_TYPE},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND_CAM}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, {}},
            {{RelationType::Master, ROLE_PLACED_AT, CATEGORY_COND_TRAFFIC_LIGHT}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, {}},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND_TOLL}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, {}},
            {{RelationType::Master, ROLE_LOCATED_AT, CATEGORY_COND_RAILWAY_CROSSING}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, {}},
            {{RelationType::Master, ROLE_LOCATED_AT, CATEGORY_COND_SPEED_BUMP}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, {}},
            {{RelationType::Master, ROLE_LOCATED_AT, CATEGORY_COND_BORDER_CHECKPOINT}}
        });
        registerAttr(RD_JC_IS_ALL_COND_VALID, CallbackWrapper<Junction>(isAllCondValid));
        registerAttr(RD_JC_IS_INVOLVE_IN_COND,
            CallbackWrapper<Junction, std::string>(isInvolveInCond, CATEGORY_COND));

        registerAttr(RD_JC_IS_INVOLVE_IN_COND_RESTRICTED,
            CallbackWrapper<Junction>(isInvolveInCondRestricted))
            .depends(
        {
            {Aspect::Type::Attribute, {}},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND}}
        });
        registerAttr(RD_JC_IS_INVOLVE_IN_COND_RESTRICTED_TRUCK,
            CallbackWrapper<Junction>(isInvolveInCondRestrictedTruck))
            .depends(
        {
            {Aspect::Type::Attribute, {}},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND}}
        });
        registerAttr(RD_JC_IS_INVOLVE_IN_COND_RESTRICTED_V_R,
            CallbackWrapper<Junction>(isInvolveInCondRestrictedVR))
            .depends(
        {
            {Aspect::Type::Attribute, {}},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND}}
        });
        registerAttr(RD_JC_IS_INVOLVE_IN_COND_RESTRICTED_TRUCK_V_R,
            CallbackWrapper<Junction>(isInvolveInCondRestrictedTruckVR))
            .depends(
        {
            {Aspect::Type::Attribute, {}},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND}}
        });
        registerAttr(RD_JC_IS_INVOLVE_IN_COND_PASS_TRUCK,
            CallbackWrapper<Junction>(isInvolveInCondPassTruck))
            .depends(
        {
            {Aspect::Type::Attribute, {}},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND}}
        });
        registerAttr(RD_JC_IS_INVOLVE_IN_COND_PASS_TRUCK_V_R,
         CallbackWrapper<Junction>(isInvolveInCondPassTruckVR))
            .depends(
        {
            {Aspect::Type::Attribute, {}},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND}}
        });

        registerAttr(RD_JC_IS_INVOLVE_IN_COND_DS,
            CallbackWrapper<Junction, std::string>(isInvolveInCond, CATEGORY_COND_DS));
        registerAttr(RD_JC_IS_INVOLVE_IN_COND_WITH_COND_DT,
            CallbackWrapper<Junction>(isInvolveInCondWithCondDt))
            .depends(
        {
            {Aspect::Type::Attribute, ATTR_COND_COND_DT},
            {{RelationType::Master, ROLE_VIA, CATEGORY_COND}}
        });
        registerAttr(RD_JC_IS_INVOLVE_IN_COND_ANNOTATION,
            CallbackWrapper<Junction, std::string>(isInvolveInCond, CATEGORY_COND_ANNOTATION));
        registerAttr(RD_JC_IS_INVOLVE_IN_COND_CAM,
            CallbackWrapper<Junction, std::string>(isInvolveInCond, CATEGORY_COND_CAM));
        registerAttr(RD_JC_IS_INVOLVE_IN_COND_TRAFFIC_LIGHT,
            CallbackWrapper<Junction, std::string>(isInvolveInCond, CATEGORY_COND_TRAFFIC_LIGHT));
        registerAttr(RD_JC_IS_INVOLVE_IN_COND_LANE,
            CallbackWrapper<Junction, std::string>(isInvolveInCond, CATEGORY_COND_LANE));
        registerAttr(RD_JC_IS_INVOLVE_IN_COND_CLOSURE,
            CallbackWrapper<Junction>(isInvolveInCondClosure));
        registerAttr(RD_JC_IS_INVOLVE_IN_COND_TOLL,
            CallbackWrapper<Junction, std::string>(isInvolveInCond, CATEGORY_COND_TOLL));
        registerAttr(RD_JC_IS_INVOLVE_IN_COND_RAILWAY_CROSSING,
            CallbackWrapper<Junction, std::string>(isInvolveInCond, CATEGORY_COND_RAILWAY_CROSSING));
        registerAttr(RD_JC_IS_INVOLVE_IN_COND_SPEED_BUMP,
            CallbackWrapper<Junction, std::string>(isInvolveInCond, CATEGORY_COND_SPEED_BUMP));
        registerAttr(RD_JC_IS_INVOLVE_IN_COND_BORDER_CHECKPOINT,
            CallbackWrapper<Junction, std::string>(isInvolveInCond, CATEGORY_COND_BORDER_CHECKPOINT));
    }

    static std::string canDelete(const Junction* jc, ObjectsCache&)
    {
        return maps::wiki::canDelete(jc, 0) ? SRV_ATTR_TRUE : SRV_ATTR_FALSE;
    }

    static std::string valency(const Junction* jc, ObjectsCache&)
    {
        return std::to_string(jc->valency());
    }

    static std::string isAllCondValid(const Junction* jc, ObjectsCache& cache)
    {
        for (const auto& masterInfo : jc->masterRelations().range(RD_JC_INVOLVE_IN_COND_ROLES)) {
            if (!isComplexObjectTopologyValid(as<ComplexObject>(masterInfo.relative()), cache)) {
                return SRV_ATTR_FALSE;
            }
        }
        return SRV_ATTR_TRUE;
    }

    static std::string hotSpotLabelPart(const Junction* jc, const std::string& condCat, const std::string& condTypeAttr)
    {
        typedef std::map<std::string, size_t> CondTypeCounts;
        CondTypeCounts condTypeCounts;
        for (const auto& masterInfo : jc->masterRelations().range(ROLE_VIA)) {
            if (masterInfo.categoryId() != condCat) {
                continue;
            }
            auto cond = masterInfo.relative();
            std::string condTypeLabel = valueLabel(condTypeAttr, cond->attributes().value(condTypeAttr));
            if (condHasVehicleRestrictions(cond)) {
                condTypeLabel = categoryLabel(CATEGORY_VEHICLE_RESTRICTION) + ": " + condTypeLabel;
            }
            ASSERT(!condTypeLabel.empty());
            auto p = condTypeCounts.insert({condTypeLabel, 1});
            if (!p.second) {
                ++(p.first->second);
            }
        }
        std::string hotspotLabel;
        for (const auto& [label, count] : condTypeCounts) {
            if (!hotspotLabel.empty()) {
                hotspotLabel += ", ";
            }
            hotspotLabel += label;
            if (count > 1) {
                hotspotLabel += " (" + std::to_string(count) + ")";
            }

        }
        return hotspotLabel;
    }

    static std::string hotSpotLabel(const Junction* jc, ObjectsCache& cache)
    {
        StringVec labels;
        auto hotspotLabelPartCOND = hotSpotLabelPart(jc, CATEGORY_COND, ATTR_COND_COND_TYPE);
        if (!hotspotLabelPartCOND.empty()) {
            labels.push_back(hotspotLabelPartCOND);
        }
        auto hotspotLabelPartCAM = hotSpotLabelPart(jc, CATEGORY_COND_CAM, ATTR_COND_CAM_COND_TYPE);
        if (!hotspotLabelPartCAM.empty()) {
            labels.push_back(hotspotLabelPartCAM);
        }
        auto lights = jc->masterRelations().range(ROLE_PLACED_AT);
        if (!lights.empty()) {
            auto lightsCount = lights.size();
            labels.push_back(
                lights.begin()->relative()->category().label() +
                (lightsCount > 1
                    ? " (" + std::to_string(lightsCount) + ")"
                    : s_emptyString));
        }
        for (const auto& rel : jc->masterRelations().range(ROLE_VIA)) {
            if (rel.categoryId() == CATEGORY_COND_LANE) {
                labels.push_back(RD_COND_LANE::screenLabel(as<ComplexObject>(rel.relative()), cache));
            }
        }
        for (const auto& rel : jc->masterRelations().range(ROLE_LOCATED_AT)) {
            if (rel.categoryId() == CATEGORY_COND_RAILWAY_CROSSING) {
                labels.push_back(categoryLabel(CATEGORY_COND_RAILWAY_CROSSING));
            }
            if (rel.categoryId() == CATEGORY_COND_SPEED_BUMP) {
                labels.push_back(categoryLabel(CATEGORY_COND_SPEED_BUMP));
            }
            if (rel.categoryId() == CATEGORY_COND_BORDER_CHECKPOINT) {
                labels.push_back(categoryLabel(CATEGORY_COND_BORDER_CHECKPOINT));
            }
        }
        return common::join(labels, ",\n");
    }

    static std::string isInvolveInCond(const Junction* jc, ObjectsCache&, const std::string& condCat)
    {
        for (const auto& rel : jc->masterRelations().range(RD_JC_INVOLVE_IN_COND_ROLES)) {
            if (rel.categoryId() == condCat) {
                return SRV_ATTR_TRUE;
            }
        }
        return SRV_ATTR_FALSE;
    }

    static std::string isInvolveInCondRestricted(const Junction* jc, ObjectsCache&)
    {
        for (const auto& rel : jc->masterRelations().range(ROLE_VIA)) {
            const auto* cond = rel.relative();
            if (isRestrictedCond(cond) &&
                accessId(cond) != common::AccessId::Truck &&
                !condHasVehicleRestrictions(cond))
            {
                return SRV_ATTR_TRUE;
            }
        }
        return SRV_ATTR_FALSE;
    }

    static std::string isInvolveInCondRestrictedTruck(const Junction* jc, ObjectsCache&)
    {
        for (const auto& rel : jc->masterRelations().range(ROLE_VIA)) {
            const auto* cond = rel.relative();
            if (isRestrictedCond(cond) &&
                accessId(cond) == common::AccessId::Truck &&
                !condHasVehicleRestrictions(cond)) {
                return SRV_ATTR_TRUE;
            }
        }
        return SRV_ATTR_FALSE;
    }

    static std::string isInvolveInCondRestrictedTruckVR(const Junction* jc, ObjectsCache&)
    {
        for (const auto& rel : jc->masterRelations().range(ROLE_VIA)) {
            const auto* cond = rel.relative();
            if (isRestrictedCond(cond) &&
                accessId(cond) == common::AccessId::Truck &&
                condHasVehicleRestrictions(cond)) {
                return SRV_ATTR_TRUE;
            }
        }
        return SRV_ATTR_FALSE;
    }

    static std::string isInvolveInCondRestrictedVR(const Junction* jc, ObjectsCache&)
    {
        for (const auto& rel : jc->masterRelations().range(ROLE_VIA)) {
            const auto* cond = rel.relative();
            if (isRestrictedCond(cond) &&
                accessId(cond) != common::AccessId::Truck &&
                condHasVehicleRestrictions(cond)) {
                return SRV_ATTR_TRUE;
            }
        }
        return SRV_ATTR_FALSE;
    }

    static std::string isInvolveInCondPassTruck(const Junction* jc, ObjectsCache&)
    {
        for (const auto& rel : jc->masterRelations().range(ROLE_VIA)) {
            const auto* cond = rel.relative();
            if (isPassCond(cond) &&
                accessId(cond) == common::AccessId::Truck &&
                !condHasVehicleRestrictions(cond)) {
                return SRV_ATTR_TRUE;
            }
        }
        return SRV_ATTR_FALSE;
    }

    static std::string isInvolveInCondPassTruckVR(const Junction* jc, ObjectsCache&)
    {
        for (const auto& rel : jc->masterRelations().range(ROLE_VIA)) {
            const auto* cond = rel.relative();
            if (isPassCond(cond) &&
                accessId(cond) == common::AccessId::Truck &&
                condHasVehicleRestrictions(cond)) {
                return SRV_ATTR_TRUE;
            }
        }
        return SRV_ATTR_FALSE;
    }

    static std::string isInvolveInCondWithCondDt(const Junction* jc, ObjectsCache&)
    {
        for (const auto& rel : jc->masterRelations().range(ROLE_VIA)) {
            if (rel.categoryId() != CATEGORY_COND) {
                continue;
            }
            const auto* cond = rel.relative();
            if (!cond->tableAttributes().find(ATTR_COND_COND_DT).empty()) {
                return SRV_ATTR_TRUE;
            }
        }
        return SRV_ATTR_FALSE;
    }

    static std::string isInvolveInCondClosure(const Junction* jc, ObjectsCache&)
    {
        for (const auto& rel : jc->masterRelations().range(ROLE_COND_CLOSURE_FROM)) {
            if (rel.categoryId() == CATEGORY_COND_CLOSURE) {
                return SRV_ATTR_TRUE;
            }
        }
        return SRV_ATTR_FALSE;
    }
};

class RD_EL : public ServiceAttributesRegistry::Registrar
{
public:
    RD_EL(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_RD_EL)
    {
        registerAttr(RD_EL_IS_PART_OF_ROAD, CallbackWrapper<LinearElement, std::string, std::string>(
            hasMasterWithRole,
            CATEGORY_RD,
            ROLE_PART));
        registerAttr(RD_EL_IS_PART_OF_SPORT_TRACK, CallbackWrapper<LinearElement, std::string, std::string>(
            hasMasterWithRole,
            CATEGORY_SPORT_TRACK,
            ROLE_SPORT_TRACK_PART));
        registerAttr(RD_EL_IS_PART_OF_BUS_THREAD, CallbackWrapper<LinearElement, std::string, std::string>(
            hasMasterWithRole,
            CATEGORY_TRANSPORT_BUS_THREAD,
            ROLE_BUS_PART));
        registerAttr(RD_EL_IS_PART_OF_BUS_THREAD_CONNECTOR, CallbackWrapper<LinearElement, std::string, std::string>(
            hasMasterWithRole,
            CATEGORY_TRANSPORT_BUS_THREAD_CONNECTOR,
            ROLE_BUS_PART));
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<LinearElement, std::string>(
                screenAndRenderLabel,
                NAME_TYPE_OFFICIAL))
            .depends(
        {
            {Aspect::Type::Attribute, RD_NM},
            {{RelationType::Master, ROLE_PART, CATEGORY_RD}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, SPORT_TRACK_NM},
            {{RelationType::Master, ROLE_SPORT_TRACK_PART, CATEGORY_SPORT_TRACK}}
        });
        registerAttr(SRV_HOTSPOT_LABEL, CallbackWrapper<LinearElement>(hotspotLabel))
            .depends(
        {
            {Aspect::Type::Attribute, RD_NM},
            {{RelationType::Master, ROLE_PART, CATEGORY_RD}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, SPORT_TRACK_NM},
            {{RelationType::Master, ROLE_SPORT_TRACK_PART, CATEGORY_SPORT_TRACK}}
        });
        registerAttr(SRV_RENDER_LABEL, CallbackWrapper<LinearElement, std::string>(
                screenAndRenderLabel,
                NAME_TYPE_RENDER_LABEL))
            .depends(
        {
            {Aspect::Type::Attribute, RD_NM},
            {{RelationType::Master, ROLE_PART, CATEGORY_RD}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, SPORT_TRACK_NM},
            {{RelationType::Master, ROLE_SPORT_TRACK_PART, CATEGORY_SPORT_TRACK}}
        });
        registerAttr(RD_EL_IS_INVOLVE_IN_COND_CLOSURE,
            CallbackWrapper<LinearElement>(isInvolveInCondClosure));

        registerAttr(RD_EL_VEHICLE_RESTRICTION_UNIVERSAL_IDS,
            CallbackWrapper<LinearElement, std::set<std::string>>(vehicleRestrictionUniversalIds,
                {ROLE_RESTRICTS, ROLE_RESTRICTS_FROM, ROLE_RESTRICTS_TO}))
            .depends(
        {
            {Aspect::Type::Attribute, ATTR_VEHICLE_RESTRICTION_UNIVERSAL_ID},
            {{RelationType::Master, ROLE_RESTRICTS, CATEGORY_VEHICLE_RESTRICTION}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, ATTR_VEHICLE_RESTRICTION_UNIVERSAL_ID},
            {{RelationType::Master, ROLE_RESTRICTS_FROM, CATEGORY_VEHICLE_RESTRICTION}}
        })
            .depends(
        {
            {Aspect::Type::Attribute, ATTR_VEHICLE_RESTRICTION_UNIVERSAL_ID},
            {{RelationType::Master, ROLE_RESTRICTS_TO, CATEGORY_VEHICLE_RESTRICTION}}
        });

        registerAttr(RD_EL_VEHICLE_RESTRICTION_UNIVERSAL_IDS_B,
            CallbackWrapper<LinearElement, std::set<std::string>>(vehicleRestrictionUniversalIds,
                {ROLE_RESTRICTS}))
            .depends(
        {
            {Aspect::Type::Attribute, ATTR_VEHICLE_RESTRICTION_UNIVERSAL_ID},
            {{RelationType::Master, ROLE_RESTRICTS, CATEGORY_VEHICLE_RESTRICTION}}
        });
        registerAttr(RD_EL_VEHICLE_RESTRICTION_UNIVERSAL_IDS_T,
            CallbackWrapper<LinearElement, std::set<std::string>>(vehicleRestrictionUniversalIds,
                {ROLE_RESTRICTS_TO}))
            .depends(
        {
            {Aspect::Type::Attribute, ATTR_VEHICLE_RESTRICTION_UNIVERSAL_ID},
            {{RelationType::Master, ROLE_RESTRICTS_TO, CATEGORY_VEHICLE_RESTRICTION}}
        });

        registerAttr(RD_EL_VEHICLE_RESTRICTION_UNIVERSAL_IDS_F,
            CallbackWrapper<LinearElement, std::set<std::string>>(vehicleRestrictionUniversalIds,
                {ROLE_RESTRICTS_FROM}))
            .depends(
        {
            {Aspect::Type::Attribute, ATTR_VEHICLE_RESTRICTION_UNIVERSAL_ID},
            {{RelationType::Master, ROLE_RESTRICTS_FROM, CATEGORY_VEHICLE_RESTRICTION}}
        });

        registerAttr(RD_EL_SPORT_TRACK_FT_TYPE_IDS, CallbackWrapper<LinearElement>(sportTrackFtTypeIds))
            .depends(
        {
            {Aspect::Type::Attribute, ATTR_SPORT_TRACK_FT_TYPE_ID},
            {{RelationType::Master, ROLE_SPORT_TRACK_PART, CATEGORY_SPORT_TRACK}}
        });
    }

private:
    static std::string hasMasterWithRole(
        const LinearElement* el,
        ObjectsCache&,
        const std::string& masterCategoryId,
        const std::string& roleId)
    {
        for (const auto& rel : el->masterRelations().range(roleId)) {
            if (rel.categoryId() == masterCategoryId) {
                return SRV_ATTR_TRUE;
            }
        }
        return SRV_ATTR_FALSE;
    }

    static std::string screenAndRenderLabel(
        const LinearElement* el,
        ObjectsCache& cache,
        const std::string& nameType)
    {
        const auto& roadsMasters = el->masterRelations().range(ROLE_PART);
        std::map<std::string, TOid> rdTypeToRdId;
        for (const auto& rd : roadsMasters) {
            if (rd.categoryId() == CATEGORY_RD) {
                rdTypeToRdId.insert(
                    std::make_pair(rd.relative()->attributes().value(ATTR_RD_RD_TYPE),
                    rd.id()));
            }
        }
        for (const auto& [_, rdId] : rdTypeToRdId) {
            std::string rdName = objectNameByType(rdId, nameType, cache);
            if (!rdName.empty()) {
                return rdName;
            }
        }

        const auto& sportTrackMasters = el->masterRelations().range(ROLE_SPORT_TRACK_PART);
        std::map<std::string, TOid> sportTrackFtTypeIdToId;
        for (const auto& st : sportTrackMasters) {
            if (st.categoryId() == CATEGORY_SPORT_TRACK) {
                sportTrackFtTypeIdToId.insert(
                    std::make_pair(st.relative()->attributes().value(ATTR_SPORT_TRACK_FT_TYPE_ID),
                    st.id()));
            }
        }
        for (const auto& [_, stId] : sportTrackFtTypeIdToId) {
            std::string stName = objectNameByType(stId, nameType, cache);
            if (!stName.empty()) {
                return stName;
            }
        }

        return s_emptyString;
    }

    static std::string hotspotLabel(const LinearElement* el, ObjectsCache& cache)
    {
        const auto& roadsMasters = el->masterRelations().range(ROLE_PART);
        std::multimap<std::string, TOid> rdTypeToRdId;
        for (const auto& rd : roadsMasters) {
            if (rd.categoryId() == CATEGORY_RD) {
                rdTypeToRdId.insert(
                    std::make_pair(rd.relative()->attributes().value(ATTR_RD_RD_TYPE),
                    rd.id()));
            }
        }

        const auto& sportTrackMasters = el->masterRelations().range(ROLE_SPORT_TRACK_PART);
        std::multimap<std::string, TOid> sportTrackFtTypeIdToId;
        for (const auto& st : sportTrackMasters) {
            if (st.categoryId() == CATEGORY_SPORT_TRACK) {
                sportTrackFtTypeIdToId.insert(
                    std::make_pair(st.relative()->attributes().value(ATTR_SPORT_TRACK_FT_TYPE_ID),
                    st.id()));
            }
        }

        StringSet usedNames;
        StringVec namesToJoin;

        for (const auto& [_, rdId] : rdTypeToRdId) {
            std::string rdName = objectNameByType(rdId, NAME_TYPE_OFFICIAL, cache);
            if (!rdName.empty() && !usedNames.count(rdName)){
                namesToJoin.push_back(rdName);
                usedNames.insert(rdName);
            }
        }
        for (const auto& [_, stId] : sportTrackFtTypeIdToId) {
            std::string stName = objectNameByType(stId, NAME_TYPE_OFFICIAL, cache);
            if (!stName.empty() && !usedNames.count(stName)){
                namesToJoin.push_back(stName);
                usedNames.insert(stName);
            }
        }

        namesToJoin.push_back(valueLabel(ATTR_RD_EL_FC, el->attributes().value(ATTR_RD_EL_FC)));

        const auto& fowValue = el->attributes().value(ATTR_RD_EL_FOW);
        if (RD_EL_FOW_NOT_SET != fowValue) {
            namesToJoin.push_back(valueLabel(ATTR_RD_EL_FOW, fowValue));
        }

        return common::join(namesToJoin, ", ");
    }

    static std::string isInvolveInCondClosure(const LinearElement* rdEl, ObjectsCache&)
    {
        for (const auto& rel : rdEl->masterRelations().range(ROLE_COND_CLOSURE_ELEMENT)) {
            if (rel.categoryId() == CATEGORY_COND_CLOSURE) {
                return SRV_ATTR_TRUE;
            }
        }
        return SRV_ATTR_FALSE;
    }

    static std::string vehicleRestrictionUniversalIds(
        const LinearElement* rdEl,
        ObjectsCache&,
        const std::set<std::string>& roles)
    {
        auto vehicleRestrictionsRels = rdEl->masterRelations().range(roles);
        if (vehicleRestrictionsRels.empty()) {
            return s_emptyString;
        }
        json::Builder builder;
        builder << [&](json::ArrayBuilder array) {
            for (const auto& vehicleRestrictionsRel : vehicleRestrictionsRels) {
                array << vehicleRestrictionsRel.relative()->attributes()
                            .value(ATTR_VEHICLE_RESTRICTION_UNIVERSAL_ID);
            }
        };
        return builder.str();
    }

    static std::string sportTrackFtTypeIds(const LinearElement* el, ObjectsCache&)
    {
        const auto sportTrackMasters = el->masterRelations().range(ROLE_SPORT_TRACK_PART);
        if (sportTrackMasters.empty()) {
            return s_emptyString;
        }
        json::Builder builder;
        StringSet usedFtTypeIds;
        builder << [&](json::ArrayBuilder array) {
            for (const auto& st : sportTrackMasters) {
                const auto ftTypeId = st.relative()->attributes().value(
                    ATTR_SPORT_TRACK_FT_TYPE_ID);
                if (!usedFtTypeIds.contains(ftTypeId)) {
                    array << std::stoi(ftTypeId);
                    usedFtTypeIds.insert(ftTypeId);
                }
            }
        };
        return builder.str();
    }
};

class RD_COND : public ServiceAttributesRegistry::Registrar
{
public:
    RD_COND(ServiceAttributesRegistry& registry, const std::string& condCategory, const std::string& labelAttr)
        : ServiceAttributesRegistry::Registrar(registry, condCategory)
    {
        registerAttr(SRV_INVALID, CallbackWrapper<ComplexObject>(srvCondInvalid))
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_TO, CATEGORY_RD_EL}}
        })
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_FROM, CATEGORY_RD_EL}}
        })
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_VIA, CATEGORY_RD_JC}}
        });
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<ComplexObject, const std::string&>(screenLabel, labelAttr));
    }

private:
    static std::string screenLabel(const ComplexObject* cond, ObjectsCache&, const std::string& labelAttr)
    {
        return valueLabel(labelAttr, cond->attributes().value(labelAttr));
    }
};

class RD_COND_ANNOTATION : public ServiceAttributesRegistry::Registrar
{
public:
    RD_COND_ANNOTATION(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_COND_ANNOTATION)
    {
        registerAttr(SRV_INVALID, CallbackWrapper<ComplexObject>(srvCondInvalid))
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_TO, CATEGORY_RD_EL}}
        })
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_FROM, CATEGORY_RD_EL}}
        })
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_VIA, CATEGORY_RD_JC}}
        });
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<ComplexObject>(screenLabel))
            .depends(
            {
                {Aspect::Type::Attribute, ATTR_COND_ANNOTATION_PHRASE_NAME},
                {{RelationType::Master, ROLE_ANNOTATING, CATEGORY_COND_ANNOTATION_PHRASE}}
            });
    }

private:
    static std::string screenLabel(const ComplexObject* cond, ObjectsCache&)
    {
        auto text =  valueLabel(ATTR_COND_ANNOTATION_ANNOTATION_ID, cond->attributes().value(ATTR_COND_ANNOTATION_ANNOTATION_ID));
        for (const auto& phrase : cond->masterRelations().range(ROLE_ANNOTATING)) {
            text += " (" + phrase.relative()->attributes().value(ATTR_COND_ANNOTATION_PHRASE_NAME) + ")";
        }
        return text;
    }
};



class RD_COND_CAM : public ServiceAttributesRegistry::Registrar
{
public:
    RD_COND_CAM(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_COND_CAM)
    {
        registerAttr(SRV_INVALID, CallbackWrapper<ComplexObject>(srvCondInvalid))
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_FROM, CATEGORY_RD_EL}}
        })
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_VIA, CATEGORY_RD_JC}}
        });
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<ComplexObject>(screenLabel));
    }

private:
    static std::string screenLabel(const ComplexObject* cond, ObjectsCache&)
    {
        return valueLabel(ATTR_COND_CAM_COND_TYPE, cond->attributes().value(ATTR_COND_CAM_COND_TYPE));
    }
};

class RD_COND_TOLL : public ServiceAttributesRegistry::Registrar
{
public:
    RD_COND_TOLL(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_COND_TOLL)
    {
        registerAttr(SRV_INVALID, CallbackWrapper<ComplexObject>(srvCondInvalid))
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_FROM, CATEGORY_RD_EL}}
        })
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_VIA, CATEGORY_RD_JC}}
        });
    }
};

class RD_COND_TRAFFIC_LIGHT : public ServiceAttributesRegistry::Registrar
{
public:
    RD_COND_TRAFFIC_LIGHT(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_COND_TRAFFIC_LIGHT)
    {
        registerAttr(SRV_INVALID, CallbackWrapper<ComplexObject>(srvCondInvalid))
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_CONTROLLED, CATEGORY_RD_EL}}
        })
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_PLACED_AT, CATEGORY_RD_JC}}
        });
    }
};

class RD_COND_DS : public ServiceAttributesRegistry::Registrar
{
public:
    RD_COND_DS(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_COND_DS)
    {
        registerAttr(SRV_INVALID, CallbackWrapper<ComplexObject>(srvCondInvalid))
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_FROM, CATEGORY_RD_EL}}
        })
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_VIA, CATEGORY_RD_JC}}
        })
            .depends(
        {
            {Aspect::Type::Geometry, s_emptyString},
            {{RelationType::Slave, ROLE_TO, CATEGORY_RD_EL}}
        });

        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<ComplexObject>(screenLabel));
    }
private:
    static std::string screenLabel(const ComplexObject* condDs, ObjectsCache&)
    {
        std::string label = valueLabel(ATTR_COND_DS_DIRECTION, condDs->attributes().value(ATTR_COND_DS_DIRECTION));
        const auto& condDsElValue = condDs->tableAttributes().find(ATTR_COND_DS_COND_DS_EL);
        if (condDsElValue.empty()) {
            return label;
        }
        std::map<size_t, std::string> orderTocondDsElLabel;
        for (size_t row = 0; row < condDsElValue.numRows(); ++row) {
            std::string condDsElLabel = valueLabel(ATTR_COND_DS_EL_TYPE,
                                                condDsElValue.value(row, ATTR_COND_DS_EL_TYPE));
            const auto order = boost::lexical_cast<size_t>(condDsElValue.value(row, ATTR_COND_DS_EL_ORDER));
            const auto& icon = condDsElValue.value(row, ATTR_COND_DS_EL_ICON);
            if (!icon.empty()) {
                condDsElLabel += ", " + valueLabel(ATTR_COND_DS_EL_ICON, icon);
            }
            const auto& text = condDsElValue.value(row, ATTR_COND_DS_EL_NAME);
            if (!text.empty()) {
                condDsElLabel += "," + text;
            }
            orderTocondDsElLabel.emplace(order, condDsElLabel);
        }
        const auto condDsElLabels = common::join(orderTocondDsElLabel,
            [](const std::pair<size_t, std::string>& toCondDsElLabel) {
                return toCondDsElLabel.second;
            },
            "; ");
        if (!condDsElLabels.empty()) {
            label += "; " + condDsElLabels;
        }
        return label;
    }

};

class RD : public ServiceAttributesRegistry::Registrar
{
public:
    RD(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_RD)
    {
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<ComplexObject>(screenLabel));
        registerSuggestCallback(SuggestCallbackWrapper<ComplexObject>(GenericNamedObject::suggestTexts));
    }

private:
    static std::string screenLabel(const ComplexObject* rd, ObjectsCache& cache)
    {
        return objectNameByType(rd->id(), NAME_TYPE_OFFICIAL, cache);
    }
};

class SPORT_TRACK : public ServiceAttributesRegistry::Registrar
{
public:
    SPORT_TRACK(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_SPORT_TRACK)
    {
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<ComplexObject>(screenLabel));
        registerSuggestCallback(SuggestCallbackWrapper<ComplexObject>(GenericNamedObject::suggestTexts));
    }

private:
    static std::string screenLabel(const ComplexObject* sport_track, ObjectsCache& cache)
    {
        return objectNameByType(sport_track->id(), NAME_TYPE_OFFICIAL, cache);
    }
};

class COND_ANNOTATION_PHRASE : public ServiceAttributesRegistry::Registrar
{
public:
    COND_ANNOTATION_PHRASE(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_COND_ANNOTATION_PHRASE)
    {
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<ComplexObject>(screenLabel));
        registerSuggestCallback(SuggestCallbackWrapper<ComplexObject>(suggestTexts));
    }

private:
    static std::string screenLabel(const ComplexObject* phrase, ObjectsCache&)
    {
        return phrase->attributes().value(ATTR_COND_ANNOTATION_PHRASE_NAME);
    }
    static SuggestTexts suggestTexts(const ComplexObject* phrase, ObjectsCache&)
    {
        return {
            {
                NM_LANG_RU,
                phrase->attributes().value(ATTR_COND_ANNOTATION_PHRASE_NAME)
            }
        };
    }
};


class VEHICLE_RESTRICTION : public ServiceAttributesRegistry::Registrar
{
public:
    VEHICLE_RESTRICTION(ServiceAttributesRegistry& registry)
        : ServiceAttributesRegistry::Registrar(registry, CATEGORY_VEHICLE_RESTRICTION)
    {
        registerAttr(SRV_SCREEN_LABEL, CallbackWrapper<ComplexObject>(screenLabel));
        registerSuggestCallback(SuggestCallbackWrapper<ComplexObject>(suggestTexts));
    }

private:
    static std::string screenLabel(const ComplexObject* vehicleRestriction, ObjectsCache& cache)
    {
        const auto name = objectNameByType(
            vehicleRestriction->id(), NAME_TYPE_OFFICIAL, cache);
        const auto id = vehicleRestriction->attributes().value(
            ATTR_VEHICLE_RESTRICTION_UNIVERSAL_ID);
        return name.empty()
            ? id
            : name + " (" + id + ")";
    }
    static SuggestTexts suggestTexts(const ComplexObject* vehicleRestriction, ObjectsCache& cache)
    {
        auto texts = GenericNamedObject::suggestTexts(vehicleRestriction, cache);
        const auto id = vehicleRestriction->attributes().value(
            ATTR_VEHICLE_RESTRICTION_UNIVERSAL_ID);
        if (texts.empty()) {
            texts.emplace(
                    NM_LANG_RU,
                    id
                );
        } else {
            for (auto& [_, name] : texts) {
                name = name + " (" + id + ")";
            }
        }
        return texts;
    }
};

RDServiceAttributes::RDServiceAttributes(ServiceAttributesRegistry &registry)
{
    RD_JC rdjc(registry);
    RD_EL rdel(registry);
    RD_COND cond(registry, CATEGORY_COND, ATTR_COND_COND_TYPE);
    RD_COND_ANNOTATION condAnnotation(registry);
    COND_ANNOTATION_PHRASE condAnnotationPhrase(registry);
    RD_COND_DS condDs(registry);
    RD_COND_LANE condLane(registry);
    RD_COND_CAM condCam(registry);
    RD_COND_TRAFFIC_LIGHT condTrafficLight(registry);
    RD_COND_TOLL condToll(registry);
    RD rd(registry);
    SPORT_TRACK sport_track(registry);
    VEHICLE_RESTRICTION vr(registry);
}

} // namespace maps::wiki::srv_attrs
