#include "json_formatter.h"
#include "formatter_context.h"
#include "json_writer.h"
#include <maps/wikimap/mapspro/services/editor/src/edit_options.h>
#include <maps/wikimap/mapspro/services/editor/src/actions/filters/serialization.h>
#include <maps/wikimap/mapspro/services/editor/src/commit.h>
#include "common.h"
#include "json_common.h"
#include "tile.h"

#include <yandex/maps/wiki/common/date_time.h>
#include <yandex/maps/wiki/common/natural_sort.h>
#include <maps/libs/json/include/builder.h>

#include <boost/lexical_cast.hpp>

namespace maps::wiki {
namespace {
std::string
simpleUpdateTaskResponseJson(
    const std::string& taskName,
    const Token& token,
    const std::optional<CommitModel>& commitModel)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_TASK_NAME] = taskName;
        objectBuilder[STR_TOKEN] = token;
        if (commitModel) {
            objectBuilder[STR_COMMIT] << [&](json::ObjectBuilder builder) {
                putCommitModel(builder, *commitModel);
            };
        }
    };
    return builder.str();
}
} // namespace

std::string
JSONFormatter::formatException(const maps::Exception& ex, bool withBacktrace)
{
    std::ostringstream bodyStream;
    exceptionToStream(bodyStream, ex, withBacktrace);
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_ERROR] << [&](json::ObjectBuilder errorBuilder) {
            for (const auto& attr : ex.attrs()) {
                errorBuilder[attr.first] = attr.second;
            }
            auto logicExceptionPtr = dynamic_cast<const maps::wiki::LogicException*>(&ex);
            if (logicExceptionPtr && logicExceptionPtr->location()) {
                errorBuilder[STR_LOCATION] = json::Verbatim(
                    prepareGeomJson(pointToGeom(*logicExceptionPtr->location()), GEODETIC_GEOM_PRECISION));
            }
            errorBuilder[STR_MESSAGE] = bodyStream.str();
        };
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<SaveObject>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_TOKEN] = result.token;
        context().setUserId(result.userId);
        putCollection(context(), objectBuilder, result.collection, *result.cache, AppendApproveStatus::Yes);
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<CloneObject>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuildStarter) {
        objectBuildStarter[STR_TOKEN] = result.token;
        objectBuildStarter[STR_GEO_OBJECT] << [&](json::ObjectBuilder objectBuilder) {
            fillObject(context(), objectBuilder, result.clone.get(),
                ExpandMasters::Yes, *result.cache);
        };
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<CreateIntersections>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_TOKEN] = result.token;
        objectBuilder["splitObjectIds"] = [&](json::ArrayBuilder splitIdsBuilder) {
            for (const auto& split : result.splitObjects) {
                splitIdsBuilder << [&](json::ObjectBuilder splitObjectBuilder) {
                    splitObjectBuilder[STR_ID] = common::idToJson(split.first);
                    splitObjectBuilder["parts"] << [&](json::ArrayBuilder partsBuilder) {
                        for (const auto& part : split.second) {
                            partsBuilder << [&](json::ObjectBuilder partBuilder) {
                                partBuilder[STR_ID] = common::idToJson(part->id());
                                partBuilder[STR_REVISION_ID] = boost::lexical_cast<std::string>(part->revision());
                                partBuilder[STR_CATEGORY_ID] = part->categoryId();
                                partBuilder[STR_GEOMETRY] =
                                    json::Verbatim(prepareGeomJson(part->geom(), GEODETIC_GEOM_PRECISION));
                            };
                        }
                    };
                };
            }
        };
        objectBuilder[STR_GEO_OBJECTS] = [&](json::ArrayBuilder adjustedObjectsBuilder) {
            for (const auto& topoObjData : result.adjustedObjects) {
                adjustedObjectsBuilder << [&](json::ObjectBuilder topoObjectBuilder) {
                    topoObjectBuilder[STR_ID] = common::idToJson(topoObjData.id().objectId());
                    topoObjectBuilder[STR_UUID] = topoObjData.id().uuid();
                    if (topoObjData.revision().valid()) {
                        topoObjectBuilder[STR_REVISION_ID] =
                            boost::lexical_cast<std::string>(topoObjData.revision());
                    }
                    topoObjectBuilder[STR_CATEGORY_ID] = topoObjData.categoryId();
                    topoObjectBuilder[STR_GEOMETRY] =
                        json::Verbatim(prepareGeomJson(topoObjData.geometry(), GEODETIC_GEOM_PRECISION));
                };
            }
        };
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<AsyncBaseController>& result)
{
    return result.toJson();
}

