#include <yandex/maps/wiki/social/feedback/attributes.h>
#include <yandex/maps/wiki/social/feedback/attribute_names.h>
#include <maps/libs/enum_io/include/enum_io.h>
#include <maps/libs/common/include/exception.h>
#include <map>

namespace maps::wiki::social::feedback {

namespace {

const enum_io::Representations<AttrType>
PREDEFINED_ATTRS_STRINGS {
    {AttrType::SourceContext, attrs::SOURCE_CONTEXT},
    {AttrType::ObjectDiff, attrs::OBJECT_DIFF},
    {AttrType::UserData, attrs::USER_DATA},
    {AttrType::UserDataPhotoUrls, attrs::USER_DATA_PHOTO_URLS},
    {AttrType::PoiEntrances, attrs::POI_ENTRANCES},
};

bool collideWithPredefinedAttrs(const std::string& attrName)
{
    return static_cast<bool>(enum_io::tryFromString<AttrType>(attrName));
}

const std::string TRUE_VALUE = "true";
const std::string FALSE_VALUE = "false";

} // anonymous namespace

DEFINE_ENUM_IO(AttrType, PREDEFINED_ATTRS_STRINGS);

Attrs::Attrs(const json::Value& json)
{
    for (const auto& attrName : json.fields()) {
        const auto& attrVal = json[attrName];
        auto predefinedType = enum_io::tryFromString<AttrType>(attrName);

        if (predefinedType) {
            REQUIRE(attrVal.isObject() || attrVal.isArray(),
                attrName + " is neither an object nor an array");
            add(*predefinedType, attrVal);
        } else {
            REQUIRE(attrVal.isString(), attrName + " is not a string");
            addCustom(attrName, attrVal.as<std::string>());
        }
    }
}

json::Value Attrs::toJson() const
{
    json::repr::ObjectRepr fields;

    for (const auto& [type, content] : predefinedAttrs_) {
        fields.emplace(std::string(toString(type)), content);
    }

    for (const auto& [name, val] : customAttrs_) {
        fields.emplace(name, json::Value(val));
    }

    return json::Value(std::move(fields));
}

void Attrs::add(AttrType type, json::Value value)
{
    ASSERT(value.isObject() || value.isArray());
    predefinedAttrs_.emplace(type, std::move(value));
}

bool Attrs::exist(AttrType type) const
{
    return predefinedAttrs_.count(type);
}

const json::Value& Attrs::get(AttrType type) const
{
    ASSERT(exist(type));
    return predefinedAttrs_.at(type);
}

void Attrs::addCustom(const std::string& name, const std::string& val)
{
    ASSERT(!collideWithPredefinedAttrs(name));
    customAttrs_[name] = val;
}

void Attrs::setFlag(const std::string& name, bool flag)
{
    ASSERT(!collideWithPredefinedAttrs(name));
    customAttrs_[name] = flag ? TRUE_VALUE : FALSE_VALUE;
}

bool Attrs::existCustom(const std::string& name) const
{
    return customAttrs_.count(name);
}

const std::string& Attrs::getCustom(const std::string& name) const
{
    ASSERT(existCustom(name));
    return customAttrs_.at(name);
}

bool Attrs::getFlag(const std::string& name) const
{
    if (!existCustom(name)) {
        return false;
    }
    return customAttrs_.at(name) == TRUE_VALUE;
}

} // namespace maps::wiki::social::feedback
