"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: Object.getOwnPropertyDescriptor(all, name).get
    });
}
_export(exports, {
    get AutoColumnSize () {
        return AutoColumnSize;
    },
    get PLUGIN_KEY () {
        return PLUGIN_KEY;
    },
    get PLUGIN_PRIORITY () {
        return PLUGIN_PRIORITY;
    }
});
const _base = require("../base");
const _feature = require("../../helpers/feature");
const _ghostTable = /*#__PURE__*/ _interop_require_default(require("../../utils/ghostTable"));
const _hooks = require("../../core/hooks");
const _object = require("../../helpers/object");
const _number = require("../../helpers/number");
const _samplesGenerator = /*#__PURE__*/ _interop_require_default(require("../../utils/samplesGenerator"));
const _string = require("../../helpers/string");
const _src = require("../../3rdparty/walkontable/src");
const _translations = require("../../translations");
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
    };
}
_hooks.Hooks.getSingleton().register('modifyAutoColumnSizeSeed');
const PLUGIN_KEY = 'autoColumnSize';
const PLUGIN_PRIORITY = 10;
const COLUMN_SIZE_MAP_NAME = 'autoColumnSize';
var /**
   * `true` value indicates that the #onInit() function has been already called.
   *
   * @type {boolean}
   */ _isInitialized = /*#__PURE__*/ new WeakMap(), /**
   * Cached column header names. It is used to diff current column headers with previous state and detect which
   * columns width should be updated.
   *
   * @type {Array}
   */ _cachedColumnHeaders = /*#__PURE__*/ new WeakMap(), /**
   * An array of column indexes whose width will be recalculated.
   *
   * @type {number[]}
   */ _visualColumnsToRefresh = /*#__PURE__*/ new WeakMap(), /**
   * Disposer function for the column widths map observer. Called on disable to clean up.
   *
   * @type {Function|null}
   */ _disposeMapObserver = /*#__PURE__*/ new WeakMap(), /**
   * Calculates specific columns width (overwrite cache values).
   *
   * @param {number[]} visualColumns List of visual columns to calculate.
   */ _calculateSpecificColumnsWidth = /*#__PURE__*/ new WeakSet(), /**
   * Processes a single column for width calculation.
   *
   * @param {number} visualColumn Visual column index.
   * @param {object} rowsRange Range of rows to process.
   */ _fillGhostTableWithSamples = /*#__PURE__*/ new WeakSet(), /**
   * Updates the column widths map with calculated widths from the ghost table.
   *
   */ _updateColumnWidthsMapBasedOnGhostTable = /*#__PURE__*/ new WeakSet(), /**
   * On before view render listener.
   */ _onBeforeRender = /*#__PURE__*/ new WeakSet(), /**
   * On after load data listener.
   *
   * @param {Array} sourceData Source data.
   * @param {boolean} isFirstLoad `true` if this is the first load.
   */ _onAfterLoadData = /*#__PURE__*/ new WeakSet(), /**
   * On before change listener.
   *
   * @param {Array} changes An array of modified data.
   */ _onBeforeChange = /*#__PURE__*/ new WeakSet(), /**
   * On before column resize listener.
   *
   * @param {number} size Calculated new column width.
   * @param {number} column Visual index of the resized column.
   * @param {boolean} isDblClick  Flag that determines whether there was a double-click.
   * @returns {number}
   */ _onBeforeColumnResize = /*#__PURE__*/ new WeakSet(), /**
   * On after Handsontable init fill plugin with all necessary values.
   */ _onInit = /*#__PURE__*/ new WeakSet(), /**
   * After formulas values updated listener.
   *
   * @param {Array} changes An array of modified data.
   */ _onAfterFormulasValuesUpdate = /*#__PURE__*/ new WeakSet();