std::string
JSONFormatter::operator ()(const ResultType<GetAsyncResult>& result)
{
    return (*this)(*result.asyncTaskResult);
}

std::string
JSONFormatter::operator ()(const AsyncTaskResult& asyncResult)
{
    return asyncResult.toJson();
}

std::string
tokenTaskResponseJson(const std::string& taskName, const Token& token)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_TASK_NAME] = taskName;
        objectBuilder[STR_TOKEN] = token;
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateAttributes>& result)
{
    return simpleUpdateTaskResponseJson(result.taskName, result.token, result.commit);
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateState>& result)
{
    return simpleUpdateTaskResponseJson(result.taskName, result.token, result.commit);
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateStateMeta>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_CATEGORY_IDS] = [&](json::ArrayBuilder objectsArrayBuilder) {
            for (const auto& categoryId: result.categoryIds) {
                objectsArrayBuilder << categoryId;
            }
        };
        objectBuilder[STR_MAX_OBJECTS] = result.maxObjects;
    };
    return builder.str();
}


std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateMove>& result)
{
    return simpleUpdateTaskResponseJson(result.taskName, result.token, result.commit);
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateSyncGeometry>& result)
{
    return simpleUpdateTaskResponseJson(result.taskName, result.token, result.commit);
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateRelation>& result)
{
    return simpleUpdateTaskResponseJson(result.taskName, result.token, result.commit);
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateSnap>& result)
{
    return simpleUpdateTaskResponseJson(result.taskName, result.token, result.commit);
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateUnion>& result)
{
    return simpleUpdateTaskResponseJson(result.taskName, result.token, result.commit);
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateMerge>& result)
{
    return simpleUpdateTaskResponseJson(result.taskName, result.token, result.commit);
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsCleanupRelations>& result)
{
    return simpleUpdateTaskResponseJson(result.taskName, result.token, result.commit);
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateRelations>& result)
{
    return simpleUpdateTaskResponseJson(result.taskName, result.token, result.commit);
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateRelationsMeta>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_CATEGORY_IDS] = [&](json::ArrayBuilder objectsArrayBuilder) {
            for (const auto& categoryId: result.categoryIds) {
                objectsArrayBuilder << categoryId;
            }
        };
        objectBuilder[STR_MAX_OBJECTS] = result.maxObjects;
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateSnapMeta>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_CATEGORY_IDS] = [&](json::ArrayBuilder objectsArrayBuilder) {
            for (const auto& categoryId: result.categoryIds) {
                objectsArrayBuilder << categoryId;
            }
        };
        objectBuilder[STR_MAX_OBJECTS] = result.maxObjects;
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateUnionMeta>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_CATEGORY_IDS] = [&](json::ArrayBuilder objectsArrayBuilder) {
            for (const auto& categoryId: result.categoryIds) {
                objectsArrayBuilder << categoryId;
            }
        };
        objectBuilder[STR_MAX_OBJECTS] = result.maxObjects;
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateMoveMeta>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_CATEGORY_IDS] = [&](json::ArrayBuilder objectsArrayBuilder) {
            for (const auto& categoryId: result.categoryIds) {
                objectsArrayBuilder << categoryId;
            }
        };
        objectBuilder[STR_MAX_OBJECTS] = result.maxObjects;
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateSyncGeometryMeta>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_CATEGORY_IDS] = [&](json::ArrayBuilder objectsArrayBuilder) {
            for (const auto& categoryId: result.categoryIds) {
                objectsArrayBuilder << categoryId;
            }
        };
        objectBuilder[STR_MAX_OBJECTS] = result.maxObjects;
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsUpdateAttributesMeta>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_ATTR_IDS_BY_CATEGORY_ID] = [&](json::ObjectBuilder attrsByCategoryBuilder) {
            for (const auto& pair: result.attrIdsByCategoryId) {
                const auto& categoryId = pair.first;
                const auto& attrIds = pair.second;
                attrsByCategoryBuilder[categoryId] << [&](json::ArrayBuilder attrArrayBuilder) {
                    for (const auto& attrId : attrIds) {
                        attrArrayBuilder << attrId;
                    }
                };
            }
        };
        objectBuilder[STR_MAX_OBJECTS] = result.maxObjects;
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<GetObject>& result)
{
    REQUIRE(result.collection.size() == 1, "Only one object expected as result.");
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        context().setActualObjectRevisionId(result.actualObjectRevisionId);
        context().setUserId(result.userId);
        fillObject(context(), objectBuilder, result.collection.begin()->get(),
            ExpandMasters::Yes, *result.cache, result.noLoadSlavesRoleId, AppendApproveStatus::Yes);
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<GetGeoLocks>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_BRANCH] << [&](json::ObjectBuilder builder) {
            builder[STR_ID] = common::idToJson(result.branchId);
            builder[STR_STATE] = boost::lexical_cast<std::string>(result.branchState);
        };
        objectBuilder[STR_GEOLOCKS] << [&](json::ArrayBuilder arrayBuilder) {
            for (const auto& geolock : result.locks) {
                arrayBuilder << [&](json::ObjectBuilder ob) {
                    ob[STR_ID] = common::idToJson(geolock.id());
                    ob[STR_UID] = common::idToJson(geolock.createdBy());
                    ob[STR_DATE] = common::canonicalDateTimeString(geolock.createdAt(), common::WithTimeZone::Yes);
                    ob[STR_BRANCH_ID] = common::idToJson(geolock.branchId());
                };
            }
        };
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<CreateToken>& result)
{
    return tokenTaskResponseJson(result.taskName, result.token);
}

