#pragma once

#include <maps/wikimap/mapspro/services/mrc/libs/db/include/feature.h>
#include <maps/libs/http/include/url.h>
#include <maps/libs/geolib/include/polyline.h>
#include <yandex/maps/proto/mrcugc/ride.pb.h>


#include <maps/infra/yacare/include/yacare.h>

namespace maps::mrc::ugc_back {

struct Subpolyline;

struct PolylinePosition {
    size_t segmentIndex;
    double segmentPosition;

    /**
     * Translates position on a subpolyline to a position on the global polyline
     * @param subpolyline - bounds on the global polyline
     */
    void translateTo(const Subpolyline& subpolyline);

    geolib3::Point2 pointOf(const geolib3::Polyline2& polyline) const
    {
        return polyline.segmentAt(segmentIndex)
            .pointByPosition(segmentPosition);
    }
};

struct Subpolyline {
    PolylinePosition begin;
    PolylinePosition end;
};

struct PhotoStream {
    struct Item {
        PolylinePosition position;
        std::reference_wrapper<const db::Feature> photo;
    };

    std::vector<Item> items;
};

auto encode(const PolylinePosition&)
    -> yandex::maps::proto::common2::geometry::PolylinePosition;

auto decode(const yandex::maps::proto::common2::geometry::PolylinePosition&)
    -> PolylinePosition;

auto encode(const Subpolyline&)
    -> yandex::maps::proto::common2::geometry::Subpolyline;

auto decode(const yandex::maps::proto::common2::geometry::Subpolyline&)
    -> Subpolyline;

auto encode(const PhotoStream&)
    -> yandex::maps::proto::mrcphoto::ugc::ride::PhotoStream;

auto makeTrack(const db::Features&)
    -> std::pair<geolib3::Polyline2, PhotoStream>;

auto makeSubpolyline(const geolib3::Polyline2&, double startPos, double endPos)
    -> Subpolyline;

auto makePartition(const geolib3::Polyline2&, const Subpolyline&)
    -> geolib3::Polyline2;

void transformPhotoStream(const geolib3::Polyline2& src,
                          const geolib3::Polyline2& dst,
                          PhotoStream&);

inline double ratio(double num, double denom)
{
    return denom > 0. ? std::clamp(num / denom, 0., 1.) : 0.;
}

template <class... Ts>
double ratio(std::chrono::duration<Ts...> num,
             std::chrono::duration<Ts...> denom)
{
    return ratio((double)num.count(), (double)denom.count());
}

auto getTimestamp = [](const db::Feature& photo) { return photo.timestamp(); };

auto hasPos = [](const db::Feature& photo) { return photo.hasPos(); };

auto getGeodeticPos = [](const db::Feature& photo) {
    return photo.geodeticPos();
};

auto getOrientation = [](const db::Feature& photo) {
    return photo.orientation();
};

inline http::URL makeBaseUrl(const yacare::Request& request)
{
    return "https://" + request.env("HTTP_HOST");
}

template <class Proto>
std::optional<std::string> getClientRideId(const Proto& val)
{
    if (val.has_client_ride_id()) {
        return std::string(val.client_ride_id());
    }
    return std::nullopt;
}

template <class Table>
sql_chemistry::FiltersCollection makeFilter(
    const std::string& userId,
    const std::string& sourceId,
    const std::optional<std::string>& clientRideId)
{
    auto result =
        sql_chemistry::FiltersCollection{sql_chemistry::op::Logical::And};
    result.add(Table::userId == userId);
    result.add(Table::sourceId == sourceId);
    if (clientRideId) {
        result.add(Table::clientRideId == *clientRideId);
    }
    else {
        result.add(Table::clientRideId.isNull());
    }
    return result;
}

}  // namespace maps::mrc::ugc_back
