/*!
 * Built by Revolist OU ❤️
 */
import size from "lodash/size";
import debounce from "lodash/debounce";
import { BasePlugin } from "../base.plugin";
import { getColumnByProp } from "../../utils/column.utils";
import { columnTypes, rowTypes } from "../../store/index";
import { getComparer, getNextOrder, sortIndexByItems } from "./sorting.func";
export * from './sorting.types';
export * from './sorting.func';
export * from './sorting.sign';
/**
 * Lifecycle
 * 1. @event `beforesorting` - Triggered when sorting just starts. Nothing has happened yet. This can be triggered from a column or from the source. If the type is from rows, the column will be undefined.
 * 2. @event `beforesourcesortingapply` - Triggered before the sorting data is applied to the data source. You can prevent this event, and the data will not be sorted.
 * 3. @event `beforesortingapply` - Triggered before the sorting data is applied to the data source. You can prevent this event, and the data will not be sorted. This event is only called from a column sorting click.
 * 4. @event `aftersortingapply` - Triggered after sorting has been applied and completed. This event occurs for both row and column sorting.
 *
 * Note: If you prevent an event, it will not proceed to the subsequent steps.
 */
export class SortingPlugin extends BasePlugin {
    constructor(revogrid, providers, config) {
        super(revogrid, providers);
        this.revogrid = revogrid;
        /**
         * Delayed sorting promise
         */
        this.sortingPromise = null;
        /**
         * We need to sort only so often
         */
        this.postponeSort = debounce((order, comparison, ignoreViewportUpdate) => this.runSorting(order, comparison, ignoreViewportUpdate), 50);
        const setConfig = (cfg) => {
            var _a;
            if (cfg) {
                const sortingFunc = {};
                const order = {};
                (_a = cfg.columns) === null || _a === void 0 ? void 0 : _a.forEach(col => {
                    sortingFunc[col.prop] = getComparer(col, col.order);
                    order[col.prop] = col.order;
                });
                if (cfg.additive) {
                    this.sorting = Object.assign(Object.assign({}, this.sorting), order);
                    this.sortingFunc = Object.assign(Object.assign({}, this.sortingFunc), sortingFunc);
                }
                else {
                    // // set sorting
                    this.sorting = order;
                    this.sortingFunc = sortingFunc;
                }
            }
        };
        setConfig(config);
        this.addEventListener('sortingconfigchanged', ({ detail }) => {
            config = detail;
            setConfig(detail);
            this.startSorting(this.sorting, this.sortingFunc);
        });
        this.addEventListener('beforeheaderrender', ({ detail, }) => {
            var _a;
            const { data: column } = detail;
            if (column.sortable) {
                detail.data = Object.assign(Object.assign({}, column), { order: (_a = this.sorting) === null || _a === void 0 ? void 0 : _a[column.prop] });
            }
        });
        this.addEventListener('beforeanysource', ({ detail: { type }, }) => {
            // if sorting was provided - sort data
            if (!!this.sorting && this.sortingFunc) {
                const event = this.emit('beforesourcesortingapply', { type, sorting: this.sorting });
                if (event.defaultPrevented) {
                    return;
                }
                this.startSorting(this.sorting, this.sortingFunc);
            }
        });
        this.addEventListener('aftercolumnsset', ({ detail: { order }, }) => {
            // if config provided - do nothing, read from config
            if (config) {
                return;
            }
            const columns = this.providers.column.getColumns();
            const sortingFunc = {};
            for (let prop in order) {
                const cmp = getComparer(getColumnByProp(columns, prop), order[prop]);
                sortingFunc[prop] = cmp;
            }
            // set sorting
            this.sorting = order;
            this.sortingFunc = order && sortingFunc;
        });
        this.addEventListener('beforeheaderclick', (e) => {
            var _a, _b, _c, _d;
            if (e.defaultPrevented) {
                return;
            }
            if (!((_b = (_a = e.detail) === null || _a === void 0 ? void 0 : _a.column) === null || _b === void 0 ? void 0 : _b.sortable)) {
                return;
            }
            this.headerclick(e.detail.column, (_d = (_c = e.detail) === null || _c === void 0 ? void 0 : _c.originalEvent) === null || _d === void 0 ? void 0 : _d.shiftKey);
        });
    }
    /**
     * Entry point for sorting, waits for all delays, registers jobs
     */
    startSorting(order, sortingFunc, ignoreViewportUpdate) {
        if (!this.sortingPromise) {
            // add job before render
            this.revogrid.jobsBeforeRender.push(new Promise(resolve => {
                this.sortingPromise = resolve;
            }));
        }
        this.postponeSort(order, sortingFunc, ignoreViewportUpdate);
    }
    /**
     * Apply sorting to data on header click
     * If additive - add to existing sorting, multiple columns can be sorted
     */
    headerclick(column, additive) {
        var _a, _b, _c;
        const columnProp = column.prop;
        let order = getNextOrder((_a = this.sorting) === null || _a === void 0 ? void 0 : _a[columnProp]);
        const beforeEvent = this.emit('beforesorting', { column, order, additive });
        if (beforeEvent.defaultPrevented) {
            return;
        }
        order = beforeEvent.detail.order;
        // apply sort data
        const beforeApplyEvent = this.emit('beforesortingapply', {
            column: beforeEvent.detail.column,
            order,
            additive,
        });
        if (beforeApplyEvent.defaultPrevented) {
            return;
        }
        const cmp = getComparer(beforeApplyEvent.detail.column, beforeApplyEvent.detail.order);
        if (beforeApplyEvent.detail.additive && this.sorting) {
            const sorting = {};
            const sortingFunc = {};
            if (columnProp in sorting && size(sorting) > 1 && order === undefined) {
                delete sorting[columnProp];
                delete sortingFunc[columnProp];
            }
            else {
                sorting[columnProp] = order;
                sortingFunc[columnProp] = cmp;
            }
            this.sorting = Object.assign(Object.assign({}, this.sorting), sorting);
            // extend sorting function with new sorting for multiple columns sorting
            this.sortingFunc = Object.assign(Object.assign({}, this.sortingFunc), sortingFunc);
        }
        else {
            if (order) {
                // reset sorting
                this.sorting = { [columnProp]: order };
                this.sortingFunc = { [columnProp]: cmp };
            }
            else {
                (_b = this.sorting) === null || _b === void 0 ? true : delete _b[columnProp];
                (_c = this.sortingFunc) === null || _c === void 0 ? true : delete _c[columnProp];
            }
        }
        this.startSorting(this.sorting, this.sortingFunc);
    }
    runSorting(order, comparison, ignoreViewportUpdate) {
        var _a;
        this.sort(order, comparison, undefined, ignoreViewportUpdate);
        (_a = this.sortingPromise) === null || _a === void 0 ? void 0 : _a.call(this);
        this.sortingPromise = null;
    }
    /**
     * Sort items by sorting function
     * @requires proxyItems applied to row store
     * @requires source applied to row store
     *
     * @param sorting - per column sorting
     * @param data - this.stores['rgRow'].store.get('source')
     */
    sort(sorting, sortingFunc, types = rowTypes, ignoreViewportUpdate = false) {
        // if no sorting - reset
        if (!Object.keys(sorting || {}).length) {
            for (let type of types) {
                const storeService = this.providers.data.stores[type];
                // row data
                const source = storeService.store.get('source');
                // row indexes
                const proxyItems = storeService.store.get('proxyItems');
                // row indexes
                const newItemsOrder = Array.from({ length: source.length }, (_, i) => i); // recover indexes range(0, source.length)
                this.providers.dimension.updateSizesPositionByNewDataIndexes(type, newItemsOrder, proxyItems);
                storeService.setData({ proxyItems: newItemsOrder, source: [...source], });
            }
        }
        else {
            for (let type of types) {
                const storeService = this.providers.data.stores[type];
                // row data
                const source = storeService.store.get('source');
                // row indexes
                const proxyItems = storeService.store.get('proxyItems');
                const newItemsOrder = sortIndexByItems([...proxyItems], source, sortingFunc);
                // take row indexes before trim applied and proxy items
                const prevItems = storeService.store.get('items');
                storeService.setData({
                    proxyItems: newItemsOrder,
                    source: [...source],
                });
                // take currently visible row indexes
                const newItems = storeService.store.get('items');
                if (!ignoreViewportUpdate) {
                    this.providers.dimension
                        .updateSizesPositionByNewDataIndexes(type, newItems, prevItems);
                }
            }
        }
        // refresh columns to redraw column headers and show correct icon
        columnTypes.forEach((type) => {
            this.providers.column.dataSources[type].refresh();
        });
        this.emit('aftersortingapply');
    }
}