std::string
JSONFormatter::operator ()(const ResultType<GetObjectsByTile>& result)
{
    JsonBuilder builder;
    auto orderedObjects = sortTileObjects(result.objects);

    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder["type"] = "FeatureCollection";
        objectBuilder["features"] = [&](json::ArrayBuilder tileObjects) {
            for (const auto& object : orderedObjects) {
                for (auto sameZOrderObj : object.second) {
                    putTileObject(tileObjects, sameZOrderObj);
                }
            }
        };
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<GetSlaveInfos>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        if (result.hasMore) {
            objectBuilder[STR_HAS_MORE] = *result.hasMore;
        } else {
            if (result.bbox.empty()) {
                objectBuilder["limit"] = (int64_t)result.limit;
                objectBuilder["offset"] = (int64_t)result.offset;
            }
        }
        objectBuilder[STR_ROLE] = result.roleId;
        objectBuilder[STR_SLAVES] = [&](json::ArrayBuilder slaveObjects) {
            for (const auto& slaveInfo : result.slaveInfos) {
                putObjectInfo(slaveObjects, &slaveInfo);
            }
        };
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<GetSuggest>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder resultBuilder) {
        resultBuilder[STR_GEO_OBJECTS] << [&](json::ArrayBuilder arrayBuilder) {
            for (const auto& suggestedObject : result.suggestedObjects) {
                arrayBuilder << [&](json::ObjectBuilder ob) {
                        ob[STR_ID] = common::idToJson(suggestedObject.id);
                        ob[STR_REVISION_ID] =
                            std::to_string(suggestedObject.id) + ":" +
                            std::to_string(suggestedObject.commitId);
                        ob[STR_CATEGORY_ID] = suggestedObject.categoryId;
                        ob[STR_TITLE] = suggestedObject.name;
                        if (!suggestedObject.path.empty()) {
                            ob["path"] = suggestedObject.path;
                        }
                };
            }
        };
    };
    return builder.str();
}

