import { __decorate } from "tslib";
import { attr, Observable, observable, DOM, volatile } from '@ni/fast-element';
import { Checkbox, DesignSystem, FoundationElement } from '@ni/fast-foundation';
import { createTable as tanStackCreateTable, getCoreRowModel as tanStackGetCoreRowModel, getSortedRowModel as tanStackGetSortedRowModel, getGroupedRowModel as tanStackGetGroupedRowModel, getExpandedRowModel as tanStackGetExpandedRowModel } from '@tanstack/table-core';
import { keyEnter } from '@ni/fast-web-utilities';
import { TableColumn } from '../table-column/base';
import { TableValidator } from './models/table-validator';
import { styles } from './styles';
import { template } from './template';
import { TableColumnSortDirection, TableRowSelectionMode, TableRowSelectionState } from './types';
import { Virtualizer } from './models/virtualizer';
import { getTanStackSortingFunction } from './models/sort-operations';
import { TableLayoutManager } from './models/table-layout-manager';
import { TableUpdateTracker } from './models/table-update-tracker';
import { ColumnInternals } from '../table-column/base/models/column-internals';
import { InteractiveSelectionManager } from './models/interactive-selection-manager';
import { DataHierarchyManager } from './models/data-hierarchy-manager';
import { ExpansionManager } from './models/expansion-manager';
import { waitUntilCustomElementsDefinedAsync } from '../utilities/wait-until-custom-elements-defined-async';
import { ColumnValidator } from '../table-column/base/models/column-validator';
import { uniquifySlotNameForColumnId } from './models/utilities';
import { KeyboardNavigationManager } from './models/keyboard-navigation-manager';
import { borderWidth, controlHeight } from '../theme-provider/design-tokens';
/**
 * A nimble-styled table.
 */