class AutoColumnSize extends _base.BasePlugin {
    static get PLUGIN_KEY() {
        return PLUGIN_KEY;
    }
    static get PLUGIN_PRIORITY() {
        return PLUGIN_PRIORITY;
    }
    static get SETTING_KEYS() {
        return true;
    }
    static get DEFAULT_SETTINGS() {
        return {
            useHeaders: true,
            samplingRatio: null,
            allowSampleDuplicates: false
        };
    }
    static get CALCULATION_STEP() {
        return 50;
    }
    static get SYNC_CALCULATION_LIMIT() {
        return 50;
    }
    /**
   * Checks if the plugin is enabled in the handsontable settings. This method is executed in {@link Hooks#beforeInit}
   * hook and if it returns `true` then the {@link #enablePlugin} method is called.
   *
   * @returns {boolean}
   */ isEnabled() {
        return this.hot.getSettings()[PLUGIN_KEY] !== false && !this.hot.getSettings().colWidths;
    }
    /**
   * Enables the plugin functionality for this Handsontable instance.
   */ enablePlugin() {
        if (this.enabled) {
            return;
        }
        this.ghostTable.setSetting('useHeaders', this.getSetting('useHeaders'));
        this.samplesGenerator.setAllowDuplicates(this.getSetting('allowSampleDuplicates'));
        const samplingRatio = this.getSetting('samplingRatio');
        if (samplingRatio && !isNaN(samplingRatio)) {
            this.samplesGenerator.setSampleCount(parseInt(samplingRatio, 10));
        }
        this.addHook('afterLoadData', (...args)=>_class_private_method_get(this, _onAfterLoadData, onAfterLoadData).call(this, ...args));
        this.addHook('beforeChangeRender', (...args)=>_class_private_method_get(this, _onBeforeChange, onBeforeChange).call(this, ...args));
        this.addHook('afterFormulasValuesUpdate', (...args)=>_class_private_method_get(this, _onAfterFormulasValuesUpdate, onAfterFormulasValuesUpdate).call(this, ...args));
        this.addHook('beforeRender', ()=>_class_private_method_get(this, _onBeforeRender, onBeforeRender).call(this));
        this.addHook('modifyColWidth', (width, col)=>this.getColumnWidth(col, width));
        this.addHook('init', ()=>_class_private_method_get(this, _onInit, onInit).call(this));
        _class_private_field_set(this, _disposeMapObserver, this.hot.columnIndexMapper.observeMapChange(this.columnWidthsMap, ()=>{
            this.hot.view?.invalidateColumnWidthCache();
        }));
        super.enablePlugin();
    }
    /**
   * Updates the plugin's state. This method is executed when {@link Core#updateSettings} is invoked.
   */ updatePlugin() {
        _class_private_field_set(this, _visualColumnsToRefresh, this.findColumnsWhereHeaderWasChanged());
        super.updatePlugin();
    }
    /**
   * Disables the plugin functionality for this Handsontable instance.
   */ disablePlugin() {
        if (_class_private_field_get(this, _disposeMapObserver)) {
            _class_private_field_get(this, _disposeMapObserver).call(this);
            _class_private_field_set(this, _disposeMapObserver, null);
        }
        super.disablePlugin();
        // Leave the listener active to allow auto-sizing the columns when the plugin is disabled.
        // This is necessary for width recalculation for resize handler doubleclick (ManualColumnResize).
        this.addHook('beforeColumnResize', (size, column, isDblClick)=>_class_private_method_get(this, _onBeforeColumnResize, onBeforeColumnResize).call(this, size, column, isDblClick));
    }
    /**
   * Calculates widths for visible columns in the viewport only.
   */ calculateVisibleColumnsWidth() {
        // Keep last column widths unchanged for situation when all rows was deleted or trimmed (pro #6)
        if (!this.hot.countRows()) {
            return;
        }
        const firstVisibleColumn = this.getFirstVisibleColumn();
        const lastVisibleColumn = this.getLastVisibleColumn();
        if (firstVisibleColumn === -1 || lastVisibleColumn === -1) {
            return;
        }
        const overwriteCache = this.hot.forceFullRender;
        this.calculateColumnsWidth({
            from: firstVisibleColumn,
            to: lastVisibleColumn
        }, undefined, overwriteCache);
    }
    /**
   * Calculates a columns width.
   *
   * @param {number|object} colRange Visual column index or an object with `from` and `to` visual indexes as a range.
   * @param {number|object} rowRange Visual row index or an object with `from` and `to` visual indexes as a range.
   * @param {boolean} [overwriteCache=false] If `true` the calculation will be processed regardless of whether the width exists in the cache.
   */ calculateColumnsWidth(colRange = {
        from: 0,
        to: this.hot.countCols() - 1
    }, rowRange = {
        from: 0,
        to: this.hot.countRows() - 1
    }, overwriteCache = false) {
        const columnsRange = typeof colRange === 'number' ? {
            from: colRange,
            to: colRange
        } : colRange;
        const rowsRange = typeof rowRange === 'number' ? {
            from: rowRange,
            to: rowRange
        } : rowRange;
        (0, _number.rangeEach)(columnsRange.from, columnsRange.to, (visualColumn)=>{
            let physicalColumn = this.hot.toPhysicalColumn(visualColumn);
            if (physicalColumn === null) {
                physicalColumn = visualColumn;
            }
            if (overwriteCache || this.columnWidthsMap.getValueAtIndex(physicalColumn) === null && !this.hot._getColWidthFromSettings(physicalColumn)) {
                _class_private_method_get(this, _fillGhostTableWithSamples, fillGhostTableWithSamples).call(this, visualColumn, rowsRange);
            }
        });
        if (this.ghostTable.columns.length) {
            _class_private_method_get(this, _updateColumnWidthsMapBasedOnGhostTable, updateColumnWidthsMapBasedOnGhostTable).call(this);
            this.measuredColumns = columnsRange.to + 1;
            this.ghostTable.clean();
        }
    }
    /**
   * Calculates all columns width. The calculated column will be cached in the {@link AutoColumnSize#widths} property.
   * To retrieve width for specified column use {@link AutoColumnSize#getColumnWidth} method.
   *
   * @param {object|number} rowRange Row index or an object with `from` and `to` properties which define row range.
   * @param {boolean} [overwriteCache] If `true` the calculation will be processed regardless of whether the width exists in the cache.
   */ calculateAllColumnsWidth(rowRange = {
        from: 0,
        to: this.hot.countRows() - 1
    }, overwriteCache = false) {
        let current = 0;
        const length = this.hot.countCols() - 1;
        let timer = null;
        this.inProgress = true;
        const loop = ()=>{
            // When hot was destroyed after calculating finished cancel frame
            if (!this.hot) {
                (0, _feature.cancelAnimationFrame)(timer);
                this.inProgress = false;
                return;
            }
            this.calculateColumnsWidth({
                from: current,
                to: Math.min(current + AutoColumnSize.CALCULATION_STEP, length)
            }, rowRange, overwriteCache);
            current = current + AutoColumnSize.CALCULATION_STEP + 1;
            if (current < length) {
                timer = (0, _feature.requestAnimationFrame)(loop);
            } else {
                (0, _feature.cancelAnimationFrame)(timer);
                this.inProgress = false;
                // @TODO Should call once per render cycle, currently fired separately in different plugins
                this.hot.view.adjustElementsSize();
            }
        };
        const syncLimit = this.getSyncCalculationLimit();
        // sync
        if (syncLimit >= 0) {
            this.calculateColumnsWidth({
                from: 0,
                to: syncLimit
            }, rowRange, overwriteCache);
            current = syncLimit + 1;
        }
        // async
        if (current < length) {
            loop();
        } else {
            this.inProgress = false;
        }
    }
    /**
   * Recalculates all columns width (overwrite cache values).
   */ recalculateAllColumnsWidth() {
        if (this.hot.view.isVisible()) {
            this.calculateAllColumnsWidth({
                from: 0,
                to: this.hot.countRows() - 1
            }, true);
        }
    }
    /**
   * Gets value which tells how many columns should be calculated synchronously (rest of the columns will be calculated
   * asynchronously). The limit is calculated based on `syncLimit` set to `autoColumnSize` option (see {@link Options#autoColumnSize}).
   *
   * @returns {number}
   */ getSyncCalculationLimit() {
        const settings = this.hot.getSettings()[PLUGIN_KEY];
        /* eslint-disable no-bitwise */ let limit = AutoColumnSize.SYNC_CALCULATION_LIMIT;
        const colsLimit = this.hot.countCols() - 1;
        if ((0, _object.isObject)(settings)) {
            limit = settings.syncLimit;
            if ((0, _string.isPercentValue)(limit)) {
                limit = (0, _number.valueAccordingPercent)(colsLimit, limit);
            } else {
                // Force to Number
                limit >>= 0;
            }
        }
        return Math.min(limit, colsLimit);
    }
    /**
   * Gets the calculated column width.
   *
   * @param {number} column Visual column index.
   * @param {number} [defaultWidth] Default column width. It will be picked up if no calculated width found.
   * @param {boolean} [keepMinimum=true] If `true` then returned value won't be smaller then 50 (default column width).
   * @returns {number}
   */ getColumnWidth(column, defaultWidth, keepMinimum = true) {
        let width = defaultWidth;
        if (width === undefined) {
            width = this.columnWidthsMap.getValueAtIndex(this.hot.toPhysicalColumn(column));
            if (keepMinimum && typeof width === 'number') {
                width = Math.max(width, _src.DEFAULT_COLUMN_WIDTH);
            }
        }
        return width;
    }
    /**
   * Gets the first visible column.
   *
   * @returns {number} Returns visual column index, -1 if table is not rendered or if there are no columns to base the the calculations on.
   */ getFirstVisibleColumn() {
        return this.hot.getFirstRenderedVisibleColumn() ?? -1;
    }
    /**
   * Gets the last visible column.
   *
   * @returns {number} Returns visual column index or -1 if table is not rendered.
   */ getLastVisibleColumn() {
        return this.hot.getLastRenderedVisibleColumn() ?? -1;
    }
    /**
   * Collects all columns which titles has been changed in comparison to the previous state.
   *
   * @private
   * @returns {Array} It returns an array of visual column indexes.
   */ findColumnsWhereHeaderWasChanged() {
        const columnHeaders = this.hot.getColHeader();
        const changedColumns = columnHeaders.reduce((acc, columnTitle, physicalColumn)=>{
            const cachedColumnsLength = _class_private_field_get(this, _cachedColumnHeaders).length;
            if (cachedColumnsLength - 1 < physicalColumn || _class_private_field_get(this, _cachedColumnHeaders)[physicalColumn] !== columnTitle) {
                acc.push(this.hot.toVisualColumn(physicalColumn));
            }
            if (cachedColumnsLength - 1 < physicalColumn) {
                _class_private_field_get(this, _cachedColumnHeaders).push(columnTitle);
            } else {
                _class_private_field_get(this, _cachedColumnHeaders)[physicalColumn] = columnTitle;
            }
            return acc;
        }, []);
        return changedColumns;
    }
    /**
   * Clears cache of calculated column widths. If you want to clear only selected columns pass an array with their indexes.
   * Otherwise whole cache will be cleared.
   *
   * @param {number[]} [physicalColumns] List of physical column indexes to clear.
   */ clearCache(physicalColumns) {
        if (Array.isArray(physicalColumns)) {
            this.hot.batchExecution(()=>{
                physicalColumns.forEach((physicalIndex)=>{
                    this.columnWidthsMap.setValueAtIndex(physicalIndex, null);
                });
            }, true);
        } else {
            this.columnWidthsMap.clear();
        }
    }
    /**
   * Checks if all widths were calculated. If not then return `true` (need recalculate).
   *
   * @returns {boolean}
   */ isNeedRecalculate() {
        return !!this.columnWidthsMap.getValues().slice(0, this.measuredColumns).filter((item)=>item === null).length;
    }
    /**
   * Destroys the plugin instance.
   */ destroy() {
        this.ghostTable.clean();
        super.destroy();
    }
    constructor(hotInstance){
        super(hotInstance), _class_private_method_init(this, _calculateSpecificColumnsWidth), _class_private_method_init(this, _fillGhostTableWithSamples), _class_private_method_init(this, _updateColumnWidthsMapBasedOnGhostTable), _class_private_method_init(this, _onBeforeRender), _class_private_method_init(this, _onAfterLoadData), _class_private_method_init(this, _onBeforeChange), _class_private_method_init(this, _onBeforeColumnResize), _class_private_method_init(this, _onInit), _class_private_method_init(this, _onAfterFormulasValuesUpdate), _class_private_field_init(this, _isInitialized, {
            writable: true,
            value: void 0
        }), _class_private_field_init(this, _cachedColumnHeaders, {
            writable: true,
            value: void 0
        }), _class_private_field_init(this, _visualColumnsToRefresh, {
            writable: true,
            value: void 0
        }), _class_private_field_init(this, _disposeMapObserver, {
            writable: true,
            value: void 0
        }), /**
   * Instance of {@link GhostTable} for rows and columns size calculations.
   *
   * @private
   * @type {GhostTable}
   */ this.ghostTable = new _ghostTable.default(this.hot), /**
   * Instance of {@link SamplesGenerator} for generating samples necessary for columns width calculations.
   *
   * @private
   * @type {SamplesGenerator}
   * @fires Hooks#modifyAutoColumnSizeSeed
   */ this.samplesGenerator = new _samplesGenerator.default((row, column)=>{
            const physicalRow = this.hot.toPhysicalRow(row);
            const physicalColumn = this.hot.toPhysicalColumn(column);
            if (this.hot.rowIndexMapper.isHidden(physicalRow) || this.hot.columnIndexMapper.isHidden(physicalColumn)) {
                return false;
            }
            const cellMeta = this.hot.getCellMeta(row, column);
            let cellValue = '';
            if (!cellMeta.spanned) {
                cellValue = this.hot.getDataAtCell(row, column);
                if (typeof cellMeta.valueFormatter === 'function') {
                    cellValue = cellMeta.valueFormatter(cellValue, cellMeta);
                }
            }
            let bundleSeed = '';
            if (this.hot.hasHook('modifyAutoColumnSizeSeed')) {
                bundleSeed = this.hot.runHooks('modifyAutoColumnSizeSeed', bundleSeed, cellMeta, cellValue);
            }
            return {
                value: cellValue,
                bundleSeed
            };
        }), /**
   * `true` if the size calculation is in progress.
   *
   * @type {boolean}
   */ this.inProgress = false, /**
   * Number of already measured columns (we already know their sizes).
   *
   * @type {number}
   */ this.measuredColumns = 0, /**
   * PhysicalIndexToValueMap to keep and track widths for physical column indexes.
   *
   * @private
   * @type {PhysicalIndexToValueMap}
   */ this.columnWidthsMap = new _translations.PhysicalIndexToValueMap(), _class_private_field_set(this, _isInitialized, false), _class_private_field_set(this, _cachedColumnHeaders, []), _class_private_field_set(this, _visualColumnsToRefresh, []), _class_private_field_set(this, _disposeMapObserver, null);
        this.hot.columnIndexMapper.registerMap(COLUMN_SIZE_MAP_NAME, this.columnWidthsMap);
        // Leave the listener active to allow auto-sizing the columns when the plugin is disabled.
        // This is necessary for width recalculation for resize handler doubleclick (ManualColumnResize).
        this.addHook('beforeColumnResize', (size, column, isDblClick)=>_class_private_method_get(this, _onBeforeColumnResize, onBeforeColumnResize).call(this, size, column, isDblClick));
    }
}
function calculateSpecificColumnsWidth(visualColumns) {
    const rowsRange = {
        from: 0,
        to: this.hot.countRows() - 1
    };
    visualColumns.forEach((visualColumn)=>{
        const physicalColumn = this.hot.toPhysicalColumn(visualColumn);
        if (physicalColumn === null) {
            return;
        }
        if (!this.hot._getColWidthFromSettings(physicalColumn)) {
            _class_private_method_get(this, _fillGhostTableWithSamples, fillGhostTableWithSamples).call(this, visualColumn, rowsRange);
        }
    });
    if (this.ghostTable.columns.length) {
        _class_private_method_get(this, _updateColumnWidthsMapBasedOnGhostTable, updateColumnWidthsMapBasedOnGhostTable).call(this);
        this.ghostTable.clean();
    }
}
function fillGhostTableWithSamples(visualColumn, rowsRange) {
    const samples = this.samplesGenerator.generateColumnSamples(visualColumn, rowsRange);
    samples.forEach((sample, column)=>this.ghostTable.addColumn(column, sample));
}
function updateColumnWidthsMapBasedOnGhostTable() {
    this.hot.batchExecution(()=>{
        this.ghostTable.getWidths((visualColumn, width)=>{
            const physicalColumn = this.hot.toPhysicalColumn(visualColumn);
            this.columnWidthsMap.setValueAtIndex(physicalColumn, width);
        });
    }, true);
}
function onBeforeRender() {
    this.calculateVisibleColumnsWidth();
    if (!this.inProgress) {
        _class_private_method_get(this, _calculateSpecificColumnsWidth, calculateSpecificColumnsWidth).call(this, _class_private_field_get(this, _visualColumnsToRefresh));
        _class_private_field_set(this, _visualColumnsToRefresh, []);
    }
}
function onAfterLoadData(sourceData, isFirstLoad) {
    if (!isFirstLoad) {
        this.recalculateAllColumnsWidth();
    }
}
function onBeforeChange(changes) {
    const changedColumns = changes.reduce((acc, [, columnProperty])=>{
        const visualColumn = this.hot.propToCol(columnProperty);
        if (Number.isInteger(visualColumn) && acc.indexOf(visualColumn) === -1) {
            acc.push(visualColumn);
        }
        return acc;
    }, []);
    _class_private_field_get(this, _visualColumnsToRefresh).push(...changedColumns);
}
function onBeforeColumnResize(size, column, isDblClick) {
    let newSize = size;
    if (isDblClick) {
        this.calculateColumnsWidth(column, undefined, true);
        newSize = this.getColumnWidth(column, undefined, false);
    }
    return newSize;
}
function onInit() {
    _class_private_field_set(this, _cachedColumnHeaders, this.hot.getColHeader());
    this.recalculateAllColumnsWidth();
    _class_private_field_set(this, _isInitialized, true);
}
function onAfterFormulasValuesUpdate(changes) {
    if (!_class_private_field_get(this, _isInitialized)) {
        return;
    }
    const formulasPlugin = this.hot.getPlugin('formulas');
    const sheetId = formulasPlugin?.sheetId;
    const changedColumns = changes.reduce((acc, change)=>{
        if (sheetId !== null && sheetId !== undefined && change.address?.sheet !== sheetId) {
            return acc;
        }
        const physicalColumn = change.address?.col;
        if (Number.isInteger(physicalColumn)) {
            const visualColumn = this.hot.toVisualColumn(physicalColumn);
            if (acc.indexOf(visualColumn) === -1) {
                acc.push(visualColumn);
            }
        }
        return acc;
    }, []);
    _class_private_field_get(this, _visualColumnsToRefresh).push(...changedColumns);
}