namespace {

void
putRendererLayer(json::ArrayBuilder& layersBuilder,
    const RendererLayersTraverse::Layer& layer,
    const RendererLayersTraverse::ProjectParams& requestParams)
{
    layersBuilder << [&](json::ObjectBuilder layerBuilder) {
        layerBuilder["id"] = (int64_t)layer.id();
        layerBuilder["type"] = layer.type();
        if (layer.label()) {
            layerBuilder["name"] = layer.name();
            layerBuilder["label"] = (*layer.label());
        }
        if (!layer.categoryIds().empty()) {
            layerBuilder["categories"] << [&](json::ArrayBuilder categoriesBuilder) {
                for (const auto& catId : layer.categoryIds()) {
                    categoriesBuilder << catId;
                }
            };
        }
        if (layer.textLayer() && layer.textLayer()->projectParams.match(requestParams)) {
            layerBuilder["textId"] = static_cast<int64_t>(layer.textLayer()->id);
        }
        std::list<const RendererLayersTraverse::Representation*> matchingRepresentations;
        for (const auto& repr : layer.representations()) {
            if (repr.projectParams.match(requestParams)) {
                matchingRepresentations.push_back(&repr);
            }
        }
        if (!matchingRepresentations.empty()) {
            layerBuilder["representations"] << [&](json::ArrayBuilder representationsBuilder) {
                for (const auto& repr : matchingRepresentations) {
                    representationsBuilder << [&](json::ObjectBuilder representationBuilder) {
                        representationBuilder["id"] = (int64_t)repr->id;
                        representationBuilder["name"] = repr->name;
                        representationBuilder["label"] = repr->label;
                    };
                }
            };
        }
    };
}

} // namespace

std::string
JSONFormatter::operator ()(const ResultType<GetLayers>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder resultBuilder) {
        const auto& layersTraverse = result.layersTraverse;
        resultBuilder["layersGroups"] << [&](json::ArrayBuilder layersGroups) {
            for (const auto& group : layersTraverse.layersByGroup()) {
                std::vector<const RendererLayersTraverse::Layer*> matchingLayers;
                for (const auto& layer : group.second) {
                    if (layer->isFilterLayer() &&
                        result.showFilterTypeLayers == ShowFilterTypeLayers::No)
                    {
                        continue;
                    }
                    if (layer->projectParams().match(result.requestProjectParams)) {
                        matchingLayers.push_back(layer);
                    }
                }
                if (matchingLayers.empty()) {
                    continue;
                }
                layersGroups << [&](json::ObjectBuilder layersGroup) {
                    const auto& id = group.first;
                    layersGroup["id"] = id;
                    layersGroup["categories"] << [&](json::ArrayBuilder categoriesBuilder) {
                        for (const auto& catId : result.layersTraverse.layersGroup(id).complexCategories) {
                            categoriesBuilder << catId;
                        }
                    };
                    layersGroup["label"] = layersGroupLabel(id);
                    layersGroup["layers"] << [&](json::ArrayBuilder layers) {
                        for (const auto& layer : matchingLayers) {
                            if (layer->isFilterLayer() &&
                                result.showFilterTypeLayers == ShowFilterTypeLayers::No)
                            {
                                continue;
                            }
                            if (layer->projectParams().match(result.requestProjectParams)) {
                                putRendererLayer(layers, *layer, result.requestProjectParams);
                            }
                        }
                    };
                };
            }
        };
    };
    return builder.str();
}

namespace {

void
putRendererLayer(json::ArrayBuilder& layersBuilder,
                 const GetLayers2::Layer& layer)
{
    layersBuilder << [&](json::ObjectBuilder layerBuilder) {
        layerBuilder["id"] = layer.id;
        layerBuilder["type"] = layer.type;
        layerBuilder["label"] = layer.label;
        layerBuilder["name"] = layer.name;
        if (!layer.categories.empty()) {
            layerBuilder["categories"] << [&](json::ArrayBuilder categoriesBuilder) {
                for (const auto& catId : layer.categories) {
                    categoriesBuilder << catId;
                }
            };
        }
        if (!layer.representations.empty()) {
            layerBuilder["representations"] << [&](json::ArrayBuilder representationsBuilder) {
                for (const auto& repr : layer.representations) {
                    representationsBuilder << [&](json::ObjectBuilder representationBuilder) {
                        representationBuilder["id"] = repr.id;
                        representationBuilder["name"] = repr.name;
                        representationBuilder["label"] = repr.label;
                    };
                }
            };
        }
    };
}

} // namespace

