#include "formatter_context.h"
#include "common.h"
#include <maps/wikimap/mapspro/services/editor/src/objects/object.h>
#include <maps/wikimap/mapspro/services/editor/src/objects/category_traits.h>
#include <maps/wikimap/mapspro/services/editor/src/magic_strings.h>
#include <maps/wikimap/mapspro/services/editor/src/configs/categories_strings.h>
#include <maps/wikimap/mapspro/services/editor/src/configs/config.h>

#include <yandex/maps/wiki/configs/editor/slave_role.h>
#include <yandex/maps/wiki/configs/editor/master_role.h>
#include <yandex/maps/wiki/configs/editor/categories.h>

namespace maps::wiki {
namespace {

const std::list<std::list<std::string>> EXTENDED_OUTPUT_PATHS {
    /* Addr unity */

    {CATEGORY_ADDR_UNITY, CATEGORY_AD},
    {CATEGORY_ADDR_UNITY, CATEGORY_RD},

    /* Traffic conditions */

    {CATEGORY_COND},
    {CATEGORY_COND_LANE},
    {CATEGORY_COND_TRAFFIC_LIGHT},
    {CATEGORY_COND_CAM},
    {CATEGORY_COND_TOLL},
    {CATEGORY_COND_RAILWAY_CROSSING},
    {CATEGORY_COND_SPEED_BUMP},
    {CATEGORY_COND_BORDER_CHECKPOINT},
    {CATEGORY_COND_CLOSURE},

    {CATEGORY_COND, CATEGORY_RD_JC, CATEGORY_RD_EL},
    {CATEGORY_COND_LANE, CATEGORY_RD_JC, CATEGORY_RD_EL},
    {CATEGORY_COND_TRAFFIC_LIGHT, CATEGORY_RD_JC, CATEGORY_RD_EL},
    {CATEGORY_COND_CAM, CATEGORY_RD_JC, CATEGORY_RD_EL},
    {CATEGORY_COND_TOLL, CATEGORY_RD_JC, CATEGORY_RD_EL},
    {CATEGORY_COND_RAILWAY_CROSSING, CATEGORY_RD_JC, CATEGORY_RD_EL},
    {CATEGORY_COND_SPEED_BUMP, CATEGORY_RD_JC, CATEGORY_RD_EL},
    {CATEGORY_COND_BORDER_CHECKPOINT, CATEGORY_RD_JC, CATEGORY_RD_EL},
    {CATEGORY_COND_CLOSURE, CATEGORY_RD_JC, CATEGORY_RD_EL},

    {CATEGORY_COND_LANE, CATEGORY_RD_EL},

    {CATEGORY_RD_JC, CATEGORY_RD_EL},
    {CATEGORY_RD_JC, CATEGORY_COND},

    /* Transport */

    {CATEGORY_TRANSPORT_OPERATOR, CATEGORY_TRANSPORT_BUS_ROUTE},
    {CATEGORY_TRANSPORT_OPERATOR, CATEGORY_TRANSPORT_TRAM_ROUTE},
    {CATEGORY_TRANSPORT_OPERATOR, CATEGORY_TRANSPORT_WATERWAY_ROUTE},
    {CATEGORY_TRANSPORT_OPERATOR, CATEGORY_TRANSPORT_METRO_LINE},

    {CATEGORY_TRANSPORT_STOP},
    {CATEGORY_TRANSPORT_WATERWAY_STOP},
    {CATEGORY_TRANSPORT_METRO_STATION},

    {CATEGORY_TRANSPORT_STOP, CATEGORY_TRANSPORT_BUS_ROUTE},
    {CATEGORY_TRANSPORT_STOP, CATEGORY_TRANSPORT_TRAM_ROUTE},
    {CATEGORY_TRANSPORT_WATERWAY_STOP, CATEGORY_TRANSPORT_WATERWAY_ROUTE},

    {CATEGORY_TRANSPORT_THREAD_STOP},
    {CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_STOP},
    {CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_WATERWAY_STOP},
    {CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_METRO_STATION},

    {CATEGORY_TRANSPORT_BUS_THREAD},
    {CATEGORY_TRANSPORT_TRAM_THREAD},
    {CATEGORY_TRANSPORT_WATERWAY_THREAD},
    {CATEGORY_TRANSPORT_METRO_THREAD},

    {CATEGORY_TRANSPORT_BUS_THREAD, CATEGORY_TRANSPORT_BUS_ROUTE, CATEGORY_TRANSPORT_BUS_THREAD},
    {CATEGORY_TRANSPORT_TRAM_THREAD, CATEGORY_TRANSPORT_TRAM_ROUTE, CATEGORY_TRANSPORT_TRAM_THREAD},
    {CATEGORY_TRANSPORT_WATERWAY_THREAD, CATEGORY_TRANSPORT_WATERWAY_ROUTE, CATEGORY_TRANSPORT_WATERWAY_THREAD},
    {CATEGORY_TRANSPORT_METRO_THREAD, CATEGORY_TRANSPORT_METRO_LINE, CATEGORY_TRANSPORT_METRO_THREAD},

    {CATEGORY_TRANSPORT_BUS_THREAD, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_STOP},
    {CATEGORY_TRANSPORT_TRAM_THREAD, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_STOP},
    {CATEGORY_TRANSPORT_WATERWAY_THREAD, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_WATERWAY_STOP},
    {CATEGORY_TRANSPORT_METRO_THREAD, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_METRO_STATION},

    {CATEGORY_TRANSPORT_STOP, CATEGORY_TRANSPORT_THREAD_STOP},
    {CATEGORY_TRANSPORT_STOP, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_BUS_THREAD},
    {CATEGORY_TRANSPORT_STOP, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_BUS_THREAD, CATEGORY_TRANSPORT_BUS_ROUTE},
    {CATEGORY_TRANSPORT_STOP, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_TRAM_THREAD},
    {CATEGORY_TRANSPORT_STOP, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_TRAM_THREAD, CATEGORY_TRANSPORT_TRAM_ROUTE},
    {CATEGORY_TRANSPORT_WATERWAY_STOP, CATEGORY_TRANSPORT_THREAD_STOP},
    {CATEGORY_TRANSPORT_WATERWAY_STOP, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_WATERWAY_THREAD},
    {CATEGORY_TRANSPORT_WATERWAY_STOP, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_WATERWAY_THREAD, CATEGORY_TRANSPORT_WATERWAY_ROUTE},
    {CATEGORY_TRANSPORT_METRO_STATION, CATEGORY_TRANSPORT_THREAD_STOP},
    {CATEGORY_TRANSPORT_METRO_STATION, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_METRO_THREAD},
    {CATEGORY_TRANSPORT_METRO_STATION, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_METRO_THREAD, CATEGORY_TRANSPORT_METRO_LINE},

    {CATEGORY_TRANSPORT_BUS_THREAD_CONNECTOR},
    {CATEGORY_TRANSPORT_TRAM_THREAD_CONNECTOR},
    {CATEGORY_TRANSPORT_WATERWAY_THREAD_CONNECTOR},
    {CATEGORY_TRANSPORT_METRO_THREAD_CONNECTOR},

    {CATEGORY_TRANSPORT_BUS_THREAD_CONNECTOR, CATEGORY_TRANSPORT_BUS_THREAD, CATEGORY_TRANSPORT_BUS_ROUTE, CATEGORY_TRANSPORT_BUS_THREAD},
    {CATEGORY_TRANSPORT_TRAM_THREAD_CONNECTOR, CATEGORY_TRANSPORT_TRAM_THREAD, CATEGORY_TRANSPORT_TRAM_ROUTE, CATEGORY_TRANSPORT_TRAM_THREAD},
    {CATEGORY_TRANSPORT_WATERWAY_THREAD_CONNECTOR, CATEGORY_TRANSPORT_WATERWAY_THREAD, CATEGORY_TRANSPORT_WATERWAY_ROUTE, CATEGORY_TRANSPORT_WATERWAY_THREAD},
    {CATEGORY_TRANSPORT_METRO_THREAD_CONNECTOR, CATEGORY_TRANSPORT_METRO_THREAD, CATEGORY_TRANSPORT_METRO_LINE, CATEGORY_TRANSPORT_METRO_THREAD},

    {CATEGORY_TRANSPORT_BUS_THREAD_CONNECTOR, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_STOP},
    {CATEGORY_TRANSPORT_TRAM_THREAD_CONNECTOR, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_STOP},
    {CATEGORY_TRANSPORT_WATERWAY_THREAD_CONNECTOR, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_WATERWAY_STOP},
    {CATEGORY_TRANSPORT_METRO_THREAD_CONNECTOR, CATEGORY_TRANSPORT_THREAD_STOP, CATEGORY_TRANSPORT_METRO_STATION},

    {CATEGORY_TRANSPORT_TRANSITION_BOARDING, CATEGORY_TRANSPORT_TRANSITION, CATEGORY_TRANSPORT_METRO_STATION},
    {CATEGORY_TRANSPORT_PASSAGEWAY_BOARDING, CATEGORY_TRANSPORT_PASSAGEWAY, CATEGORY_TRANSPORT_METRO_STATION},
    {CATEGORY_TRANSPORT_PASSAGEWAY_BOARDING, CATEGORY_TRANSPORT_PASSAGEWAY, CATEGORY_TRANSPORT_METRO_EXIT},

    {CATEGORY_RD_EL, CATEGORY_TRANSPORT_BUS_THREAD},
    {CATEGORY_RD_EL, CATEGORY_TRANSPORT_BUS_THREAD, CATEGORY_TRANSPORT_BUS_ROUTE},
    {CATEGORY_TRANSPORT_TRAM_EL, CATEGORY_TRANSPORT_TRAM_THREAD},
    {CATEGORY_TRANSPORT_TRAM_EL, CATEGORY_TRANSPORT_TRAM_THREAD, CATEGORY_TRANSPORT_TRAM_ROUTE},
    {CATEGORY_TRANSPORT_WATERWAY_EL, CATEGORY_TRANSPORT_WATERWAY_THREAD},
    {CATEGORY_TRANSPORT_WATERWAY_EL, CATEGORY_TRANSPORT_WATERWAY_THREAD, CATEGORY_TRANSPORT_WATERWAY_ROUTE},

    /* Indoor */

    {CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_AREA, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_BARRIER, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_RADIOMAP_CAPTURER_PATH, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},

    {CATEGORY_IMAGE_OVERLAY, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_IMAGE_OVERLAY_PUBLIC, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_AREA, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_BARRIER, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_RADIOMAP_CAPTURER_PATH, CATEGORY_INDOOR_LEVEL},

    {CATEGORY_INDOOR_POI_AUTO, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_EDU, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_FINANCE, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_FOOD, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_GOVERMENT, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_INFO, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_INFRA, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_LEISURE, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_MEDICINE, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_RELIGION, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_SERVICE, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_SHOPPING, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_SPORT, CATEGORY_INDOOR_LEVEL},

    {CATEGORY_INDOOR_POI_AUTO, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_EDU, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_FINANCE, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_FOOD, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_GOVERMENT, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_INFO, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_INFRA, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_LEISURE, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_MEDICINE, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_RELIGION, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_SERVICE, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_SHOPPING, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_INDOOR_POI_SPORT, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},

    {CATEGORY_IMAGE_OVERLAY, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_IMAGE_OVERLAY, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_AREA, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_BARRIER, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_RADIOMAP_CAPTURER_PATH, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},

    {CATEGORY_INDOOR_POI_AUTO, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_POI_EDU, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_POI_FINANCE, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_POI_FOOD, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_POI_GOVERMENT, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_POI_INFO, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_POI_INFRA, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_POI_LEISURE, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_POI_MEDICINE, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_POI_RELIGION, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_POI_SERVICE, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_POI_SHOPPING, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},
    {CATEGORY_INDOOR_POI_SPORT, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY},

    {CATEGORY_IMAGE_OVERLAY_PUBLIC, CATEGORY_INDOOR_LEVEL, CATEGORY_INDOOR_PLAN, CATEGORY_INDOOR_LEVEL},
    {CATEGORY_IMAGE_OVERLAY_PUBLIC, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_AREA, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_BARRIER, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_RADIOMAP_CAPTURER_PATH, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},

    {CATEGORY_INDOOR_POI_AUTO, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_POI_EDU, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_POI_FINANCE, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_POI_FOOD, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_POI_GOVERMENT, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_POI_INFO, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_POI_INFRA, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_POI_LEISURE, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_POI_MEDICINE, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_POI_RELIGION, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_POI_SERVICE, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_POI_SHOPPING, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},
    {CATEGORY_INDOOR_POI_SPORT, CATEGORY_INDOOR_LEVEL, CATEGORY_IMAGE_OVERLAY_PUBLIC},

    /* POI entrances */

    {CATEGORY_POI_AUTO, CATEGORY_ENTRANCE},
    {CATEGORY_POI_EDU, CATEGORY_ENTRANCE},
    {CATEGORY_POI_FINANCE, CATEGORY_ENTRANCE},
    {CATEGORY_POI_FOOD, CATEGORY_ENTRANCE},
    {CATEGORY_POI_GOVERMENT, CATEGORY_ENTRANCE},
    {CATEGORY_POI_LEISURE, CATEGORY_ENTRANCE},
    {CATEGORY_POI_MEDICINE, CATEGORY_ENTRANCE},
    {CATEGORY_POI_RELIGION, CATEGORY_ENTRANCE},
    {CATEGORY_POI_SERVICE, CATEGORY_ENTRANCE},
    {CATEGORY_POI_SHOPPING, CATEGORY_ENTRANCE},
    {CATEGORY_POI_SPORT, CATEGORY_ENTRANCE},
    {CATEGORY_POI_URBAN, CATEGORY_ENTRANCE},

    {CATEGORY_INDOOR_POI_AUTO, CATEGORY_ENTRANCE},
    {CATEGORY_INDOOR_POI_EDU, CATEGORY_ENTRANCE},
    {CATEGORY_INDOOR_POI_FINANCE, CATEGORY_ENTRANCE},
    {CATEGORY_INDOOR_POI_FOOD, CATEGORY_ENTRANCE},
    {CATEGORY_INDOOR_POI_GOVERMENT, CATEGORY_ENTRANCE},
    {CATEGORY_INDOOR_POI_LEISURE, CATEGORY_ENTRANCE},
    {CATEGORY_INDOOR_POI_MEDICINE, CATEGORY_ENTRANCE},
    {CATEGORY_INDOOR_POI_RELIGION, CATEGORY_ENTRANCE},
    {CATEGORY_INDOOR_POI_SERVICE, CATEGORY_ENTRANCE},
    {CATEGORY_INDOOR_POI_SHOPPING, CATEGORY_ENTRANCE},
    {CATEGORY_INDOOR_POI_SPORT, CATEGORY_ENTRANCE},
};

const StringSet EXTENDED_OUTPUT_CATEGORIES {
    CATEGORY_COND,
    CATEGORY_COND_LANE,
    CATEGORY_COND_TRAFFIC_LIGHT,
    CATEGORY_COND_CAM,
    CATEGORY_COND_TOLL,
    CATEGORY_COND_RAILWAY_CROSSING,
    CATEGORY_COND_SPEED_BUMP,
    CATEGORY_COND_BORDER_CHECKPOINT,
    CATEGORY_COND_CLOSURE,

    CATEGORY_INDOOR_PLAN,
    CATEGORY_IMAGE_OVERLAY,
    CATEGORY_IMAGE_OVERLAY_PUBLIC,

    CATEGORY_TRANSPORT_BUS_ROUTE,
    CATEGORY_TRANSPORT_TRAM_ROUTE,
    CATEGORY_TRANSPORT_WATERWAY_ROUTE,
    CATEGORY_TRANSPORT_METRO_LINE,

    CATEGORY_TRANSPORT_STOP,
    CATEGORY_TRANSPORT_WATERWAY_STOP,
    CATEGORY_TRANSPORT_METRO_STATION,

    CATEGORY_TRANSPORT_THREAD_STOP,

    CATEGORY_TRANSPORT_BUS_THREAD,
    CATEGORY_TRANSPORT_TRAM_THREAD,
    CATEGORY_TRANSPORT_WATERWAY_THREAD,
    CATEGORY_TRANSPORT_METRO_THREAD,

    CATEGORY_TRANSPORT_BUS_THREAD_CONNECTOR,
    CATEGORY_TRANSPORT_TRAM_THREAD_CONNECTOR,
    CATEGORY_TRANSPORT_WATERWAY_THREAD_CONNECTOR,
    CATEGORY_TRANSPORT_METRO_THREAD_CONNECTOR,

    CATEGORY_TRANSPORT_TRANSITION,
    CATEGORY_TRANSPORT_PASSAGEWAY,

    CATEGORY_ENTRANCE,
};

const StringSet ASSIGNED_INDOOR_CATEGORIES_ROLES {
    ROLE_ASSIGNED_INDOOR_POI_AUTO,
    ROLE_ASSIGNED_INDOOR_POI_EDU,
    ROLE_ASSIGNED_INDOOR_POI_FINANCE,
    ROLE_ASSIGNED_INDOOR_POI_FOOD,
    ROLE_ASSIGNED_INDOOR_POI_GOVERMENT,
    ROLE_ASSIGNED_INDOOR_POI_INFO,
    ROLE_ASSIGNED_INDOOR_POI_INFRA,
    ROLE_ASSIGNED_INDOOR_POI_LEISURE,
    ROLE_ASSIGNED_INDOOR_POI_MEDICINE,
    ROLE_ASSIGNED_INDOOR_POI_RELIGION,
    ROLE_ASSIGNED_INDOOR_POI_SERVICE,
    ROLE_ASSIGNED_INDOOR_POI_SHOPPING,
    ROLE_ASSIGNED_INDOOR_POI_SPORT,
};

const std::map<std::string, StringSet> IGNORE_SLAVES_LIMIT_CATEGORIES_ROLES {
    {CATEGORY_TRANSPORT_OPERATOR, StringSet {ROLE_ASSIGNED_METRO, ROLE_ASSIGNED_WATERWAY, ROLE_ASSIGNED_TRAM, ROLE_ASSIGNED_BUS}},
    {CATEGORY_TRANSPORT_METRO_OPERATOR, StringSet {ROLE_ASSIGNED}},
    {CATEGORY_TRANSPORT_STOP, StringSet {ROLE_ASSIGNED_THREAD_STOP}},
    {CATEGORY_TRANSPORT_BUS_THREAD, StringSet {ROLE_PART}},
    {CATEGORY_TRANSPORT_TRAM_THREAD, StringSet {ROLE_PART}},
    {CATEGORY_TRANSPORT_WATERWAY_THREAD, StringSet {ROLE_PART}},
    {CATEGORY_TRANSPORT_METRO_THREAD, StringSet {ROLE_PART}},
    {CATEGORY_INDOOR_LEVEL, ASSIGNED_INDOOR_CATEGORIES_ROLES},
};

const std::set<std::string> PUT_MASTERS_WHEN_SLAVE_CATEGORIES {
    CATEGORY_TRANSPORT_BUS_THREAD,
    CATEGORY_TRANSPORT_TRAM_THREAD,
    CATEGORY_TRANSPORT_WATERWAY_THREAD,
    CATEGORY_TRANSPORT_METRO_THREAD,
    CATEGORY_ENTRANCE,
};

} // namespace

void
FormatterContext::markExpanded(TOid oid)
{
    expandedObjects_.insert(oid);
}

bool
FormatterContext::isExpanded(TOid oid) const
{
    return expandedObjects_.count(oid);
}

bool
FormatterContext::needsExpansion(const GeoObject* obj)
{
    auto isStackINExtOutputCat = [&]() {
        for (const auto& cat : EXTENDED_OUTPUT_CATEGORIES) {
            if (visitedObjectsStack_.isEqual({cat})) {
                return true;
            }
        }
        return false;
    };
    return !isExpanded(obj->id()) &&
           (isContourPart_(obj) || isStackINExtOutputCat());
}

bool
FormatterContext::shouldPutMastersWhenSlave(const GeoObject* obj) const
{
    return PUT_MASTERS_WHEN_SLAVE_CATEGORIES.contains(obj->categoryId());
}

namespace {
bool compare(
    const std::list<std::string>& categoriesPath,
    const std::list<const GeoObject*>& objects,
    size_t length)
{
    auto categoryIt = categoriesPath.begin();
    auto objectIt = objects.begin();
    for (size_t i = 0; i < length &&
        categoryIt != categoriesPath.end() &&
        objectIt != objects.end();
        ++i, ++categoryIt, ++objectIt)
    {
        if (*categoryIt != (*objectIt)->categoryId()) {
            return false;
        }
    }
    return true;
}
} // namespace

bool
FormatterContext::VisitedObjectsStack::isEqual(const std::list<std::string>& categoriesPath) const
{
    if (categoriesPath.size() != objects_.size()) {
        return false;
    }
    return compare(categoriesPath, objects_, categoriesPath.size());
}

bool
FormatterContext::VisitedObjectsStack::isPartOfPath(const std::list<std::string>& categoriesPath) const
{
    if (categoriesPath.size() < objects_.size() - 1) {
        return false;
    }
    return compare(categoriesPath, objects_, categoriesPath.size());
}

const std::string&
FormatterContext::VisitedObjectsStack::currentCategoryId() const
{
    ASSERT(!objects_.empty());
    return objects_.back()->categoryId();
}

const GeoObject*
FormatterContext::VisitedObjectsStack::currentObject() const
{
    ASSERT(!objects_.empty());
    return objects_.back();
}

std::list<std::string>
FormatterContext::VisitedObjectsStack::categoryIds() const
{
    std::list<std::string> categoryIdsList;
    for (const auto* object : objects_) {
        categoryIdsList.push_back(object->categoryId());
    }
    return categoryIdsList;
}

std::string
FormatterContext::VisitedObjectsStack::dump() const
{
    std::ostringstream ss;
    ss << "[";
    for( const auto& obj : objects_) {
        ss << "(" << obj->id() << "," << obj->categoryId() << ")";
    }
    return ss.str();
}

bool
FormatterContext::needExtendedRelativesOutput() const
{
    return EXTENDED_OUTPUT_PATHS.end() != std::find_if(
        EXTENDED_OUTPUT_PATHS.begin(), EXTENDED_OUTPUT_PATHS.end(),
        [this](const std::list<std::string>& path) {
                return this->visitedObjectsStack().isEqual(path);
            });
}

bool
FormatterContext::continueToOutputMasters(const std::string& roleId) const
{
    const auto& categoryId = visitedObjectsStack().currentCategoryId();
    return
        isContourCategory(categoryId)
        || isThreadElementRole(categoryId, roleId)
        || (isTransportThread(categoryId) && roleId == ROLE_PART);
}

namespace {

template <typename C>
bool contains(const C& big, const C& small)
{
    if (small.size() > big.size()) {
        return false;
    }
    for (auto is = small.begin(), ib = big.begin();
        is != small.end() && ib != big.end();
        ++is, ++ib) {
        if (*is != *ib) {
            return false;
        }
    }
    return true;
}
} // namespace

bool
FormatterContext::mastersOnExtendedOutputPath() const
{
    const auto stackCategoryIds = this->visitedObjectsStack().categoryIds();
    const auto* curObject = this->visitedObjectsStack().currentObject();
    for (const auto& masterRole : curObject->category().masterRoles()) {
        auto stackCategoryIdsWithMaster = stackCategoryIds;
        stackCategoryIdsWithMaster.push_back(masterRole.categoryId());
        if (std::any_of(EXTENDED_OUTPUT_PATHS.begin(), EXTENDED_OUTPUT_PATHS.end(),
            [&](const auto& path) {
                return contains(path, stackCategoryIdsWithMaster);
            }))
        {
            return true;
        }
    }
    return false;
}

bool
FormatterContext::slavesOnExtendedOutputPath() const
{
    const auto stackCategoryIds = this->visitedObjectsStack().categoryIds();
    const auto* curObject = this->visitedObjectsStack().currentObject();
    for (const auto& slaveRole : curObject->category().slavesRoles()) {
        auto stackCategoryIdsWithSlave = stackCategoryIds;
        stackCategoryIdsWithSlave.push_back(slaveRole.categoryId());
        if (std::any_of(EXTENDED_OUTPUT_PATHS.begin(), EXTENDED_OUTPUT_PATHS.end(),
            [&](const auto& path) {
                return contains(path, stackCategoryIdsWithSlave);
            }))
        {
            return true;
        }
    }
    return false;
}

boost::optional<const StringSet&>
FormatterContext::putAllSlavesRoles(const GeoObject* obj) const
{
    auto it = IGNORE_SLAVES_LIMIT_CATEGORIES_ROLES.find(obj->categoryId());
    return
        IGNORE_SLAVES_LIMIT_CATEGORIES_ROLES.end() == it
        ? boost::optional<const StringSet&>()
        : it->second;
}

TRevisionId
FormatterContext::actualObjectRevisionId(const TRevisionId& revisionId) const
{
    auto it = actualObjectRevisionIds_.find(revisionId.objectId());
    return it == actualObjectRevisionIds_.end() ? revisionId : it->second;
}

void
FormatterContext::setActualObjectRevisionId(const TRevisionId& revisionId)
{
    if (revisionId.valid()) {
        actualObjectRevisionIds_[revisionId.objectId()] = revisionId;
    }
}

RelativesLimit
FormatterContext::slavesPerRoleLimit(const std::string& slaveRoleId, const std::string& categoryId)
{
    const auto& categories = cfg()->editor()->categories();
    ASSERT(categories.defined(categoryId));
    const auto& category = categories[categoryId];
    ASSERT(category.isSlaveRoleDefined(slaveRoleId));
    const auto& role = category.slaveRole(slaveRoleId);
    return RelativesLimit {
        role.slavesPerRoleOutputLimit()
            ? *role.slavesPerRoleOutputLimit()
            : cfg()->editor()->system().slavesPerRoleLimit(),
        RelativesLimit::PerRole
    };
}

RelativesLimit
FormatterContext::slavesPerRoleLimitMax(const std::string& categoryId)
{
    const auto& categories = cfg()->editor()->categories();
    ASSERT(categories.defined(categoryId));
    const auto& category = categories[categoryId];
    size_t curMax = cfg()->editor()->system().slavesPerRoleLimit();
    for (const auto& slaveRole : category.slavesRoles()) {
        if (slaveRole.slavesPerRoleOutputLimit()) {
            curMax = std::max(curMax, *slaveRole.slavesPerRoleOutputLimit());
        }
    }
    return RelativesLimit {
        curMax,
        RelativesLimit::PerRole
    };
}

RelativesLimit
FormatterContext::slavesPerRoleLimit(const std::string& slaveRoleId) const
{
    const auto& categoryId = visitedObjectsStack().currentCategoryId();
    return slavesPerRoleLimit(slaveRoleId, categoryId);
}

RelativesLimit
FormatterContext::slavesPerRoleLimitMax() const
{
    const auto& categoryId = visitedObjectsStack().currentCategoryId();
    return slavesPerRoleLimitMax(categoryId);
}

} // namespace maps::wiki
