/*!
 * Built by Revolist OU ❤️
 */
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s)
        if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
            t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
import { getPhysical, setItems, columnTypes, } from "../../store/index";
import { BasePlugin } from "../base.plugin";
import { FILTER_TRIMMED_TYPE } from "../filter/filter.plugin";
import { SortingPlugin } from "../sorting/sorting.plugin";
import { GROUP_EXPAND_EVENT, GROUPING_ROW_TYPE, PSEUDO_GROUP_COLUMN, } from "./grouping.const";
import { doExpand, doCollapse } from "./grouping.row.expand.service";
import { gatherGrouping, getExpanded, getSource, isGrouping, isGroupingColumn, } from "./grouping.service";
import { filterOutEmptyGroupRows, processDoubleConversionTrimmed, TRIMMED_GROUPING, } from "./grouping.trimmed.service";
export * from './grouping.const';
export * from './grouping.row.expand.service';
export * from './grouping.row.types';
export * from './grouping.service';
export * from './grouping.row.renderer';
export class GroupingRowPlugin extends BasePlugin {
    getStore(type = GROUPING_ROW_TYPE) {
        return this.providers.data.stores[type].store;
    }
    constructor(revogrid, providers) {
        super(revogrid, providers);
    }
    // befoce cell focus
    onFocus(e) {
        if (isGrouping(e.detail.model)) {
            e.preventDefault();
        }
    }
    // expand event triggered
    onExpand({ virtualIndex }) {
        const { source } = getSource(this.getStore().get('source'), this.getStore().get('proxyItems'));
        let newTrimmed = this.getStore().get('trimmed')[TRIMMED_GROUPING];
        let i = getPhysical(this.getStore(), virtualIndex);
        const isExpanded = getExpanded(source[i]);
        if (!isExpanded) {
            const { trimmed, items } = doExpand(virtualIndex, source, this.getStore().get('items'));
            newTrimmed = Object.assign(Object.assign({}, newTrimmed), trimmed);
            if (items) {
                setItems(this.getStore(), items);
            }
        }
        else {
            const { trimmed } = doCollapse(i, source);
            newTrimmed = Object.assign(Object.assign({}, newTrimmed), trimmed);
            this.revogrid.clearFocus();
        }
        this.getStore().set('source', source);
        this.revogrid.addTrimmed(newTrimmed, TRIMMED_GROUPING);
    }
    setColumnGrouping(cols) {
        // if 0 column as holder
        if (cols === null || cols === void 0 ? void 0 : cols.length) {
            cols[0][PSEUDO_GROUP_COLUMN] = true;
            return true;
        }
        return false;
    }
    setColumns({ columns }) {
        for (let type of columnTypes) {
            if (this.setColumnGrouping(columns[type])) {
                break;
            }
        }
    }
    // evaluate drag between groups
    onDrag(e) {
        const { from, to } = e.detail;
        const isDown = to - from >= 0;
        const { source } = getSource(this.getStore().get('source'), this.getStore().get('proxyItems'));
        const items = this.getStore().get('items');
        let i = isDown ? from : to;
        const end = isDown ? to : from;
        for (; i < end; i++) {
            const model = source[items[i]];
            const isGroup = isGrouping(model);
            if (isGroup) {
                e.preventDefault();
                return;
            }
        }
    }
    beforeTrimmedApply(trimmed, type) {
        /** Filter trim must keep group headers in sync with their visible children. */
        if (type === FILTER_TRIMMED_TYPE) {
            const source = this.getStore().get('source');
            const updatedTrimmed = filterOutEmptyGroupRows(source, trimmed);
            Object.keys(trimmed).forEach(index => delete trimmed[Number.parseInt(index, 10)]);
            Object.assign(trimmed, updatedTrimmed);
        }
    }
    beforeFilterTrimmed(trimmed) {
        const source = this.getStore().get('source');
        return filterOutEmptyGroupRows(source, trimmed);
    }
    isSortingRunning() {
        const sortingPlugin = this.providers.plugins.getByClass(SortingPlugin);
        return !!(sortingPlugin === null || sortingPlugin === void 0 ? void 0 : sortingPlugin.sortingPromise);
    }
    /**
     * Returns grouping options for regrouping that must preserve current UI state.
     *
     * `expandedAll` and config `prevExpanded` are initial/config instructions.
     * Reusing them after sorting would reopen groups the user collapsed before
     * sorting instead of using the current grouped source state.
     */
    getCurrentExpandedOptions() {
        var _a;
        const _b = (_a = this.options) !== null && _a !== void 0 ? _a : {}, { expandedAll: _expandedAll, prevExpanded: _prevExpanded } = _b, options = __rest(_b, ["expandedAll", "prevExpanded"]);
        return options;
    }
    /**
     * Starts global source update with group clearing and applying new one
     * Initiated when need to reapply grouping
     */
    doSourceUpdate(options) {
        var _a;
        /**
         * Get source without grouping
         * @param newOldIndexMap - provides us mapping with new indexes vs old indexes, we would use it for trimmed mapping
         */
        const store = this.getStore();
        const { source, prevExpanded, oldNewIndexes } = getSource(store.get('source'), store.get('proxyItems'), true);
        const expanded = Object.assign({ prevExpanded }, options);
        /**
         * Group again
         * @param oldNewIndexMap - provides us mapping with new indexes vs old indexes
         */
        const { sourceWithGroups, depth, trimmed, oldNewIndexMap, } = gatherGrouping(source, ((_a = this.options) === null || _a === void 0 ? void 0 : _a.props) || [], expanded);
        const customRenderer = options === null || options === void 0 ? void 0 : options.groupLabelTemplate;
        // setup source
        this.providers.data.setData(sourceWithGroups, GROUPING_ROW_TYPE, this.revogrid.disableVirtualY, { depth, customRenderer }, true);
        this.updateTrimmed(trimmed, oldNewIndexes !== null && oldNewIndexes !== void 0 ? oldNewIndexes : {}, oldNewIndexMap, sourceWithGroups);
    }
    /**
     * Apply grouping on data set
     * Clear grouping from source
     * If source came from other plugin
     */
    onDataSet(data) {
        var _a, _b;
        let preservedExpanded = {};
        if (((_a = this.options) === null || _a === void 0 ? void 0 : _a.preserveGroupingOnUpdate) !== false) {
            let { prevExpanded } = getSource(this.getStore().get('source'), this.getStore().get('proxyItems'), true);
            preservedExpanded = prevExpanded;
        }
        const source = data.source.filter(s => !isGrouping(s));
        const options = Object.assign(Object.assign({}, (this.revogrid.grouping || {})), { prevExpanded: preservedExpanded });
        const { sourceWithGroups, depth, trimmed, oldNewIndexMap, } = gatherGrouping(source, ((_b = this.options) === null || _b === void 0 ? void 0 : _b.props) || [], options);
        data.source = sourceWithGroups;
        this.providers.data.setGrouping({ depth });
        this.updateTrimmed(trimmed, oldNewIndexMap, undefined, sourceWithGroups);
    }
    /**
     * External call to apply grouping. Called by revogrid when prop changed.
     */
    setGrouping(options) {
        var _a, _b;
        // unsubscribe from all events when group applied
        this.clearSubscriptions();
        this.options = options;
        // clear props, no grouping exists
        if (!((_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.length)) {
            this.clearGrouping();
            return;
        }
        // props exist and source initd
        const store = this.getStore();
        const { source } = getSource(store.get('source'), store.get('proxyItems'));
        if (source.length) {
            this.doSourceUpdate(Object.assign({}, options));
        }
        // props exist and columns initd
        for (let t of columnTypes) {
            if (this.setColumnGrouping(this.providers.column.getColumns(t))) {
                this.providers.column.refreshByType(t);
                break;
            }
        }
        // if has any grouping subscribe to events again
        /** if grouping present and new data source arrived */
        this.addEventListener('beforesourceset', ({ detail }) => {
            var _a, _b, _c;
            if (!(((_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.length) && ((_c = detail === null || detail === void 0 ? void 0 : detail.source) === null || _c === void 0 ? void 0 : _c.length))) {
                return;
            }
            // if sorting is running don't apply grouping, wait for sorting, then it'll apply in @aftersortingapply
            if (this.isSortingRunning()) {
                return;
            }
            this.onDataSet(detail);
        });
        this.addEventListener('beforecolumnsset', ({ detail }) => {
            this.setColumns(detail);
        });
        /**
         * filter applied need to clear grouping and apply again
         * based on new results can be new grouping
         */
        this.addEventListener('beforetrimmed', ({ detail: { trimmed, trimmedType } }) => this.beforeTrimmedApply(trimmed, trimmedType));
        /** Filter plugin owns data-row matching; grouping decides which headers remain visible. */
        this.addEventListener('beforefiltertrimmed', ({ detail }) => {
            detail.itemsToFilter = this.beforeFilterTrimmed(detail.itemsToFilter);
        });
        /**
         * sorting applied need to clear grouping and apply again
         * based on new results whole grouping order will changed
         */
        this.addEventListener('aftersortingapply', () => {
            var _a, _b;
            if (!((_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.length)) {
                return;
            }
            this.doSourceUpdate(this.getCurrentExpandedOptions());
        });
        /**
         * Apply logic for focus inside of grouping
         * We can't focus on grouping rows, navigation only inside of groups for now
         */
        this.addEventListener('beforecellfocus', e => this.onFocus(e));
        /**
         * Prevent rgRow drag outside the group
         */
        this.addEventListener('roworderchanged', e => this.onDrag(e));
        /**
         * When grouping expand icon was clicked
         */
        this.addEventListener(GROUP_EXPAND_EVENT, e => this.onExpand(e.detail));
    }
    // clear grouping
    clearGrouping() {
        // clear columns
        columnTypes.forEach(t => {
            const cols = this.providers.column.getColumns(t);
            let deleted = false;
            cols.forEach(c => {
                if (isGroupingColumn(c)) {
                    delete c[PSEUDO_GROUP_COLUMN];
                    deleted = true;
                }
            });
            // if column store had grouping clear and refresh
            if (deleted) {
                this.providers.column.refreshByType(t);
            }
        });
        // clear rows
        const { source, oldNewIndexes } = getSource(this.getStore().get('source'), this.getStore().get('proxyItems'), true);
        this.providers.data.setData(source, GROUPING_ROW_TYPE, this.revogrid.disableVirtualY, undefined, true);
        this.updateTrimmed(undefined, undefined, oldNewIndexes, source);
    }
    updateTrimmed(trimmedGroup = {}, firstLevelMap = {}, secondLevelMap, source = this.getStore().get('source')) {
        // map previously trimmed data
        const trimemedOptionsToUpgrade = processDoubleConversionTrimmed(this.getStore().get('trimmed'), firstLevelMap, secondLevelMap);
        for (let type in trimemedOptionsToUpgrade) {
            if (type === FILTER_TRIMMED_TYPE) {
                /** Regrouping changes physical indexes, so filter trim needs fresh group-header state. */
                trimemedOptionsToUpgrade[type] = filterOutEmptyGroupRows(source, trimemedOptionsToUpgrade[type]);
            }
            this.revogrid.addTrimmed(trimemedOptionsToUpgrade[type], type);
        }
        // const emptyGroups = this.filterOutEmptyGroups(trimemedOptionsToUpgrade, childrenByGroup);
        // setup trimmed data for grouping
        this.revogrid.addTrimmed(Object.assign({}, trimmedGroup), TRIMMED_GROUPING);
    }
}