std::string
JSONFormatter::operator ()(const ResultType<GetLayers2>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder resultBuilder) {
        const auto& groups = result.groups;
        resultBuilder["layersGroups"] << [&](json::ArrayBuilder layersGroups) {
            for (const auto& group : groups) {
                layersGroups << [&](json::ObjectBuilder layersGroup) {
                    const auto& id = group.id;
                    layersGroup["id"] = id;
                    layersGroup["categories"] << [&](json::ArrayBuilder categoriesBuilder) {
                        for (const auto& catId : group.categories) {
                            categoriesBuilder << catId;
                        }
                    };
                    layersGroup["label"] = group.label;
                    layersGroup["layers"] << [&](json::ArrayBuilder layers) {
                        for (const auto& layer : group.layers) {
                            putRendererLayer(layers, layer);
                        }
                    };
                };
            }
        };
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<GetFilters>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder filtersJson) {
        if (result.hasMore) {
            filtersJson[STR_HAS_MORE] = *result.hasMore;
        } else {
            filtersJson[STR_TOTAL_COUNT] = result.totalCount;
            filtersJson[STR_PAGE] = result.page;
            filtersJson[STR_PER_PAGE] = result.perPage;
        }
        filtersJson[STR_FILTERS] << [&](json::ArrayBuilder filtersBuilder) {
            for (const auto& filter : result.filters) {
                filtersBuilder << [&](json::ObjectBuilder filterBuilder) {
                    serializeFilter(filter, filterBuilder);
                };
            }
        };
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<SaveFilter>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_TOKEN] = result.token;
        objectBuilder[STR_FILTER] <<
            [&](json::ObjectBuilder filterBuilder) {
                serializeFilter(*result.filter, filterBuilder);
            };
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<DeleteFilter>& result)
{
    return tokenTaskResponseJson(result.taskName, result.token);
}

std::string
JSONFormatter::operator ()(const ResultType<SaveExpression>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_TOKEN] = result.token;
        objectBuilder[STR_EXPRESSION] <<
            [&](json::ObjectBuilder exprBuilder) {
                serializeExpression(*result.expression, exprBuilder);
            };
    };
    return builder.str();
}

namespace
{
void
putTopologyParts(
    json::ObjectBuilder& resultJsonBuilder,
    const std::string& partsType,
    const std::string& relationType,
    const GeoObjectCollection& collection,
    const ResultType<GetTopology>& result)
{
    resultJsonBuilder[partsType] = [&](json::ArrayBuilder arrayBuilder) {
        for (const auto& collectionObject : collection) {
            arrayBuilder << [&](json::ObjectBuilder geoObjectBuilder) {
                putObjectInfo(geoObjectBuilder, collectionObject.get());
                geoObjectBuilder[relationType] << [&](json::ObjectBuilder relatives) {
                    auto links = result.objectsLinks.find(collectionObject->id());
                    if (links == result.objectsLinks.end())
                        return;
                    auto roles = readKeys(links->second);
                    for (const auto& role : roles) {
                        relatives[role] << [&](json::ObjectBuilder roleObjectsBuilder) {
                            auto range = result.objectsLinks.find(collectionObject->id())->second.equal_range(role);
                            roleObjectsBuilder[STR_GEO_OBJECTS] << [&](json::ArrayBuilder objs) {
                                for (auto it = range.first; it != range.second; ++it) {
                                    objs << [&](json::ObjectBuilder o) {
                                        putObjectIdentity(o, it->second);
                                    };
                                }
                            };
                        };
                    }
                };
            };
        }
    };
}
}

std::string
JSONFormatter::operator ()(const ResultType<GetTopology>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        putTopologyParts(objectBuilder, "linearElements",
            STR_SLAVES, result.linearElements, result);
        putTopologyParts(objectBuilder, "junctions",
            STR_MASTERS, result.junctions, result);
        putTopologyParts(objectBuilder, "adjacentLinearElements",
            STR_SLAVES, result.adjacentLinearElements, result);
        objectBuilder["isComplete"] = result.isComplete;
    };
    return builder.str();
}

