"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "BaseTransformation", {
    enumerable: true,
    get: function() {
        return BaseTransformation;
    }
});
const _object = require("../../helpers/object");
const _errors = require("../../helpers/errors");
const _localHooks = /*#__PURE__*/ _interop_require_default(require("../../mixins/localHooks"));
function _check_private_redeclaration(obj, privateCollection) {
    if (privateCollection.has(obj)) {
        throw new TypeError("Cannot initialize the same private elements twice on an object");
    }
}
function _class_apply_descriptor_get(receiver, descriptor) {
    if (descriptor.get) {
        return descriptor.get.call(receiver);
    }
    return descriptor.value;
}
function _class_apply_descriptor_set(receiver, descriptor, value) {
    if (descriptor.set) {
        descriptor.set.call(receiver, value);
    } else {
        if (!descriptor.writable) {
            throw new TypeError("attempted to set read only private field");
        }
        descriptor.value = value;
    }
}
function _class_extract_field_descriptor(receiver, privateMap, action) {
    if (!privateMap.has(receiver)) {
        throw new TypeError("attempted to " + action + " private field on non-instance");
    }
    return privateMap.get(receiver);
}
function _class_private_field_get(receiver, privateMap) {
    var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
    return _class_apply_descriptor_get(receiver, descriptor);
}
function _class_private_field_init(obj, privateMap, value) {
    _check_private_redeclaration(obj, privateMap);
    privateMap.set(obj, value);
}
function _class_private_field_set(receiver, privateMap, value) {
    var descriptor = _class_extract_field_descriptor(receiver, privateMap, "set");
    _class_apply_descriptor_set(receiver, descriptor, value);
    return value;
}
function _class_private_method_get(receiver, privateSet, fn) {
    if (!privateSet.has(receiver)) {
        throw new TypeError("attempted to get private field on non-instance");
    }
    return fn;
}
function _class_private_method_init(obj, privateSet) {
    _check_private_redeclaration(obj, privateSet);
    privateSet.add(obj);
}
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
var /**
   * Instance of the SelectionRange, holder for visual coordinates applied to the table.
   *
   * @type {SelectionRange}
   */ _range = /*#__PURE__*/ new WeakMap(), /**
   * Index of the currently active selection layer.
   *
   * @type {number}
   */ _activeLayerIndex = /*#__PURE__*/ new WeakMap(), /**
   * Increases the table size by applying the offsets. The option is used by the `navigableHeaders`
   * option.
   *
   * @type {{ x: number, y: number }}
   */ _offset = /*#__PURE__*/ new WeakMap(), /**
   * Chooses the next selection layer as active one.
   */ _chooseNextSelectionLayer = /*#__PURE__*/ new WeakSet(), /**
   * Chooses the previous selection layer as active one.
   */ _choosePreviousSelectionLayer = /*#__PURE__*/ new WeakSet(), /**
   * Clamps the coords to make sure they points to the cell (or header) in the table range.
   *
   * @param {CellCoords} zeroBasedCoords The coords object to clamp.
   * @returns {{rowDir: 1|0|-1, colDir: 1|0|-1}}
   */ _clampCoords = /*#__PURE__*/ new WeakSet(), /**
   * Gets the table size in number of rows with headers as "height" and number of columns with
   * headers as "width".
   *
   * @returns {{width: number, height: number}}
   */ _getTableSize = /*#__PURE__*/ new WeakSet(), /**
   * Finds non-hidden zero-based coordinates in the current selection range.
   *
   * @param {'forward' | 'backward'} direction The direction to search within the selection.
   * @returns {CellCoords | null} Zero-based coordinates or null if not found.
   */ _findNonHiddenZeroBasedCoordsInSelection = /*#__PURE__*/ new WeakSet(), /**
   * Finds the first non-hidden zero-based row in the table range.
   *
   * @param {number} visualRowFrom The visual row from which the search should start.
   * @param {number} visualRowTo The visual row to which the search should end.
   * @returns {number | null}
   */ _findFirstNonHiddenZeroBasedRow = /*#__PURE__*/ new WeakSet(), /**
   * Finds the first non-hidden zero-based column in the table range.
   *
   * @param {number} visualColumnFrom The visual column from which the search should start.
   * @param {number} visualColumnTo The visual column to which the search should end.
   * @returns {number | null}
   */ _findFirstNonHiddenZeroBasedColumn = /*#__PURE__*/ new WeakSet(), /**
   * Translates the visual coordinates to zero-based ones.
   *
   * @param {CellCoords} visualCoords The visual coords to process.
   * @returns {CellCoords}
   */ _visualToZeroBasedCoords = /*#__PURE__*/ new WeakSet(), /**
   * Translates the zero-based coordinates to visual ones.
   *
   * @param {CellCoords} zeroBasedCoords The coordinates to process.
   * @returns {CellCoords}
   */ _zeroBasedToVisualCoords = /*#__PURE__*/ new WeakSet();
