#pragma once

#include "diff_context_impl.h"

#include <yandex/maps/wiki/diffalert/common.h>
#include <yandex/maps/wiki/diffalert/revision/commit_filter.h>

#include <yandex/maps/wiki/revision/branch.h>
#include <yandex/maps/wiki/revision/commit.h>
#include <yandex/maps/wiki/revision/filters.h>
#include <yandex/maps/wiki/revision/revisionid.h>
#include <yandex/maps/wiki/revision/snapshot_id.h>

#include <maps/libs/pgpool/include/pgpool3.h>

#include <unordered_map>
#include <unordered_set>

namespace maps {
namespace wiki {
namespace diffalert {

using CommitId = uint64_t;
using ObjectId = uint64_t;

struct CommitInfo
{
    CommitId commitId;
    TUId createdBy;
    UserType userType;
    ActionType actionType;

    explicit CommitInfo(const revision::Commit& commit);

    bool isCommitExcluded(const CommitFilter& commitFilter) const;
    bool isCommitIncluded(const CommitFilter& commitFilter) const;
};

using CommitIdToInfo = std::unordered_map<CommitId, CommitInfo>;
using ObjectIdToCommitIds = std::unordered_map<ObjectId, std::unordered_set<CommitId>>;

/**
 * Loads all the commits that are different between the branches.
 * For each object it stores the commits that change this object.
 * For each master object it store the commits that change its geometry parts too.
 */
class ExclusionFinder
{
public:
    ExclusionFinder(
        pgpool3::Pool& tdsConnPool,
        const CommitFilter& commitFilter,
        const revision::Branch& oldBranch,
        const revision::SnapshotId& oldSnapshotId,
        const revision::Branch& newBranch,
        const revision::SnapshotId& newSnapshotId);

    void loadCommits(
        const revision::filters::ProxyFilterExpr& oldSnapshotFilter,
        const revision::filters::ProxyFilterExpr& newSnapshotFilter);

    void propagateCommitsToMasters(
        const std::map<ObjectId, LongtaskDiffContext::Impl>& diffContextImpls);

    /**
     * Check if the object satisfies the filtering criteria.
     * Exclude criteria: exclude an object if all the commits match the exclude condition otherwise include
     * Include criteria: include an object if all the commits match the include condition otherwise exclude
     */
    bool isObjectExcluded(ObjectId objectId) const;

private:
    /**
     *  @return master ids
     */
    std::unordered_set<ObjectId> propagateCommitsToMasters(
        const std::unordered_set<ObjectId>& geomPartIds);

    pgpool3::Pool& tdsConnPool_;
    CommitFilter commitFilter_;

    revision::Branch oldBranch_;
    revision::SnapshotId oldSnapshotId_;
    revision::Branch newBranch_;
    revision::SnapshotId newSnapshotId_;

    CommitIdToInfo commitIdToInfo_;
    ObjectIdToCommitIds objectIdsToCommitIds_;
};

} // namespace diffalert
} // namespace wiki
} // namespace maps
