#pragma once

#include "common.h"
#include "title.h"
#include "json_common.h"
#include <maps/wikimap/mapspro/services/editor/src/relation_infos.h>
#include <maps/wikimap/mapspro/services/editor/src/configs/categories.h>
#include <maps/wikimap/mapspro/services/editor/src/configs/config.h>
#include "formatter.h"

#include <maps/libs/json/include/builder.h>

namespace maps::wiki {

namespace revision {
class Commit;
} // namespace revision

namespace views {
class ViewObject;
} // namespace views

class GeoObjectCollection;
class GeoObject;
class Attribute;
class Attributes;
class TileObject;
class HotSpot;
class ObjectEditContext;
class ObjectsCache;
class FormatterContext;

enum class ExpandMasters
{
    Yes,
    No
};

enum class AppendApproveStatus
{
    Yes,
    No
};

template<typename ObjectInfo>
class ObjectInfoTraits{
public:
    static const bool isGeoObject=false;
};

template<>
class ObjectInfoTraits<const maps::wiki::GeoObject>{
public:
    static const bool isGeoObject=true;
};

template<>
class ObjectInfoTraits <maps::wiki::GeoObject>{
public:
    static const bool isGeoObject=true;
};

std::string escapeAttrValue(ValueType valueType, const std::string& value);
void putTableAttributeContents(
        json::ArrayBuilder& arrayBuilder, const TableValues& contents);

void putAttributes(
        json::ObjectBuilder& ob,
        const Attributes& attributes,
        const TableAttributesValues* tableAttributes);

template<typename ObjectInfo, bool IsGeoObject = ObjectInfoTraits<ObjectInfo>::isGeoObject >
struct AttributesPutter {
    void operator()(json::ObjectBuilder& ob, const ObjectInfo* obj){
        putAttributes(ob, obj->attributes(), &obj->tableAttributes());
    }
};

template<typename ObjectInfo>
struct AttributesPutter<ObjectInfo, false>{
    void operator()(json::ObjectBuilder& /*unused*/, const ObjectInfo* /*unused*/){
    }
};

template<typename ObjectInfo>
void putAttributes(
        json::ObjectBuilder& ob,
        const ObjectInfo* obj)
{
    AttributesPutter<ObjectInfo>()(ob, obj);
}

void putPlainAttributes(json::ObjectBuilder& ob, const std::string& categoryId, const StringMap& attributes);

void putCollection(FormatterContext&, json::ObjectBuilder& objectBuilder,
        const GeoObjectCollection& collection, ObjectsCache& cache,
        AppendApproveStatus appendApproveStatus = AppendApproveStatus::No);
void putObject(FormatterContext&, json::ArrayBuilder& arrayBuilder, const GeoObject* object,
        ExpandMasters expandMasters, ObjectsCache& cache,
        AppendApproveStatus appendApproveStatus = AppendApproveStatus::No);
void fillObject(FormatterContext&, json::ObjectBuilder& geoObjectBuilder, const GeoObject* object,
        ExpandMasters expandMasters, ObjectsCache& cache,
        const std::string& noLoadSlavesRoleId = std::string{},
        AppendApproveStatus appendApproveStatus = AppendApproveStatus::No);
void putTileObject(json::ArrayBuilder& arrayBuilder, const TileObject* tileObject);

std::string
prepareGeomJson(
    const common::Geom& geom, size_t precision,
    SpatialRefSystem spatialRefSys = SpatialRefSystem::Geodetic);

template <typename ObjectInfo>
void
putObjectIdentity(json::ObjectBuilder& objectBuilder, const ObjectInfo* obj)
{
    objectBuilder[STR_ID] = common::idToJson(obj->id());
    objectBuilder[STR_REVISION_ID] = boost::lexical_cast<std::string>(obj->revision());
    objectBuilder[STR_CATEGORY_ID] = obj->categoryId();
    objectBuilder[STR_TITLE] = title(obj);
}

template <typename ObjectInfo>
void
putObjectIdentity(json::ArrayBuilder& arrayBuilder, const ObjectInfo* obj)
{
    arrayBuilder << [&](json::ObjectBuilder objectBuilder) {
        putObjectIdentity(objectBuilder, obj);
    };
}

template <typename ObjectInfo>
void
putObjectInfo(
    json::ObjectBuilder& objectBuilder,
    const ObjectInfo* obj,
    ObjectsQueryIds::SerializeDetailsFlags serializeFlags = {ObjectsQueryIds::SerializeDetails::Brief})
{
    putObjectIdentity(objectBuilder, obj);
    if (!obj->serviceAttrValue("srv:invalid").empty()) {
        objectBuilder[STR_VALID] = json::Verbatim(STR_FALSE);
    }
    if (serializeFlags.isSet(ObjectsQueryIds::SerializeDetails::Attrs)) {
        putAttributes(objectBuilder, obj);
    }
    if (!obj->geom().isNull()) {
        objectBuilder[STR_GEOMETRY] =
            json::Verbatim(prepareGeomJson(obj->geom(), GEODETIC_GEOM_PRECISION));
    }
}

template <typename ObjectInfo>
void
putObjectInfo(
    json::ArrayBuilder& arrayBuilder,
    const ObjectInfo* objectInfo,
    ObjectsQueryIds::SerializeDetailsFlags serializeFlags = {ObjectsQueryIds::SerializeDetails::Brief})
{
    arrayBuilder << [&](json::ObjectBuilder objectBuilder) {
        putObjectInfo(objectBuilder, objectInfo, serializeFlags);
    };
}

void putObjectPermissions(FormatterContext& formatterContext, json::ObjectBuilder& geoObjectBuilder, const GeoObject* object);
void putMasters(FormatterContext& formatterContext, json::ObjectBuilder& ob, const GeoObject* object);

} // namespace maps::wiki
