import { BindingEventService } from '@slickgrid-universal/binding';
import { createDomElement, getInnerSize, getOffset, isPrimitiveOrHTML, stripTags } from '@slickgrid-universal/utils';
import { SlickEventHandler } from '../core/index.js';
import { parseFormatterWhenExist } from '../formatters/formatterUtilities.js';
// using external non-typed js libraries
const DATAGRID_BOTTOM_PADDING = 20;
const DATAGRID_FOOTER_HEIGHT = 25;
const DATAGRID_PAGINATION_HEIGHT = 35;
const DATAGRID_MIN_HEIGHT = 180;
const DATAGRID_MIN_WIDTH = 300;
const DEFAULT_INTERVAL_RETRY_DELAY = 200;
export class ResizerService {
    get autoHeightRecalcRow() {
        return this._autoHeightRecalcRow || 100;
    }
    get isAutoHeightEnabled() {
        return !!(this.gridOptions.enableAutoResize && this.gridOptions.autoResize?.autoHeight);
    }
    get eventHandler() {
        return this._eventHandler;
    }
    /** Getter for the Grid Options pulled through the Grid Object */
    get gridOptions() {
        return this._grid?.getOptions() ?? {};
    }
    /** Getter for the SlickGrid DataView */
    get dataView() {
        return this._grid?.getData();
    }
    /** Getter for the grid uid */
    get gridUid() {
        return this._grid?.getUID() ?? '';
    }
    get gridUidSelector() {
        return this.gridUid ? `.${this.gridUid}` : '';
    }
    get intervalRetryDelay() {
        return this._intervalRetryDelay;
    }
    set intervalRetryDelay(delay) {
        this._intervalRetryDelay = delay;
    }
    get resizeByContentOptions() {
        return this.gridOptions?.resizeByContentOptions ?? {};
    }
    constructor(pubSubService) {
        this.pubSubService = pubSubService;
        this._allHeaderHeight = 0;
        this._autoHeightRecalcRow = 0;
        this._intervalRetryDelay = DEFAULT_INTERVAL_RETRY_DELAY;
        this._isStopResizeIntervalRequested = false;
        this._hasResizedByContentAtLeastOnce = false;
        this._cResizeCheckRequired = false;
        this._totalColumnsWidthByContent = 0;
        this._resizePaused = false;
        this._subscriptions = [];
        this._singleCharWidth = 0; // single char text width
        this._eventHandler = new SlickEventHandler();
        this._bindingEventService = new BindingEventService();
    }
    /** Dispose function when service is destroyed */
    dispose() {
        // unsubscribe all SlickGrid events
        this._eventHandler?.unsubscribeAll();
        this.pubSubService.unsubscribeAll(this._subscriptions);
        clearInterval(this._intervalId);
        clearTimeout(this._timer);
        if (this.gridOptions.autoResize?.resizeDetection === 'container' && this._resizeObserver) {
            this._resizeObserver.disconnect();
        }
        this._bindingEventService.unbindAll();
    }
    init(grid, gridParentContainerElm) {
        if (!grid || !this.gridOptions || !gridParentContainerElm) {
            throw new Error(`
      [Slickgrid-Universal] Resizer Service requires a valid Grid object and DOM Element Container to be provided.
      You can fix this by setting your gridOption to use "enableAutoResize" or create an instance of the ResizerService by calling bindAutoResizeDataGrid() once.`);
        }
        this._grid = grid;
        this._gridContainerElm = gridParentContainerElm;
        const fixedGridSizes = this.gridOptions?.gridHeight || this.gridOptions?.gridWidth
            ? { height: this.gridOptions?.gridHeight, width: this.gridOptions?.gridWidth }
            : undefined;
        this._autoResizeOptions = this.gridOptions?.autoResize ?? { container: 'grid1', bottomPadding: 0 };
        if (fixedGridSizes?.width && gridParentContainerElm?.style) {
            gridParentContainerElm.style.width = typeof fixedGridSizes.width === 'string' ? fixedGridSizes.width : `${fixedGridSizes.width}px`;
        }
        this._gridDomElm = grid.getContainerNode();
        if (typeof this._autoResizeOptions.container === 'string') {
            // prettier-ignore
            this._pageContainerElm = typeof this._autoResizeOptions.container === 'string' ? document.querySelector(this._autoResizeOptions.container) : this._autoResizeOptions.container;
        }
        else {
            this._pageContainerElm = this._autoResizeOptions.container;
        }
        if (fixedGridSizes) {
            this._fixedHeight = fixedGridSizes.height;
            this._fixedWidth = fixedGridSizes.width;
        }
        if (this.gridOptions.enableAutoResize) {
            this._autoHeightRecalcRow = this.gridOptions.autoResize?.autoHeightRecalcRow ?? 100;
            this.bindAutoResizeDataGrid();
        }
        // Events
        if (this.gridOptions.autoResize) {
            // resize by content could be called from the outside by other services via pub/sub event
            this._subscriptions.push(this.pubSubService.subscribe('onFullResizeByContentRequested', () => this.resizeColumnsByCellContent(true)));
        }
        // whenever the autosizeColumns() is called, we'll recalculate our header height total cache
        this._eventHandler.subscribe(this._grid.onAutosizeColumns, () => {
            this.cacheHeaderHeightTotal();
        });
        // on double-click resize, should we resize the cell by its cell content?
        // the same action can be called from a double-click and/or from column header menu
        if (this.gridOptions.enableColumnResizeOnDoubleClick) {
            this._subscriptions.push(this.pubSubService.subscribe('onHeaderMenuColumnResizeByContent', (data) => {
                this.handleSingleColumnResizeByContent(data.columnId);
            }));
            this._eventHandler.subscribe(this._grid.onColumnsResizeDblClick, (_e, args) => {
                this.handleSingleColumnResizeByContent(args.triggeredByColumn);
            });
        }
    }
    /** Bind an auto resize trigger on the datagrid, if that is enable then it will resize itself to the available space
     * Options: we could also provide a % factor to resize on each height/width independently
     */
    bindAutoResizeDataGrid(newSizes) {
        this.saveLastWindowSize();
        if (this.gridOptions.autoResize?.resizeDetection === 'container') {
            if (!this._pageContainerElm) {
                throw new Error(`
          [Slickgrid-Universal] Resizer Service requires a container when gridOption.autoResize.resizeDetection="container"
          You can fix this by setting your gridOption.autoResize.container`);
            }
            if (!this._resizeObserver) {
                this._resizeObserver = new ResizeObserver(() => this.resizeObserverCallback());
            }
            this._resizeObserver.observe(this._pageContainerElm);
        }
        else {
            // if we can't find the grid to resize, return without binding anything
            if (this._gridDomElm === undefined) {
                return null;
            }
            // -- 1st resize the datagrid size at first load (we need this because the .on event is not triggered on first load)
            this.resizeGrid()
                .then(() => this.resizeGridWhenStylingIsBrokenUntilCorrected())
                .catch((rejection) => console.log('Error:', rejection));
            // -- do a 2nd resize with a slight delay (in ms) so that we resize after the grid render is done
            this.resizeGrid(10, newSizes);
            // -- 2nd bind a trigger on the Window DOM element, so that it happens also when resizing after first load
            // -- bind auto-resize to Window object only if it exist
            this._bindingEventService.bind(window, 'resize', () => {
                this.handleResizeGrid(newSizes);
            });
        }
    }
    handleResizeGrid(newSizes) {
        this.pubSubService.publish('onGridBeforeResize');
        this.saveLastWindowSize();
        if (!this._resizePaused) {
            // for some yet unknown reason, calling the resize twice removes any stuttering/flickering
            // when changing the height and makes it much smoother experience
            this.resizeGrid(0, newSizes);
            this.resizeGrid(0, newSizes);
        }
    }
    resizeObserverCallback() {
        if (!this._resizePaused) {
            this.resizeGrid();
        }
    }
    /**
     * cache all header height total which will be used to calculate grid autoHeight with autoResize,
     * if you toggle any of the headers (column, header row, header menu), you should call this method to recalculate the header height total
     */
    cacheHeaderHeightTotal() {
        const topHeaderElm = this._gridContainerElm.querySelector(`${this.gridUidSelector} .slick-topheader-panel`);
        const paneHeaderElm = this._gridContainerElm.querySelector(`${this.gridUidSelector} .slick-pane-header`);
        const headerRowElm = this._gridContainerElm.querySelector(`${this.gridUidSelector} .slick-headerrow`);
        this._allHeaderHeight = (topHeaderElm?.offsetHeight || 0) + (paneHeaderElm?.offsetHeight || 0) + (headerRowElm?.offsetHeight || 0);
    }
    /**
     * Calculate the datagrid new height/width from the available space, also consider that a % factor might be applied to calculation
     * @param {GridOption} gridOptions
     */
    calculateGridNewDimensions(gridOptions) {
        const autoResizeOptions = gridOptions?.autoResize ?? {};
        const gridElmOffset = getOffset(this._gridDomElm);
        if (!window || !this._gridDomElm) {
            return null;
        }
        // calculate bottom padding
        // if using pagination, we need to add the pagination height to this bottom padding
        let bottomPadding = autoResizeOptions?.bottomPadding ?? DATAGRID_BOTTOM_PADDING;
        if (bottomPadding && gridOptions.enablePagination) {
            bottomPadding += DATAGRID_PAGINATION_HEIGHT;
        }
        // optionally show a custom footer with the data metrics(dataset length and last updated timestamp)
        if (bottomPadding && gridOptions.showCustomFooter) {
            const footerHeight = this.gridOptions?.customFooterOptions?.footerHeight ?? DATAGRID_FOOTER_HEIGHT;
            bottomPadding += parseInt(`${footerHeight}`, 10);
        }
        let gridHeight = 0;
        let gridOffsetTop = 0;
        // which DOM element are we using to calculate the available size for the grid?
        if (autoResizeOptions.calculateAvailableSizeBy === 'container') {
            // uses the container's height to calculate grid height without any top offset
            gridHeight = getInnerSize(this._pageContainerElm, 'height') || 0;
        }
        else {
            // uses the browser's window height with its top offset to calculate grid height
            gridHeight = window.innerHeight || 0;
            gridOffsetTop = gridElmOffset.top;
        }
        const availableHeight = gridHeight - gridOffsetTop - bottomPadding;
        const availableWidth = getInnerSize(this._pageContainerElm, 'width') ||
            (autoResizeOptions.calculateAvailableSizeBy !== 'container' && window.innerWidth) ||
            0;
        const maxHeight = autoResizeOptions?.maxHeight;
        const minHeight = autoResizeOptions?.minHeight ?? DATAGRID_MIN_HEIGHT;
        const maxWidth = autoResizeOptions?.maxWidth;
        const minWidth = autoResizeOptions?.minWidth ?? DATAGRID_MIN_WIDTH;
        let newHeight = availableHeight;
        let newWidth = autoResizeOptions?.rightPadding ? availableWidth - autoResizeOptions.rightPadding : availableWidth;
        // when `autoResize.autoHeight` is enabled, we'll calculate the available height by the data length + header height
        if (gridOptions.enableAutoResize && this.isAutoHeightEnabled) {
            const dataLn = this.dataView.getLength();
            if (dataLn < this.autoHeightRecalcRow) {
                this._allHeaderHeight || this.cacheHeaderHeightTotal();
                const dataHeight = dataLn * gridOptions.rowHeight;
                const calcAutoHeight = this._allHeaderHeight + dataHeight;
                if (calcAutoHeight < newHeight) {
                    newHeight = calcAutoHeight;
                }
            }
        }
        // optionally (when defined), make sure that grid height & width are within their thresholds
        if (newHeight < minHeight) {
            newHeight = minHeight;
        }
        if (maxHeight && newHeight > maxHeight) {
            newHeight = maxHeight;
        }
        if (newWidth < minWidth) {
            newWidth = minWidth;
        }
        if (maxWidth && newWidth > maxWidth) {
            newWidth = maxWidth;
        }
        // return the new dimensions unless a fixed height/width was defined
        return {
            height: this._fixedHeight || newHeight,
            width: this._fixedWidth || newWidth,
        };
    }
    /**
     * Return the last resize dimensions used by the service
     * @return {object} last dimensions (height, width)
     */
    getLastResizeDimensions() {
        return this._lastGridSizes;
    }
    /**
     * Provide the possibility to pause the resizer for some time, until user decides to re-enabled it later if he wish to.
     * @param {boolean} isResizePaused are we pausing the resizer?
     */
    pauseResizer(isResizePaused) {
        this._resizePaused = isResizePaused;
    }
    /**
     * Resize the datagrid to fit the browser height & width.
     * @param {number} delay to wait before resizing, defaults to 0 (in milliseconds)
     * @param {object} newSizes can optionally be passed (height, width)
     * @param {object} event that triggered the resize, defaults to null
     * @return If the browser supports it, we can return a Promise that would resolve with the new dimensions
     */
    resizeGrid(delay, newSizes) {
        return new Promise((resolve) => {
            // because of the JavaScript async nature, we might want to delay the resize a little bit
            delay = delay || 0;
            if (delay > 0) {
                clearTimeout(this._timer);
                this._timer = setTimeout(() => resolve(this.resizeGridCallback(newSizes)), delay);
            }
            else {
                resolve(this.resizeGridCallback(newSizes));
            }
        });
    }
    resizeGridCallback(newSizes) {
        const dimensions = this.resizeGridWithDimensions(newSizes);
        this.pubSubService.publish('onGridAfterResize', dimensions);
        // we can call our resize by content here (when enabled)
        // since the core SlickResizer plugin only supports the "autosizeColumns"
        if (this.gridOptions.enableAutoResizeColumnsByCellContent &&
            (!this._lastGridSizes?.width || dimensions?.width !== this._lastGridSizes.width)) {
            this.resizeColumnsByCellContent(false);
        }
        this._lastGridSizes = dimensions;
        return dimensions;
    }
    resizeGridWithDimensions(newSizes) {
        // calculate the available sizes with minimum height defined as a constant
        const availableDimensions = this.calculateGridNewDimensions(this.gridOptions);
        if ((newSizes || availableDimensions) && this._gridDomElm) {
            // get the new sizes, if new sizes are passed (not 0), we will use them else use available space
            // basically if user passes 1 of the dimension, let say he passes just the height,
            // we will use the height as a fixed height but the width will be resized by it's available space
            const newHeight = newSizes?.height ? newSizes.height : availableDimensions?.height;
            const newWidth = newSizes?.width ? newSizes.width : availableDimensions?.width;
            // apply these new height/width to the datagrid
            if (!this.gridOptions.autoHeight) {
                this._gridDomElm.style.height = `${newHeight}px`;
            }
            this._gridDomElm.style.width = typeof newWidth === 'string' ? newWidth : `${newWidth || 1}px`;
            if (this._gridContainerElm) {
                this._gridContainerElm.style.width = typeof newWidth === 'string' ? newWidth : `${newWidth}px`;
            }
            // resize the slickgrid canvas on all browser
            if (this._grid?.resizeCanvas && this._gridContainerElm) {
                this._grid.resizeCanvas();
            }
            // also call the grid auto-size columns so that it takes available space when going bigger
            if (this._grid && this.gridOptions?.enableAutoSizeColumns) {
                // make sure that the grid still exist (by looking if the Grid UID is found in the DOM tree) to avoid SlickGrid error "missing stylesheet"
                if (this.gridUid && document.querySelector(this.gridUidSelector)) {
                    // don't call autosize unless dimension really changed
                    if (!this._lastGridSizes || this._lastGridSizes.height !== newHeight || this._lastGridSizes.width !== newWidth) {
                        this._grid.autosizeColumns();
                    }
                }
            }
            else if (this.gridOptions.enableAutoResizeColumnsByCellContent &&
                (!this._lastGridSizes?.width || newWidth !== this._lastGridSizes.width)) {
                // we can call our resize by content here (when enabled)
                // since the core SlickResizer plugin only supports the "autosizeColumns"
                this.resizeColumnsByCellContent(false);
            }
            // patch for Chromium browsers to avoid scrollbar from showing way too early when using `overflow:auto`
            // the canvas size equals exactly the size of its container and for some users (not all) it will show the horizontal scrollbar too early.
            // not exactly sure but, this might be caused by a floating precision on some computers
            this._gridDomElm.style.width = typeof newWidth === 'string' ? newWidth : `${(newWidth || 1) + 0.2}px`;
            // keep last resized dimensions & resolve them to the Promise
            this._lastGridSizes = {
                height: newHeight || 0,
                width: newWidth || 0,
            };
        }
        return this._lastGridSizes;
    }
    requestStopOfAutoFixResizeGrid(isStopRequired = true) {
        this._isStopResizeIntervalRequested = isStopRequired;
    }
    /**
     * Resize each column width by their cell text/value content (this could potentially go wider than the viewport and end up showing an horizontal scroll).
     * This operation requires to loop through each dataset item to inspect each cell content width and has a performance cost associated to this process.
     *
     * NOTE: please that for performance reasons we will only inspect the first 1000 rows,
     * however user could override it by using the grid option `resizeMaxItemToInspectCellContentWidth` to increase/decrease how many items to inspect.
     * @param {Boolean} recalculateColumnsTotalWidth - defaults to false, do we want to recalculate the necessary total columns width even if it was already calculated?
     */
    resizeColumnsByCellContent(recalculateColumnsTotalWidth = false) {
        const columnDefinitions = this._grid.getColumns();
        const dataset = this.dataView.getItems();
        const columnWidths = {};
        let reRender = false;
        let readItemCount = 0;
        const viewportWidth = this._gridContainerElm?.offsetWidth ?? 0;
        // if our columns total width is smaller than the grid viewport, we can call the column autosize directly without the need to recalculate all column widths
        if (!Array.isArray(dataset) ||
            dataset.length === 0 ||
            (!recalculateColumnsTotalWidth && this._totalColumnsWidthByContent > 0 && this._totalColumnsWidthByContent < viewportWidth)) {
            this._grid.autosizeColumns();
            return;
        }
        if (this._hasResizedByContentAtLeastOnce && this.gridOptions?.resizeByContentOnlyOnFirstLoad && !recalculateColumnsTotalWidth) {
            return;
        }
        this.pubSubService.publish('onBeforeResizeByContent', undefined, 0);
        // calculate total width necessary by each cell content
        // we won't re-evaluate if we already had calculated the total
        if (this._totalColumnsWidthByContent === 0 || recalculateColumnsTotalWidth) {
            // loop through all columns to get their minWidth or width for later usage
            for (const columnDef of columnDefinitions) {
                columnWidths[columnDef.id] = columnDef.originalWidth ?? columnDef.minWidth ?? 0;
            }
            // calculate cell width by reading all data from dataset and also parse through any Formatter(s) when exist
            readItemCount = this.calculateCellWidthByReadingDataset(columnDefinitions, columnWidths, this.resizeByContentOptions.maxItemToInspectCellContentWidth);
            // finally loop through all column definitions one last time to apply new calculated `width` on each elligible column
            let totalColsWidth = 0;
            for (const column of columnDefinitions) {
                const resizeAlwaysRecalculateWidth = column.resizeAlwaysRecalculateWidth ?? this.resizeByContentOptions.alwaysRecalculateColumnWidth ?? false;
                if (column.originalWidth && !resizeAlwaysRecalculateWidth) {
                    column.width = column.originalWidth;
                }
                else if (columnWidths[column.id] !== undefined) {
                    if (column.rerenderOnResize) {
                        reRender = true;
                    }
                    // let's start with column width found in previous column & data analysis
                    this.applyNewCalculatedColumnWidthByReference(column, columnWidths[column.id]);
                }
                // add the new column width to the total width which we'll use later to compare against viewport width
                totalColsWidth += column.width || 0;
                this._totalColumnsWidthByContent = totalColsWidth;
            }
        }
        // send updated column definitions widths to SlickGrid
        this._grid.updateColumns();
        this._hasResizedByContentAtLeastOnce = true;
        const calculateColumnWidths = {};
        for (const columnDef of columnDefinitions) {
            calculateColumnWidths[columnDef.id] = columnDef.width;
        }
        // get the grid container viewport width and if our viewport calculated total columns is greater than the viewport width
        // then we'll call reRenderColumns() when getting wider than viewport or else the default autosizeColumns() when we know we have plenty of space to shrink the columns
        this._totalColumnsWidthByContent > viewportWidth ? this._grid.reRenderColumns(reRender) : this._grid.autosizeColumns();
        this.pubSubService.publish('onAfterResizeByContent', { readItemCount, calculateColumnWidths });
    }
    // --
    // protected functions
    // ------------------
    /**
     * Step 1 - The first step will read through the entire dataset (unless max item count is reached),
     * it will analyze each cell of the grid and calculate its max width via its content and column definition info (it will do so by calling step 2 method while looping through each cell).
     * @param columnOrColumns - single or array of column definition(s)
     * @param columnWidths - column width object that will be updated by reference pointers
     * @param columnIndexOverride - an optional column index, if provided it will override the column index position
     * @returns - count of items that was read
     */
    calculateCellWidthByReadingDataset(columnOrColumns, columnWidths, maxItemToInspect = 1000, columnIndexOverride) {
        const columnDefinitions = Array.isArray(columnOrColumns) ? columnOrColumns : [columnOrColumns];
        const dataset = this.dataView.getItems();
        if (!this._singleCharWidth) {
            this._singleCharWidth = this.getAverageCharWidthByFont();
        }
        // Track the largest sanitized formatted text for each column
        let readItemCount = 0;
        const maxSanitizedTextMap = {};
        for (const [rowIdx, item] of dataset.entries()) {
            if (rowIdx > maxItemToInspect) {
                break;
            }
            columnDefinitions.forEach((columnDef, colIdx) => {
                const formattedData = parseFormatterWhenExist(columnDef?.formatter, rowIdx, columnIndexOverride ?? colIdx, columnDef, item, this._grid);
                const formattedDataSanitized = isPrimitiveOrHTML(formattedData) ? stripTags(formattedData) : '';
                if (!maxSanitizedTextMap[columnDef.id] || formattedDataSanitized.length > maxSanitizedTextMap[columnDef.id].length) {
                    maxSanitizedTextMap[columnDef.id] = formattedDataSanitized;
                }
            });
            readItemCount = rowIdx + 1;
        }
        // After the loop, calculate the pixel width for the largest string per column
        columnDefinitions.forEach((columnDef) => {
            const resizeCellCharWidthInPx = this.resizeByContentOptions.cellCharWidthInPx ?? this._singleCharWidth;
            const charWidthPx = columnDef?.resizeCharWidthInPx ?? resizeCellCharWidthInPx;
            const maxSanitizedText = maxSanitizedTextMap[columnDef.id] || '';
            const formattedTextWidthInPx = Math.ceil(maxSanitizedText.length * charWidthPx);
            const resizeMaxWidthThreshold = columnDef.resizeMaxWidthThreshold;
            let finalWidth = formattedTextWidthInPx;
            if (resizeMaxWidthThreshold !== undefined && formattedTextWidthInPx > resizeMaxWidthThreshold) {
                finalWidth = resizeMaxWidthThreshold;
            }
            else if (columnDef.maxWidth !== undefined && formattedTextWidthInPx > columnDef.maxWidth) {
                finalWidth = columnDef.maxWidth;
            }
            // Use minWidth if larger
            if (columnDef.minWidth !== undefined && finalWidth < columnDef.minWidth) {
                finalWidth = columnDef.minWidth;
            }
            columnWidths[columnDef.id] = finalWidth;
        });
        return readItemCount;
    }
    /** Get an average width in pixel of a single character, we'll make an average by using all alphabetical chars and common symbols and calculate the average. */
    getAverageCharWidthByFont() {
        let charWidth = 0;
        const gCanvas = this._gridContainerElm.querySelector('.grid-canvas');
        if (gCanvas) {
            let isTmpCellCreated = false;
            let sCell = gCanvas.querySelector('.slick-cell');
            if (!sCell) {
                // if we don't have any grid cells yet, let's create a temp one and add it to the grid which we'll remove later
                const sRow = createDomElement('div', { className: 'slick-row' });
                sCell = createDomElement('div', { className: 'slick-cell' });
                sRow.appendChild(sCell);
                gCanvas.appendChild(sRow);
                isTmpCellCreated = true;
            }
            if (sCell) {
                const { fontFamily, fontSize } = getComputedStyle(sCell);
                const ctx = this.getBrowserCanvas();
                ctx.font = `${fontSize} ${fontFamily}`;
                const text = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-={}:<>?,./ ';
                charWidth = ctx.measureText(text).width / text.length;
                isTmpCellCreated && gCanvas.querySelector('.slick-row')?.remove();
            }
        }
        return charWidth;
    }
    getBrowserCanvas() {
        return document.createElement('canvas').getContext('2d');
    }
    /**
     * Step 3 - Apply the new calculated width, it might or might not use this calculated width depending on a few conditions.
     * One of those condition will be to check that the new width doesn't go over a maxWidth and/or a maxWidthThreshold
     * @param {Object} column - column definition to apply the width
     * @param {Number} calculatedColumnWidth - new calculated column width to possibly apply
     */
    applyNewCalculatedColumnWidthByReference(column, calculatedColumnWidth) {
        // read a few optional resize by content grid options
        const resizeCellPaddingWidthInPx = this.resizeByContentOptions.cellPaddingWidthInPx ?? 6;
        const resizeFormatterPaddingWidthInPx = this.resizeByContentOptions.formatterPaddingWidthInPx ?? 6;
        const fieldType = column?.filter?.type ?? column?.type ?? 'string';
        // let's start with column width found in previous column & data analysis
        let newColWidth = calculatedColumnWidth;
        // apply optional ratio which is typically 1, except for string where we use a ratio of around ~0.9 since we have more various thinner characters like (i, l, t, ...)
        const stringWidthRatio = column?.resizeCalcWidthRatio ?? this.resizeByContentOptions.defaultRatioForStringType ?? 0.9;
        newColWidth *= fieldType === 'string' ? stringWidthRatio : 1;
        // apply extra cell padding, custom padding & editor formatter padding
        // --
        newColWidth += resizeCellPaddingWidthInPx;
        if (column.resizeExtraWidthPadding) {
            newColWidth += column.resizeExtraWidthPadding;
        }
        if (column.editor && this.gridOptions.editable) {
            newColWidth += resizeFormatterPaddingWidthInPx;
        }
        // make sure we're not over a column max width and/or optional custom max width threshold
        if (column.maxWidth !== undefined && newColWidth > column.maxWidth) {
            newColWidth = column.maxWidth;
        }
        if (column.resizeMaxWidthThreshold !== undefined && newColWidth > column.resizeMaxWidthThreshold) {
            newColWidth = column.resizeMaxWidthThreshold;
        }
        // make the value the closest bottom integer
        newColWidth = Math.ceil(newColWidth);
        // finally only apply the new width if user didn't yet provide one and/or if user really wants to specifically ask for a recalculate
        // prettier-ignore
        if (column.originalWidth === undefined || column.resizeAlwaysRecalculateWidth === true || this.resizeByContentOptions.alwaysRecalculateColumnWidth === true) {
            column.width = this.readjustNewColumnWidthWhenOverLimit(column, newColWidth);
        }
    }
    handleSingleColumnResizeByContent(columnId) {
        const columnDefinitions = this._grid.getColumns();
        const columnDefIdx = columnDefinitions.findIndex((col) => col.id === columnId);
        if (columnDefIdx >= 0) {
            // provide the initial column width by reference to the calculation and the result will also be returned by reference
            const columnDef = columnDefinitions[columnDefIdx];
            const columnWidths = { [columnId]: columnDef.originalWidth ?? columnDef.minWidth ?? 0 };
            columnDef.originalWidth = undefined; // reset original width since we want to recalculate it
            this.calculateCellWidthByReadingDataset(columnDef, columnWidths, this.resizeByContentOptions.maxItemToInspectSingleColumnWidthByContent, columnDefIdx);
            this.applyNewCalculatedColumnWidthByReference(columnDef, columnWidths[columnId]);
            // finally call the re-render for the UI to render the new column width
            this._grid.reRenderColumns(columnDef?.rerenderOnResize ?? false);
        }
    }
    /**
     * Checks wether the new calculated column width is valid or not, if it's not then return a lower and acceptable width.
     * When using frozen (pinned) column, we cannot make our column wider than the grid viewport because it would become unusable/unscrollable
     * and so if we do reach that threshold then our calculated column width becomes officially invalid
     * @param {Object} column - column definition
     * @param {Number} newColumnWidth - calculated column width input
     * @returns boolean
     */
    readjustNewColumnWidthWhenOverLimit(column, newColumnWidth) {
        const frozenColumnIdx = this.gridOptions.frozenColumn ?? -1;
        const columnIdx = this._grid.getColumns().findIndex((col) => col.id === column.id) ?? 0;
        let adjustedWidth = newColumnWidth;
        if (frozenColumnIdx >= 0 && columnIdx <= frozenColumnIdx) {
            const allViewports = Array.from(this._grid.getViewports());
            if (allViewports) {
                const leftViewportWidth = allViewports.find((viewport) => viewport.classList.contains('slick-viewport-left'))?.clientWidth ?? 0;
                const rightViewportWidth = allViewports.find((viewport) => viewport.classList.contains('slick-viewport-right'))?.clientWidth ?? 0;
                const viewportFullWidth = leftViewportWidth + rightViewportWidth;
                const leftViewportWidthMinusCurrentCol = leftViewportWidth - (column.width ?? 0);
                const isGreaterThanFullViewportWidth = leftViewportWidthMinusCurrentCol + newColumnWidth > viewportFullWidth;
                if (isGreaterThanFullViewportWidth) {
                    const resizeWidthToRemoveFromExceededWidthReadjustment = this.resizeByContentOptions.widthToRemoveFromExceededWidthReadjustment ?? 50;
                    adjustedWidth =
                        leftViewportWidth - leftViewportWidthMinusCurrentCol + rightViewportWidth - resizeWidthToRemoveFromExceededWidthReadjustment;
                }
            }
        }
        return Math.ceil(adjustedWidth);
    }
    /**
     * Just check if the grid is still shown in the DOM/UI (visible to the end user and not hidden by a parent element, e.g. Tab, Accordion, etc.)
     * @returns is grid shown
     */
    isGridVisibleInUI() {
        return !!document.querySelector(`${this.gridUidSelector}`)?.offsetParent;
    }
    /**
     * Patch for SalesForce, some issues arise when having a grid inside a Tab and user clicks in a different Tab without waiting for the grid to be rendered
     * in ideal world, we would simply call a resize when user comes back to the Tab with the grid (tab focused) but this is an extra step and we might not always have this event available.
     * The grid seems broken, the header titles seems to be showing up behind the grid data and the rendering seems broken.
     * Why it happens? Because SlickGrid can resize problem when the DOM element is hidden and that happens when user doesn't wait for the grid to be fully rendered and go in a different Tab.
     *
     * So the patch is to call a grid resize if the following 2 conditions are met
     *   1- header row is Y coordinate 0 (happens when user is not in current Tab)
     *   2- header titles are lower than the viewport of dataset (this can happen when user change Tab and DOM is not shown),
     * for these cases we'll resize until it's no longer true or until we reach a max time limit (70min)
     */
    resizeGridWhenStylingIsBrokenUntilCorrected() {
        // how many time we want to check before really stopping the resize check?
        // We do this because user might be switching to another tab too quickly for the resize be really finished, so better recheck few times to make sure
        const autoFixResizeTimeout = this.gridOptions?.autoFixResizeTimeout ?? 5 * 60 * 60; // interval is 200ms, so 4x is 1sec, so (4 * 60 * 60 = 60min)
        const autoFixResizeRequiredGoodCount = this.gridOptions?.autoFixResizeRequiredGoodCount ?? 5;
        const headerElm = this._gridContainerElm.querySelector(`${this.gridUidSelector} .slick-header`);
        const viewportElm = this._gridContainerElm.querySelector(`${this.gridUidSelector} .slick-viewport`);
        let intervalExecutionCounter = 0;
        let resizeGoodCount = 0;
        if (headerElm && viewportElm && this.gridOptions.autoFixResizeWhenBrokenStyleDetected) {
            const dataLn = this.dataView.getItemCount();
            const columns = this._grid.getColumns() || [];
            this._intervalId = setInterval(async () => {
                if (!this.isGridVisibleInUI()) {
                    this._cResizeCheckRequired = true; // needs to be resized when it becomes visible again
                }
                const headerTitleRowHeight = 44; // this one is set by SASS/CSS so let's hard code it
                const headerPos = getOffset(headerElm);
                let headerOffsetTop = headerPos.top;
                // make sure that there's actual window sizes difference before executing a recalc, otherwise there's no need to go further
                if (this.isGridVisibleInUI() &&
                    (this._cResizeCheckRequired ||
                        !this._lastWindowSize ||
                        this._lastWindowSize.height !== window.innerHeight ||
                        this._lastWindowSize.width !== window.innerWidth)) {
                    if (this.gridOptions?.enableFiltering && this.gridOptions.headerRowHeight) {
                        headerOffsetTop += this.gridOptions.headerRowHeight; // filter row height
                    }
                    if (this.gridOptions?.createPreHeaderPanel && this.gridOptions.showPreHeaderPanel && this.gridOptions.preHeaderPanelHeight) {
                        headerOffsetTop += this.gridOptions.preHeaderPanelHeight; // header grouping titles row height
                    }
                    headerOffsetTop += headerTitleRowHeight; // header title row height
                    const viewportPos = getOffset(viewportElm);
                    const viewportOffsetTop = viewportPos.top;
                    // if header row is Y coordinate 0 (happens when user is not in current Tab) or when header titles are lower than the viewport of dataset (this can happen when user change Tab and DOM is not shown)
                    // another resize condition could be that if the grid location is at coordinate x/y 0/0, we assume that it's in a hidden tab and we'll need to resize whenever that tab becomes active
                    // for these cases we'll resize until it's no longer true or until we reach a max time limit (70min)
                    const containerElmOffset = getOffset(this._gridContainerElm);
                    let isResizeRequired = headerPos?.top === 0 ||
                        headerOffsetTop - viewportOffsetTop > 2 ||
                        (containerElmOffset.left === 0 && containerElmOffset.top === 0)
                        ? true
                        : false;
                    // another condition for a required resize is when the grid is hidden (not in current tab) then its "rightPx" rendered range will be 0px
                    // if that's the case then we know the grid is still hidden and we need to resize it whenever it becomes visible (when its "rightPx" becomes greater than 0 then it's visible)
                    const renderedRangeRightPx = this._grid.getRenderedRange()?.rightPx ?? 0;
                    if (!isResizeRequired && dataLn > 0 && renderedRangeRightPx === 0 && columns.length > 1) {
                        isResizeRequired = true;
                    }
                    // user could choose to manually stop the looped of auto resize fix
                    if (this._isStopResizeIntervalRequested) {
                        isResizeRequired = false;
                        intervalExecutionCounter = autoFixResizeTimeout;
                    }
                    // visible grid (shown to the user and not hidden in another Tab will have an offsetParent defined)
                    if (this.isGridVisibleInUI() && (isResizeRequired || containerElmOffset.left === 0 || containerElmOffset.top === 0)) {
                        await this.resizeGrid();
                        if (resizeGoodCount < 5) {
                            this._grid.updateColumns(); // also refresh header titles after grid becomes visible in new tab, this fixes an issue observed in Salesforce
                        }
                        // make sure the grid is still visible after doing the resize
                        if (this.isGridVisibleInUI()) {
                            isResizeRequired = false;
                            this._cResizeCheckRequired = false;
                        }
                    }
                    // make sure the grid is still visible after optionally doing a resize
                    // if it visible then we can consider it a good resize (it might not be visible if user quickly switch to another Tab)
                    if (this.isGridVisibleInUI()) {
                        resizeGoodCount++;
                    }
                    if (this.isGridVisibleInUI() &&
                        !isResizeRequired &&
                        (resizeGoodCount >= autoFixResizeRequiredGoodCount || intervalExecutionCounter++ >= autoFixResizeTimeout)) {
                        clearInterval(this._intervalId); // stop the interval if we don't need resize or if we passed let say 70min
                    }
                }
            }, this.intervalRetryDelay);
        }
    }
    /** keep reference of the last window size for possible comparisons */
    saveLastWindowSize() {
        this._lastWindowSize = {
            height: window.innerHeight,
            width: window.innerWidth,
        };
    }
}
//# sourceMappingURL=resizer.service.js.map