namespace {
template <typename Result>
void putPagedCommits(json::ObjectBuilder& objectBuilder, const Result& result)
{
    objectBuilder[STR_TOTAL_COUNT] = result.totalCount;
    objectBuilder[STR_PAGE] = result.page;
    objectBuilder[STR_PER_PAGE] = result.perPage;
    objectBuilder[STR_COMMITS] << [&](json::ArrayBuilder commitsBuilder) {
        for (const auto& commitModel : result.commitModels) {
            commitsBuilder << [&](json::ObjectBuilder commitBuilder) {
                putCommitModel(commitBuilder, commitModel);
            };
        }
    };
}
} // namespace

std::string
JSONFormatter::operator ()(const ResultType<GetHistory>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_OBJECT_ID] = common::idToJson(result.oid);
        putPagedCommits(objectBuilder, result);
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<GetDependentCommits>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        putPagedCommits(objectBuilder, result);
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<GetCommit>& result)
{
    ASSERT(result.commitModelPtr);
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder resultBuilder) {
        putCommitModel(resultBuilder, *result.commitModelPtr);
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<CommitsApprove>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_TOKEN] = result.token;
        objectBuilder[STR_COMMITS] << [&](json::ArrayBuilder commitsBuilder) {
            for (const auto& commitModel : result.commitModels) {
                commitsBuilder << [&](json::ObjectBuilder commitBuilder) {
                    putCommitModel(commitBuilder, commitModel);
                };
            }
        };
        putTaskIds(objectBuilder, result.taskIds);
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<CommitsRevert>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_TOKEN] = result.token;
        objectBuilder[STR_COMMIT] << [&](json::ObjectBuilder commitBuilder) {
            putCommitModel(commitBuilder, *result.commitModel);
        };
        putTaskIds(objectBuilder, result.taskIds);
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsQueryIds>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder rootObjectBuilder) {
        rootObjectBuilder[STR_GEO_OBJECTS] = [&](json::ArrayBuilder objectsArrayBuilder) {
            for (const auto& object : result.collection) {
                objectsArrayBuilder << [&](json::ObjectBuilder objectBuilder) {
                    putObjectInfo(objectBuilder, object.get(), result.serializeDetailsFlags);
                    if (result.serializeDetailsFlags.isSet(ObjectsQueryIds::SerializeDetails::Permissions)) {
                        putObjectPermissions(context(), objectBuilder, object.get());
                    }
                    objectBuilder[STR_STATE] <<
                        boost::lexical_cast<std::string>(object->state());
                    if (result.serializeDetailsFlags.isSet(ObjectsQueryIds::SerializeDetails::Masters)) {
                        putMasters(context(), objectBuilder, object.get());
                    }
                };
            }
        };
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsQueryCategory>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder rootObjectBuilder) {
        rootObjectBuilder[STR_GEO_OBJECTS] = [&](json::ArrayBuilder objectsArrayBuilder) {
            for (const auto& object : result.collection) {
                objectsArrayBuilder << [&](json::ObjectBuilder objectBuilder) {
                    putObjectInfo(
                        objectBuilder,
                        object.get(),
                        {ObjectsQueryIds::SerializeDetails::Attrs});
                    objectBuilder[STR_STATE] <<
                        boost::lexical_cast<std::string>(object->state());
                };
            }
        };
    };
    return builder.str();
}

namespace {

void
writeViewObjects(json::ArrayBuilder arrayBuilder, const views::ViewObjects& objects)
{
    for (const auto& object : objects) {
        arrayBuilder << [&](json::ObjectBuilder objectBuilder) {
            putObjectIdentity(objectBuilder, &object);
            putPlainAttributes(objectBuilder, object.categoryId(), object.domainAttrs());
            if (!object.geom().isNull()) {
                objectBuilder[STR_GEOMETRY] = json::Verbatim(
                    prepareGeomJson(object.geom(), GEODETIC_GEOM_PRECISION)
                );
            }
        };
    }
}

std::string
writeViewObjects(const views::ViewObjects& objects)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_GEO_OBJECTS] = [&](json::ArrayBuilder objectsArrayBuilder) {
            writeViewObjects(objectsArrayBuilder, objects);
        };
    };
    return builder.str();
}

} //namespace