class BaseTransformation {
    /**
   * Sets the currently active selection layer index.
   *
   * @param {number} layerIndex The layer index to set as active.
   */ setActiveLayerIndex(layerIndex) {
        _class_private_field_set(this, _activeLayerIndex, layerIndex);
    }
    /**
   * Gets the currently active selection layer range.
   *
   * @returns {CellRange}
   */ getCurrentSelection() {
        return _class_private_field_get(this, _range).peekByIndex(_class_private_field_get(this, _activeLayerIndex));
    }
    /**
   * Selects cell relative to the current cell (if possible).
   *
   * @param {number} rowDelta Rows number to move, value can be passed as negative number.
   * @param {number} colDelta Columns number to move, value can be passed as negative number.
   * @param {boolean} [createMissingRecords=false] If `true` the new rows/columns will be created if necessary. Otherwise, row/column will
   *                        be created according to `minSpareRows/minSpareCols` settings of Handsontable.
   * @returns {{selectionLayer: number, visualCoords: CellCoords}} Visual coordinates with selection layer after transformation.
   */ transformStart(rowDelta, colDelta, createMissingRecords = false) {
        _class_private_field_set(this, _offset, this.calculateOffset());
        const delta = this.tableApi.createCellCoords(rowDelta, colDelta);
        let visualCoords = this.getCurrentSelection().highlight;
        const highlightRenderableCoords = this.tableApi.visualToRenderableCoords(visualCoords);
        let rowTransformDir = 0;
        let colTransformDir = 0;
        this.runLocalHooks('beforeTransformStart', delta);
        if (highlightRenderableCoords.row !== null && highlightRenderableCoords.col !== null) {
            let { width, height } = _class_private_method_get(this, _getTableSize, getTableSize).call(this);
            const { row, col } = _class_private_method_get(this, _visualToZeroBasedCoords, visualToZeroBasedCoords).call(this, visualCoords);
            const fixedRowsBottom = this.tableApi.fixedRowsBottom();
            const minSpareRows = this.tableApi.minSpareRows();
            const minSpareCols = this.tableApi.minSpareCols();
            const autoWrapRow = this.tableApi.autoWrapRow();
            const autoWrapCol = this.tableApi.autoWrapCol();
            const zeroBasedCoords = this.tableApi.createCellCoords(row + delta.row, col + delta.col);
            if (zeroBasedCoords.row >= height) {
                const isActionInterrupted = (0, _object.createObjectPropListener)(createMissingRecords && minSpareRows > 0 && fixedRowsBottom === 0);
                const nextColumn = zeroBasedCoords.col + 1;
                const isColumnOutOfRange = nextColumn >= width;
                const newCoords = this.tableApi.createCellCoords(zeroBasedCoords.row - height, isColumnOutOfRange ? nextColumn - width : nextColumn);
                this.runLocalHooks('beforeColumnWrap', isActionInterrupted, _class_private_method_get(this, _zeroBasedToVisualCoords, zeroBasedToVisualCoords).call(this, newCoords), isColumnOutOfRange);
                if (isActionInterrupted.value) {
                    this.runLocalHooks('insertRowRequire', this.countRenderableRows());
                } else if (autoWrapCol) {
                    if (this.shouldSwitchSelectionLayer() && isColumnOutOfRange && _class_private_field_get(this, _range).size() > 1) {
                        _class_private_method_get(this, _chooseNextSelectionLayer, chooseNextSelectionLayer).call(this);
                        const nextLayerCoords = _class_private_method_get(this, _findNonHiddenZeroBasedCoordsInSelection, findNonHiddenZeroBasedCoordsInSelection).call(this, 'forward');
                        if (nextLayerCoords !== null) {
                            newCoords.assign(nextLayerCoords);
                        }
                    }
                    zeroBasedCoords.assign(newCoords);
                }
            } else if (zeroBasedCoords.row < 0) {
                const isActionInterrupted = (0, _object.createObjectPropListener)(autoWrapCol);
                const previousColumn = zeroBasedCoords.col - 1;
                const isColumnOutOfRange = previousColumn < 0;
                const newCoords = this.tableApi.createCellCoords(height + zeroBasedCoords.row, isColumnOutOfRange ? width + previousColumn : previousColumn);
                this.runLocalHooks('beforeColumnWrap', isActionInterrupted, _class_private_method_get(this, _zeroBasedToVisualCoords, zeroBasedToVisualCoords).call(this, newCoords), isColumnOutOfRange);
                if (autoWrapCol) {
                    if (this.shouldSwitchSelectionLayer() && isColumnOutOfRange && _class_private_field_get(this, _range).size() > 1) {
                        _class_private_method_get(this, _choosePreviousSelectionLayer, choosePreviousSelectionLayer).call(this);
                        const nextLayerCoords = _class_private_method_get(this, _findNonHiddenZeroBasedCoordsInSelection, findNonHiddenZeroBasedCoordsInSelection).call(this, 'backward');
                        if (nextLayerCoords !== null) {
                            newCoords.assign(nextLayerCoords);
                        }
                    }
                    zeroBasedCoords.assign(newCoords);
                }
            }
            // the range size may be changed after the column wrap, so we need to recalculate it for row wrap
            ({ width, height } = _class_private_method_get(this, _getTableSize, getTableSize).call(this));
            if (zeroBasedCoords.col >= width) {
                const isActionInterrupted = (0, _object.createObjectPropListener)(createMissingRecords && minSpareCols > 0);
                const nextRow = zeroBasedCoords.row + 1;
                const isRowOutOfRange = nextRow >= height;
                const newCoords = this.tableApi.createCellCoords(isRowOutOfRange ? nextRow - height : nextRow, zeroBasedCoords.col - width);
                this.runLocalHooks('beforeRowWrap', isActionInterrupted, _class_private_method_get(this, _zeroBasedToVisualCoords, zeroBasedToVisualCoords).call(this, newCoords), isRowOutOfRange);
                if (isActionInterrupted.value) {
                    this.runLocalHooks('insertColRequire', this.countRenderableColumns());
                } else if (autoWrapRow) {
                    if (this.shouldSwitchSelectionLayer() && isRowOutOfRange && _class_private_field_get(this, _range).size() > 1) {
                        _class_private_method_get(this, _chooseNextSelectionLayer, chooseNextSelectionLayer).call(this);
                        const nextLayerCoords = _class_private_method_get(this, _findNonHiddenZeroBasedCoordsInSelection, findNonHiddenZeroBasedCoordsInSelection).call(this, 'forward');
                        if (nextLayerCoords !== null) {
                            newCoords.assign(nextLayerCoords);
                        }
                    }
                    zeroBasedCoords.assign(newCoords);
                }
            } else if (zeroBasedCoords.col < 0) {
                const isActionInterrupted = (0, _object.createObjectPropListener)(autoWrapRow);
                const previousRow = zeroBasedCoords.row - 1;
                const isRowOutOfRange = previousRow < 0;
                const newCoords = this.tableApi.createCellCoords(isRowOutOfRange ? height + previousRow : previousRow, width + zeroBasedCoords.col);
                this.runLocalHooks('beforeRowWrap', isActionInterrupted, _class_private_method_get(this, _zeroBasedToVisualCoords, zeroBasedToVisualCoords).call(this, newCoords), isRowOutOfRange);
                if (autoWrapRow) {
                    if (this.shouldSwitchSelectionLayer() && isRowOutOfRange && _class_private_field_get(this, _range).size() > 1) {
                        _class_private_method_get(this, _choosePreviousSelectionLayer, choosePreviousSelectionLayer).call(this);
                        const nextLayerCoords = _class_private_method_get(this, _findNonHiddenZeroBasedCoordsInSelection, findNonHiddenZeroBasedCoordsInSelection).call(this, 'backward');
                        if (nextLayerCoords !== null) {
                            newCoords.assign(nextLayerCoords);
                        }
                    }
                    zeroBasedCoords.assign(newCoords);
                }
            }
            const { rowDir, colDir } = _class_private_method_get(this, _clampCoords, clampCoords).call(this, zeroBasedCoords);
            rowTransformDir = rowDir;
            colTransformDir = colDir;
            visualCoords = _class_private_method_get(this, _zeroBasedToVisualCoords, zeroBasedToVisualCoords).call(this, zeroBasedCoords);
        }
        this.runLocalHooks('afterTransformStart', visualCoords, rowTransformDir, colTransformDir);
        return {
            selectionLayer: _class_private_field_get(this, _activeLayerIndex),
            visualCoords
        };
    }
    /**
   * Sets selection end cell relative to the current selection end cell (if possible).
   *
   * @param {number} rowDelta Rows number to move, value can be passed as negative number.
   * @param {number} colDelta Columns number to move, value can be passed as negative number.
   * @returns {{selectionLayer: number, visualCoords: CellCoords}} Visual coordinates with selection layer after transformation.
   */ transformEnd(rowDelta, colDelta) {
        _class_private_field_set(this, _offset, this.calculateOffset());
        const delta = this.tableApi.createCellCoords(rowDelta, colDelta);
        const cellRange = this.getCurrentSelection();
        const highlightRenderableCoords = this.tableApi.visualToRenderableCoords(cellRange.highlight);
        const toRow = _class_private_method_get(this, _findFirstNonHiddenZeroBasedRow, findFirstNonHiddenZeroBasedRow).call(this, cellRange.to.row, cellRange.from.row);
        const toColumn = _class_private_method_get(this, _findFirstNonHiddenZeroBasedColumn, findFirstNonHiddenZeroBasedColumn).call(this, cellRange.to.col, cellRange.from.col);
        const visualCoords = cellRange.to.clone();
        let rowTransformDir = 0;
        let colTransformDir = 0;
        this.runLocalHooks('beforeTransformEnd', delta);
        if (highlightRenderableCoords.row !== null && highlightRenderableCoords.col !== null && toRow !== null && toColumn !== null) {
            const { row: highlightRow, col: highlightColumn } = _class_private_method_get(this, _visualToZeroBasedCoords, visualToZeroBasedCoords).call(this, cellRange.highlight);
            const coords = this.tableApi.createCellCoords(toRow + delta.row, toColumn + delta.col);
            const topStartCorner = cellRange.getTopStartCorner();
            const topEndCorner = cellRange.getTopEndCorner();
            const bottomEndCorner = cellRange.getBottomEndCorner();
            if (delta.col < 0 && toColumn >= highlightColumn && coords.col < highlightColumn) {
                const columnRestDelta = coords.col - highlightColumn;
                coords.col = _class_private_method_get(this, _findFirstNonHiddenZeroBasedColumn, findFirstNonHiddenZeroBasedColumn).call(this, topStartCorner.col, topEndCorner.col) + columnRestDelta;
            } else if (delta.col > 0 && toColumn <= highlightColumn && coords.col > highlightColumn) {
                const endColumnIndex = _class_private_method_get(this, _findFirstNonHiddenZeroBasedColumn, findFirstNonHiddenZeroBasedColumn).call(this, topEndCorner.col, topStartCorner.col);
                const columnRestDelta = Math.max(coords.col - endColumnIndex, 1);
                coords.col = endColumnIndex + columnRestDelta;
            }
            if (delta.row < 0 && toRow >= highlightRow && coords.row < highlightRow) {
                const rowRestDelta = coords.row - highlightRow;
                coords.row = _class_private_method_get(this, _findFirstNonHiddenZeroBasedRow, findFirstNonHiddenZeroBasedRow).call(this, topStartCorner.row, bottomEndCorner.row) + rowRestDelta;
            } else if (delta.row > 0 && toRow <= highlightRow && coords.row > highlightRow) {
                const bottomRowIndex = _class_private_method_get(this, _findFirstNonHiddenZeroBasedRow, findFirstNonHiddenZeroBasedRow).call(this, bottomEndCorner.row, topStartCorner.row);
                const rowRestDelta = Math.max(coords.row - bottomRowIndex, 1);
                coords.row = bottomRowIndex + rowRestDelta;
            }
            const { rowDir, colDir } = _class_private_method_get(this, _clampCoords, clampCoords).call(this, coords);
            rowTransformDir = rowDir;
            colTransformDir = colDir;
            const newVisualCoords = _class_private_method_get(this, _zeroBasedToVisualCoords, zeroBasedToVisualCoords).call(this, coords);
            if (delta.row === 0 && delta.col !== 0) {
                visualCoords.col = newVisualCoords.col;
            } else if (delta.row !== 0 && delta.col === 0) {
                visualCoords.row = newVisualCoords.row;
            } else {
                visualCoords.row = newVisualCoords.row;
                visualCoords.col = newVisualCoords.col;
            }
        }
        this.runLocalHooks('afterTransformEnd', visualCoords, rowTransformDir, colTransformDir);
        return {
            selectionLayer: _class_private_field_get(this, _activeLayerIndex),
            visualCoords
        };
    }
    /**
   * Abstract method that child classes must implement to calculate offset coordinates based on
   * the current processed selection layer.
   */ calculateOffset() {
        (0, _errors.throwWithCause)('`calculateOffset` is not implemented');
    }
    /**
   * Abstract method that child classes must implement to provide the count of renderable rows
   * based on their specific transformation logic.
   */ countRenderableRows() {
        (0, _errors.throwWithCause)('`countRenderableRows` is not implemented');
    }
    /**
   * Abstract method that child classes must implement to provide the count of renderable columns
   * based on their specific transformation logic.
   */ countRenderableColumns() {
        (0, _errors.throwWithCause)('`countRenderableColumns` is not implemented');
    }
    /**
   * Determines whether selection layer switching should occur during transformation.
   * Child classes can override this method to control the behavior.
   */ shouldSwitchSelectionLayer() {
        (0, _errors.throwWithCause)('`shouldSwitchSelectionLayer` is not implemented');
    }
    constructor(range, tableApi){
        _class_private_method_init(this, _chooseNextSelectionLayer);
        _class_private_method_init(this, _choosePreviousSelectionLayer);
        _class_private_method_init(this, _clampCoords);
        _class_private_method_init(this, _getTableSize);
        _class_private_method_init(this, _findNonHiddenZeroBasedCoordsInSelection);
        _class_private_method_init(this, _findFirstNonHiddenZeroBasedRow);
        _class_private_method_init(this, _findFirstNonHiddenZeroBasedColumn);
        _class_private_method_init(this, _visualToZeroBasedCoords);
        _class_private_method_init(this, _zeroBasedToVisualCoords);
        _class_private_field_init(this, _range, {
            writable: true,
            value: void 0
        });
        _class_private_field_init(this, _activeLayerIndex, {
            writable: true,
            value: void 0
        });
        _class_private_field_init(this, _offset, {
            writable: true,
            value: void 0
        });
        _class_private_field_set(this, _activeLayerIndex, 0);
        _class_private_field_set(this, _offset, {
            x: 0,
            y: 0
        });
        _class_private_field_set(this, _range, range);
        this.tableApi = tableApi;
    }
}
function chooseNextSelectionLayer() {
    const layerIndex = _class_private_field_get(this, _activeLayerIndex) + 1;
    this.setActiveLayerIndex(layerIndex >= _class_private_field_get(this, _range).size() ? 0 : layerIndex);
    _class_private_field_set(this, _offset, this.calculateOffset());
}
function choosePreviousSelectionLayer() {
    const layerIndex = _class_private_field_get(this, _activeLayerIndex) - 1;
    this.setActiveLayerIndex(layerIndex < 0 ? _class_private_field_get(this, _range).size() - 1 : layerIndex);
    _class_private_field_set(this, _offset, this.calculateOffset());
}
function clampCoords(zeroBasedCoords) {
    const { width, height } = _class_private_method_get(this, _getTableSize, getTableSize).call(this);
    let rowDir = 0;
    let colDir = 0;
    if (zeroBasedCoords.row < 0) {
        rowDir = -1;
        zeroBasedCoords.row = 0;
    } else if (zeroBasedCoords.row > 0 && zeroBasedCoords.row >= height) {
        rowDir = 1;
        zeroBasedCoords.row = height - 1;
    }
    if (zeroBasedCoords.col < 0) {
        colDir = -1;
        zeroBasedCoords.col = 0;
    } else if (zeroBasedCoords.col > 0 && zeroBasedCoords.col >= width) {
        colDir = 1;
        zeroBasedCoords.col = width - 1;
    }
    return {
        rowDir,
        colDir
    };
}
function getTableSize() {
    return {
        width: _class_private_field_get(this, _offset).x + this.countRenderableColumns(),
        height: _class_private_field_get(this, _offset).y + this.countRenderableRows()
    };
}
function findNonHiddenZeroBasedCoordsInSelection(direction) {
    if (![
        'forward',
        'backward'
    ].includes(direction)) {
        return null;
    }
    const topStartCoords = this.getCurrentSelection().getTopStartCorner();
    const bottomEndCoords = this.getCurrentSelection().getBottomEndCorner();
    const [visualRowFrom, visualRowTo, visualColumnFrom, visualColumnTo] = direction === 'forward' ? [
        topStartCoords.row,
        bottomEndCoords.row,
        topStartCoords.col,
        bottomEndCoords.col
    ] : [
        bottomEndCoords.row,
        topStartCoords.row,
        bottomEndCoords.col,
        topStartCoords.col
    ];
    const zeroBasedRow = _class_private_method_get(this, _findFirstNonHiddenZeroBasedRow, findFirstNonHiddenZeroBasedRow).call(this, visualRowFrom, visualRowTo);
    const zeroBasedCol = _class_private_method_get(this, _findFirstNonHiddenZeroBasedColumn, findFirstNonHiddenZeroBasedColumn).call(this, visualColumnFrom, visualColumnTo);
    if (zeroBasedRow === null || zeroBasedCol === null) {
        return null;
    }
    return this.tableApi.createCellCoords(zeroBasedRow, zeroBasedCol);
}
function findFirstNonHiddenZeroBasedRow(visualRowFrom, visualRowTo) {
    const row = this.tableApi.findFirstNonHiddenRenderableRow(visualRowFrom, visualRowTo);
    if (row === null) {
        return null;
    }
    return _class_private_field_get(this, _offset).y + row;
}
function findFirstNonHiddenZeroBasedColumn(visualColumnFrom, visualColumnTo) {
    const column = this.tableApi.findFirstNonHiddenRenderableColumn(visualColumnFrom, visualColumnTo);
    if (column === null) {
        return null;
    }
    return _class_private_field_get(this, _offset).x + column;
}
function visualToZeroBasedCoords(visualCoords) {
    const { row, col } = this.tableApi.visualToRenderableCoords(visualCoords);
    if (row === null || col === null) {
        (0, _errors.throwWithCause)('Renderable coords are not visible.');
    }
    return this.tableApi.createCellCoords(_class_private_field_get(this, _offset).y + row, _class_private_field_get(this, _offset).x + col);
}
function zeroBasedToVisualCoords(zeroBasedCoords) {
    const coords = zeroBasedCoords.clone();
    coords.col = zeroBasedCoords.col - _class_private_field_get(this, _offset).x;
    coords.row = zeroBasedCoords.row - _class_private_field_get(this, _offset).y;
    return this.tableApi.renderableToVisualCoords(coords);
}
(0, _object.mixin)(BaseTransformation, _localHooks.default);
