As part of #97030, some interesting behaviour surfaced about the terminate_after functionality: there are situations in which the actual collection early terminates (throws `CollectionTerminatedException`), both in the case where only top docs are collected as well as when aggs and top docs are collected. In this scenario, if `terminate_after` was provided in the search request, we keep on collecting documents, with the only purpose of counting and setting `early_terminated` to true once the threshold is reached.  

This may be perceived as a change in behaviour, because there are cases where we will not return `early_terminated` set to `true` while we previously would. In reality, we should clarify expectations and perhaps update the docs to indicate that `terminate_after` will terminate the actual collection of documents, but there are situations in which the collection either does not happen (because doc count is retrieved from the index statistics through `Weight#count`) or early terminates in which `terminate_after` cannot be honoured. This is fine, as the purpose of the functionality is to stop doing work early, and we should not cause additional work (the needless counting) to honour the functionality.

There are 5 TODOs in different places where `CollectionTerminatedException` is thrown in `QueryPhaseCollector`, that begin with:

> // TODO we keep on collecting although we have nothing to collect

The goal of this issue is to remove those TODOs and adjust the code to unconditionally rethrow `CollectionTerminatedException` when the collection has terminated. In the case of top docs collection without aggs, this should lead to a further simplification around removing null checks for the underlying leaf collector. There are some tests that will fail in `QueryPhaseTests` which need to have their expectations adjusted.
server/src/main/java/org/elasticsearch/search/query/QueryPhaseCollector.java
import java.io.IOException;
import java.util.Objects;
/**
 * Top-level collector used in the query phase to perform top hits collection as well as aggs collection.
 * Inspired by {@link org.apache.lucene.search.MultiCollector} but specialized for wrapping two collectors and filtering collected
 * documents as follows:
 * - through an optional <code>post_filter</code> that is applied to the top hits collection
 * - through an optional <code>min_score</code> threshold, which is applied to both the top hits as well as aggs.
 * Supports also terminating the collection after a certain number of documents have been collected (<code>terminate_after</code>).
 *
 * When top docs as well as aggs are collected (because both collectors were provided), skipping low scoring hits via
 * {@link Scorable#setMinCompetitiveScore(float)} is not supported for either of the collectors.
 */
final class QueryPhaseCollector implements Collector {
    private final Collector aggsCollector;
    private final Collector topDocsCollector;
    private final int terminateAfter;
    private final Weight postFilterWeight;
    private final Float minScore;
    private final boolean cacheScores;
    private int numCollected;
    private boolean terminatedAfter = false;
    QueryPhaseCollector(Collector topDocsCollector, Weight postFilterWeight, int terminateAfter, Collector aggsCollector, Float minScore) {
        this.topDocsCollector = Objects.requireNonNull(topDocsCollector);
        this.postFilterWeight = postFilterWeight;
        if (terminateAfter < 0) {
            throw new IllegalArgumentException("terminateAfter must be greater than or equal to 0");
        }
        this.terminateAfter = terminateAfter;
        this.aggsCollector = aggsCollector;