import { hashObject } from "../../utils/hashObject.js";
import { createAggregator } from "../Aggregators/index.js";
import { getInitialHeader, mergeHeaders } from "../headerUtils.js";
import { emptyGroup, extractAggregateResult } from "./emptyGroup.js";
import { createParallelQueryResult } from "../parallelQueryResult.js";
/** @hidden */
export class GroupByValueEndpointComponent {
    executionContext;
    queryInfo;
    aggregators = new Map();
    aggregateResultArray = [];
    aggregateType;
    completed = false;
    constructor(executionContext, queryInfo) {
        this.executionContext = executionContext;
        this.queryInfo = queryInfo;
        // VALUE queries will only every have a single grouping
        this.aggregateType = this.queryInfo.aggregates[0];
    }
    hasMoreResults() {
        return this.executionContext.hasMoreResults();
    }
    async fetchMore(diagnosticNode) {
        if (this.completed) {
            return {
                result: undefined,
                headers: getInitialHeader(),
            };
        }
        const aggregateHeaders = getInitialHeader();
        const response = await this.executionContext.fetchMore(diagnosticNode);
        if (!response) {
            if (this.aggregators.size > 0) {
                return this.generateAggregateResponse(aggregateHeaders);
            }
            return { result: undefined, headers: aggregateHeaders };
        }
        mergeHeaders(aggregateHeaders, response.headers);
        if (response.result === undefined ||
            !Array.isArray(response.result.buffer) ||
            response.result.buffer.length === 0) {
            if (this.aggregators.size > 0) {
                return this.generateAggregateResponse(aggregateHeaders);
            }
            return { result: undefined, headers: aggregateHeaders };
        }
        const parallelResult = response.result;
        const dataToProcess = parallelResult.buffer;
        for (const item of dataToProcess) {
            if (item) {
                let grouping = emptyGroup;
                let payload = item;
                if (item.groupByItems) {
                    // If the query contains a GROUP BY clause, it will have a payload property and groupByItems
                    payload = item.payload;
                    grouping = await hashObject(item.groupByItems);
                }
                const aggregator = this.aggregators.get(grouping);
                if (!aggregator) {
                    // This is the first time we have seen a grouping so create a new aggregator
                    this.aggregators.set(grouping, createAggregator(this.aggregateType));
                }
                if (this.aggregateType) {
                    const aggregateResult = extractAggregateResult(payload[0]);
                    // if aggregate result is null, we need to short circuit aggregation and return undefined
                    if (aggregateResult === null) {
                        this.completed = true;
                    }
                    this.aggregators.get(grouping).aggregate(aggregateResult);
                }
                else {
                    // Queries with no aggregates pass the payload directly to the aggregator
                    // Example: SELECT VALUE c.team FROM c GROUP BY c.team
                    this.aggregators.get(grouping).aggregate(payload);
                }
            }
        }
        // We bail early since we got an undefined result back `[{}]`
        if (this.completed) {
            const result = createParallelQueryResult([], new Map());
            return {
                result,
                headers: aggregateHeaders,
            };
        }
        if (this.executionContext.hasMoreResults()) {
            // Return empty buffer but preserve the structure and pass-through fields
            const result = createParallelQueryResult([], // empty buffer
            new Map());
            return { result, headers: aggregateHeaders };
        }
        else {
            // If no results are left in the underlying execution context, convert our aggregate results to an array
            return this.generateAggregateResponse(aggregateHeaders);
        }
    }
    generateAggregateResponse(aggregateHeaders) {
        for (const aggregator of this.aggregators.values()) {
            const result = aggregator.getResult();
            if (result !== undefined) {
                this.aggregateResultArray.push(result);
            }
        }
        this.completed = true;
        const result = createParallelQueryResult(this.aggregateResultArray, new Map());
        return {
            result,
            headers: aggregateHeaders,
        };
    }
}
//# sourceMappingURL=GroupByValueEndpointComponent.js.map