#pragma once
#include "tools.h"

#include <maps/libs/tile/include/tile.h>
#include <maps/libs/introspection/include/comparison.h>
#include <maps/libs/introspection/include/hashing.h>
#include <maps/renderer/libs/design/include/designs.h>
#include <maps/renderer/libs/style2_renderer/include/render_params.h>
#include <maps/renderer/libs/vecdata3/include/tile_renderer.h>
#include <yandex/maps/renderer/vecdata/tile_renderer.h>

#include <optional>
#include <tuple>

namespace maps::mrc::browser {

struct TileRequestCacheKey {
    TileRequestCacheKey(std::string layer_, int32_t x_, int32_t y_, int32_t z_, FeatureFilter filter_)
        : layer(std::move(layer_))
        , x(x_)
        , y(y_)
        , z(z_)
        , filter(std::move(filter_))
    {}

    TileRequestCacheKey& setScale(float scale_) {
        scale = scale_;
        return *this;
    }

    TileRequestCacheKey& setWithAuthors(bool withAuthors_) {
        withAuthors = withAuthors_;
        return *this;
    }

    TileRequestCacheKey& setContentType(std::string contentType_) {
        contentType = std::move(contentType_);
        return *this;
    }

    TileRequestCacheKey& setMultiZoom(std::optional<uint32_t> zmin_,
                                      std::optional<uint32_t> zmax_,
                                      uint32_t z) {
        constexpr uint32_t MAX_Z_RANGE_LEN = 6;
        constexpr uint32_t MAX_Z_DIST = 1;

        // allow multizoom only detailed zooms to avoid enormous slow tiles
        constexpr uint32_t START_MULTIZOOM_AT = 15;

        if (zmin_ || zmax_) {
            if (zmin_ && zmax_) {
                bool isOrdered = (*zmax_ >= *zmin_) && (z <= *zmin_);
                bool isValidRangeLen = (*zmax_ - *zmin_) <= MAX_Z_RANGE_LEN;
                bool isValidRangeZDist = (*zmin_ - z) <= MAX_Z_DIST;
                bool isMultizoomEnabled = (*zmax_ != *zmin_);
                bool isMultizoomAcceptable = (z >= START_MULTIZOOM_AT);
                bool isValidRange = isOrdered && isValidRangeLen && isValidRangeZDist;

                if (!isValidRange) {
                    throw yacare::errors::BadRequest() << "invalid zmin or zmax parameters";
                }
                if (isMultizoomEnabled && !isMultizoomAcceptable) {
                    throw yacare::errors::BadRequest() << "multizoom tiles not enabled on wider zooms";
                }
            } else {
                  throw yacare::errors::BadRequest() << "both zmin and zmax parameters are required";
            }
        }

        zmin = zmin_;
        zmax = zmax_;
        return *this;
    }

    TileRequestCacheKey& setVersion(std::string version_) {
        version = std::move(version_);
        return *this;
    }

    TileRequestCacheKey& setTheme(std::string theme_) {
        theme = std::move(theme_);
        return *this;
    }

    TileRequestCacheKey& setDesignVersion(std::string version) {
        designVersion = std::move(version);
        return *this;
    }

    TileRequestCacheKey& setVecProtocolVersion(unsigned version) {
        vecProtocol = version;
        return *this;
    }

    std::string layer;
    int32_t x;
    int32_t y;
    int32_t z;
    std::optional<uint32_t> zmin{};
    std::optional<uint32_t> zmax{};
    FeatureFilter filter;
    float scale{1.};
    bool withAuthors{false};
    std::string contentType{};
    std::string version{};
    std::string theme{};
    std::string designVersion{};
    unsigned vecProtocol{2u};
    std::optional<renderer::vecdata::Equidistants> equidistantFormat{};

    auto introspect() const {
        return std::tie(layer, x, y, z, zmin, zmax, filter,
                        scale, withAuthors, contentType, version,
                        theme, designVersion, vecProtocol, equidistantFormat);
    }
};

using maps::introspection::operator==;

int maxDrawableFcByZoom(int zoom, const std::string& layer);

std::optional<std::vector<char>>
renderRasterTile(const TileRequestCacheKey& tileRequest,
                 IDataAccessPtr dataAccess,
                 IGraphPtr graph,
                 IGraphPtr pedestrianGraph,
                 std::shared_ptr<renderer::image::ImageStorage> imageStorage,
                 const renderer::design::Design& design);

template<typename ResultFormat>
std::optional<std::vector<char>>
renderVectorTile(const TileRequestCacheKey& tileRequest,
                 IDataAccessPtr dataAccess,
                 IGraphPtr graph,
                 IGraphPtr pedestrianGraph,
                 ResultFormat format,
                 std::shared_ptr<renderer::image::ImageStorage> imageStorage,
                 const renderer::design::Design& design);

std::string
renderHotspotTile(const TileRequestCacheKey& tileRequest,
                  IDataAccessPtr dataAccess,
                  IGraphPtr graph,
                  IGraphPtr pedestrianGraph,
                  const std::string& baseUrl,
                  const std::string& callback,
                  std::shared_ptr<renderer::image::ImageStorage> imageStorage,
                  const renderer::design::Design& design);


} // namespace maps::mrc::browser
