import { DeepMap } from './deepMap';
function DEFAULT_TO_KEY(value) {
    return value;
}
export function group(groupParams, data) {
    const { groupBy, defaultToKey = DEFAULT_TO_KEY, reducers } = groupParams;
    const groupByLength = groupBy.length;
    const deepMap = new DeepMap();
    let currentGroupKeys = [];
    const initialReducerValue = initReducers(reducers);
    const globalReducerResults = { ...initialReducerValue };
    for (let i = 0, len = data.length; i < len; i++) {
        let item = data[i];
        for (let groupByIndex = 0; groupByIndex < groupByLength; groupByIndex++) {
            const { field: groupByProperty, toKey: groupToKey } = groupBy[groupByIndex];
            const key = (groupToKey || defaultToKey)(item[groupByProperty], item);
            currentGroupKeys.push(key);
            if (!deepMap.has(currentGroupKeys)) {
                const deepMapGroupValue = {
                    items: [],
                    reducerResults: { ...initialReducerValue },
                };
                deepMap.set(currentGroupKeys, deepMapGroupValue);
            }
            const { items: currentGroupItems, reducerResults } = deepMap.get(currentGroupKeys);
            currentGroupItems.push(item);
            if (reducers) {
                computeReducersFor(item, reducers, reducerResults);
            }
        }
        if (reducers) {
            computeReducersFor(item, reducers, globalReducerResults);
        }
        currentGroupKeys.length = 0;
    }
    const hasReducersWithDoneFn = reducers && Object.values(reducers).some((reducer) => reducer.done);
    if (hasReducersWithDoneFn) {
        deepMap.visitDepthFirst((deepMapValue, _keys, _indexInGroup, next) => {
            completeReducers(reducers, deepMapValue.reducerResults, deepMapValue.items);
            next?.();
        });
    }
    if (hasReducersWithDoneFn) {
        completeReducers(reducers, globalReducerResults, data);
    }
    const result = {
        deepMap,
        groupParams,
        initialData: data,
        reducerResults: globalReducerResults,
    };
    return result;
}
function initReducers(reducers) {
    if (!reducers || !Object.keys(reducers).length) {
        return {};
    }
    const result = {};
    for (let key in reducers)
        if (reducers.hasOwnProperty(key)) {
            result[key] = reducers[key].initialValue;
        }
    return result;
}
/**
 *
 * This fn mutates the reducerResults array!!!
 *
 * @param data data item
 * @param reducers an array of reducers
 * @param reducerResults the results on which to operate
 *
 */
function computeReducersFor(data, reducers, reducerResults) {
    if (!reducers || !Object.keys(reducers).length) {
        return;
    }
    for (let key in reducers)
        if (reducers.hasOwnProperty(key)) {
            const reducer = reducers[key];
            if (typeof reducer.reducer !== 'function') {
                continue;
            }
            const currentValue = reducerResults[key];
            const value = reducer.field ? data[reducer.field] : reducer.getter?.(data) ?? null;
            reducerResults[key] = reducer.reducer(currentValue, value, data);
        }
}
function completeReducers(reducers, reducerResults, items) {
    if (reducers) {
        for (let key in reducers)
            if (reducers.hasOwnProperty(key)) {
                const reducer = reducers[key];
                if (reducer.done) {
                    reducerResults[key] = reducer.done(reducerResults[key], items);
                }
            }
    }
    return reducerResults;
}
