#pragma once

#include "tile_object.h"
#include "user_acl_data.h"

#include "maps/wikimap/mapspro/libs/controller/include/basecontroller.h"

#include <yandex/maps/wiki/filters/common.h>
#include <maps/wikimap/mapspro/services/editor/src/utils.h>

#include <maps/libs/tile/include/coord.h>
#include <deque>
#include <memory>

namespace maps::wiki {

class GetObjectsByTile;
template<>
struct controller::ResultType<GetObjectsByTile>
{
    std::deque<std::unique_ptr<TileObject>> objects;
};

class GetObjectsByTile : public controller::BaseController<GetObjectsByTile>
{
public:
    enum class ZMaxPolicy {
        Ignore,
        Compare
    };
    enum class ZMinPolicy {
        Ignore,
        Compare
    };

    struct Request
    {
        unsigned long tileX;
        unsigned long tileY;
        TZoom zoom;
        TUid uid;
        Token dbToken;
        TBranchId branchId;
        std::optional<std::string> categoriesRangeList;
        std::optional<double> hotspotWidth;
        TOid objectId;
        PolygonBufferPolicy polygonBufferPolicy;
        bool deadEnds;
        ZMaxPolicy zMaxPolicy;
        ZMinPolicy zMinPolicy;
        TileObject::SimpificationPolicy simpificationPolicy;
        TOid indoorLevelId;
        std::optional<filters::TExpressionId> expressionId;
    };

    explicit GetObjectsByTile(const Request& request);

    virtual ~GetObjectsByTile() {}

    virtual std::string printRequest() const;

protected:

    virtual void control();
    void prepareTileQuery(
        const std::string& queryName,
        const std::string& query,
        Transaction& work) const;

    struct TileQueryParams {
        geolib3::Point2 point1;
        geolib3::Point2 point2;
        TZoom zminLimit;
        TZoom zmaxLimit;

        std::string bboxStr() const;

        std::string zoomFilter(
            const std::string& zminName,
            const std::string& zmaxName) const;
    };

    TileQueryParams getTileQueryParams() const;

    void loadArealObjects(Transaction& work, TOid indoorLevelId = 0);
    void loadPointObjects(Transaction& work, TOid indoorLevelId = 0);
    void loadLineObjects(Transaction& work, TOid indoorLevelId = 0);

    void loadLinearElements(Transaction& work,
        const std::optional<StringSet>& categoriesSet);

    void loadContourLinearElements(Transaction& work);

    void collectTileObjects(const pqxx::result& result, const StringSet& customFields = StringSet());

protected:

    Request request_;

    tile::RealCoord ltTile_;
    tile::RealCoord rbTile_;
    TGeoPoint tileGeodetic_;
    Geom extraBox_;
    std::optional<StringSet> categoriesSet_;
    std::string filterViewSql_;
    TileQueryParams tileQueryParams_;
    std::optional<UserAclData> userAclData_;
};

} // namespace maps::wiki
