/**
 * @license
 * Copyright Tyler Technologies, Inc. 
 * License: Apache-2.0
 */
import { addClass, createPredicate, debounce, findWhere, getPropertyValue, isDefined, isFunction, isNumber, isObject, isString, removeAllChildren, removeClass, removeElement, replaceElement, safeCssWidth, toggleClass } from '@tylertech/forge-core';
import { CHECKBOX_CONSTANTS } from '../checkbox';
import { EXPANSION_PANEL_CONSTANTS } from '../expansion-panel';
import { TABLE_CONSTANTS } from './table-constants';
import { CellAlign, SortDirection } from './types';
import { ICON_CONSTANTS } from '../icon';
import { FormFieldComponentDelegate } from '../core/delegates/form-field-component-delegate';
import { BaseComponentDelegate } from '../core';
function isTemplateResultObject(val) {
    return val && typeof val === 'object' && 'content' in val;
}
/**
 * Provides facilities for creating and manipulating a table component.
 */
export class TableUtils {
    /**
     * Creates a table using the provided configuration.
     * @param {ITableConfiguration} configuration The table configuration.
     */
    static createTable(configuration) {
        // Reset the table back to its original unpopulated state
        TableUtils._resetTable(configuration.tableElement);
        // Create the header and body rows
        const thead = TableUtils._createTableHead(configuration);
        const tbody = TableUtils._createTableBody(configuration);
        // If we are allowing click events on the table rows then attach the row click listeners and attributes
        if (configuration.clickListener) {
            TableUtils._attachRowClickListeners(tbody, configuration.clickListener, configuration.doubleClickListener);
            TableUtils._addRowClickAttributes(tbody);
        }
        // Set the fixed state
        if (configuration.fixedHeaders) {
            TableUtils.setFixedHeaders(configuration);
        }
        // Set the resizable state
        if (configuration.resizable) {
            TableUtils.setResizable(configuration);
        }
        // Set the dense state
        if (configuration.dense) {
            TableUtils.setDenseState(configuration.tableElement, configuration.dense);
        }
        // Set the wrap state
        if (configuration.wrapContent) {
            TableUtils.setWrapContentState(configuration.tableElement, configuration.wrapContent);
        }
        // Add the select column
        if (configuration.selectListener) {
            TableUtils._addSelectColumn(thead, tbody, configuration.selectListener, configuration.selectAllListener, configuration.selectAllTemplate, configuration.selectCheckboxAlignment, configuration.data, configuration.tooltipSelect, configuration.tooltipSelectAll);
        }
        if (configuration.resizable || configuration.columnConfigurations.some(c => !!c.sortable)) {
            TableUtils._attachHeadRowMouseDownListener(thead, configuration.headRowMouseDownListener);
            if (TableUtils._isMac()) {
                TableUtils._attachHeadRowContextMenuListener(thead, configuration.headRowContextMenuListener);
            }
        }
        TableUtils.setLayoutType(configuration);
        TableUtils._setTableHead(configuration.tableElement, thead);
        // Add the filter row (must come after adding the select column and table head)
        if (configuration.filter) {
            TableUtils.setFilterRow(configuration);
        }
        TableUtils._setTableBody(configuration.tableElement, tbody);
    }
    static _setTableHead(tableElement, thead) {
        if (tableElement.tHead) {
            replaceElement(thead, tableElement.tHead);
        }
        else {
            tableElement.appendChild(thead);
        }
    }
    static _setTableBody(tableElement, tbody) {
        if (tableElement.tBodies.length) {
            replaceElement(tbody, tableElement.tBodies[0]);
        }
        else {
            tableElement.appendChild(tbody);
        }
    }
    /**
     * Destroys and recreates the table body section only.
     * @param {ITableConfiguration} configuration The table configuration.
     */
    static recreateTableBody(configuration) {
        // Create the table body
        const tbody = TableUtils._createTableBody(configuration);
        // If we are allowing click events on the table rows then attach the row click listeners
        if (configuration.clickListener) {
            TableUtils._attachRowClickListeners(tbody, configuration.clickListener, configuration.doubleClickListener);
            TableUtils._addRowClickAttributes(tbody);
        }
        // Add the select column if necessary
        if (configuration.selectListener) {
            TableUtils._createBodySelectColumn(tbody, configuration.selectCheckboxAlignment, configuration.data, configuration.tooltipSelect, configuration.tooltipSelectAll);
            TableUtils._attachRowSelectListeners(tbody, configuration.selectListener);
        }
        TableUtils._setTableBody(configuration.tableElement, tbody);
    }
    /**
     * Removes all DOM nodes from the table.
     * @param {HTMLTableElement} tableElement The table element to remove all children from.
     */
    static _resetTable(tableElement) {
        removeAllChildren(tableElement);
    }
    /**
     * Creates a `forge-tooltip` for multi sort column headers
     */
    static _createMultisortTooltip() {
        const tooltip = document.createElement('forge-tooltip');
        tooltip.textContent = 'Ctrl + click to sort multiple columns';
        tooltip.placement = 'bottom';
        tooltip.delay = 0;
        return tooltip;
    }
    /**
     * Creates the table header section by adding a row for the column headers based on column configuration.
     * @param columnDataMap The column based data map.
     * @param tableElement The table element.
     */
    static _createTableHead(tableConfiguration) {
        const thead = document.createElement('thead');
        // Create the table head row for our column headers with required class
        const tr = thead.insertRow();
        addClass([TABLE_CONSTANTS.classes.TABLE_ROW, TABLE_CONSTANTS.classes.TABLE_HEAD_ROW], tr);
        // We use this to determine if the initial sort column has already been set during the loop.
        // We set the initial sort to the first column that requests it.
        let setInitialSort = false;
        // Create a header cell for each column in our column data map
        for (let i = 0; i < tableConfiguration.columnConfigurations.length; i++) {
            const columnConfig = tableConfiguration.columnConfigurations[i];
            // Create the th element with required classes
            const th = document.createElement('th');
            th.scope = 'col';
            addClass([TABLE_CONSTANTS.classes.TABLE_CELL, TABLE_CONSTANTS.classes.TABLE_HEAD_CELL], th);
            // We wrap the header text in a div for ease of alignment
            let cellContainer;
            if (columnConfig.sortable) {
                cellContainer = document.createElement('button');
                cellContainer.type = 'button';
                cellContainer.addEventListener('keydown', tableConfiguration.sortableHeadCellKeydownListener);
            }
            else {
                cellContainer = document.createElement('div');
            }
            cellContainer.classList.add(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_CONTAINER);
            // Add tooltip for multisort
            if (tableConfiguration.multiColumnSort) {
                const tooltip = TableUtils._createMultisortTooltip();
                cellContainer.appendChild(tooltip);
            }
            // Set the cell alignment from config
            if (columnConfig.align) {
                TableUtils._setCellAlignmentClass(cellContainer, columnConfig.align);
            }
            // Check if width was specified
            if (isDefined(columnConfig.width)) {
                const width = safeCssWidth(columnConfig.width);
                if (width) {
                    th.style.width = width;
                }
            }
            // Check if the resizable column handle should be appended
            if (tableConfiguration.resizable && columnConfig.resizable !== false) {
                const resizeHandle = document.createElement('div');
                resizeHandle.classList.add(TABLE_CONSTANTS.classes.TABLE_RESIZE_HANDLE);
                th.appendChild(resizeHandle);
            }
            // Check if we were provided any inline style declarations and apply to th AND wrapper content div
            if (isDefined(columnConfig.headerCellStyle) && isObject(columnConfig.headerCellStyle)) {
                Object.assign(th.style, columnConfig.headerCellStyle);
                Object.assign(cellContainer.style, columnConfig.headerCellStyle);
            }
            const span = document.createElement('span');
            span.classList.add(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_TEXT);
            span.textContent = columnConfig.header && typeof columnConfig.header === 'string' ? columnConfig.header.trim() : '';
            if (span.textContent.trim().length === 0) {
                th.setAttribute('aria-hidden', 'true');
            }
            // Add the sort icon if this column is sortable
            if (columnConfig.sortable) {
                th.classList.add(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORTABLE);
                const sortOrderElement = document.createElement('span');
                addClass([TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORT_ORDER_HIDDEN, TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORT_ORDER], sortOrderElement);
                const iconElement = document.createElement(ICON_CONSTANTS.elementName);
                iconElement.name = TABLE_CONSTANTS.icons.SORT_DOWN;
                addClass([TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORT_ICON], iconElement);
                // Ignore the initial sort when multi column sort is enabled
                if (tableConfiguration.multiColumnSort) {
                    if (typeof columnConfig.initialSort === 'object' && columnConfig.initialSort.sortOrder) {
                        sortOrderElement.textContent = columnConfig.initialSort.sortOrder.toString();
                        if (tableConfiguration.columnConfigurations.filter(c => typeof c.initialSort === 'object').length > 1) {
                            removeClass(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORT_ORDER_HIDDEN, sortOrderElement);
                        }
                        th.classList.add(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORTED_DESCENDING);
                        iconElement.classList.add(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORT_ICON_ACTIVE);
                        TableUtils._setColumnSortDirection(th, columnConfig.initialSort.direction);
                    }
                }
                else {
                    if (tableConfiguration.sortedColumn === i && !setInitialSort && !tableConfiguration.multiColumnSort) {
                        th.classList.add(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORTED_DESCENDING);
                        iconElement.classList.add(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORT_ICON_ACTIVE);
                        TableUtils._setColumnSortDirection(th, tableConfiguration.sortDirection);
                        setInitialSort = true;
                    }
                }
                cellContainer.appendChild(iconElement);
                cellContainer.appendChild(sortOrderElement);
            }
            if (typeof columnConfig.headerTemplate === 'function') {
                Promise.resolve(columnConfig.headerTemplate(i, cellContainer, columnConfig)).then(element => {
                    if (element) {
                        const node = document.createElement('div');
                        addClass(TABLE_CONSTANTS.classes.TABLE_CELL_CONTAINER, node);
                        if (typeof element === 'string') {
                            node.innerHTML = element;
                        }
                        else {
                            TableUtils._prepend(element, node);
                        }
                        node.setAttribute(TABLE_CONSTANTS.attributes.CUSTOM_CELL_TEMPLATE, '');
                        TableUtils._prepend(node, cellContainer);
                    }
                });
            }
            else {
                TableUtils._prepend(span, cellContainer);
            }
            th.appendChild(cellContainer);
            tr.appendChild(th);
        }
        return thead;
    }
    /**
     * Sets the sort direction on the table to the provided column.
     * @param tableElement
     * @param columnIndex
     * @param sortDirection
     */
    static setSortDirection(tableElement, columnIndex, sortDirection) {
        const cell = TableUtils._getHeaderCellByIndex(tableElement, columnIndex);
        tableElement.querySelectorAll('th[aria-sort]').forEach(th => th.removeAttribute('aria-sort'));
        TableUtils._setColumnSortDirection(cell, sortDirection);
    }
    /**
     * Sets the sort direction on the provided table header cell element.
     * @param thElement
     * @param sortDirection
     */
    static _setColumnSortDirection(thElement, sortDirection) {
        if (thElement.classList.contains(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORTED_ASCENDING)) {
            thElement.classList.remove(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORTED_ASCENDING);
        }
        if (thElement.classList.contains(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORTED_DESCENDING)) {
            thElement.classList.remove(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORTED_DESCENDING);
        }
        if (!sortDirection || sortDirection === SortDirection.Descending) {
            thElement.setAttribute('aria-sort', 'descending');
            thElement.classList.add(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORTED_DESCENDING);
        }
        else {
            thElement.setAttribute('aria-sort', 'ascending');
            thElement.classList.add(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORTED_ASCENDING);
        }
    }
    /**
     * Creates the table body section by adding rows/cells for our data/column configuration.
     * @param columnDataMap The column based data map.
     * @param tableElement The table element.
     */
    static _createTableBody(configuration) {
        const tbody = document.createElement('tbody');
        const rowData = TableUtils._getOrderedRowData(configuration.columnConfigurations, configuration.data);
        // Create the rows
        rowData.forEach((cellData, rowIndex) => {
            const tr = tbody.insertRow();
            addClass([TABLE_CONSTANTS.classes.TABLE_ROW, TABLE_CONSTANTS.classes.TABLE_BODY_ROW], tr);
            // Create the row data cells
            TableUtils._populateRowCells(configuration, tr, cellData, rowIndex);
            // Check if we need to call the row created callback to give consumers a chance to modify the new row element
            if (typeof configuration.rowCreatedCallback === 'function') {
                configuration.rowCreatedCallback(tr, rowIndex, Object.assign({}, configuration.data[rowIndex].data));
            }
        });
        return tbody;
    }
    /**
     * Populates a table row element with provided data.
     * @param configuration
     * @param tr
     * @param cellData
     * @param rowIndex
     */
    static _populateRowCells(configuration, tr, cellData, rowIndex) {
        for (let i = 0; i < cellData.length; i++) {
            // Find the configuration for this column
            const columnConfig = configuration.columnConfigurations[i];
            // Create the row data cell with required classes
            const td = tr.insertCell();
            addClass([TABLE_CONSTANTS.classes.TABLE_CELL, TABLE_CONSTANTS.classes.TABLE_BODY_CELL], td);
            // Check if width was specified
            if (isDefined(columnConfig.width)) {
                const width = safeCssWidth(columnConfig.width);
                if (width) {
                    td.style.width = width;
                }
            }
            // We wrap the value in a div to allow for flex styling
            const div = document.createElement('div');
            div.classList.add(TABLE_CONSTANTS.classes.TABLE_CELL_CONTAINER);
            // Check if we were provided any inline style declarations and apply to BOTH td and content wrapper div
            if (isDefined(columnConfig.cellStyle) && isObject(columnConfig.cellStyle)) {
                Object.assign(td.style, columnConfig.cellStyle);
                Object.assign(div.style, columnConfig.cellStyle);
            }
            // Add the cell content. If there is a template function, then use that.
            // Otherwise use the property to go get the value from the row data...
            if (columnConfig.template && typeof columnConfig.template === 'function') {
                const rowData = configuration.data[rowIndex] ? configuration.data[rowIndex].data : undefined;
                Promise.resolve(columnConfig.template(rowIndex, div, rowData, i)).then(result => {
                    const config = isTemplateResultObject(result) ? result : { content: result, stopClickPropagation: columnConfig.stopCellTemplateClickPropagation };
                    if (!config.content) {
                        return;
                    }
                    if (typeof config.content === 'string') {
                        div.innerHTML = config.content;
                    }
                    else {
                        div.appendChild(config.content);
                    }
                    div.setAttribute(TABLE_CONSTANTS.attributes.CUSTOM_CELL_TEMPLATE, '');
                    if (config.stopClickPropagation) {
                        div.setAttribute(TABLE_CONSTANTS.attributes.CUSTOM_CELL_TEMPLATE_STOP_PROPAGATION, '');
                    }
                });
            }
            else if (columnConfig.property) {
                // Place the text content in a span
                const span = document.createElement('span');
                span.classList.add(TABLE_CONSTANTS.classes.TABLE_CELL_CONTAINER_TEXT);
                div.appendChild(span);
                if (columnConfig.transform && typeof columnConfig.transform === 'function') {
                    Promise.resolve(columnConfig.transform(cellData[i])).then(value => span.textContent = value);
                }
                else {
                    if (cellData[i] == null) {
                        span.textContent = '';
                    }
                    else {
                        span.textContent = cellData[i].toString();
                    }
                }
            }
            // Set the cell alignment from config
            if (columnConfig.align) {
                TableUtils._setCellAlignmentClass(div, columnConfig.align);
            }
            td.appendChild(div);
            // Check for column span
            if (isDefined(columnConfig.columnSpan)) {
                if (columnConfig.columnSpan === 'all') {
                    td.colSpan = cellData.length - i;
                    break;
                }
                else if (typeof columnConfig.columnSpan === 'number' && columnConfig.columnSpan > 0) {
                    let colspan = columnConfig.columnSpan;
                    if (columnConfig.columnSpan > cellData.length - i) {
                        colspan = cellData.length;
                    }
                    td.colSpan = colspan;
                    i = i + (colspan - 1);
                }
            }
            // Check if we need to call the cell created callback to give consumers a chance to modify the new cell element
            if (typeof configuration.cellCreatedCallback === 'function') {
                configuration.cellCreatedCallback(td, rowIndex, i);
            }
        }
    }
    /**
     * Sets the proper alignment class on the provided element.
     * @param el The element to add the class to.
     * @param align The alignment value.
     */
    static _setCellAlignmentClass(el, align) {
        switch (align) {
            case CellAlign.Center:
                el.classList.add(TABLE_CONSTANTS.classes.TABLE_CELL_CENTER);
                break;
            case CellAlign.Right:
                el.classList.add(TABLE_CONSTANTS.classes.TABLE_CELL_RIGHT);
                break;
        }
    }
    /**
     * Returns all non-expanded rows in a tbody.
     * @param rows All rows in the tbody.
     */
    static _getNonExpandedRows(rows) {
        return Array.from(rows).filter(row => !row.classList.contains(TABLE_CONSTANTS.classes.TABLE_ROW_EXPANDABLE_CONTENT));
    }
    /**
     * Returns all expanded rows in a tbody.
     * @param rows All rows in the tbody.
     */
    static _getExpandedRows(rows) {
        return Array.from(rows).filter(row => row.classList.contains(TABLE_CONSTANTS.classes.TABLE_ROW_EXPANDABLE_CONTENT));
    }
    /**
     * Creates the column data map which organizes the data by column.
     * @param {IColumnConfiguration[]} columnConfigurations The column configurations.
     * @param {IColumnData[]} data The row data.
     */
    static _createColumnDataMap(columnConfigurations, data) {
        return columnConfigurations.map(columnConfig => {
            return {
                config: columnConfig,
                data: data.map(item => {
                    if (columnConfig.property) {
                        const value = getPropertyValue(item.data, columnConfig.property);
                        return isDefined(value) ? value : null;
                    }
                    return null;
                })
            };
        });
    }
    /**
     * Returns the row data in a column ordered fashion.
     * @param columnConfigurations
     * @param data
     */
    static _getOrderedRowData(columnConfigurations, data) {
        const columnDataMap = TableUtils._createColumnDataMap(columnConfigurations, data);
        const rowData = [];
        for (const columnData of columnDataMap) {
            for (let j = 0; j < columnData.data.length; j++) {
                if (!rowData[j]) {
                    rowData[j] = [];
                }
                rowData[j].push(columnData.data[j]);
            }
        }
        return rowData;
    }
    /**
     * Attaches a click listener to each row in the table.
     * @param tbodyElement
     * @param listener
     */
    static _attachRowClickListeners(tbodyElement, clickListener, doubleClickListener) {
        const nonExpandedRows = TableUtils._getNonExpandedRows(tbodyElement.rows);
        nonExpandedRows.forEach(row => {
            row.addEventListener('click', clickListener);
            if (doubleClickListener) {
                row.addEventListener('dblclick', doubleClickListener);
            }
        });
    }
    /**
     * Sets attributes for each row in the table.
     * @param tbodyElement
     * @param clickListener
     */
    static _addRowClickAttributes(tbodyElement) {
        const nonExpandedRows = TableUtils._getNonExpandedRows(tbodyElement.rows);
        nonExpandedRows.forEach(row => {
            row.classList.add(TABLE_CONSTANTS.classes.TABLE_BODY_ROW_CLICKABLE);
        });
    }
    /**
     * Removes attributes for each row in the table.
     * @param tbodyElement
     * @param clickListener
     */
    static _removeRowClickAttributes(tbodyElement) {
        const nonExpandedRows = TableUtils._getNonExpandedRows(tbodyElement.rows);
        nonExpandedRows.forEach(row => {
            row.classList.remove(TABLE_CONSTANTS.classes.TABLE_BODY_ROW_CLICKABLE);
        });
    }
    /**
     * Attaches a click listener to each row checkbox in the table for handling selections.
     * @param tbodyElement
     * @param listener
     */
    static _attachRowSelectListeners(tbodyElement, clickListener) {
        const nonExpandedRows = TableUtils._getNonExpandedRows(tbodyElement.rows);
        nonExpandedRows.forEach(row => TableUtils._attachRowSelectListener(row, clickListener));
    }
    static _attachRowSelectListener(row, clickListener) {
        const checkboxElement = TableUtils._getCheckboxElement(row);
        checkboxElement === null || checkboxElement === void 0 ? void 0 : checkboxElement.addEventListener('click', clickListener);
    }
    /**
     * Removes the select listeners from every table row.
     * @param tbodyElement The table body element.
     * @param listener The click listener.
     */
    static _detachRowSelectListeners(tbodyElement, clickListener) {
        Array.from(tbodyElement.rows).forEach(row => {
            const checkboxElement = TableUtils._getCheckboxElement(row);
            checkboxElement === null || checkboxElement === void 0 ? void 0 : checkboxElement.removeEventListener('click', clickListener);
        });
    }
    /**
     * Removes the click listeners from every table row.
     * @param tbodyElement The table body element.
     * @param listener The click listener.
     */
    static _detachRowClickListeners(tbodyElement, clickListener, doubleClickListener) {
        Array.from(tbodyElement.rows).forEach(row => {
            row.removeEventListener('click', clickListener);
            row.removeEventListener('dblclick', doubleClickListener);
        });
    }
    /**
     * Attaches a click listener to the last table header row select all cell.
     */
    static _attachSelectAllListener(theadElement, listener) {
        const lastTheadRow = theadElement.rows[theadElement.rows.length - 1];
        const checkboxElement = TableUtils._getCheckboxElement(lastTheadRow);
        if (!checkboxElement) {
            throw new Error('Checkbox element not found.');
        }
        checkboxElement.addEventListener('change', listener);
    }
    /**
     * Attaches a click listener to the last table header row select all cell.
     */
    static _tryAttachSelectAllTemplateListener(theadElement, listener) {
        const lastTheadRow = theadElement.rows[theadElement.rows.length - 1];
        const checkboxElement = TableUtils._tryGetSelectAllCheckboxElement(lastTheadRow);
        if (checkboxElement) {
            checkboxElement.addEventListener('change', listener);
        }
    }
    /**
     * Removes the select all click listener.
     * @param theadElement The table head element.
     * @param listener The click listener.
     */
    static _detachSelectAllListener(theadElement, listener) {
        const lastTheadRow = theadElement.rows[theadElement.rows.length - 1];
        const checkboxElement = TableUtils._tryGetSelectAllCheckboxElement(lastTheadRow);
        if (!checkboxElement) {
            return;
        }
        checkboxElement.removeEventListener('change', listener);
    }
    /**
     * Attaches a click listener to the first row of the table header to handle mouse events.
     * @param theadElement
     * @param listener
     */
    static _attachHeadRowMouseDownListener(theadElement, listener) {
        const firstRow = theadElement.rows[0];
        if (!firstRow) {
            throw new Error('Missing table header row. Unable to attach sort listener.');
        }
        firstRow.addEventListener('mousedown', listener);
    }
    /**
     * Attaches a click listener to the first row of the table header to handle context menu events.
     * @param theadElement
     * @param listener
     */
    static _attachHeadRowContextMenuListener(theadElement, listener) {
        const firstRow = theadElement.rows[0];
        if (!firstRow) {
            throw new Error('Missing table header row. Unable to attach sort listener.');
        }
        firstRow.addEventListener('contextmenu', listener);
    }
    /**
     * Creates the select column as the first column in the table.
     * @param theadElement
     * @param tbodyElement
     */
    static _createSelectColumn(theadElement, tbodyElement, showSelectAll, selectAllTemplate, registerListener, selectCheckboxAlignment, data, tooltipSelect, tooltipSelectAll) {
        if (theadElement) {
            TableUtils._createHeadSelectColumn(theadElement, showSelectAll, selectAllTemplate, registerListener, selectCheckboxAlignment, tooltipSelectAll);
        }
        if (tbodyElement) {
            TableUtils._createBodySelectColumn(tbodyElement, selectCheckboxAlignment, data, tooltipSelect, tooltipSelectAll);
        }
    }
    /**
     * Creates the select column in the table head.
     * @param {HTMLTableSectionElement} theadElement The table head element.
     * @param {boolean} showSelectAll Whether to show the select all checkbox or not.
     */
    static _createHeadSelectColumn(theadElement, showSelectAll, selectAllTemplate, registerListener, selectCheckboxAlignment, tooltipSelectAll) {
        var _a;
        Array.from(theadElement.rows).forEach(row => {
            const th = document.createElement('th');
            addClass([TABLE_CONSTANTS.classes.TABLE_CELL, TABLE_CONSTANTS.classes.TABLE_HEAD_CELL], th);
            row.insertBefore(th, row.cells.item(0));
        });
        if (showSelectAll) {
            const lastRowFirstCell = (_a = theadElement.rows.item(theadElement.rows.length - 1)) === null || _a === void 0 ? void 0 : _a.cells.item(0);
            lastRowFirstCell.classList.add(TABLE_CONSTANTS.classes.TABLE_CELL_SELECT);
            if (typeof selectAllTemplate === 'function') {
                TableUtils._renderSelectAllTemplate(selectAllTemplate, theadElement).then(() => {
                    if (typeof registerListener === 'function') {
                        registerListener();
                    }
                });
            }
            else {
                lastRowFirstCell === null || lastRowFirstCell === void 0 ? void 0 : lastRowFirstCell.appendChild(TableUtils._createCheckboxElement(true, selectCheckboxAlignment, null, null, null, tooltipSelectAll));
            }
        }
    }
    /**
     * Creates the select column in the table body.
     * @param {HTMLTableSectionElement} tbodyElement The table body element.
     */
    static _createBodySelectColumn(tbodyElement, selectCheckboxAlignment, data, tooltipSelect, tooltipSelectAll) {
        const nonExpandedRows = TableUtils._getNonExpandedRows(tbodyElement.rows);
        nonExpandedRows.forEach((row, rowIndex) => TableUtils._addRowSelectColumn(row, selectCheckboxAlignment, rowIndex, data[rowIndex], tooltipSelect, tooltipSelectAll));
        // Update the colspan on the expanded rows
        if (tbodyElement.rows.length) {
            const firstRow = tbodyElement.rows.item(0);
            if (firstRow) {
                TableUtils._updateExpandedRowColspan(tbodyElement.rows, firstRow.cells.length);
            }
        }
    }
    static _addRowSelectColumn(row, selectCheckboxAlignment, rowIndex, rowData, tooltipSelect, tooltipSelectAll) {
        const td = row.insertCell(0);
        addClass([TABLE_CONSTANTS.classes.TABLE_CELL, TABLE_CONSTANTS.classes.TABLE_BODY_CELL, TABLE_CONSTANTS.classes.TABLE_CELL_SELECT], td);
        td.appendChild(TableUtils._createCheckboxElement(false, selectCheckboxAlignment, rowIndex, rowData, tooltipSelect, tooltipSelectAll));
    }
    /**
     * Removes the first cell (select cell) in every row in the table head and table body elements.
     * @param {HTMLTableSectionElement} theadElement The table head element.
     * @param {HTMLTableSectionElement} tbodyElement The table body element.
     */
    static _destroySelectColumn(theadElement, tbodyElement) {
        const nonExpandedRows = TableUtils._getNonExpandedRows(tbodyElement.rows);
        Array.from(theadElement.rows).forEach(row => row.removeChild(row.cells.item(0)));
        Array.from(nonExpandedRows).forEach(row => row.removeChild(row.cells.item(0)));
        // Update the colspan on the expanded rows
        if (tbodyElement.rows.length) {
            const firstRow = tbodyElement.rows.item(0);
            if (firstRow) {
                TableUtils._updateExpandedRowColspan(tbodyElement.rows, firstRow.cells.length);
            }
        }
    }
    /**
     * Determines if the table has the select column being rendered currently.
     */
    static _hasSelectColumn(theadElement, tbodyElement) {
        const tableRows = [...Array.from(theadElement.rows), ...Array.from(tbodyElement.rows)];
        return tableRows.some(row => {
            const firstCell = row.cells.item(0);
            return firstCell.classList.contains(TABLE_CONSTANTS.classes.TABLE_CELL_SELECT);
        });
    }
    /**
     * Creates a checkbox element for the select column.
     */
    static _createCheckboxElement(isHeader, alignment, rowIndex, rowData, tooltipSelect, tooltipSelectAll) {
        const checkboxContainer = document.createElement('div');
        checkboxContainer.classList.add(TABLE_CONSTANTS.classes.TABLE_CELL_SELECT_CHECKBOX_CONTAINER);
        if (alignment) {
            switch (alignment) {
                case CellAlign.Center:
                    checkboxContainer.classList.add(TABLE_CONSTANTS.classes.TABLE_CELL_SELECT_CHECKBOX_CONTAINER_ALIGN_CENTER);
                    break;
                case CellAlign.Left:
                    checkboxContainer.classList.add(TABLE_CONSTANTS.classes.TABLE_CELL_SELECT_CHECKBOX_CONTAINER_ALIGN_LEFT);
                    break;
                case CellAlign.Right:
                    checkboxContainer.classList.add(TABLE_CONSTANTS.classes.TABLE_CELL_SELECT_CHECKBOX_CONTAINER_ALIGN_RIGHT);
                    break;
            }
        }
        const checkboxElement = document.createElement(CHECKBOX_CONSTANTS.elementName);
        checkboxElement.setAttribute(TABLE_CONSTANTS.attributes.SELECT_CHECKBOX, '');
        checkboxContainer.appendChild(checkboxElement);
        const tooltipFactory = (text) => {
            const tooltipEl = document.createElement('forge-tooltip');
            tooltipEl.placement = 'left';
            tooltipEl.type = 'label';
            tooltipEl.textContent = text;
            return tooltipEl;
        };
        if (isHeader) {
            const hasTooltipText = typeof tooltipSelectAll === 'string' && tooltipSelectAll.length;
            if (hasTooltipText) {
                const headerTooltipEl = tooltipFactory(tooltipSelectAll);
                checkboxContainer.appendChild(headerTooltipEl);
            }
            else {
                checkboxElement.setAttribute('aria-label', 'Select all rows');
            }
        }
        else {
            let tooltipText = tooltipSelect ? tooltipSelect : '';
            if (rowData && typeof tooltipSelect === 'function') {
                tooltipText = tooltipSelect.call(null, rowIndex, rowData.data);
            }
            if (typeof tooltipText === 'string' && tooltipText.length) {
                const rowTooltipEl = tooltipFactory(tooltipText);
                checkboxContainer.appendChild(rowTooltipEl);
            }
            else {
                checkboxElement.setAttribute('aria-label', 'Select row');
            }
        }
        return checkboxContainer;
    }
    /**
     * Retrieves the checkbox element from the given table row. Used in select mode only.
     * @param rowElement
     */
    static _getCheckboxElement(rowElement) {
        const selectCell = rowElement.querySelector(`.${TABLE_CONSTANTS.classes.TABLE_CELL_SELECT}`);
        if (!selectCell) {
            return null;
        }
        return selectCell.querySelector(TABLE_CONSTANTS.selectors.CHECKBOX_INPUT);
    }
    /**
     * Retrieves the select all checkbox element from the given table row. Used in select mode only.
     * @param rowElement
     */
    static _tryGetSelectAllCheckboxElement(rowElement) {
        const selectAllCell = rowElement.querySelector(`.${TABLE_CONSTANTS.classes.TABLE_CELL_SELECT}`);
        if (!selectAllCell) {
            return null;
        }
        return selectAllCell.querySelector(TABLE_CONSTANTS.selectors.CHECKBOX_INPUT) ||
            selectAllCell.querySelector(TABLE_CONSTANTS.selectors.SELECT_ALL_TEMPLATE_CHECKBOX_INPUT);
    }
    /**
     * Sets the checked state of the select checkbox.
     * @param checkboxElement
     * @param isSelected
     */
    static _setSelectedCheckboxState(checkboxElement, isSelected) {
        if (checkboxElement) {
            checkboxElement.checked = isSelected;
        }
    }
    /**
     * Sets the indeterminate state of the select checkbox.
     * @param checkboxElement
     * @param isIndeterminate
     */
    static _setIndeterminateCheckboxState(checkboxElement, isIndeterminate) {
        if (checkboxElement) {
            checkboxElement.indeterminate = isIndeterminate;
        }
    }
    /**
     * Updates the selected state on the provided table row element.
     * @param rowElement
     * @param isSelected
     */
    static _setRowSelectedState(rowElement, isSelected) {
        if (isSelected) {
            if (!rowElement.classList.contains(TABLE_CONSTANTS.classes.TABLE_BODY_ROW_SELECTED)) {
                rowElement.classList.add(TABLE_CONSTANTS.classes.TABLE_BODY_ROW_SELECTED);
            }
        }
        else {
            rowElement.classList.remove(TABLE_CONSTANTS.classes.TABLE_BODY_ROW_SELECTED);
        }
    }
    /**
     * Resets the colspan on all expanded rows to the proper value.
     */
    static _updateExpandedRowColspan(rows, cellCount) {
        const expandedRows = TableUtils._getExpandedRows(rows);
        expandedRows.forEach(row => {
            const cell = row.cells.item(0);
            if (cell) {
                cell.colSpan = cellCount;
            }
        });
    }
    /**
     * Updates the state of a table row element to be selected or not.
     * @param {HTMLTableRowElement} rowElement The row element.
     * @param {boolean} isSelected Whether the row is selected or not.
     */
    static updateSelectedState(rowElement, isSelected) {
        TableUtils._setRowSelectedState(rowElement, isSelected);
        const selectCheckbox = TableUtils._getCheckboxElement(rowElement);
        if (selectCheckbox) {
            TableUtils._setSelectedCheckboxState(selectCheckbox, isSelected);
        }
    }
    /**
     * Get a row from the first table body by index.
     */
    static getRowByIndex(tableElement, index) {
        return TableUtils._getNonExpandedRows(tableElement.tBodies[0].rows)[index];
    }
    /**
     * Updates the state of the select all checkbox.
     * @param {HTMLTableElement} tableElement The table element.
     * @param {boolean} isAllSelected Whether the checkboxes should be checked or not.
     */
    static updateSelectAllState(tableElement, isAllSelected, isIndeterminate) {
        if (!tableElement.tHead) {
            return;
        }
        const lastTheadRow = tableElement.tHead.rows[tableElement.tHead.rows.length - 1];
        const selectAllCheckboxElement = TableUtils._tryGetSelectAllCheckboxElement(lastTheadRow);
        if (selectAllCheckboxElement) {
            TableUtils._setSelectedCheckboxState(selectAllCheckboxElement, isAllSelected);
            TableUtils._setIndeterminateCheckboxState(selectAllCheckboxElement, !!isIndeterminate);
        }
    }
    /**
     * Sets the selected rows in the table.
     * @param tableElement
     * @param key
     * @param data
     * @param selectedRows
     * @param preserveExisting
     */
    static setSelectedRows(tableElement, key, data, selectedRows, preserveExisting = false) {
        if (!tableElement.tBodies.length) {
            return;
        }
        if (!preserveExisting) {
            TableUtils.clearSelectedRows(tableElement);
        }
        const rows = tableElement.tBodies[0].rows;
        let selectedRowCount = 0;
        if (selectedRows.length) {
            Array.from(rows).forEach((row, index) => {
                const rowData = data[index];
                const existingSelection = findWhere(selectedRows, createPredicate(key, rowData));
                if (existingSelection) {
                    TableUtils._setRowSelectedState(row, true);
                    const selectCheckbox = TableUtils._getCheckboxElement(row);
                    if (selectCheckbox) {
                        TableUtils._setSelectedCheckboxState(selectCheckbox, true);
                    }
                    selectedRowCount++;
                }
            });
        }
        TableUtils.updateSelectAllState(tableElement, selectedRowCount > 0 && rows.length === selectedRowCount);
    }
    /**
     * Clears all selected rows in the table.
     * @param tableElement
     */
    static clearSelectedRows(tableElement) {
        if (!tableElement.tBodies.length) {
            return;
        }
        const nonExpandedRows = TableUtils._getNonExpandedRows(tableElement.tBodies[0].rows);
        nonExpandedRows.forEach(row => {
            TableUtils._setRowSelectedState(row, false);
            const selectCheckbox = TableUtils._getCheckboxElement(row);
            if (selectCheckbox) {
                TableUtils._setSelectedCheckboxState(selectCheckbox, false);
            }
        });
    }
    /**
     * Sets the sorted column in the table.
     * @param tableElement
     * @param columnIndex
     * @param sortDirection
     */
    static setSortedColumn(tableElement, columnIndex, sortDirection) {
        const cell = TableUtils._getHeaderCellByIndex(tableElement, columnIndex);
        // Set the active class on the icon
        const iconElement = TableUtils._getSortIconElementFromHeaderCell(cell);
        iconElement.classList.add(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORT_ICON_ACTIVE);
        // Set the sort direction on the header cell
        TableUtils._setColumnSortDirection(cell, sortDirection);
    }
    /**
     * Sets the sorted column in the table.
     * @param tableElement
     * @param columnIndex
     * @param sortOrder
     */
    static setMultiSortColumnSortNumber(tableElement, hasMultipleColumnsSorted, columnIndex, sortOrder) {
        const cell = TableUtils._getHeaderCellByIndex(tableElement, columnIndex);
        const cellContainer = cell.querySelector(`.${TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_CONTAINER}`);
        const sortOrderElement = cellContainer.querySelector(`.${TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORT_ORDER}`);
        if (sortOrderElement) {
            toggleClass(sortOrderElement, typeof sortOrder !== 'number' || !hasMultipleColumnsSorted, TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORT_ORDER_HIDDEN);
            if (sortOrder) {
                sortOrderElement.textContent = sortOrder.toString();
            }
        }
    }
    /**
     * Removes the sorted column in the table.
     * @param tableElement
     * @param columnIndex
     */
    static removeColumnSort(tableElement, columnIndex) {
        const cell = TableUtils._getHeaderCellByIndex(tableElement, columnIndex);
        cell.removeAttribute('aria-sort');
        // Remove any existing sort direction classes from the existing th element
        if (cell.classList.contains(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORTED_ASCENDING)) {
            cell.classList.remove(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORTED_ASCENDING);
        }
        if (cell.classList.contains(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORTED_DESCENDING)) {
            cell.classList.remove(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORTED_DESCENDING);
        }
        // Remove the active class from the existing active sorted icon element
        const existingIconElement = TableUtils._getSortIconElementFromHeaderCell(cell);
        if (existingIconElement && existingIconElement.classList.contains(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORT_ICON_ACTIVE)) {
            existingIconElement.classList.remove(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORT_ICON_ACTIVE);
        }
    }
    /**
     * Retrieves a header cell from the table based on index.
     * @param {HTMLTableElement} tableElement The table element.
     * @param {number} index The column index.
     */
    static _getHeaderCellByIndex(tableElement, index) {
        if (!tableElement.tHead) {
            throw new Error('Table head element cannot be null.');
        }
        const headerRow = tableElement.tHead.rows.item(0);
        return headerRow.cells.item(index);
    }
    /**
     * Gets the sort icon element from the table cell that contains the select all checkbox.
     * @param {HTMLTableHeaderCellElement} cell The table header cell element.
     */
    static _getSortIconElementFromHeaderCell(cell) {
        return cell.querySelector(`.${TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_SORT_ICON}`);
    }
    /**
     * Adds/removes the select column on the table.
     */
    static setSelectColumnVisibility(tableElement, isVisible, selectListener, selectAllListener, selectAllTemplate, selectCheckboxAlignment, data, tooltipSelect, tooltipSelectAll) {
        const theadElement = tableElement.tHead;
        const tbodyElement = tableElement.tBodies[0];
        if (!theadElement || !tbodyElement) {
            return;
        }
        if (isVisible) {
            TableUtils._addSelectColumn(theadElement, tbodyElement, selectListener, selectAllListener, selectAllTemplate, selectCheckboxAlignment, data, tooltipSelect, tooltipSelectAll);
        }
        else if (TableUtils._hasSelectColumn(theadElement, tbodyElement)) {
            if (selectListener) {
                TableUtils._detachRowSelectListeners(tbodyElement, selectListener);
            }
            if (selectAllListener) {
                TableUtils._detachSelectAllListener(theadElement, selectAllListener);
            }
            TableUtils._destroySelectColumn(theadElement, tbodyElement);
        }
    }
    /**
     * Adds the select column to the thead/tbody elements and attaches the provided click listeners.
     * @param theadElement The table head element.
     * @param tbodyElement The table body element.
     * @param selectListener The row select listener.
     * @param selectAllListener The select all checkbox listener.
     */
    static _addSelectColumn(theadElement, tbodyElement, selectListener, selectAllListener, selectAllTemplate, selectCheckboxAlignment, data, tooltipSelect, tooltipSelectAll) {
        TableUtils._createSelectColumn(theadElement, tbodyElement, !!selectAllListener, selectAllTemplate, () => {
            if (selectAllListener) {
                TableUtils._tryAttachSelectAllTemplateListener(theadElement, selectAllListener);
            }
        }, selectCheckboxAlignment, data, tooltipSelect, tooltipSelectAll);
        if (selectListener) {
            TableUtils._attachRowSelectListeners(tbodyElement, selectListener);
        }
        if (selectAllListener && !selectAllTemplate) {
            TableUtils._tryAttachSelectAllTemplateListener(theadElement, selectAllListener);
        }
    }
    /**
     * Adds/removes the dense class on the table.
     */
    static setDenseState(tableElement, isDense) {
        if (tableElement.classList.contains(TABLE_CONSTANTS.classes.TABLE_DENSE)) {
            tableElement.classList.remove(TABLE_CONSTANTS.classes.TABLE_DENSE);
        }
        if (isDense) {
            tableElement.classList.add(TABLE_CONSTANTS.classes.TABLE_DENSE);
        }
    }
    /**
     * Adds/removes the roomy class on the table.
     */
    static setRoomyState(tableElement, isRoomy) {
        if (tableElement.classList.contains(TABLE_CONSTANTS.classes.TABLE_ROOMY)) {
            tableElement.classList.remove(TABLE_CONSTANTS.classes.TABLE_ROOMY);
        }
        if (isRoomy) {
            tableElement.classList.add(TABLE_CONSTANTS.classes.TABLE_ROOMY);
        }
    }
    static setResizable(configuration) {
        const thead = configuration.tableElement.tHead;
        if (configuration.resizable) {
            configuration.tableElement.classList.add(TABLE_CONSTANTS.classes.TABLE_RESIZABLE);
            TableUtils._addResizeHandles(thead, configuration);
        }
        else {
            configuration.tableElement.classList.remove(TABLE_CONSTANTS.classes.TABLE_RESIZABLE);
            TableUtils._removeResizeHandles(thead, configuration);
        }
    }
    static _addResizeHandles(thead, configuration) {
        if (!thead) {
            return;
        }
        const firstRow = thead.rows.item(0);
        if (firstRow) {
            let cells = Array.from(firstRow.cells);
            // If the select column is on, we need to skip the first cell
            if (configuration.selectListener) {
                cells = cells.slice(1);
            }
            for (const cell of cells) {
                const index = cells.indexOf(cell);
                const columnConfig = configuration.columnConfigurations[index];
                if (columnConfig.resizable !== false) {
                    const resizeHandle = document.createElement('div');
                    resizeHandle.classList.add(TABLE_CONSTANTS.classes.TABLE_RESIZE_HANDLE);
                    cell.appendChild(resizeHandle);
                }
            }
        }
    }
    static _removeResizeHandles(thead, configuration) {
        if (!thead) {
            return;
        }
        const firstRow = thead.rows.item(0);
        if (firstRow) {
            let cells = Array.from(firstRow.cells);
            // If the select column is on, we need to skip the first cell
            if (configuration.selectListener) {
                cells = cells.slice(1);
            }
            for (const cell of cells) {
                const resizeHandle = document.querySelector(`.${TABLE_CONSTANTS.classes.TABLE_RESIZE_HANDLE}`);
                if (resizeHandle) {
                    cell.removeChild(resizeHandle);
                }
            }
        }
    }
    /**
     * Adds/removes the wrap class on the table.
     */
    static setWrapContentState(tableElement, wrapContent) {
        if (wrapContent) {
            tableElement.classList.remove(TABLE_CONSTANTS.classes.TABLE_NO_WRAP_CONTENT);
        }
        else {
            tableElement.classList.add(TABLE_CONSTANTS.classes.TABLE_NO_WRAP_CONTENT);
        }
    }
    /**
     *
     * @param {HTMLTableElement} tableElement The table element.
     * @param {boolean} isVisible Whether the select all option is visible or not.
     */
    static setSelectAllVisibility(tableElement, isVisible, listener, selectAllTemplate, selectCheckboxAlignment, tooltipSelectAll) {
        const theadElement = tableElement.tHead;
        if (!theadElement) {
            return;
        }
        if (!isVisible && listener) {
            TableUtils._detachSelectAllListener(theadElement, listener);
        }
        const lastTheadRow = theadElement.rows[theadElement.rows.length - 1];
        const selectAllCell = lastTheadRow.cells.item(0);
        if (isVisible) {
            // Only add the checkbox if it doesn't already exist
            if (!selectAllCell.childElementCount) {
                selectAllCell.classList.add(TABLE_CONSTANTS.classes.TABLE_CELL_SELECT);
                if (selectAllTemplate) {
                    TableUtils._renderSelectAllTemplate(selectAllTemplate, theadElement).then(() => {
                        if (listener) {
                            TableUtils._tryAttachSelectAllTemplateListener(theadElement, listener);
                        }
                    });
                }
                else {
                    selectAllCell.appendChild(TableUtils._createCheckboxElement(true, selectCheckboxAlignment || null, null, null, null, tooltipSelectAll));
                }
                if (listener && !selectAllTemplate) {
                    TableUtils._attachSelectAllListener(theadElement, listener);
                }
            }
        }
        else {
            if (listener) {
                TableUtils._detachSelectAllListener(theadElement, listener);
            }
            removeAllChildren(selectAllCell);
        }
    }
    /**
     * Controls the visibility of the table filter row.
     * @param {ITableConfiguration} configuration The table configuration.
     */
    static setFilterRow(configuration) {
        if (!configuration.tableElement.tHead) {
            return;
        }
        const filterRowElement = TableUtils._getFilterRowElement(configuration.tableElement.tHead);
        let selectAllCell;
        // Toggle the filter visible class on the table element to control styles of rows/cells
        if (configuration.filter) {
            configuration.tableElement.classList.add(TABLE_CONSTANTS.classes.TABLE_FILTER_VISIBLE);
        }
        else {
            configuration.tableElement.classList.remove(TABLE_CONSTANTS.classes.TABLE_FILTER_VISIBLE);
        }
        if (configuration.filter && !filterRowElement) {
            // We can stop here if there are no filterable columns
            if (configuration.columnConfigurations.every(cc => !cc.filter)) {
                return;
            }
            // Gets the select all cell contents so it can be moved to the new filter row after creating the row element
            if (configuration.selectListener && configuration.selectAllListener) {
                const lastTheadRow = configuration.tableElement.tHead.rows[configuration.tableElement.tHead.rows.length - 1];
                selectAllCell = lastTheadRow.cells.item(0);
            }
            const rowElement = TableUtils._createFilterRowElement(configuration);
            // Move the select all cell to the new filter row
            if (rowElement && selectAllCell) {
                Array.from(selectAllCell.children).forEach(child => {
                    var _a;
                    (_a = rowElement.cells.item(0)) === null || _a === void 0 ? void 0 : _a.appendChild(child);
                });
            }
        }
        else if (!configuration.filter && filterRowElement) {
            // Move the select all cell to the previous row
            if (configuration.selectListener && configuration.selectAllListener) {
                selectAllCell = filterRowElement.cells.item(0);
                const previousRowElementIndex = Array.from(configuration.tableElement.tHead.rows).indexOf(filterRowElement) - 1;
                const previousRowElement = configuration.tableElement.tHead.rows.item(previousRowElementIndex);
                if (previousRowElement) {
                    Array.from(selectAllCell.children).forEach(child => {
                        var _a;
                        (_a = previousRowElement.cells.item(0)) === null || _a === void 0 ? void 0 : _a.appendChild(child);
                    });
                }
            }
            removeElement(filterRowElement);
        }
    }
    /**
     * Creates the table filter row element.
     * @param {ITableConfiguration} configuration The table configuration.
     */
    static _createFilterRowElement(configuration) {
        if (!configuration.tableElement.tHead) {
            throw new Error('Table head element cannot be null.');
        }
        const filterRow = configuration.tableElement.tHead.insertRow();
        addClass([TABLE_CONSTANTS.classes.TABLE_ROW, TABLE_CONSTANTS.classes.TABLE_HEAD_ROW_FILTER], filterRow);
        if (configuration.selectListener) {
            const th = document.createElement('th');
            addClass([TABLE_CONSTANTS.classes.TABLE_CELL, TABLE_CONSTANTS.classes.TABLE_HEAD_CELL], th);
            filterRow.insertBefore(th, filterRow.cells[0]);
            th.classList.add(TABLE_CONSTANTS.classes.TABLE_CELL_SELECT);
        }
        configuration.columnConfigurations.forEach((columnConfig, columnIndex) => {
            const th = document.createElement('th');
            addClass([TABLE_CONSTANTS.classes.TABLE_CELL, TABLE_CONSTANTS.classes.TABLE_HEAD_CELL], th);
            if (columnConfig.filter && isDefined(columnConfig.filterDelegate)) {
                const container = document.createElement('div');
                container.classList.add(TABLE_CONSTANTS.classes.TABLE_HEAD_CELL_CONTAINER);
                if (columnConfig.align) {
                    TableUtils._setCellAlignmentClass(container, columnConfig.align);
                }
                const element = TableUtils._createFilterElement(columnConfig, columnIndex, configuration.filterListener);
                container.appendChild(element);
                th.appendChild(container);
            }
            filterRow.appendChild(th);
        });
        return filterRow;
    }
    /**
     * Creates the element that will be used as the filter for this column.
     * @param {IColumnConfiguration} columnConfig The column configuration.
     */
    static _createFilterElement(columnConfig, columnIndex, filterListener) {
        let delegate;
        if (isFunction(columnConfig.filterDelegate)) {
            delegate = columnConfig.filterDelegate();
        }
        else if (columnConfig.filterDelegate instanceof FormFieldComponentDelegate || columnConfig.filterDelegate instanceof BaseComponentDelegate) {
            delegate = columnConfig.filterDelegate;
        }
        else {
            throw new Error('Invalid filter delegate.');
        }
        // If this is a FormFieldComponentDelegate then we can listen for when the value changes, otherwise we just render the custom delegate element
        if (!!filterListener && delegate instanceof FormFieldComponentDelegate && isFunction(delegate.onChange)) {
            if (!isDefined(columnConfig.filterDebounceTime) || isNumber(columnConfig.filterDebounceTime)) {
                const debounceTime = isDefined(columnConfig.filterDebounceTime) ? columnConfig.filterDebounceTime : TABLE_CONSTANTS.numbers.DEFAULT_FILTER_DEBOUNCE_TIME;
                delegate.onChange(debounce((value) => filterListener(value, columnIndex), debounceTime));
            }
            else {
                delegate.onChange((value) => filterListener(value, columnIndex));
            }
        }
        return delegate.element;
    }
    /**
     * Gets the filter row from the table head element rows, or undefined if not found.
     * @param {HTMLTableRowElement | undefined} tHeadElement The thead element.
     */
    static _getFilterRowElement(tHeadElement) {
        if (!tHeadElement) {
            return;
        }
        return Array.from(tHeadElement.rows).find(r => r.classList.contains(TABLE_CONSTANTS.classes.TABLE_HEAD_ROW_FILTER));
    }
    /**
     * Expands a row by inserting a new row beneath it and displaying the provided template content.
     * @param configuration The table configuration.
     * @param rowIndex The index of the row to expand.
     * @param template The template for the expanded row content.
     */
    static async expandRow(configuration, rowIndex, template) {
        const tbody = configuration.tableElement.tBodies[0];
        const nonExpandedRows = TableUtils._getNonExpandedRows(tbody.rows);
        const requestedRow = nonExpandedRows[rowIndex];
        requestedRow.classList.add(TABLE_CONSTANTS.classes.TABLE_ROW_EXPANDED);
        const actualRowIndex = Array.from(tbody.rows).indexOf(requestedRow);
        const expandedRow = tbody.insertRow(actualRowIndex + 1);
        addClass([TABLE_CONSTANTS.classes.TABLE_ROW, TABLE_CONSTANTS.classes.TABLE_BODY_ROW, TABLE_CONSTANTS.classes.TABLE_ROW_EXPANDABLE_CONTENT], expandedRow);
        const td = expandedRow.insertCell();
        td.setAttribute('colspan', requestedRow.cells.length.toString());
        td.classList.add(TABLE_CONSTANTS.classes.TABLE_ROW_EXPANDABLE_CONTENT_CELL);
        const contentDiv = document.createElement('div');
        const expansionPanel = document.createElement(EXPANSION_PANEL_CONSTANTS.elementName);
        expansionPanel.appendChild(contentDiv);
        td.appendChild(expansionPanel);
        let content = template;
        if (isFunction(template)) {
            const rowData = configuration.data[rowIndex] ? configuration.data[rowIndex].data : undefined;
            const templateValue = template(rowIndex, contentDiv, rowData);
            try {
                const result = await Promise.resolve(templateValue);
                content = isTemplateResultObject(result) ? result.content : result;
            }
            catch (e) {
                console.error('Failed to load row template ' + e.message);
                content = '';
            }
        }
        if (content) {
            TableUtils._setRowTemplate(contentDiv, content);
        }
        window.requestAnimationFrame(() => expansionPanel.open = true);
    }
    /**
     * Sets the template content in the table row element.
     * @param {HTMLElement} element The element to the append the template to.
     * @param {string | HTMLElement} template The template content.
     */
    static _setRowTemplate(element, template) {
        if (isString(template)) {
            element.innerHTML = template;
        }
        else {
            element.appendChild(template);
        }
    }
    /**
     * Collapses an expanded table row.
     * @param configuration The table configuration.
     * @param rowIndex The index of the row to collapse.
     */
    static collapseRow(configuration, rowIndex) {
        if (!configuration.tableElement || !configuration.tableElement.tBodies.length || !configuration.tableElement.tBodies[0].rows.length) {
            return Promise.resolve();
        }
        const tbody = configuration.tableElement.tBodies[0];
        const nonExpandedRows = TableUtils._getNonExpandedRows(tbody.rows);
        const requestedRow = nonExpandedRows[rowIndex];
        const actualRowIndex = Array.from(tbody.rows).indexOf(requestedRow);
        if (requestedRow && requestedRow.classList.contains(TABLE_CONSTANTS.classes.TABLE_ROW_EXPANDED)) {
            const expandableRow = tbody.rows[actualRowIndex + 1];
            const expansionPanel = expandableRow.querySelector(EXPANSION_PANEL_CONSTANTS.elementName);
            if (expansionPanel && expansionPanel.open) {
                const promise = new Promise(resolve => {
                    expansionPanel.addEventListener(EXPANSION_PANEL_CONSTANTS.events.ANIMATION_COMPLETE, () => {
                        requestedRow.classList.remove(TABLE_CONSTANTS.classes.TABLE_ROW_EXPANDED);
                        removeElement(expandableRow);
                        resolve();
                    }, { once: true });
                });
                expansionPanel.open = false;
                return promise;
            }
        }
        return Promise.resolve();
    }
    /**
     * Checks if a row is expanded or not.
     * @param configuration The table configuration.
     * @param rowIndex The row index.
     * @returns {boolean}
     */
    static isRowExpanded(configuration, rowIndex) {
        const tbody = configuration.tableElement.tBodies[0];
        const nonExpandedRows = TableUtils._getNonExpandedRows(tbody.rows);
        const requestedRow = nonExpandedRows[rowIndex];
        return requestedRow && requestedRow.classList.contains(TABLE_CONSTANTS.classes.TABLE_ROW_EXPANDED);
    }
    /**
     * Sets the fixed class on the table element based on whether fixed headers were requested or not.
     * @param configuration The table configuration.
     */
    static setFixedHeaders(configuration) {
        if (configuration.fixedHeaders) {
            configuration.tableElement.classList.add(TABLE_CONSTANTS.classes.TABLE_FIXED);
        }
        else {
            configuration.tableElement.classList.remove(TABLE_CONSTANTS.classes.TABLE_FIXED);
        }
    }
    /**
     * Sets the table layout algorithm.
     * @param configuration The table configuration.
     */
    static setLayoutType(configuration) {
        if (configuration.layoutType === 'fixed') {
            configuration.tableElement.classList.add(TABLE_CONSTANTS.classes.TABLE_LAYOUT_FIXED);
        }
        else {
            configuration.tableElement.classList.remove(TABLE_CONSTANTS.classes.TABLE_LAYOUT_FIXED);
        }
    }
    /**
     * Ensures that any columns width styles are within the bounds of available space.
     * @param configuration The table configuration.
     */
    static normalizeColumnWidths(configuration) {
        const tableElement = configuration.tableElement;
        if (tableElement.tHead && tableElement.tHead.rows.length) {
            const cells = Array.from(tableElement.tHead.rows[0].cells);
            for (const cell of cells) {
                const expectedWidth = getComputedStyle(cell).width;
                if (cell.style.width && cell.style.width !== expectedWidth) {
                    cell.style.width = expectedWidth;
                }
            }
        }
    }
    /**
     * Toggles the visibility of the resize cell indicator for all table cells.
     * @param configuration The table configuration.
     * @param columnIndex The cell index of the column being resized.
     * @param isVisible Whether to show the indicator or not.
     */
    static setColumnResizeIndicatorVisibility(configuration, columnIndex, isVisible) {
        const tableElement = configuration.tableElement;
        if (tableElement.tHead && tableElement.tHead.rows.length) {
            const rows = Array.from(tableElement.tHead.rows);
            for (const row of rows) {
                const cell = row.cells[columnIndex];
                if (cell) {
                    if (isVisible) {
                        cell.classList.add(TABLE_CONSTANTS.classes.TABLE_CELL_RESIZABLE);
                    }
                    else {
                        cell.classList.remove(TABLE_CONSTANTS.classes.TABLE_CELL_RESIZABLE);
                    }
                }
            }
        }
        if (tableElement.tBodies.length) {
            const rows = Array.from(tableElement.tBodies[0].rows);
            for (const row of rows) {
                const cell = row.cells[columnIndex];
                if (cell) {
                    if (isVisible) {
                        cell.classList.add(TABLE_CONSTANTS.classes.TABLE_CELL_RESIZABLE);
                    }
                    else {
                        cell.classList.remove(TABLE_CONSTANTS.classes.TABLE_CELL_RESIZABLE);
                    }
                }
            }
        }
    }
    static setRowClickListeners(tableElement, allowClick, clickListener, doubleClickListener) {
        const tbodyElement = tableElement.tBodies[0];
        if (!tbodyElement) {
            return;
        }
        if (allowClick) {
            TableUtils._attachRowClickListeners(tbodyElement, clickListener, doubleClickListener);
        }
        else {
            TableUtils._detachRowClickListeners(tbodyElement, clickListener, doubleClickListener);
        }
    }
    static setRowClickAttributes(tableElement, allowClick) {
        const tbodyElement = tableElement.tBodies[0];
        if (!tbodyElement) {
            return;
        }
        if (allowClick) {
            TableUtils._addRowClickAttributes(tbodyElement);
        }
        else {
            TableUtils._removeRowClickAttributes(tbodyElement);
        }
    }
    static getOwnerDocument(el) {
        return el.ownerDocument || document;
    }
    static _prepend(element, target) {
        const elementToSetBefore = target.childNodes.item(0);
        if (elementToSetBefore) {
            target.insertBefore(element, elementToSetBefore);
        }
        else {
            target.appendChild(element);
        }
    }
    static _isMac() {
        // Not checking for `MacIntel` because Apple Silicon
        return !!(navigator && navigator.platform && navigator.platform.substr(0, 3) === 'Mac');
    }
    static _renderSelectAllTemplate(selectAllTemplate, theadElement) {
        return Promise.resolve(selectAllTemplate()).then(element => {
            const lastTheadRow = theadElement.rows.item(theadElement.rows.length - 1);
            if (!lastTheadRow) {
                return;
            }
            const targetElement = lastTheadRow.cells.item(0);
            if (element && targetElement) {
                if (typeof element === 'string') {
                    targetElement.innerHTML = element;
                }
                else if (element instanceof HTMLElement) {
                    targetElement.appendChild(element);
                }
                targetElement.setAttribute(TABLE_CONSTANTS.attributes.CUSTOM_CELL_TEMPLATE, '');
            }
        });
    }
}
