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, 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;
}