"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 ColumnSummary () {
        return ColumnSummary;
    },
    get PLUGIN_KEY () {
        return PLUGIN_KEY;
    },
    get PLUGIN_PRIORITY () {
        return PLUGIN_PRIORITY;
    }
});
const _base = require("../base");
const _errors = require("../../helpers/errors");
const _object = require("../../helpers/object");
const _endpoints = /*#__PURE__*/ _interop_require_default(require("./endpoints"));
const _templateLiteralTag = require("../../helpers/templateLiteralTag");
const _utils = require("./utils");
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_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
    };
}
const PLUGIN_KEY = 'columnSummary';
const PLUGIN_PRIORITY = 220;
var /**
   * `afterInit` hook callback.
   */ _onAfterInit = /*#__PURE__*/ new WeakSet(), /**
   * Called after the settings were updated. There is a need to refresh cell metas after the settings update with
   * the `columns` property as the Core resets the cell metas to their initial state.
   *
   * @param {object} settings The settings object.
   */ _onAfterUpdateSettings = /*#__PURE__*/ new WeakSet(), /**
   * `afterChange` hook callback.
   *
   * @param {Array} changes 2D array containing information about each of the edited cells.
   * @param {string} source The string that identifies source of changes.
   */ _onAfterChange = /*#__PURE__*/ new WeakSet(), /**
   * `afterLoadData` hook callback.
   *
   * @param {Array} data The updated data.
   * @param {boolean} firstRun `true` if called on initial load, `false` otherwise.
   */ _onAfterLoadData = /*#__PURE__*/ new WeakSet(), /**
   * `afterUpdateData` hook callback.
   *
   * @param {Array} data The updated data.
   * @param {boolean} firstRun `true` if called on initial load, `false` otherwise.
   */ _onAfterUpdateData = /*#__PURE__*/ new WeakSet(), /**
   * `beforeRowMove` hook callback.
   *
   * @param {Array} rows Array of visual row indexes to be moved.
   * @param {number} finalIndex Visual row index, being a start index for the moved rows. Points to where the elements will be placed after the moving action.
   * To check the visualization of the final index, please take a look at [documentation](@/guides/rows/row-moving/row-moving.md).
   */ _onAfterRowMove = /*#__PURE__*/ new WeakSet();
