import { aggregate } from '../ExpressionFunctions/scalarAggregationHelper';
const doOnceFlags = {};
const logOnce = (message, loggingFn) => {
    if (doOnceFlags[message]) {
        return;
    }
    loggingFn(message);
    doOnceFlags[message] = true;
};
export class AggregatedScalarLiveValue {
    source;
    requestingModule;
    adaptableApi;
    getRowNodes;
    aggregationReducerName;
    aggregatedScalarExpression;
    expressionEvaluation;
    aggregationResult;
    constructor(source, requestingModule, adaptableApi, getRowNodes) {
        this.source = source;
        this.requestingModule = requestingModule;
        this.adaptableApi = adaptableApi;
        this.getRowNodes = getRowNodes;
        if (source.aggregatedScalarExpression) {
            this.aggregatedScalarExpression = source.aggregatedScalarExpression;
        }
        this.expressionEvaluation = source.aggregatedScalarExpression
            ? this.adaptableApi.internalApi
                .getQueryLanguageService()
                .evaluateAggregatedScalarExpression(source.aggregatedScalarExpression, requestingModule, getRowNodes).value
            : source.aggregatedScalarExpressionEvaluation;
        // currently we support only one reducer
        this.aggregationReducerName = Object.keys(this.expressionEvaluation.aggregationParams.reducers)[0];
        this.aggregationResult = this.computeAggregatedValue(this.expressionEvaluation);
    }
    refresh() {
        if (this.aggregatedScalarExpression) {
            this.expressionEvaluation = this.adaptableApi.internalApi
                .getQueryLanguageService()
                .evaluateAggregatedScalarExpression(this.aggregatedScalarExpression, this.requestingModule, this.getRowNodes).value;
        }
        // currently we support only one reducer
        this.aggregationReducerName = Object.keys(this.expressionEvaluation.aggregationParams.reducers)[0];
        this.aggregationResult = this.computeAggregatedValue(this.expressionEvaluation);
    }
    getAggregatedValueForRow(rowNode) {
        const aggregationValue = this.getAggregationValue(rowNode);
        if (aggregationValue == undefined) {
            logOnce(`${this.aggregatedScalarExpression} :: aggregation value is NOT available!`, (message) => {
                this.adaptableApi.logWarn(message);
            });
            return;
        }
        if (this.expressionEvaluation.rowValueGetter) {
            return this.expressionEvaluation.rowValueGetter(rowNode, aggregationValue);
        }
        return aggregationValue;
    }
    getAllAggregationValues() {
        if (this.expressionEvaluation.aggregationParams.groupBy?.length) {
            const aggregations = this.aggregationResult.deepMap.topDownValues();
            return aggregations.map((aggregation) => aggregation.reducerResults[this.aggregationReducerName]);
        }
        return [this.getGlobalAggregatedValue()];
    }
    getAggregationValue(rowNode) {
        if (this.expressionEvaluation.aggregationParams.groupBy?.length) {
            const groupColumns = this.expressionEvaluation.aggregationParams.groupBy.map((groupByParam) => groupByParam.field);
            const groupKeys = groupColumns.map((groupColumnName) => this.getRowNodeValueForColumnId(rowNode, groupColumnName));
            return this.aggregationResult.deepMap.get(groupKeys)?.reducerResults[this.aggregationReducerName];
        }
        return this.getGlobalAggregatedValue();
    }
    getGlobalAggregatedValue() {
        return this.aggregationResult.reducerResults[this.aggregationReducerName];
    }
    getRowNodeValueForColumnId(rowNode, columnId) {
        const rawValue = this.adaptableApi.gridApi.getRawValueFromRowNode(rowNode, columnId);
        // we need to convert the Date object to a number, otherwise the grouping will not work (each Date instance is unique)
        return rawValue instanceof Date ? rawValue.getTime() : rawValue;
    }
    computeAggregatedValue(expressionEvaluation) {
        const gridRowNodes = expressionEvaluation.getRowNodes
            ? expressionEvaluation.getRowNodes()
            : this.adaptableApi.gridApi.getAllRowNodes({
                filterFn: expressionEvaluation.rowFilterFn,
            });
        // we iterate over the RowNode list (we need this to handle complex column values (nested values, valueGetters etc)
        // so we will map the fieldNames to RowNode.data
        const mapReducerValueGetter = (columnId) => {
            return (rowNode) => this.getRowNodeValueForColumnId(rowNode, columnId);
        };
        Object.values(expressionEvaluation.aggregationParams.reducers).forEach((aggregationReducer) => {
            aggregationReducer.getter = mapReducerValueGetter(aggregationReducer.field);
            // nullify the field to force the fallback on the getter fn
            aggregationReducer.field = null;
        });
        const mapGroupByToKey = (columnId) => {
            return (_unusableProperty, rowNode) => this.getRowNodeValueForColumnId(rowNode, columnId);
        };
        expressionEvaluation.aggregationParams.groupBy?.forEach((groupByDef) => {
            groupByDef.toKey = mapGroupByToKey(groupByDef.field);
        });
        let aggregatedRowNodes = gridRowNodes;
        if (expressionEvaluation.sortByColumn) {
            const sortByColumn = expressionEvaluation.sortByColumn;
            // currently, we support only ascending sorting
            aggregatedRowNodes.sort((first, second) => {
                const firstValue = this.getRowNodeValueForColumnId(first, sortByColumn);
                const secondValue = this.getRowNodeValueForColumnId(second, sortByColumn);
                if (firstValue < secondValue) {
                    return -1;
                }
                if (firstValue > secondValue) {
                    return 1;
                }
                return 0;
            });
        }
        const result = aggregate(expressionEvaluation.aggregationParams, aggregatedRowNodes);
        return result;
    }
}
