"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AggregationQueryBuilder = void 0;
const isEmpty_js_1 = __importDefault(require("lodash/isEmpty.js"));
const where_query_builder_js_1 = require("./where-query-builder.js");
/**
 * A query builder for performing aggregation queries.
 * @template DB - The database type.
 * @template TableName - The name of the table to aggregate from.
 * @template TGroupBy - The fields to group by.
 * @template TAggregations - The aggregations to perform.
 */
class AggregationQueryBuilder extends where_query_builder_js_1.FilterableQueryBuilder {
    #node;
    #metrics;
    constructor(node, executor, metrics) {
        super(node, executor);
        this.#node = node;
        this.#metrics = metrics;
    }
    /**
     * Groups the results by a specified field.
     * @param field - The field to group by.
     * @param direction - The sort direction for the grouping.
     * @returns A new `AggregationQueryBuilder` instance with the grouping applied.
     *
     * @example
     * ```typescript
     * const userCounts = await qb
     *   .aggregateFrom('users')
     *   .groupBy('role')
     *   .metrics({ count: count('id') })
     *   .execute();
     * ```
     */
    groupBy(field, direction = 'asc') {
        const newGrouping = {
            field,
            direction,
        };
        return new AggregationQueryBuilder({
            ...this.#node,
            groupings: [...(this.#node.groupings || []), newGrouping],
        }, this._executor, this.#metrics);
    }
    /**
     * Specifies the metrics to compute using helper functions.
     * Returns a flat array of records with groupBy fields and metrics at the top level.
     * @param metrics - An object where keys are metric names and values are aggregation helper functions.
     * @returns A new `AggregationQueryBuilder` instance with the metrics applied.
     *
     * @example
     * ```typescript
     * const userStats = await qb
     *   .aggregateFrom('users')
     *   .groupBy('role', 'asc')
     *   .groupBy('permission', 'desc')
     *   .metrics({
     *     idCount: count('id'),
     *     ageAvg: avg('age'),
     *     ageSum: sum('age')
     *   })
     *   .execute();
     * ```
     */
    metrics(metrics) {
        // Convert metrics format to aggregations format for the backend
        const newAggregates = {
            ...this.#node.aggregations,
        };
        for (const metricName in metrics) {
            const helper = metrics[metricName];
            const field = helper.field;
            const aggregation = helper.aggregation;
            if (!newAggregates[field]) {
                newAggregates[field] = [];
            }
            if (!newAggregates[field].includes(aggregation)) {
                newAggregates[field].push(aggregation);
            }
        }
        return new AggregationQueryBuilder({
            ...this.#node,
            aggregations: newAggregates,
        }, this._executor, metrics);
    }
    /**
     * Sets the maximum number of records to return.
     * @param count - The maximum number of records.
     * @returns A new `AggregationQueryBuilder` instance with the limit applied.
     */
    limit(count) {
        return new AggregationQueryBuilder({
            ...this.#node,
            pagination: { ...this.#node.pagination, limit: count },
        }, this._executor, this.#metrics);
    }
    /**
     * Sets the number of records to skip.
     * @param count - The number of records to skip.
     * @returns A new `AggregationQueryBuilder` instance with the offset applied.
     */
    offset(count) {
        return new AggregationQueryBuilder({
            ...this.#node,
            pagination: { ...this.#node.pagination, offset: count },
        }, this._executor, this.#metrics);
    }
    /**
     * Paginates the results.
     * @param page - The page number to retrieve.
     * @param limit - The number of records per page.
     * @returns A new `AggregationQueryBuilder` instance with pagination applied.
     */
    paginate(page, limit) {
        return this.offset((page - 1) * limit).limit(limit);
    }
    /**
     * Sorts the results by a specified field.
     * @param field - The field to sort by.
     * @param direction - The sort direction ('asc' or 'desc').
     * @returns A new `AggregationQueryBuilder` instance with the sorting applied.
     */
    orderBy(field, direction = 'asc') {
        const newSorting = {
            field: field,
            direction,
        };
        return new AggregationQueryBuilder({
            ...this.#node,
            sorting: [...(this.#node.sorting || []), newSorting],
        }, this._executor, this.#metrics);
    }
    /**
     * Executes the aggregation query.
     * @returns A promise that resolves with an array of the aggregation results.
     * If metrics were specified, returns a flat array. Otherwise, returns the nested structure.
     *
     * @example
     * ```typescript
     * const userCounts = await qb
     *   .aggregateFrom('users')
     *   .groupBy('role')
     *   .metrics({ count: count('id') })
     *   .execute();
     * ```
     */
    async execute() {
        const response = await this._executor.execute(this);
        // If metrics are used, flatten the nested structure
        if (this.#metrics) {
            return this._flattenMetrics(response[0], this.#node.groupings
                ?.filter((g) => 'field' in g && !('formula' in g))
                .map(g => g.field) || [], this.#metrics);
        }
        return response;
    }
    /**
     * Flattens the nested AggregateRecord structure into a flat array of metrics records.
     */
    _flattenMetrics(records, groupByFields, metrics) {
        const result = [];
        const flatten = (record, path) => {
            if ('slug' in record && 'value' in record) {
                const currentPath = {
                    ...path,
                    [record.slug]: record.value,
                };
                if ('children' in record &&
                    Array.isArray(record.children) &&
                    !(0, isEmpty_js_1.default)(record.children)) {
                    // Has children, recurse
                    for (const child of record.children) {
                        flatten(child, currentPath);
                    }
                }
                else {
                    // Leaf node, create flat record
                    const flatRecord = { ...currentPath };
                    // Add metrics
                    for (const metricName in metrics) {
                        const helper = metrics[metricName];
                        const field = helper.field;
                        const aggregation = helper.aggregation;
                        // Extract the metric value from aggregates
                        if (record.aggregates &&
                            field in record.aggregates &&
                            record.aggregates[field] &&
                            typeof record.aggregates[field] === 'object' &&
                            aggregation in record.aggregates[field]) {
                            flatRecord[metricName] = record.aggregates[field][aggregation];
                        }
                        else {
                            flatRecord[metricName] = null;
                        }
                    }
                    result.push(flatRecord);
                }
            }
            else {
                // No groupBy, just metrics
                const flatRecord = {};
                // Add metrics
                for (const metricName in metrics) {
                    const helper = metrics[metricName];
                    const field = helper.field;
                    const aggregation = helper.aggregation;
                    if (record.aggregates &&
                        field in record.aggregates &&
                        record.aggregates[field] &&
                        typeof record.aggregates[field] === 'object' &&
                        aggregation in record.aggregates[field]) {
                        flatRecord[metricName] = record.aggregates[field][aggregation];
                    }
                    else {
                        flatRecord[metricName] = null;
                    }
                }
                result.push(flatRecord);
            }
        };
        for (const record of records) {
            flatten(record, {});
        }
        return result;
    }
    /**
     * Subscribes to the results of the aggregation query.
     * @param callback - A callback function that will be called with the results of the aggregation query.
     * @returns A function to unsubscribe from the query.
     *
     * @example
     * ```typescript
     * const unsubscribe = qb
     *   .aggregateFrom('users')
     *   .groupBy('role')
     *   .metrics({ count: count('id') })
     *   .subscribe((userCounts) => {
     *     console.log('User counts by role:', userCounts);
     *   });
     *
     * // To stop listening for updates
     * unsubscribe();
     * ```
     */
    subscribe(callback) {
        return this._executor.subscribe([this], (rawResult) => {
            if (this.#metrics) {
                const flattened = this._flattenMetrics(rawResult, this.#node.groupings
                    ?.filter((g) => 'field' in g && !('formula' in g))
                    .map(g => g.field) || [], this.#metrics);
                callback(flattened);
            }
            else {
                callback(rawResult);
            }
        });
    }
    compile() {
        const query = 'mutation ($metadata: JSON) { execute(metadata: $metadata) }';
        const metadata = [this._prepareMetadata()];
        return { query, variables: { metadata } };
    }
    _prepareMetadata() {
        return {
            type: 'aggregation',
            tableName: this.#node.tableName,
            groupings: this.#node.groupings,
            aggregations: this.#node.aggregations,
            ...(this.#node.filtersSet.filtersSet.length > 0
                ? { filtersSet: this.#node.filtersSet }
                : {}),
            ...(this.#node.pagination ? { pagination: this.#node.pagination } : {}),
            ...(this.#node.sorting ? { sorting: this.#node.sorting } : {}),
        };
    }
}
exports.AggregationQueryBuilder = AggregationQueryBuilder;
//# sourceMappingURL=aggregation-query-builder.js.map