StatusWith<MigrateInfoVector> BalancerChunkSelectionPolicyImpl::_getMigrateCandidatesForCollection(
    OperationContext* txn,
    const NamespaceString& nss,
    const ShardStatisticsVector& shardStats,
    bool aggressiveBalanceHint) {
    auto scopedCMStatus = ScopedChunkManager::getExisting(txn, nss);
    if (!scopedCMStatus.isOK()) {
        return scopedCMStatus.getStatus();
    }

    auto scopedCM = std::move(scopedCMStatus.getValue());
    ChunkManager* const cm = scopedCM.cm();

    auto collInfoStatus = createCollectionDistributionInfo(txn, shardStats, cm);
    if (!collInfoStatus.isOK()) {
        return collInfoStatus.getStatus();
    }

    auto collInfo = std::move(collInfoStatus.getValue());

    DistributionStatus distribution = std::move(std::get<0>(collInfo));
    ChunkMinimumsSet allChunkMinimums = std::move(std::get<1>(collInfo));

    for (const auto& tagRangeEntry : distribution.tagRanges()) {
        const auto& tagRange = tagRangeEntry.second;

        if (!allChunkMinimums.count(tagRange.min)) {
            // This tag falls somewhere at the middle of a chunk. Therefore we must skip balancing
            // this collection until it is split at the next iteration.
            //
            // TODO: We should be able to just skip chunks, which straddle tags and still make some
            // progress balancing.
            return {ErrorCodes::IllegalOperation,
                    str::stream()
                        << "Tag boundaries "
                        << tagRange.toString()
                        << " fall in the middle of an existing chunk. Balancing for collection "
                        << nss.ns()
                        << " will be postponed until the chunk is split appropriately."};
        }
    }

    return BalancerPolicy::balance(shardStats, distribution, aggressiveBalanceHint);
}
StatusWith<SplitInfoVector> BalancerChunkSelectionPolicyImpl::_getSplitCandidatesForCollection(
    OperationContext* txn, const NamespaceString& nss) {
    auto scopedCMStatus = ScopedChunkManager::getExisting(txn, nss);
    if (!scopedCMStatus.isOK()) {
        return scopedCMStatus.getStatus();
    }

    auto scopedCM = std::move(scopedCMStatus.getValue());
    ChunkManager* const cm = scopedCM.cm();

    vector<TagsType> collectionTags;
    Status tagsStatus =
        Grid::get(txn)->catalogManager(txn)->getTagsForCollection(txn, nss.ns(), &collectionTags);
    if (!tagsStatus.isOK()) {
        return {tagsStatus.code(),
                str::stream() << "Unable to load tags for collection " << nss.ns() << " due to "
                              << tagsStatus.toString()};
    }

    auto collInfo = createCollectionDistributionInfo({}, cm);
    ChunkMinimumsSet allChunkMinimums = std::move(std::get<1>(collInfo));

    SplitInfoVector splitCandidates;

    for (const auto& tagInfo : collectionTags) {
        BSONObj min =
            cm->getShardKeyPattern().getKeyPattern().extendRangeBound(tagInfo.getMinKey(), false);

        if (allChunkMinimums.count(min)) {
            continue;
        }

        shared_ptr<Chunk> chunk = cm->findIntersectingChunk(txn, min);
        invariant(chunk);

        splitCandidates.emplace_back(
            chunk->getShardId(), nss, cm->getVersion(), chunk->getMin(), chunk->getMax(), min);
    }

    return splitCandidates;
}
StatusWith<SplitInfoVector> BalancerChunkSelectionPolicyImpl::_getSplitCandidatesForCollection(
    OperationContext* txn, const NamespaceString& nss, const ShardStatisticsVector& shardStats) {
    auto scopedCMStatus = ScopedChunkManager::getExisting(txn, nss);
    if (!scopedCMStatus.isOK()) {
        return scopedCMStatus.getStatus();
    }

    auto scopedCM = std::move(scopedCMStatus.getValue());
    ChunkManager* const cm = scopedCM.cm();

    auto collInfoStatus = createCollectionDistributionInfo(txn, shardStats, cm);
    if (!collInfoStatus.isOK()) {
        return collInfoStatus.getStatus();
    }

    auto collInfo = std::move(collInfoStatus.getValue());

    DistributionStatus distribution = std::move(std::get<0>(collInfo));
    ChunkMinimumsSet allChunkMinimums = std::move(std::get<1>(collInfo));

    SplitInfoVector splitCandidates;

    // Accumulate split points for the same chunk together
    shared_ptr<Chunk> currentChunk;
    vector<BSONObj> currentSplitVector;

    for (const auto& tagRangeEntry : distribution.tagRanges()) {
        const auto& tagRange = tagRangeEntry.second;

        if (allChunkMinimums.count(tagRange.min)) {
            continue;
        }

        shared_ptr<Chunk> chunk = cm->findIntersectingChunkWithSimpleCollation(txn, tagRange.min);

        if (!currentChunk) {
            currentChunk = chunk;
        }

        invariant(currentChunk);

        if (chunk == currentChunk) {
            currentSplitVector.push_back(tagRange.min);
        } else {
            splitCandidates.emplace_back(currentChunk->getShardId(),
                                         nss,
                                         cm->getVersion(),
                                         currentChunk->getLastmod(),
                                         currentChunk->getMin(),
                                         currentChunk->getMax(),
                                         std::move(currentSplitVector));

            currentChunk = chunk;
            currentSplitVector.push_back(tagRange.min);
        }
    }

    // Drain the current split vector if there are any entries left
    if (currentChunk) {
        invariant(!currentSplitVector.empty());

        splitCandidates.emplace_back(currentChunk->getShardId(),
                                     nss,
                                     cm->getVersion(),
                                     currentChunk->getLastmod(),
                                     currentChunk->getMin(),
                                     currentChunk->getMax(),
                                     std::move(currentSplitVector));
    }

    return splitCandidates;
}
StatusWith<MigrateInfoVector> BalancerChunkSelectionPolicyImpl::_getMigrateCandidatesForCollection(
    OperationContext* txn,
    const NamespaceString& nss,
    const ShardStatisticsVector& shardStats,
    bool aggressiveBalanceHint) {
    auto scopedCMStatus = ScopedChunkManager::getExisting(txn, nss);
    if (!scopedCMStatus.isOK()) {
        return scopedCMStatus.getStatus();
    }

    auto scopedCM = std::move(scopedCMStatus.getValue());
    ChunkManager* const cm = scopedCM.cm();

    auto collInfo = createCollectionDistributionInfo(shardStats, cm);
    ShardToChunksMap shardToChunksMap = std::move(std::get<0>(collInfo));
    ChunkMinimumsSet allChunkMinimums = std::move(std::get<1>(collInfo));

    DistributionStatus distStatus(shardStats, shardToChunksMap);
    {
        vector<TagsType> collectionTags;
        Status status = Grid::get(txn)->catalogManager(txn)->getTagsForCollection(
            txn, nss.ns(), &collectionTags);
        if (!status.isOK()) {
            return status;
        }

        for (const auto& tagInfo : collectionTags) {
            BSONObj min = cm->getShardKeyPattern().getKeyPattern().extendRangeBound(
                tagInfo.getMinKey(), false);

            if (!allChunkMinimums.count(min)) {
                // This tag falls somewhere at the middle of a chunk. Therefore we must skip
                // balancing this collection until it is split at the next iteration.
                //
                // TODO: We should be able to just skip chunks, which straddle tags and still make
                // some progress balancing.
                return {ErrorCodes::IllegalOperation,
                        str::stream()
                            << "Tag boundaries " << tagInfo.toString()
                            << " fall in the middle of an existing chunk. Balancing for collection "
                            << nss.ns()
                            << " will be postponed until the chunk is split appropriately."};
            }

            // TODO: TagRange contains all the information from TagsType except for the namespace,
            // so maybe the two can be merged at some point in order to avoid the transformation
            // below.
            if (!distStatus.addTagRange(TagRange(tagInfo.getMinKey().getOwned(),
                                                 tagInfo.getMaxKey().getOwned(),
                                                 tagInfo.getTag()))) {
                return {ErrorCodes::BadValue,
                        str::stream() << "Tag ranges are not valid for collection " << nss.ns()
                                      << ". Balancing for this collection will be skipped until "
                                         "the ranges are fixed."};
            }
        }
    }

    unique_ptr<MigrateInfo> migrateInfo(
        BalancerPolicy::balance(nss.ns(), distStatus, aggressiveBalanceHint));
    if (migrateInfo) {
        return MigrateInfoVector{*migrateInfo};
    }

    return MigrateInfoVector{};
}