std::string
JSONFormatter::operator ()(const ResultType<ObjectsQueryLasso>& result)
{
    return writeViewObjects(result.viewObjects);
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsQueryLassoMeta>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_CATEGORY_IDS] = [&](json::ArrayBuilder objectsArrayBuilder) {
            for (const auto& categoryId : result.categoryIds) {
                objectsArrayBuilder << categoryId;
            }
        };
        objectBuilder[STR_MAX_OBJECTS] = result.maxObjects;
    };
    return builder.str();
}


std::string
JSONFormatter::operator ()(const ResultType<ObjectsQueryTitle>& result)
{
    return writeViewObjects(result.viewObjects);
}


void
writeConflicts(
    json::ObjectBuilder& objectBuilder,
    const PoiConflictsData& viewObjectsConflicts,
    bool criticalOnly = true)
{
    objectBuilder[STR_CONFLICTS] << [&](json::ArrayBuilder conflictsBuilder) {
        for (const auto& zoomConflicts : viewObjectsConflicts) {
            const auto& zoom = zoomConflicts.first;
            const auto& severityConflicts = zoomConflicts.second;
            if (criticalOnly && !severityConflicts.count(poi_conflicts::ConflictSeverity::Critical)) {
                continue;
            }
            conflictsBuilder << [&](json::ObjectBuilder zoomBuilder) {
                zoomBuilder[STR_ZOOM] = zoom;
                for (const auto& severityToViewObjects : severityConflicts) {
                    const auto severity = severityToViewObjects.first;
                    if (criticalOnly && severity != poi_conflicts::ConflictSeverity::Critical) {
                        continue;
                    }
                    const auto& viewObjects = severityToViewObjects.second;
                    zoomBuilder[poi_conflicts::toString(severity)] << [&](json::ObjectBuilder severityBuilder) {
                        severityBuilder[STR_GEO_OBJECTS] =
                            [&](json::ArrayBuilder objectsArrayBuilder) {
                                writeViewObjects(objectsArrayBuilder, viewObjects);
                            };
                    };
                }
            };
        }
    };
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsQueryPoiConflicts>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        writeConflicts(objectBuilder, result.viewObjectsConflicts);
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsQueryPoisConflicts>& result)
{
    JsonBuilder builder;
    builder << [&](json::ArrayBuilder arrayBuilder) {
        for (const auto& idToViewObjectsConflicts : result.perPoiConflicts) {
            arrayBuilder << [&](json::ObjectBuilder objectBuilder) {
                objectBuilder[STR_ID] = common::idToJson(idToViewObjectsConflicts.first);
                writeConflicts(objectBuilder, idToViewObjectsConflicts.second);
            };
        }
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsQueryPoisConflictsMeta>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_CATEGORY_IDS] = [&](json::ArrayBuilder objectsArrayBuilder) {
            for (const auto& categoryId: result.categoryIds) {
                objectsArrayBuilder << categoryId;
            }
        };
        objectBuilder[STR_MAX_OBJECTS] = result.maxObjects;
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ToolsResolvePoisConflicts>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder builder) {
        for (const auto& [id, geom] : result.movedPois) {
            builder[common::idToJson(id)] =
                json::Verbatim(prepareGeomJson(geom, GEODETIC_GEOM_PRECISION));
        }
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ToolsResolvePoisConflictsMeta>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_CATEGORY_IDS] = [&](json::ArrayBuilder objectsArrayBuilder) {
            for (const auto& categoryId: result.categoryIds) {
                objectsArrayBuilder << categoryId;
            }
        };
        objectBuilder[STR_MAX_OBJECTS] = result.maxObjects;
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsQueryPoiBusinessId>& result)
{
    return writeViewObjects(result.viewObjects);
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsQueryFilter>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_STATISTICS] = [&](json::ObjectBuilder statBuilder) {
            for (const auto& [categoryId, count] : result.countsByCategory) {
                statBuilder[categoryId] = count;
            }
        };
        objectBuilder[STR_GEO_OBJECTS] = [&](json::ArrayBuilder objectsArrayBuilder) {
            writeViewObjects(objectsArrayBuilder, result.viewObjects);
        };
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsQueryRouteDiff>& result)
{
    JsonBuilder builder;

    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_ADD] = [&](json::ArrayBuilder arrayBuilder) {
            writeViewObjects(arrayBuilder, result.addElements);
        };
        objectBuilder[STR_REMOVE] = [&](json::ArrayBuilder arrayBuilder) {
            writeViewObjects(arrayBuilder, result.removeElements);
        };
        if (result.elementBBox) {
            objectBuilder[STR_BOUNDS] = json::Verbatim(json(*result.elementBBox));
        }
    };

    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<GetRouteTime>& result)
{
    JsonBuilder builder;

    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_TIME] = [&](json::ArrayBuilder arrayBuilder) {
            for (const auto t: result.time) {
                arrayBuilder << t;
            }
        };
    };

    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsQueryPath>& result)
{
    JsonBuilder builder;

    builder << [&](json::ObjectBuilder objectBuilder) {
        objectBuilder[STR_ELEMENTS] = [&](json::ArrayBuilder arrayBuilder) {
            writeViewObjects(arrayBuilder, result.elements);
        };
        objectBuilder[STR_BOUNDS] = json::Verbatim(json(result.elementBBox));
    };

    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<ObjectsQueryJunction>& result)
{
    ASSERT(result.junctionId);
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder resultBuilder) {
        resultBuilder[STR_TOKEN] = result.token;
        resultBuilder[STR_GEO_OBJECT] << [&](json::ObjectBuilder objectBuilder) {
                auto obj = result.cache->getExisting(result.junctionId);
                fillObject(context(), objectBuilder, obj.get(), ExpandMasters::Yes, *result.cache);
            };
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<BusinessPhotos>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder resultBuilder) {
        resultBuilder[STR_PHOTO_IDS] = [&](json::ArrayBuilder arrayBuilder) {
            for (const auto& photoId : result.photoIds) {
                arrayBuilder << photoId;
            }
        };
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<DistributeFlats>& result)
{
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder resultBuilder) {
        resultBuilder[STR_DISTRIBUTION] = [&](json::ObjectBuilder distribBuilder) {
            distribBuilder[STR_LEVELS] = [&](json::ArrayBuilder arrayBuilder) {
                for (const auto& levelWithFlats : result.levelsWithFlats) {
                    arrayBuilder << [&](json::ObjectBuilder levelBuilder) {
                        levelBuilder[STR_NAME] = levelWithFlats.levelName;

                        std::vector<std::string> flats;
                        for (const auto& flatRange : levelWithFlats.flatRanges) {
                            for (size_t flatNum = 0; flatNum < flatRange.size(); ++flatNum) {
                                flats.push_back(flatRange.value(flatNum));
                            }
                        }
                        std::sort(flats.begin(), flats.end(), common::natural_sort());

                        levelBuilder[STR_FLATS] << [&](json::ArrayBuilder flatsBuilder) {
                            for (const auto& flat : flats) {
                                flatsBuilder << flat;
                            }
                        };
                    };
                }
            };
            if (!result.error.empty()) {
                distribBuilder[STR_ERROR] = result.error;
            }
            if (!result.warning.empty()) {
                distribBuilder[STR_WARNING] = result.warning;
            }
        };
    };
    return builder.str();
}

std::string
JSONFormatter::operator ()(const ResultType<PointToBld>& result)
{
    ASSERT(!result.point.isNull());
    JsonBuilder builder;
    builder << [&](json::ObjectBuilder resultBuilder) {
        resultBuilder[STR_POINT] = json::Verbatim(prepareGeomJson(result.point, GEODETIC_GEOM_PRECISION));
        resultBuilder[STR_DISTANCE] = result.distance;
        resultBuilder[STR_AZIMUTH] = result.azimuth;
        resultBuilder[STR_IS_INSIDE_BLD] = result.isInsideBld;
        resultBuilder[STR_BLD_ID] = common::idToJson(result.bldId);
    };
    return builder.str();
}

} // namespace maps::wiki