class ColumnSummary extends _base.BasePlugin {
    static get PLUGIN_KEY() {
        return PLUGIN_KEY;
    }
    static get PLUGIN_PRIORITY() {
        return PLUGIN_PRIORITY;
    }
    /**
   * 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 ColumnSummary#enablePlugin} method is called.
   *
   * @returns {boolean}
   */ isEnabled() {
        return !!this.hot.getSettings()[PLUGIN_KEY];
    }
    /**
   * Enables the plugin functionality for this Handsontable instance.
   */ enablePlugin() {
        if (this.enabled) {
            return;
        }
        this.settings = this.hot.getSettings()[PLUGIN_KEY];
        this.endpoints = new _endpoints.default(this, this.settings);
        this.addHook('afterInit', (...args)=>_class_private_method_get(this, _onAfterInit, onAfterInit).call(this, ...args));
        this.addHook('afterChange', (...args)=>_class_private_method_get(this, _onAfterChange, onAfterChange).call(this, ...args));
        this.addHook('afterUpdateSettings', (...args)=>_class_private_method_get(this, _onAfterUpdateSettings, onAfterUpdateSettings).call(this, ...args));
        this.addHook('afterLoadData', (...args)=>_class_private_method_get(this, _onAfterLoadData, onAfterLoadData).call(this, ...args));
        this.addHook('afterUpdateData', (...args)=>_class_private_method_get(this, _onAfterUpdateData, onAfterUpdateData).call(this, ...args));
        this.addHook('beforeCreateRow', (index, amount, source)=>this.endpoints.resetSetupBeforeStructureAlteration('insert_row', index, amount, null, source)); // eslint-disable-line max-len
        this.addHook('beforeCreateCol', (index, amount, source)=>this.endpoints.resetSetupBeforeStructureAlteration('insert_col', index, amount, null, source)); // eslint-disable-line max-len
        this.addHook('beforeRemoveRow', (...args)=>this.endpoints.resetSetupBeforeStructureAlteration('remove_row', ...args));
        this.addHook('beforeRemoveCol', (...args)=>this.endpoints.resetSetupBeforeStructureAlteration('remove_col', ...args));
        this.addHook('afterCreateRow', (index, amount, source)=>this.endpoints.resetSetupAfterStructureAlteration('insert_row', index, amount, null, source)); // eslint-disable-line max-len
        this.addHook('afterCreateCol', (index, amount, source)=>this.endpoints.resetSetupAfterStructureAlteration('insert_col', index, amount, null, source)); // eslint-disable-line max-len
        this.addHook('afterRemoveRow', (...args)=>this.endpoints.resetSetupAfterStructureAlteration('remove_row', ...args));
        this.addHook('afterRemoveCol', (...args)=>this.endpoints.resetSetupAfterStructureAlteration('remove_col', ...args));
        this.addHook('afterRowMove', (...args)=>_class_private_method_get(this, _onAfterRowMove, onAfterRowMove).call(this, ...args));
        super.enablePlugin();
    }
    /**
   * Disables the plugin functionality for this Handsontable instance.
   */ disablePlugin() {
        this.endpoints = null;
        this.settings = null;
        this.currentEndpoint = null;
        super.disablePlugin();
    }
    /**
   * Updates the plugin's state.
   *
   * This method is executed when [`updateSettings()`](@/api/core.md#updatesettings) is invoked with any of the following configuration options:
   *  - [`columnSummary`](@/api/options.md#columnsummary)
   */ updatePlugin() {
        this.disablePlugin();
        this.enablePlugin();
        this.endpoints.initEndpoints();
        super.updatePlugin();
    }
    /**
   * Calculates math for a single endpoint.
   *
   * @private
   * @param {object} endpoint Contains information about the endpoint.
   */ calculate(endpoint) {
        switch(endpoint.type.toLowerCase()){
            case 'sum':
                endpoint.result = this.calculateSum(endpoint);
                break;
            case 'min':
                endpoint.result = this.calculateMinMax(endpoint, endpoint.type);
                break;
            case 'max':
                endpoint.result = this.calculateMinMax(endpoint, endpoint.type);
                break;
            case 'count':
                endpoint.result = this.countEntries(endpoint);
                break;
            case 'average':
                endpoint.result = this.calculateAverage(endpoint);
                break;
            case 'custom':
                endpoint.result = endpoint.customFunction.call(this, endpoint);
                break;
            default:
                break;
        }
    }
    /**
   * Calculates sum of the values contained in ranges provided in the plugin config.
   *
   * @private
   * @param {object} endpoint Contains the endpoint information.
   * @returns {number} Sum for the selected range.
   */ calculateSum(endpoint) {
        let sum = 0;
        (0, _object.objectEach)(endpoint.ranges, (range)=>{
            sum += this.getPartialSum(range, endpoint.sourceColumn);
        });
        return sum;
    }
    /**
   * Returns partial sum of values from a single row range.
   *
   * @private
   * @param {Array} rowRange Range for the sum.
   * @param {number} col Column index.
   * @returns {number} The partial sum.
   */ getPartialSum(rowRange, col) {
        let sum = 0;
        let i = rowRange[1] || rowRange[0];
        let cellValue = null;
        let biggestDecimalPlacesCount = 0;
        do {
            cellValue = this.getCellValue(i, col);
            cellValue = (0, _utils.isNullishOrNaN)(cellValue) ? null : cellValue;
            if (cellValue !== null) {
                const decimalPlaces = (`${cellValue}`.split('.')[1] || []).length || 1;
                if (decimalPlaces > biggestDecimalPlacesCount) {
                    biggestDecimalPlacesCount = decimalPlaces;
                }
            }
            sum += cellValue || 0;
            i -= 1;
        }while (i >= rowRange[0])
        // Workaround for e.g. 802.2 + 1.1 = 803.3000000000001
        return Math.round(sum * 10 ** biggestDecimalPlacesCount) / 10 ** biggestDecimalPlacesCount;
    }
    /**
   * Calculates the minimal value for the selected ranges.
   *
   * @private
   * @param {object} endpoint Contains the endpoint information.
   * @param {string} type `'min'` or `'max'`.
   * @returns {number} Min or Max value.
   */ calculateMinMax(endpoint, type) {
        let result = null;
        (0, _object.objectEach)(endpoint.ranges, (range)=>{
            const partialResult = this.getPartialMinMax(range, endpoint.sourceColumn, type);
            if (result === null && partialResult !== null) {
                result = partialResult;
            }
            if (partialResult !== null) {
                switch(type){
                    case 'min':
                        result = Math.min(result, partialResult);
                        break;
                    case 'max':
                        result = Math.max(result, partialResult);
                        break;
                    default:
                        break;
                }
            }
        });
        return result === null ? 'Not enough data' : result;
    }
    /**
   * Returns a local minimum of the provided sub-range.
   *
   * @private
   * @param {Array} rowRange Range for the calculation.
   * @param {number} col Column index.
   * @param {string} type `'min'` or `'max'`.
   * @returns {number|null} Min or max value.
   */ getPartialMinMax(rowRange, col, type) {
        let result = null;
        let i = rowRange[1] || rowRange[0];
        let cellValue;
        do {
            cellValue = this.getCellValue(i, col);
            cellValue = (0, _utils.isNullishOrNaN)(cellValue) ? null : cellValue;
            if (result === null) {
                result = cellValue;
            } else if (cellValue !== null) {
                switch(type){
                    case 'min':
                        result = Math.min(result, cellValue);
                        break;
                    case 'max':
                        result = Math.max(result, cellValue);
                        break;
                    default:
                        break;
                }
            }
            i -= 1;
        }while (i >= rowRange[0])
        return result;
    }
    /**
   * Counts empty cells in the provided row range.
   *
   * @private
   * @param {Array} rowRange Row range for the calculation.
   * @param {number} col Column index.
   * @returns {number} Empty cells count.
   */ countEmpty(rowRange, col) {
        let cellValue;
        let counter = 0;
        let i = rowRange[1] || rowRange[0];
        do {
            cellValue = this.getCellValue(i, col);
            cellValue = (0, _utils.isNullishOrNaN)(cellValue) ? null : cellValue;
            if (cellValue === null) {
                counter += 1;
            }
            i -= 1;
        }while (i >= rowRange[0])
        return counter;
    }
    /**
   * Counts non-empty cells in the provided row range.
   *
   * @private
   * @param {object} endpoint Contains the endpoint information.
   * @returns {number} Entry count.
   */ countEntries(endpoint) {
        let result = 0;
        const ranges = endpoint.ranges;
        (0, _object.objectEach)(ranges, (range)=>{
            const partial = range[1] === undefined ? 1 : range[1] - range[0] + 1;
            const emptyCount = this.countEmpty(range, endpoint.sourceColumn);
            result += partial;
            result -= emptyCount;
        });
        return result;
    }
    /**
   * Calculates the average value from the cells in the range.
   *
   * @private
   * @param {object} endpoint Contains the endpoint information.
   * @returns {number} Avarage value.
   */ calculateAverage(endpoint) {
        const sum = this.calculateSum(endpoint);
        const entriesCount = this.countEntries(endpoint);
        return sum / entriesCount;
    }
    /**
   * Returns a cell value, taking into consideration a basic validation.
   *
   * @private
   * @param {number} row Row index.
   * @param {number} col Column index.
   * @returns {string} The cell value.
   */ getCellValue(row, col) {
        const visualRowIndex = this.hot.toVisualRow(row);
        const visualColumnIndex = this.hot.toVisualColumn(col);
        let cellValue = this.hot.getSourceDataAtCell(row, col);
        let cellClassName = '';
        if (visualRowIndex !== null && visualColumnIndex !== null) {
            cellClassName = this.hot.getCellMeta(visualRowIndex, visualColumnIndex).className || '';
        }
        if (cellClassName.indexOf('columnSummaryResult') > -1) {
            return null;
        }
        if (this.endpoints.currentEndpoint.forceNumeric) {
            if (typeof cellValue === 'string') {
                cellValue = cellValue.replace(/,/, '.');
            }
            cellValue = parseFloat(cellValue);
        }
        if (isNaN(cellValue)) {
            if (!this.endpoints.currentEndpoint.suppressDataTypeErrors) {
                (0, _errors.throwWithCause)((0, _templateLiteralTag.toSingleLine)`ColumnSummary plugin: cell at (${row}, ${col}) is not in a\x20
          numeric format. Cannot do the calculation.`);
            }
        }
        return cellValue;
    }
    constructor(...args){
        super(...args), _class_private_method_init(this, _onAfterInit), _class_private_method_init(this, _onAfterUpdateSettings), _class_private_method_init(this, _onAfterChange), _class_private_method_init(this, _onAfterLoadData), _class_private_method_init(this, _onAfterUpdateData), _class_private_method_init(this, _onAfterRowMove), /**
   * The Endpoints class instance. Used to make all endpoint-related operations.
   *
   * @private
   * @type {null|Endpoints}
   */ this.endpoints = null;
    }
}
function onAfterInit() {
    this.endpoints.initEndpoints();
}
function onAfterUpdateSettings(settings) {
    if (settings.columns !== undefined) {
        this.endpoints.refreshCellMetas();
    }
}
function onAfterChange(changes, source) {
    if (changes && source !== 'ColumnSummary.reset' && source !== 'ColumnSummary.set' && source !== 'loadData') {
        this.endpoints.refreshChangedEndpoints(changes);
    }
}
function onAfterLoadData(data, firstRun) {
    if (!firstRun) {
        this.endpoints.refreshAllEndpoints();
    }
}
function onAfterUpdateData(data, firstRun) {
    if (!firstRun) {
        this.endpoints.refreshAllEndpoints();
    }
}
function onAfterRowMove(rows, finalIndex) {
    this.endpoints.resetSetupBeforeStructureAlteration('move_row', rows[0], rows.length, rows, this.pluginName);
    this.endpoints.resetSetupAfterStructureAlteration('move_row', finalIndex, rows.length, rows, this.pluginName);
}
