#pragma once

#include <search/grouping/groupinfo.h>

#include <library/cpp/cgiparam/cgiparam.h>
#include <library/cpp/containers/flat_hash/flat_hash.h>
#include <library/cpp/protobuf/json/proto2json.h>
#include <library/cpp/string_utils/base64/base64.h>

#include <util/charset/utf8.h>

struct TFAExtraSearchParams {
    TString GroupingAttr;
    TString* CategName = nullptr;
};

class TStringTransform: public NProtobufJson::IStringTransform {
public:
    virtual int GetType() const {
        return IStringTransform::EscapeTransform;
    }

    virtual void Transform(TString& str) const {
        if (UTF8Detect(str) == NotUTF8) {
            str = Base64Encode(str);
        }
    }
};

struct TAttrFilter {
    TAttrFilter(const TCgiParameters& cgi, TStringBuf filterParam)
        : Range(cgi.equal_range(filterParam))
    {
        for (auto it = Range.first; it != Range.second; ++it) {
            Values.insert(it->second);
        }
    }

    bool HasAny(std::initializer_list<TStringBuf> attrs) const {
        if (Values.empty()) {
            return false;
        }

        return std::any_of(attrs.begin(), attrs.end(), [&](const TStringBuf& attr) {
            return Values.contains(attr);
        });
    }

    bool Has(TStringBuf attr) const {
        return !Values.empty() && Values.contains(attr);
    }

    bool HasPrefix(TStringBuf attr) const {
        for (auto it = Range.first; it != Range.second; ++it) {
            if (attr.StartsWith(it->second)) {
                return true;
            }
        }
        return false;
    }

    bool Empty() const {
        return Range.first == Range.second;
    }

private:
    using TIter = TCgiParameters::const_iterator;
    std::pair<TIter, TIter> Range;
    NFH::TDenseHashSetStaticMarker<TStringBuf, nullptr> Values;
};

struct TGtaFilter {
    TGtaFilter(const TCgiParameters& cgi)
        : gta(cgi, "gta")
        , gtaPrefix(cgi, "gtap")
        , gtaBan(cgi, "gta_ban")
        , gtaPrefixBan(cgi, "gta_pban")
    {
        const bool hasAllDocInfos = gta.Has("_AllDocInfos");

        NeedTimestampValue = hasAllDocInfos || gta.Has("_Timestamp");
        NeedVersionValue = hasAllDocInfos || gta.Has("_Version");
        NeedDeadlineValue = hasAllDocInfos || gta.Has("_Deadline");
        NeedKeyPrefixValue = hasAllDocInfos || gta.Has("_KeyPrefix");

        NeedAllPropsValue = gta.Empty() && gtaPrefix.Empty() || hasAllDocInfos;
        NeedBodyValue = NeedAllPropsValue || gta.Has("_Body");
    }

    bool NeedTimestamp() const {
        return NeedTimestampValue;
    }

    bool NeedVersion() const {
        return NeedVersionValue;
    }

    bool NeedDeadline() const {
        return NeedDeadlineValue;
    }

    bool NeedKeyPrefix() const {
        return NeedKeyPrefixValue;
    }

    bool NeedBody() const {
        return NeedBodyValue;
    }

    bool NeedProperty(const TStringBuf attr) const {
        return (NeedAllPropsValue || gta.Has(attr) || gtaPrefix.HasPrefix(attr))
               && !gtaBan.Has(attr) && !gtaPrefixBan.HasPrefix(attr);
    }

    bool Has(const TStringBuf attr) const {
        return gta.Has(attr);
    }

private:
    TAttrFilter gta;
    TAttrFilter gtaPrefix;
    TAttrFilter gtaBan;
    TAttrFilter gtaPrefixBan;

    bool NeedTimestampValue = false;
    bool NeedVersionValue = false;
    bool NeedDeadlineValue = false;
    bool NeedKeyPrefixValue = false;
    bool NeedBodyValue = false;
    bool NeedAllPropsValue = false;
};

TString GetGropingAttr(const TCgiParameters& cgi);

inline bool NeedGroupingProcessing(const TFAExtraSearchParams* extraParams) {
    return extraParams && extraParams->CategName && extraParams->GroupingAttr;
}

inline void UpdateCategAttr(const TFAExtraSearchParams* extraParams, const TString& attrName, const TString& attrValue) {
    if (NeedGroupingProcessing(extraParams) && (extraParams->GroupingAttr == attrName)) {
        *extraParams->CategName = attrValue;
    }
}
