"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SlickCellSelectionModel = void 0;
const utils_1 = require("@slickgrid-universal/utils");
const index_js_1 = require("../core/index.js");
const slickCellRangeSelector_js_1 = require("./slickCellRangeSelector.js");
class SlickCellSelectionModel {
    constructor(options) {
        this.pluginName = 'CellSelectionModel';
        this._cachedPageRowCount = 0;
        this._prevKeyDown = '';
        this._ranges = [];
        this._defaults = {
            selectActiveCell: true,
        };
        this.onSelectedRangesChanged = new index_js_1.SlickEvent('onSelectedRangesChanged');
        this._eventHandler = new index_js_1.SlickEventHandler();
        this._selector =
            options === undefined || options.cellRangeSelector === undefined
                ? new slickCellRangeSelector_js_1.SlickCellRangeSelector({ selectionCss: { border: '2px solid black' } })
                : options.cellRangeSelector;
        this._addonOptions = options;
    }
    get addonOptions() {
        return this._addonOptions;
    }
    get cellRangeSelector() {
        return this._selector;
    }
    get eventHandler() {
        return this._eventHandler;
    }
    init(grid) {
        this._grid = grid;
        if (this._addonOptions === undefined || this._addonOptions.cellRangeSelector === undefined) {
            this._selector = new slickCellRangeSelector_js_1.SlickCellRangeSelector({
                selectionCss: { border: `2px solid ${this._grid.getOptions().darkMode ? 'white' : 'black'}` },
            });
        }
        if (grid.hasDataView()) {
            this._dataView = grid.getData();
        }
        this._addonOptions = { ...this._defaults, ...this._addonOptions };
        // add PubSub instance to all SlickEvent
        const pubSub = grid.getPubSubService();
        if (pubSub) {
            this.onSelectedRangesChanged.setPubSubService(pubSub);
        }
        this._eventHandler
            .subscribe(this._grid.onActiveCellChanged, this.handleActiveCellChange.bind(this))
            .subscribe(this._grid.onKeyDown, this.handleKeyDown.bind(this))
            .subscribe(this._selector.onBeforeCellRangeSelected, this.handleBeforeCellRangeSelected.bind(this))
            .subscribe(this._selector.onCellRangeSelected, this.handleCellRangeSelected.bind(this));
        // register the cell range selector plugin
        grid.registerPlugin(this._selector);
    }
    destroy() {
        this.dispose();
    }
    dispose() {
        if (this._selector) {
            this._selector.onBeforeCellRangeSelected.unsubscribe(this.handleBeforeCellRangeSelected.bind(this));
            this._selector.onCellRangeSelected.unsubscribe(this.handleCellRangeSelected.bind(this));
        }
        this._eventHandler.unsubscribeAll();
        this._grid?.unregisterPlugin(this._selector);
        this._selector?.dispose();
    }
    getSelectedRanges() {
        return this._ranges;
    }
    rangesAreEqual(range1, range2) {
        let areDifferent = range1.length !== range2.length;
        if (!areDifferent) {
            for (let i = 0; i < range1.length; i++) {
                if (range1[i].fromCell !== range2[i].fromCell ||
                    range1[i].fromRow !== range2[i].fromRow ||
                    range1[i].toCell !== range2[i].toCell ||
                    range1[i].toRow !== range2[i].toRow) {
                    areDifferent = true;
                    break;
                }
            }
        }
        return !areDifferent;
    }
    refreshSelections() {
        this.setSelectedRanges(this.getSelectedRanges());
    }
    removeInvalidRanges(ranges) {
        const result = [];
        for (let i = 0; i < ranges.length; i++) {
            const r = ranges[i];
            if (this._grid.canCellBeSelected(r.fromRow, r.fromCell) && this._grid.canCellBeSelected(r.toRow, r.toCell)) {
                result.push(r);
            }
        }
        return result;
    }
    /** Provide a way to force a recalculation of page row count (for example on grid resize) */
    resetPageRowCount() {
        this._cachedPageRowCount = 0;
    }
    setSelectedRanges(ranges, caller = 'SlickCellSelectionModel.setSelectedRanges') {
        // simple check for: empty selection didn't change, prevent firing onSelectedRangesChanged
        if ((!this._ranges || this._ranges.length === 0) && (!ranges || ranges.length === 0)) {
            return;
        }
        // if range has not changed, don't fire onSelectedRangesChanged
        const rangeHasChanged = !this.rangesAreEqual(this._ranges, ranges);
        this._ranges = this.removeInvalidRanges(ranges);
        if (rangeHasChanged) {
            // provide extra "caller" argument through SlickEventData event to avoid breaking the previous pubsub event structure
            // that only accepts an array of selected range `SlickRange[]`, the SlickEventData args will be merged and used later by `onSelectedRowsChanged`
            const eventData = new index_js_1.SlickEventData(new CustomEvent('click', { detail: { caller } }), this._ranges);
            this.onSelectedRangesChanged.notify(this._ranges, eventData);
        }
    }
    //
    // protected functions
    // ---------------------
    handleActiveCellChange(_e, args) {
        this._prevSelectedRow = undefined;
        const isCellDefined = (0, utils_1.isDefined)(args.cell);
        const isRowDefined = (0, utils_1.isDefined)(args.row);
        if (this._addonOptions?.selectActiveCell && isRowDefined && isCellDefined) {
            this.setSelectedRanges([new index_js_1.SlickRange(args.row, args.cell)]);
        }
        else if (!this._addonOptions?.selectActiveCell || (!isRowDefined && !isCellDefined)) {
            // clear the previous selection once the cell changes
            this.setSelectedRanges([]);
        }
    }
    handleBeforeCellRangeSelected(e) {
        const cell = this._grid.getCellFromEvent(e);
        const activeCell = this._grid.getActiveCell();
        if (this._grid.getEditorLock().isActive() && activeCell && cell && activeCell.row === cell.row && activeCell.cell === cell.cell) {
            e.stopPropagation();
            return false;
        }
    }
    handleCellRangeSelected(_e, args) {
        this._grid.setActiveCell(args.range.fromRow, args.range.fromCell, false, false, true);
        this.setSelectedRanges([args.range]);
    }
    isKeyAllowed(key, isShiftKeyPressed) {
        return [
            'ArrowLeft',
            'ArrowRight',
            'ArrowUp',
            'ArrowDown',
            'PageDown',
            'PageUp',
            'Home',
            'End',
            ...(!isShiftKeyPressed ? ['a', 'A'] : []),
        ].some((k) => k === key);
    }
    handleKeyDown(e) {
        let ranges;
        let last;
        const colLn = this._grid.getColumns().length;
        const active = this._grid.getActiveCell();
        let dataLn = 0;
        if (this._dataView && 'getPagingInfo' in this._dataView) {
            dataLn = this._dataView?.getPagingInfo().pageSize || this._dataView.getLength();
        }
        else {
            dataLn = this._grid.getDataLength();
        }
        if (active && (e.shiftKey || e.ctrlKey) && !e.altKey && this.isKeyAllowed(e.key, e.shiftKey)) {
            ranges = this.getSelectedRanges().slice();
            if (!ranges.length) {
                ranges.push(new index_js_1.SlickRange(active.row, active.cell));
            }
            // keyboard can work with last range only
            last = ranges.pop();
            if (typeof last?.contains === 'function') {
                // can't handle selection out of active cell
                if (!last.contains(active.row, active.cell)) {
                    last = new index_js_1.SlickRange(active.row, active.cell);
                }
                let dRow = last.toRow - last.fromRow;
                let dCell = last.toCell - last.fromCell;
                let toCell;
                let toRow = 0;
                // when using Ctrl+{a, A} we will change our position to cell 0,0 and select all grid cells
                if (e.ctrlKey && e.key?.toLowerCase() === 'a') {
                    this._grid.setActiveCell(0, 0, false, false, true);
                    active.row = 0;
                    active.cell = 0;
                    toCell = colLn - 1;
                    toRow = dataLn - 1;
                }
                // walking direction
                const dirRow = active.row === last.fromRow ? 1 : -1;
                const dirCell = active.cell === last.fromCell ? 1 : -1;
                const isSingleKeyMove = e.key.startsWith('Arrow');
                if (isSingleKeyMove && !e.ctrlKey) {
                    // single cell move: (Arrow{Up/ArrowDown/ArrowLeft/ArrowRight})
                    if (e.key === 'ArrowLeft') {
                        dCell -= dirCell;
                    }
                    else if (e.key === 'ArrowRight') {
                        dCell += dirCell;
                    }
                    else if (e.key === 'ArrowUp') {
                        dRow -= dirRow;
                    }
                    else if (e.key === 'ArrowDown') {
                        dRow += dirRow;
                    }
                    toRow = active.row + dirRow * dRow;
                }
                else {
                    // multiple cell moves: (Home, End, Page{Up/Down}), we need to know how many rows are displayed on a page
                    if (this._cachedPageRowCount < 1) {
                        this._cachedPageRowCount = this._grid.getViewportRowCount();
                    }
                    if (this._prevSelectedRow === undefined) {
                        this._prevSelectedRow = active.row;
                    }
                    if ((!e.ctrlKey && e.shiftKey && e.key === 'Home') || (e.ctrlKey && e.shiftKey && e.key === 'ArrowLeft')) {
                        toCell = 0;
                        toRow = active.row;
                    }
                    else if ((!e.ctrlKey && e.shiftKey && e.key === 'End') || (e.ctrlKey && e.shiftKey && e.key === 'ArrowRight')) {
                        toCell = colLn - 1;
                        toRow = active.row;
                    }
                    else if (e.ctrlKey && e.shiftKey && e.key === 'ArrowUp') {
                        toRow = 0;
                    }
                    else if (e.ctrlKey && e.shiftKey && e.key === 'ArrowDown') {
                        toRow = dataLn - 1;
                    }
                    else if (e.ctrlKey && e.shiftKey && e.key === 'Home') {
                        toCell = 0;
                        toRow = 0;
                    }
                    else if (e.ctrlKey && e.shiftKey && e.key === 'End') {
                        toCell = colLn - 1;
                        toRow = dataLn - 1;
                    }
                    else if (e.key === 'PageUp') {
                        if (this._prevSelectedRow >= 0) {
                            toRow = this._prevSelectedRow - this._cachedPageRowCount;
                        }
                        if (toRow < 0) {
                            toRow = 0;
                        }
                    }
                    else if (e.key === 'PageDown') {
                        if (this._prevSelectedRow <= dataLn - 1) {
                            toRow = this._prevSelectedRow + this._cachedPageRowCount;
                        }
                        if (toRow > dataLn - 1) {
                            toRow = dataLn - 1;
                        }
                    }
                    this._prevSelectedRow = toRow;
                }
                // define new selection range
                toCell ??= active.cell + dirCell * dCell;
                const newLast = new index_js_1.SlickRange(active.row, active.cell, toRow, toCell);
                if (this.removeInvalidRanges([newLast]).length) {
                    ranges.push(newLast);
                    const viewRow = dirRow > 0 ? newLast.toRow : newLast.fromRow;
                    const viewCell = dirCell > 0 ? newLast.toCell : newLast.fromCell;
                    if (isSingleKeyMove) {
                        this._grid.scrollRowIntoView(viewRow);
                        this._grid.scrollCellIntoView(viewRow, viewCell, false);
                    }
                    else {
                        this._grid.scrollRowIntoView(toRow);
                        this._grid.scrollCellIntoView(toRow, viewCell, false);
                    }
                }
                else {
                    ranges.push(last);
                }
                this.setSelectedRanges(ranges);
                e.preventDefault();
                e.stopPropagation();
                this._prevKeyDown = e.key;
            }
        }
    }
}
exports.SlickCellSelectionModel = SlickCellSelectionModel;
//# sourceMappingURL=slickCellSelectionModel.js.map