export class Table extends FoundationElement {
    get validity() {
        return this.tableValidator.getValidity();
    }
    /**
     * @internal
     */
    get showRowOperationColumn() {
        return (this.selectionMode === TableRowSelectionMode.multiple
            || this.showCollapseAll);
    }
    /**
     * @internal
     */
    get collapseButtonVisibility() {
        if (!this.canHaveCollapsibleRows) {
            return 'hidden-size-reduced';
        }
        if (this.showCollapseAll) {
            return 'visible';
        }
        return '';
    }
    /**
     * @internal
     */
    get rowHeight() {
        return this._rowHeight;
    }
    constructor() {
        super();
        this.selectionMode = TableRowSelectionMode.none;
        this.actionMenusPreserveSelection = false;
        /**
         * @internal
         */
        this.tableData = [];
        /**
         * @internal
         */
        this.columns = [];
        /**
         * @internal
         */
        this.childItems = [];
        /**
         * @internal
         */
        this.rowElements = [];
        /**
         * @internal
         */
        this.canRenderRows = true;
        /**
         * @internal
         */
        this.scrollX = 0;
        /**
         * @internal
         */
        this.selectionState = TableRowSelectionState.notSelected;
        /**
         * @internal
         */
        this.showCollapseAll = false;
        /**
         * @internal
         */
        this.canHaveCollapsibleRows = false;
        /**
         * @internal
         */
        this.visibleColumns = [];
        /**
         * @internal
         * This value determines the size of the viewport area when a user has created horizontal scrollable
         * space through a column resize operation.
         */
        this.tableScrollableMinWidth = 0;
        this.windowShiftKeyDown = false;
        this.tableValidator = new TableValidator();
        this.tableUpdateTracker = new TableUpdateTracker(this);
        this.columnNotifiers = [];
        this._rowHeight = 0;
        this.isInitialized = false;
        // Programmatically updating the selection state of a checkbox fires the 'change' event.
        // Therefore, selection change events that occur due to programmatically updating
        // the selection checkbox 'checked' value should be ingored.
        // https://github.com/ni/nimble/issues/2739
        this.ignoreSelectionChangeEvents = false;
        // Map from the external slot name to the record ID of the row that should have the slot
        // and the unique slot name that the slot should be slotted into.
        this.requestedSlots = new Map();
        this.borderWidthSubscriber = {
            handleChange: () => {
                this.updateRowHeight();
            }
        };
        this.controlHeightSubscriber = {
            handleChange: () => {
                this.updateRowHeight();
            }
        };
        this.actionMenuSlots = [];
        this.onViewPortScroll = (event) => {
            this.scrollX = event.target.scrollLeft;
        };
        this.onKeyDown = (event) => {
            this.windowShiftKeyDown = event.shiftKey;
        };
        this.onKeyUp = (event) => {
            this.windowShiftKeyDown = event.shiftKey;
        };
        this.onBlur = () => {
            this.windowShiftKeyDown = false;
        };
        this.getRowCanExpand = (row) => {
            return this.expansionManager.isRowExpandable(row);
        };
        this.getIsRowExpanded = (row) => {
            return this.expansionManager.isRowExpanded(row);
        };
        this.handleRowSelectionChange = (updaterOrValue) => {
            const rowSelectionState = updaterOrValue instanceof Function
                ? updaterOrValue(this.table.getState().rowSelection)
                : updaterOrValue;
            this.updateTableOptions({
                state: {
                    rowSelection: rowSelectionState
                }
            });
        };
        this.handleExpandedChange = (updaterOrValue) => {
            const expandedState = updaterOrValue instanceof Function
                ? updaterOrValue(this.table.getState().expanded)
                : updaterOrValue;
            this.updateTableOptions({
                state: {
                    expanded: expandedState
                }
            });
        };
        this.options = {
            data: [],
            onStateChange: (_) => { },
            onRowSelectionChange: this.handleRowSelectionChange,
            onExpandedChange: this.handleExpandedChange,
            getCoreRowModel: tanStackGetCoreRowModel(),
            getSortedRowModel: tanStackGetSortedRowModel(),
            getGroupedRowModel: tanStackGetGroupedRowModel(),
            getExpandedRowModel: tanStackGetExpandedRowModel(),
            getRowCanExpand: this.getRowCanExpand,
            getIsRowExpanded: this.getIsRowExpanded,
            getSubRows: r => r.subRows,
            columns: [],
            state: {
                rowSelection: {},
                grouping: [],
                expanded: true // Workaround until we can apply a fix to TanStack regarding leveraging our getIsRowExpanded implementation
            },
            enableRowSelection: row => !row.getIsGrouped(),
            enableMultiRowSelection: false,
            enableSubRowSelection: false,
            enableSorting: true,
            enableGrouping: true,
            renderFallbackValue: null,
            autoResetAll: false
        };
        this.table = tanStackCreateTable(this.options);
        this.virtualizer = new Virtualizer(this, this.table);
        this.keyboardNavigationManager = new KeyboardNavigationManager(this, this.virtualizer);
        this.layoutManager = new TableLayoutManager(this);
        this.layoutManagerNotifier = Observable.getNotifier(this.layoutManager);
        this.layoutManagerNotifier.subscribe(this, 'isColumnBeingSized');
        this.selectionManager = new InteractiveSelectionManager(this.table, this.selectionMode, this.actionMenusPreserveSelection);
        this.expansionManager = new ExpansionManager(this.table);
    }
    async setData(newData) {
        await this.processPendingUpdates();
        // Make a shallow clone of the record to avoid holding a reference to the client's data object.
        // This also ensures that a data update will always result in a row's record being a new object.
        const clonedData = newData.map(record => ({ ...record }));
        const tanstackUpdates = this.calculateTanStackData(clonedData);
        this.updateTableOptions(tanstackUpdates);
    }
    async getSelectedRecordIds() {
        await this.processPendingUpdates();
        return this.selectionManager.getCurrentSelectedRecordIds();
    }
    async setSelectedRecordIds(recordIds) {
        await this.processPendingUpdates();
        if (this.selectionMode === TableRowSelectionMode.none) {
            return;
        }
        this.updateTableOptions({
            state: {
                rowSelection: this.calculateTanStackSelectionState(recordIds)
            }
        });
    }
    async setRecordHierarchyOptions(hierarchyOptions) {
        await this.processPendingUpdates();
        const clonedOptions = structuredClone(hierarchyOptions);
        const presentOptions = this.tableValidator.getOptionsWithPresentIds(clonedOptions);
        this.expansionManager.setHierarchyOptions(presentOptions);
        this.refreshRows();
    }
    connectedCallback() {
        super.connectedCallback();
        this.initialize();
        this.updateRowHeight();
        this.virtualizer.connect();
        this.viewport.addEventListener('scroll', this.onViewPortScroll, {
            passive: true
        });
        this.keyboardNavigationManager.connect();
        window.addEventListener('keydown', this.onKeyDown);
        window.addEventListener('keyup', this.onKeyUp);
        window.addEventListener('blur', this.onBlur);
        borderWidth.subscribe(this.borderWidthSubscriber, this);
        controlHeight.subscribe(this.controlHeightSubscriber, this);
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        this.virtualizer.disconnect();
        this.keyboardNavigationManager.disconnect();
        this.viewport.removeEventListener('scroll', this.onViewPortScroll);
        window.removeEventListener('keydown', this.onKeyDown);
        window.removeEventListener('keyup', this.onKeyUp);
        window.removeEventListener('blur', this.onBlur);
        borderWidth.unsubscribe(this.borderWidthSubscriber);
        controlHeight.unsubscribe(this.controlHeightSubscriber);
    }
    checkValidity() {
        return this.tableValidator.isValid();
    }
    /**
     * @internal
     *
     * The event handler that is called when a notifier detects a change. Notifiers are added
     * to each column, so `source` is expected to be an instance of `TableColumn`, and `args`
     * is the string name of the property that changed on that column.
     */
    handleChange(source, args) {
        if ((source instanceof TableColumn
            || source instanceof ColumnInternals)
            && typeof args === 'string') {
            this.tableUpdateTracker.trackColumnPropertyChanged(args);
        }
        else if (source instanceof ColumnValidator
            && args === 'isColumnValid') {
            this.tableValidator.validateColumnConfigurations(this.columns);
        }
        else if (source instanceof TableLayoutManager
            && args === 'isColumnBeingSized'
            && !this.layoutManager.isColumnBeingSized) {
            // 'isColumnBeingSized' changing to 'false' indicates an interactive
            // column sizing operation has been completed
            this.emitColumnConfigurationChangeEvent();
        }
    }
    /** @internal */
    onRowSelectionToggle(rowIndex, event) {
        event.stopImmediatePropagation();
        const selectionChanged = this.selectionManager.handleRowSelectionToggle(this.tableData[rowIndex], event.detail.newState, this.windowShiftKeyDown);
        if (selectionChanged) {
            void this.emitSelectionChangeEvent();
        }
    }
    /** @internal */
    onRowClick(rowIndex, event) {
        const selectionChanged = this.selectionManager.handleRowClick(this.tableData[rowIndex], event.shiftKey, event.ctrlKey || event.metaKey);
        if (selectionChanged) {
            void this.emitSelectionChangeEvent();
        }
        return true;
    }
    /** @internal */
    onRowFocusIn(event) {
        this.keyboardNavigationManager.onRowFocusIn(event);
    }
    /** @internal */
    onRowBlur(event) {
        this.keyboardNavigationManager.onRowBlur(event);
    }
    /** @internal */
    onAllRowsSelectionChange(event) {
        event.stopPropagation();
        if (this.ignoreSelectionChangeEvents) {
            return;
        }
        this.table.toggleAllRowsSelected(this.selectionCheckbox.checked);
        void this.emitSelectionChangeEvent();
    }
    /** @internal */
    onRowActionMenuBeforeToggle(rowIndex, event) {
        event.stopImmediatePropagation();
        void this.handleActionMenuBeforeToggleEvent(rowIndex, event);
    }
    /** @internal */
    onRowActionMenuToggle(event) {
        event.stopImmediatePropagation();
        void this.handleRowActionMenuToggleEvent(event);
    }
    /** @internal */
    onRowSlotsRequest(event) {
        event.stopImmediatePropagation();
        for (const slotMetadata of event.detail.slots) {
            const uniqueSlot = uniquifySlotNameForColumnId(event.detail.columnInternalId, slotMetadata.slot);
            this.requestedSlots.set(slotMetadata.name, {
                recordId: event.detail.recordId,
                uniqueSlot
            });
        }
        this.refreshRows();
    }
    /** @internal */
    handleCollapseAllRows() {
        this.expansionManager.collapseAll();
    }
    /** @internal */
    onRightDividerPointerDown(event, columnIndex) {
        if (event.pointerType !== 'mouse' || event.button === 0) {
            this.layoutManager.beginColumnInteractiveSize(event.target, event.pointerId, event.clientX, this.getRightDividerIndex(columnIndex));
        }
    }
    /** @internal */
    onLeftDividerPointerDown(event, columnIndex) {
        if (event.pointerType !== 'mouse' || event.button === 0) {
            this.layoutManager.beginColumnInteractiveSize(event.target, event.pointerId, event.clientX, this.getLeftDividerIndex(columnIndex));
        }
    }
    /** @internal */
    getLeftDividerIndex(columnIndex) {
        return columnIndex * 2 - 1;
    }
    /** @internal */
    getRightDividerIndex(columnIndex) {
        return columnIndex * 2;
    }
    /** @internal */
    handleGroupRowExpanded(rowIndex, event) {
        this.toggleRowExpanded(rowIndex);
        event.stopPropagation();
    }
    /** @internal */
    handleRowExpanded(rowIndex) {
        this.toggleRowExpanded(rowIndex);
    }
    /**
     * @internal
     */
    toggleColumnSort(column, allowMultiSort) {
        if (column.columnInternals.sortingDisabled) {
            return;
        }
        const allSortedColumns = this.getColumnsParticipatingInSorting().sort((x, y) => x.columnInternals.currentSortIndex
            - y.columnInternals.currentSortIndex);
        const columnIndex = allSortedColumns.indexOf(column);
        const columnAlreadySorted = columnIndex > -1;
        const oldSortDirection = column.columnInternals.currentSortDirection;
        let newSortDirection = TableColumnSortDirection.ascending;
        if (columnAlreadySorted) {
            if (oldSortDirection === TableColumnSortDirection.descending) {
                allSortedColumns.splice(columnIndex, 1);
                newSortDirection = TableColumnSortDirection.none;
                column.columnInternals.currentSortIndex = undefined;
            }
            else {
                newSortDirection = TableColumnSortDirection.descending;
            }
        }
        else {
            allSortedColumns.push(column);
        }
        column.columnInternals.currentSortDirection = newSortDirection;
        for (let i = 0; i < allSortedColumns.length; i++) {
            const currentColumn = allSortedColumns[i];
            if (allowMultiSort) {
                allSortedColumns[i].columnInternals.currentSortIndex = i;
            }
            else if (currentColumn === column) {
                currentColumn.columnInternals.currentSortIndex = 0;
            }
            else {
                currentColumn.columnInternals.currentSortIndex = undefined;
                currentColumn.columnInternals.currentSortDirection = TableColumnSortDirection.none;
            }
        }
        this.emitColumnConfigurationChangeEvent();
    }
    /**
     * @internal
     */
    onHeaderKeyDown(column, event) {
        const allowMultiSort = event.shiftKey;
        if (event.key === keyEnter) {
            this.toggleColumnSort(column, allowMultiSort);
        }
        // Return true so that we don't prevent default behavior. Without this, Tab navigation
        // gets stuck on the column headers.
        return true;
    }
    /**
     * @internal
     */
    update() {
        this.validate();
        if (this.tableUpdateTracker.requiresTanStackUpdate) {
            this.updateTanStack();
        }
        if (this.tableUpdateTracker.updateRowParentIds
            || this.tableUpdateTracker.updateGroupRows) {
            const hierarchyEnabled = this.isHierarchyEnabled();
            const hasGroupableColumns = this.columns.some(x => !x.columnInternals.groupingDisabled);
            this.canHaveCollapsibleRows = hierarchyEnabled || hasGroupableColumns;
        }
        if (this.tableUpdateTracker.updateActionMenuSlots) {
            this.updateActionMenuSlots();
        }
        if (this.tableUpdateTracker.updateActionMenusPreserveSelection) {
            this.selectionManager.handleActionMenusPreserveSelectionChanged(this.actionMenusPreserveSelection);
        }
        if (this.tableUpdateTracker.updateColumnWidths) {
            this.rowGridColumns = this.layoutManager.getGridTemplateColumns();
            this.visibleColumns = this.columns.filter(column => !column.columnHidden);
        }
        if (this.tableUpdateTracker.requiresKeyboardFocusReset) {
            this.keyboardNavigationManager.resetFocusState();
        }
    }
    get ariaMultiSelectable() {
        switch (this.selectionMode) {
            case TableRowSelectionMode.multiple:
                return 'true';
            case TableRowSelectionMode.single:
                return 'false';
            default:
                return null;
        }
    }
    /**
     * @internal
     */
    getHeaderContainerElements() {
        return this.columnHeadersContainer.querySelectorAll('.header-container');
    }
    /**
     * @internal
     */
    calculateTanStackData(data) {
        this.dataHierarchyManager = new DataHierarchyManager(data, this.idFieldName, this.parentIdFieldName);
        const tableNodes = this.dataHierarchyManager.hierarchicalData;
        this.tableValidator.setParentIdConfigurationValidity(this.dataHierarchyManager.parentIdConfigurationValid);
        const tanStackUpdates = {
            data: tableNodes
        };
        this.validateWithData(data);
        if (this.tableValidator.areRecordIdsValid()) {
            // Update the selection state to remove previously selected records that no longer exist in the
            // data set while maintaining the selection state of records that still exist in the data set.
            const previousSelection = this.selectionManager.getCurrentSelectedRecordIds();
            tanStackUpdates.state = {
                rowSelection: this.calculateTanStackSelectionState(previousSelection),
                // Reset the TanStack expanded state to "true" to clear TanStack's cache of expanded state that may include
                // rows that no longer exist in the table. The nimble table's expansion manager tracks the expanded
                // state of each row, so no state is lost by resetting TanStack.
                expanded: true
            };
        }
        return tanStackUpdates;
    }
    selectionModeChanged(_prev, _next) {
        if (!this.$fastController.isConnected) {
            return;
        }
        this.tableUpdateTracker.trackSelectionModeChanged();
    }
    actionMenusPreserveSelectionChanged(_prev, _next) {
        if (!this.$fastController.isConnected) {
            return;
        }
        this.tableUpdateTracker.trackActionMenusPreserveSelectionChanged();
    }
    idFieldNameChanged(_prev, _next) {
        if (!this.$fastController.isConnected) {
            return;
        }
        this.tableUpdateTracker.trackIdFieldNameChanged();
    }
    parentIdFieldNameChanged(_prev, _next) {
        if (!this.$fastController.isConnected) {
            return;
        }
        this.tableUpdateTracker.trackParentIdFieldNameChanged();
    }
    columnsChanged(_prev, _next) {
        if (!this.$fastController.isConnected) {
            return;
        }
        this.observeColumns();
        this.tableUpdateTracker.trackColumnInstancesChanged();
    }
    updateRequestedSlotsForOpeningActionMenu(openActionMenuRecordId) {
        for (const actionMenuSlot of this.actionMenuSlots) {
            this.requestedSlots.set(actionMenuSlot, {
                recordId: openActionMenuRecordId,
                uniqueSlot: `row-action-menu-${actionMenuSlot}`
            });
        }
        this.refreshRows();
    }
    async handleActionMenuBeforeToggleEvent(rowIndex, event) {
        const selectionChanged = this.selectionManager.handleActionMenuOpening(this.tableData[rowIndex]);
        if (selectionChanged) {
            await this.emitSelectionChangeEvent();
        }
        this.openActionMenuRecordId = event.detail.operatingRecordId;
        this.updateRequestedSlotsForOpeningActionMenu(this.openActionMenuRecordId);
        const detail = await this.getActionMenuToggleEventDetail(event);
        this.$emit('action-menu-beforetoggle', detail);
    }
    async handleRowActionMenuToggleEvent(event) {
        this.keyboardNavigationManager.onRowActionMenuToggle(event);
        const detail = await this.getActionMenuToggleEventDetail(event);
        this.$emit('action-menu-toggle', detail);
        if (!event.detail.newState) {
            this.openActionMenuRecordId = undefined;
        }
    }
    async getActionMenuToggleEventDetail(originalEvent) {
        let recordIds;
        if (this.selectionMode === TableRowSelectionMode.multiple
            || this.actionMenusPreserveSelection) {
            recordIds = await this.getSelectedRecordIds();
        }
        else {
            recordIds = [this.openActionMenuRecordId];
        }
        return {
            ...originalEvent.detail,
            recordIds
        };
    }
    removeColumnObservers() {
        this.columnNotifiers.forEach(notifier => {
            notifier.unsubscribe(this);
        });
        this.columnNotifiers = [];
    }
    initialize() {
        if (this.isInitialized) {
            // The table is already initialized. There is nothing more to do.
            return;
        }
        this.isInitialized = true;
        // Initialize the controller to ensure that FAST functionality such as Observables work as expected.
        this.$fastController.onConnectedCallback();
        this.tableUpdateTracker.trackAllStateChanged();
        this.observeColumns();
    }
    async processPendingUpdates() {
        this.initialize();
        await DOM.nextUpdate();
        if (this.tableUpdateTracker.hasPendingUpdates) {
            throw new Error('Expected pending updates to be resolved');
        }
    }
    observeColumns() {
        this.removeColumnObservers();
        for (const column of this.columns) {
            const notifier = Observable.getNotifier(column);
            notifier.subscribe(this);
            this.columnNotifiers.push(notifier);
            const notifierInternals = Observable.getNotifier(column.columnInternals);
            notifierInternals.subscribe(this);
            this.columnNotifiers.push(notifierInternals);
            const validatorNotifier = Observable.getNotifier(column.columnInternals.validator);
            validatorNotifier.subscribe(this);
            this.columnNotifiers.push(validatorNotifier);
        }
    }
    getColumnsParticipatingInSorting() {
        return this.columns.filter(x => !x.columnInternals.sortingDisabled
            && x.columnInternals.currentSortDirection
                !== TableColumnSortDirection.none
            && typeof x.columnInternals.currentSortIndex === 'number');
    }
    getColumnsParticipatingInGrouping() {
        return this.columns.filter(x => !x.columnInternals.groupingDisabled
            && typeof x.columnInternals.groupIndex === 'number');
    }
    childItemsChanged() {
        void this.updateColumnsFromChildItems();
    }
    async updateColumnsFromChildItems() {
        await waitUntilCustomElementsDefinedAsync(this.childItems);
        this.columns = this.childItems.filter((x) => x instanceof TableColumn);
    }
    updateTanStack() {
        const updatedOptions = {};
        updatedOptions.state = {};
        if (this.tableUpdateTracker.updateColumnSort) {
            updatedOptions.state.sorting = this.calculateTanStackSortState();
        }
        if (this.tableUpdateTracker.updateColumnDefinition) {
            updatedOptions.columns = this.calculateTanStackColumns();
        }
        if (this.tableUpdateTracker.updateRowIds) {
            updatedOptions.getRowId = this.calculateTanStackRowIdFunction();
            updatedOptions.state.rowSelection = {};
            this.selectionManager.handleSelectionReset();
            this.expansionManager.resetHierarchyOptions();
        }
        if (this.tableUpdateTracker.updateRowParentIds) {
            this.expansionManager.setHierarchyEnabled(this.isHierarchyEnabled());
        }
        if (this.tableUpdateTracker.updateSelectionMode) {
            updatedOptions.enableMultiRowSelection = this.selectionMode === TableRowSelectionMode.multiple;
            updatedOptions.enableSubRowSelection = this.selectionMode === TableRowSelectionMode.multiple;
            updatedOptions.state.rowSelection = {};
            this.selectionManager.handleSelectionModeChanged(this.selectionMode);
        }
        if (this.dataHierarchyManager
            && this.tableUpdateTracker.requiresTanStackDataReset) {
            if (!this.isHierarchyEnabled()
                && !this.tableUpdateTracker.updateRowParentIds) {
                // Perform a shallow copy of the data to trigger tanstack to regenerate the row models and columns.
                updatedOptions.data = [...this.table.options.data];
            }
            else {
                const orderedRecords = this.dataHierarchyManager.getAllRecords(true);
                const tanstackUpdates = this.calculateTanStackData(orderedRecords);
                if (tanstackUpdates.state) {
                    updatedOptions.state.rowSelection = tanstackUpdates.state.rowSelection;
                }
                updatedOptions.data = tanstackUpdates.data;
            }
        }
        if (this.tableUpdateTracker.updateGroupRows) {
            updatedOptions.state.grouping = this.calculateTanStackGroupingState();
        }
        if (this.tableUpdateTracker.updateRowIds
            || this.tableUpdateTracker.updateRowParentIds
            || this.tableUpdateTracker.updateGroupRows) {
            updatedOptions.state.expanded = true;
            this.expansionManager.resetExpansionState();
        }
        this.updateTableOptions(updatedOptions);
    }
    updateActionMenuSlots() {
        if (this.openActionMenuRecordId !== undefined) {
            // If the action menu is open, delete all the slots associated
            // with the old action menu slots.
            for (const actionMenuSlot of this.actionMenuSlots) {
                this.requestedSlots.delete(actionMenuSlot);
            }
        }
        const slots = new Set();
        for (const column of this.columns) {
            if (column.actionMenuSlot) {
                slots.add(column.actionMenuSlot);
            }
        }
        this.actionMenuSlots = Array.from(slots);
        if (this.openActionMenuRecordId !== undefined) {
            // If the action menu is open, create slots for all the new
            // action menu slots.
            this.updateRequestedSlotsForOpeningActionMenu(this.openActionMenuRecordId);
        }
    }
    validate() {
        this.tableValidator.validateIdFieldConfiguration(this.selectionMode, this.idFieldName, this.parentIdFieldName);
        this.tableValidator.validateColumnIds(this.columns.map(x => x.columnId));
        this.tableValidator.validateColumnSortIndices(this.getColumnsParticipatingInSorting().map(x => x.columnInternals.currentSortIndex));
        this.tableValidator.validateColumnGroupIndices(this.getColumnsParticipatingInGrouping().map(x => x.columnInternals.groupIndex));
        this.tableValidator.validateColumnConfigurations(this.columns);
        if (this.dataHierarchyManager) {
            this.validateWithData(this.dataHierarchyManager.getAllRecords());
        }
    }
    validateWithData(data) {
        this.tableValidator.validateRecordIds(data, this.idFieldName);
        this.canRenderRows = this.checkValidity();
    }
    emitColumnConfigurationChangeEvent() {
        const detail = {
            columns: this.columns.map(column => ({
                columnId: column.columnId,
                sortIndex: column.columnInternals.currentSortIndex ?? undefined,
                sortDirection: column.columnInternals.currentSortDirection,
                groupIndex: column.columnInternals.groupIndex,
                hidden: column.columnHidden,
                fractionalWidth: column.columnInternals.currentFractionalWidth,
                pixelWidth: column.columnInternals.currentPixelWidth
            }))
        };
        this.$emit('column-configuration-change', detail);
    }
    async emitSelectionChangeEvent() {
        const detail = {
            selectedRecordIds: await this.getSelectedRecordIds()
        };
        this.$emit('selection-change', detail);
    }
    selectionStateChanged() {
        this.setSelectionCheckboxState();
    }
    selectionCheckboxChanged() {
        this.setSelectionCheckboxState();
    }
    setSelectionCheckboxState() {
        if (this.selectionCheckbox) {
            this.ignoreSelectionChangeEvents = true;
            this.selectionCheckbox.checked = this.selectionState === TableRowSelectionState.selected;
            this.selectionCheckbox.indeterminate = this.selectionState
                === TableRowSelectionState.partiallySelected;
            this.ignoreSelectionChangeEvents = false;
        }
    }
    isHierarchyEnabled() {
        return typeof this.parentIdFieldName === 'string';
    }
    refreshRows() {
        this.selectionState = this.getTableSelectionState();
        let hasDataHierarchy = false;
        const rows = this.table.getRowModel().rows;
        const slotsByRecordId = this.getRequestedSlotsByRecordId();
        this.tableData = rows.map(row => {
            const isGroupRow = row.getIsGrouped();
            const isParent = !isGroupRow && this.getRowCanExpand(row);
            const isDataChildOfGroupRowWithNoHierarchy = !isGroupRow // is a data row (not a group row)
                && !this.isHierarchyEnabled() // table does not have hierarchy enabled
                && row.getParentRow()?.getIsGrouped(); // row has a parent that is a group row
            const rowState = {
                record: row.original.clientRecord,
                id: row.id,
                selectionState: this.getRowSelectionState(row),
                isGroupRow,
                isExpanded: row.getIsExpanded(),
                groupRowValue: isGroupRow
                    ? row.getValue(row.groupingColumnId)
                    : undefined,
                nestingLevel: isDataChildOfGroupRowWithNoHierarchy
                    ? row.depth - 1
                    : row.depth,
                isParentRow: isParent,
                immediateChildCount: row.subRows.length,
                groupColumn: this.getGroupRowColumn(row),
                resolvedRowIndex: row.index,
                isLoadingChildren: this.expansionManager.isLoadingChildren(row.id),
                requestedSlots: slotsByRecordId[row.id] ?? []
            };
            hasDataHierarchy = hasDataHierarchy || isParent;
            return rowState;
        });
        this.showCollapseAll = hasDataHierarchy
            || this.getColumnsParticipatingInGrouping().length > 0;
        this.virtualizer.dataChanged();
    }
    getRequestedSlotsByRecordId() {
        const slotsByRecordId = {};
        for (const [slotName, { recordId, uniqueSlot }] of this
            .requestedSlots) {
            if (!Object.prototype.hasOwnProperty.call(slotsByRecordId, recordId)) {
                slotsByRecordId[recordId] = [];
            }
            slotsByRecordId[recordId].push({
                name: slotName,
                slot: uniqueSlot
            });
        }
        return slotsByRecordId;
    }
    getTableSelectionState() {
        if (this.table.getIsAllRowsSelected()) {
            return TableRowSelectionState.selected;
        }
        if (this.table.getIsSomeRowsSelected()) {
            return TableRowSelectionState.partiallySelected;
        }
        return TableRowSelectionState.notSelected;
    }
    getRowSelectionState(row) {
        if (row.getIsGrouped()) {
            return this.getGroupedRowSelectionState(row);
        }
        return row.getIsSelected()
            ? TableRowSelectionState.selected
            : TableRowSelectionState.notSelected;
    }
    getGroupedRowSelectionState(groupedRow) {
        const leafRows = groupedRow.getLeafRows().filter(x => !x.getIsGrouped()) ?? [];
        let foundSelectedRow = false;
        let foundNotSelectedRow = false;
        for (const row of leafRows) {
            if (row.getIsSelected()) {
                foundSelectedRow = true;
            }
            else {
                foundNotSelectedRow = true;
            }
            if (foundSelectedRow && foundNotSelectedRow) {
                return TableRowSelectionState.partiallySelected;
            }
        }
        return foundSelectedRow
            ? TableRowSelectionState.selected
            : TableRowSelectionState.notSelected;
    }
    getGroupRowColumn(row) {
        const groupedId = row.groupingColumnId;
        if (groupedId !== undefined) {
            return this.columns.find(c => c.columnInternals.uniqueId === groupedId);
        }
        return undefined;
    }
    updateTableOptions(updatedOptions) {
        this.options = {
            ...this.options,
            ...updatedOptions,
            state: { ...this.options.state, ...updatedOptions.state }
        };
        this.table.setOptions(this.options);
        if (updatedOptions.data && this.tableValidator.areRecordIdsValid()) {
            const rows = this.table.getRowModel().flatRows;
            this.expansionManager.processDataUpdate(rows);
        }
        this.refreshRows();
    }
    toggleRowExpanded(rowIndex) {
        const rows = this.table.getRowModel().rows;
        const row = rows[rowIndex];
        this.expansionManager.toggleRowExpansion(row);
    }
    calculateTanStackSortState() {
        const sortedColumns = this.getColumnsParticipatingInSorting().sort((x, y) => x.columnInternals.currentSortIndex
            - y.columnInternals.currentSortIndex);
        this.firstSortedColumn = sortedColumns.length > 0
            ? sortedColumns[0]
            : undefined;
        return sortedColumns.map(column => {
            return {
                id: column.columnInternals.uniqueId,
                desc: column.columnInternals.currentSortDirection
                    === TableColumnSortDirection.descending
            };
        });
    }
    calculateTanStackGroupingState() {
        const groupedColumns = this.getColumnsParticipatingInGrouping().sort((x, y) => x.columnInternals.groupIndex - y.columnInternals.groupIndex);
        return groupedColumns.map(column => column.columnInternals.uniqueId);
    }
    calculateTanStackRowIdFunction() {
        return this.idFieldName === null || this.idFieldName === undefined
            ? undefined
            : (record) => record.clientRecord[this.idFieldName];
    }
    calculateTanStackColumns() {
        return this.columns.map(column => {
            return {
                id: column.columnInternals.uniqueId,
                accessorFn: (record) => {
                    const fieldName = column.columnInternals.operandDataRecordFieldName;
                    if (typeof fieldName !== 'string') {
                        return undefined;
                    }
                    return record.clientRecord[fieldName];
                },
                sortingFn: getTanStackSortingFunction(column.columnInternals.sortOperation),
                sortUndefined: false
            };
        });
    }
    calculateTanStackSelectionState(recordIdsToSelect) {
        if (this.selectionMode === TableRowSelectionMode.none) {
            return {};
        }
        const tanstackSelectionState = {};
        const selectableRecordIds = this.tableValidator.getPresentRecordIds(recordIdsToSelect);
        for (const recordId of selectableRecordIds) {
            tanstackSelectionState[recordId] = true;
            if (this.selectionMode === TableRowSelectionMode.single) {
                // In single selection mode, only select the first record ID that is requested
                break;
            }
        }
        return tanstackSelectionState;
    }
    updateRowHeight() {
        this._rowHeight = parseFloat(controlHeight.getValueFor(this))
            + 2 * parseFloat(borderWidth.getValueFor(this));
        this.virtualizer?.updateRowHeight();
    }
}
__decorate([
    attr({ attribute: 'id-field-name' })
], Table.prototype, "idFieldName", void 0);
__decorate([
    attr({ attribute: 'parent-id-field-name' })
], Table.prototype, "parentIdFieldName", void 0);
__decorate([
    attr({ attribute: 'selection-mode' })
], Table.prototype, "selectionMode", void 0);
__decorate([
    attr({ attribute: 'action-menus-preserve-selection', mode: 'boolean' })
], Table.prototype, "actionMenusPreserveSelection", void 0);
__decorate([
    observable
], Table.prototype, "tableData", void 0);
__decorate([
    observable
], Table.prototype, "columns", void 0);
__decorate([
    observable
], Table.prototype, "childItems", void 0);
__decorate([
    observable
], Table.prototype, "rowElements", void 0);
__decorate([
    observable
], Table.prototype, "openActionMenuRecordId", void 0);
__decorate([
    observable
], Table.prototype, "canRenderRows", void 0);
__decorate([
    observable
], Table.prototype, "scrollX", void 0);
__decorate([
    observable
], Table.prototype, "rowGridColumns", void 0);
__decorate([
    observable
], Table.prototype, "selectionState", void 0);
__decorate([
    observable
], Table.prototype, "selectionCheckbox", void 0);
__decorate([
    observable
], Table.prototype, "collapseAllButton", void 0);
__decorate([
    observable
], Table.prototype, "showCollapseAll", void 0);
__decorate([
    observable
], Table.prototype, "canHaveCollapsibleRows", void 0);
__decorate([
    volatile
], Table.prototype, "collapseButtonVisibility", null);
__decorate([
    observable
], Table.prototype, "firstSortedColumn", void 0);
__decorate([
    observable
], Table.prototype, "visibleColumns", void 0);
__decorate([
    observable
], Table.prototype, "tableScrollableMinWidth", void 0);
__decorate([
    observable
], Table.prototype, "windowShiftKeyDown", void 0);
const nimbleTable = Table.compose({
    baseName: 'table',
    template,
    styles
});
DesignSystem.getOrCreate().withPrefix('nimble').register(nimbleTable());
export const tableTag = 'nimble-table';
//# sourceMappingURL=index.js.map