import { Observable } from '@ni/fast-element';
import { keyArrowDown, keyArrowLeft, keyArrowRight, keyArrowUp, keyEnd, keyEnter, keyEscape, keyFunction2, keyHome, keyPageDown, keyPageUp, keySpace, keyTab } from '@ni/fast-web-utilities';
import { FoundationElement } from '@ni/fast-foundation';
import { TableFocusType } from '../types';
import { TableGroupRow } from '../components/group-row';
import { TableRow } from '../components/row';
import { TableCell } from '../components/cell';
import { tableHeaderTag } from '../components/header';
import { TableCellView } from '../../table-column/base/cell-view';
/**
 * Manages the keyboard navigation and focus within the table.
 * @internal
 */
export class KeyboardNavigationManager {
    get inNavigationMode() {
        return (this.focusType !== TableFocusType.cellActionMenu
            && this.focusType !== TableFocusType.cellContent);
    }
    constructor(table, virtualizer) {
        this.table = table;
        this.virtualizer = virtualizer;
        this.focusType = TableFocusType.none;
        this.headerActionIndex = -1;
        this.rowIndex = -1;
        this.cellContentIndex = -1;
        this.columnIndex = -1;
        this.focusWithinTable = false;
        this.isCurrentlyFocusingElement = false;
        this.focusedViaPointer = false;
        this.visibleRowNotifiers = [];
        this.onTableFocusIn = (event) => {
            this.focusWithinTable = true;
            this.updateFocusStateFromActiveElement();
            // Sets initial focus on the appropriate table content
            const actionMenuOpen = this.table.openActionMenuRecordId !== undefined;
            if (!actionMenuOpen) {
                if (this.focusType === TableFocusType.none) {
                    this.focusSomethingOtherThanTheTable();
                }
                else if (event.target === this.table) {
                    // restore focus to last focused element
                    if (this.hasRowOrCellFocusType() && this.rowIndexIsValid(this.rowIndex)) {
                        this.scrollToAndFocusRow(this.rowIndex);
                    }
                    else if (this.hasHeaderFocusType()) {
                        this.focusHeaderElement();
                    }
                    else {
                        // should only get here if focusType was row/cell, but rowIndex was invalid
                        this.focusSomethingOtherThanTheTable();
                    }
                }
            }
            this.focusedViaPointer = false;
        };
        this.onTableFocusOut = () => {
            this.focusWithinTable = false;
        };
        this.onCellActionMenuBlur = (event) => {
            event.stopPropagation();
            const cell = event.detail;
            if (cell.actionMenuButton) {
                // Ensure that action menu buttons get hidden when no longer focused
                this.setActionMenuButtonFocused(cell.actionMenuButton, false);
            }
        };
        this.onCellViewFocusIn = (event) => {
            event.stopPropagation();
            this.updateFocusStateFromActiveElement();
        };
        this.onCellFocusIn = (event) => {
            event.stopPropagation();
            const cell = event.detail;
            const row = this.updateFocusStateFromActiveElement();
            if (row) {
                if (this.hasRowOrCellFocusType()
                    && this.rowIndex !== row.resolvedRowIndex) {
                    this.setRowFocusState(row.resolvedRowIndex);
                }
            }
            // Currently, clicking a non-interactive cell only updates the focus state to that row, it
            // doesn't focus the cell. If we revisit this, we most likely need to set the cells to tabindex=-1
            // upfront too, so their focusing behavior is consistent whether they've been previously keyboard
            // focused or not.
            if (this.focusType === TableFocusType.row
                && this.getActiveElement() === cell) {
                this.focusCurrentRow(false);
            }
        };
        this.onCellBlur = (event) => {
            event.stopPropagation();
            const cell = event.detail;
            this.setElementFocusable(cell, false);
        };
        this.onCaptureKeyDown = (event) => {
            let handled = false;
            if (event.key === keyTab) {
                handled = this.onTabPressed(event.shiftKey);
            }
            else if (this.inNavigationMode) {
                switch (event.key) {
                    case keyArrowLeft:
                        handled = this.onLeftArrowPressed();
                        break;
                    case keyArrowRight:
                        handled = this.onRightArrowPressed();
                        break;
                    case keyArrowUp:
                        handled = this.onUpArrowPressed();
                        break;
                    case keyArrowDown:
                        handled = this.onDownArrowPressed();
                        break;
                    case keyPageUp:
                        handled = this.onPageUpPressed();
                        break;
                    case keyPageDown:
                        handled = this.onPageDownPressed();
                        break;
                    case keyHome:
                        handled = this.onHomePressed(event.ctrlKey);
                        break;
                    case keyEnd:
                        handled = this.onEndPressed(event.ctrlKey);
                        break;
                    case keyEnter:
                        handled = this.onEnterPressed(event.ctrlKey);
                        break;
                    case keySpace:
                        handled = this.onSpacePressed(event.shiftKey);
                        break;
                    case keyFunction2:
                        handled = this.onF2Pressed();
                        break;
                    default:
                        break;
                }
            }
            if (handled) {
                event.preventDefault();
            }
        };
        this.onKeyDown = (event) => {
            if (!this.inNavigationMode && !event.defaultPrevented) {
                if (event.key === keyEscape || event.key === keyFunction2) {
                    const row = this.getCurrentRow();
                    if (row) {
                        this.trySetCellFocus(row.getFocusableElements());
                    }
                }
            }
        };
        this.onPointerDown = () => {
            this.focusedViaPointer = true;
        };
        this.onPointerUpOrCancel = () => {
            this.focusedViaPointer = false;
        };
        this.onViewportKeyDown = (event) => {
            let handleEvent = !this.inNavigationMode
                && (event.key === keyArrowUp || event.key === keyArrowDown);
            switch (event.key) {
                case keyPageUp:
                case keyPageDown:
                case keyHome:
                case keyEnd:
                    handleEvent = true;
                    break;
                default:
                    break;
            }
            if (handleEvent) {
                // Swallow key presses that would cause table scrolling, independently of keyboard navigation
                event.preventDefault();
                event.stopImmediatePropagation();
            }
        };
        this.tableNotifier = Observable.getNotifier(this.table);
        this.tableNotifier.subscribe(this, 'rowElements');
        this.virtualizerNotifier = Observable.getNotifier(this.virtualizer);
        this.virtualizerNotifier.subscribe(this, 'visibleItems');
    }
    resetFocusState() {
        this.focusType = TableFocusType.none;
        const activeElement = this.getActiveElement();
        if (activeElement && this.isInTable(activeElement)) {
            this.setDefaultFocus();
        }
    }
    get hasActiveRowOrCellFocus() {
        return this.focusWithinTable && this.hasRowOrCellFocusType();
    }
    connect() {
        this.table.addEventListener('keydown', this.onCaptureKeyDown, { capture: true });
        this.table.addEventListener('keydown', this.onKeyDown);
        this.table.addEventListener('pointerdown', this.onPointerDown);
        this.table.addEventListener('pointerup', this.onPointerUpOrCancel);
        this.table.addEventListener('pointercancel', this.onPointerUpOrCancel);
        this.table.addEventListener('focusin', this.onTableFocusIn);
        this.table.addEventListener('focusout', this.onTableFocusOut);
        this.table.viewport.addEventListener('keydown', this.onViewportKeyDown);
        this.table.viewport.addEventListener('cell-action-menu-blur', this.onCellActionMenuBlur);
        this.table.viewport.addEventListener('cell-view-focus-in', this.onCellViewFocusIn);
        this.table.viewport.addEventListener('cell-focus-in', this.onCellFocusIn);
        this.table.viewport.addEventListener('cell-blur', this.onCellBlur);
    }
    disconnect() {
        this.table.removeEventListener('keydown', this.onCaptureKeyDown, { capture: true });
        this.table.removeEventListener('keydown', this.onKeyDown);
        this.table.removeEventListener('pointerdown', this.onPointerDown);
        this.table.removeEventListener('pointerup', this.onPointerUpOrCancel);
        this.table.removeEventListener('pointercancel', this.onPointerUpOrCancel);
        this.table.removeEventListener('focusin', this.onTableFocusIn);
        this.table.removeEventListener('focusout', this.onTableFocusOut);
        this.table.viewport.removeEventListener('keydown', this.onViewportKeyDown);
        this.table.viewport.removeEventListener('cell-action-menu-blur', this.onCellActionMenuBlur);
        this.table.viewport.removeEventListener('cell-view-focus-in', this.onCellViewFocusIn);
        this.table.viewport.removeEventListener('cell-focus-in', this.onCellFocusIn);
        this.table.viewport.removeEventListener('cell-blur', this.onCellBlur);
    }
    handleChange(source, args) {
        let focusRowAndCell = false;
        if (source === this.virtualizer && args === 'visibleItems') {
            focusRowAndCell = true;
        }
        else if (source === this.table && args === 'rowElements') {
            for (const notifier of this.visibleRowNotifiers) {
                notifier.unsubscribe(this);
            }
            this.visibleRowNotifiers = [];
            for (const visibleRow of this.table.rowElements) {
                const rowNotifier = Observable.getNotifier(visibleRow);
                rowNotifier.subscribe(this, 'resolvedRowIndex');
                this.visibleRowNotifiers.push(rowNotifier);
                if (visibleRow.resolvedRowIndex === this.rowIndex) {
                    focusRowAndCell = true;
                }
            }
        }
        else if (args === 'resolvedRowIndex'
            && this.isResolvedRowType(source)) {
            if (source.resolvedRowIndex === this.rowIndex) {
                focusRowAndCell = true;
            }
        }
        if (focusRowAndCell) {
            // We want open action menus to be closed, and focused interactive cell content blurred, on scroll. We also don't want to
            // refocus the interactive cell content after the scroll, as the element no longer represents the same table data at that
            // point. So in both those cases, we focus the cell here. This also lets us maintain what row/ cell the user had focused
            // previously.
            if (this.focusType === TableFocusType.cellActionMenu
                || this.focusType === TableFocusType.cellContent) {
                this.setCellFocusState(this.columnIndex, this.rowIndex, false);
            }
            if (this.inNavigationMode && this.hasRowOrCellFocusType()) {
                if (this.rowIndex > this.table.tableData.length - 1) {
                    // Focused row index no longer valid, coerce to 1st row if possible
                    if (this.table.tableData.length > 0) {
                        this.rowIndex = 0;
                    }
                    else {
                        if (this.focusWithinTable) {
                            this.setDefaultFocus();
                        }
                        else {
                            this.focusType = TableFocusType.none;
                        }
                        return;
                    }
                }
                if (this.focusWithinTable) {
                    this.focusCurrentRow(false);
                }
            }
        }
    }
    onRowFocusIn(event) {
        if (this.isCurrentlyFocusingElement) {
            return;
        }
        const row = event.target;
        if (this.isResolvedRowType(row)) {
            if (this.rowIndex !== row.resolvedRowIndex) {
                // If user focuses a row some other way (e.g. mouse), update our focus state so future keyboard nav
                // will start from that row
                this.setRowFocusState(row.resolvedRowIndex);
            }
        }
    }
    onRowBlur(event) {
        const row = event.target;
        if (this.isResolvedRowType(row)) {
            this.setElementFocusable(row, false);
        }
    }
    onRowActionMenuToggle(event) {
        const isOpen = event.detail.newState;
        if (isOpen) {
            const row = event.target;
            const columnIndex = this.table.visibleColumns.findIndex(column => column.columnId === event.detail.columnId);
            this.setCellActionMenuFocusState(row.resolvedRowIndex, columnIndex, false);
        }
    }
    focusSomethingOtherThanTheTable() {
        this.setDefaultFocus();
        if (this.focusType === TableFocusType.none) {
            // nothing within the table to focus
            this.table.blur();
        }
    }
    onEnterPressed(ctrlKey) {
        let row;
        let rowElements;
        if (this.hasRowOrCellFocusType()) {
            row = this.getCurrentRow();
            rowElements = row?.getFocusableElements();
        }
        if (this.focusType === TableFocusType.row) {
            if (row instanceof TableGroupRow) {
                this.toggleRowExpanded(row);
                return true;
            }
        }
        if (this.focusType === TableFocusType.cell) {
            if (ctrlKey) {
                const cell = rowElements?.cells[this.columnIndex];
                if (cell?.actionMenuButton && !cell.actionMenuButton.open) {
                    cell.actionMenuButton.toggleButton.control.click();
                    return true;
                }
            }
            return this.focusFirstInteractiveElementInCurrentCell(rowElements);
        }
        return false;
    }
    onF2Pressed() {
        if (this.focusType === TableFocusType.cell) {
            const row = this.getCurrentRow();
            const rowElements = row?.getFocusableElements();
            return this.focusFirstInteractiveElementInCurrentCell(rowElements);
        }
        return false;
    }
    onSpacePressed(shiftKey) {
        if (this.focusType === TableFocusType.row
            || this.focusType === TableFocusType.cell) {
            if (this.focusType === TableFocusType.row || shiftKey) {
                const row = this.getCurrentRow();
                if (row instanceof TableRow && row.selectable) {
                    row.onSelectionChange(row.selected, !row.selected);
                }
                else if (row instanceof TableGroupRow) {
                    this.toggleRowExpanded(row);
                }
            }
            // Default Space behavior scrolls down, which is redundant given the rest of our keyboard nav code, and we'd still try to focus a
            // row that you scrolled away from. So suppress default Space behavior if a row or cell is selected, regardless of if we're
            // toggling selection or not.
            return true;
        }
        return false;
    }
    onLeftArrowPressed() {
        let row;
        let rowElements;
        let headerElements;
        if (this.hasRowOrCellFocusType()) {
            row = this.getCurrentRow();
            rowElements = row?.getFocusableElements();
        }
        else if (this.hasHeaderFocusType()) {
            headerElements = this.getTableHeaderFocusableElements();
        }
        switch (this.focusType) {
            case TableFocusType.headerActions:
                return this.trySetHeaderActionFocus(headerElements, this.headerActionIndex - 1);
            case TableFocusType.columnHeader:
                return (this.trySetColumnHeaderFocus(headerElements, this.columnIndex - 1)
                    || this.trySetHeaderActionFocus(headerElements, headerElements.headerActions.length - 1));
            case TableFocusType.row:
                if (this.isRowExpanded(row) === true) {
                    this.toggleRowExpanded(row);
                    return true;
                }
                return false;
            case TableFocusType.rowSelectionCheckbox:
                this.setRowFocusState();
                return this.focusCurrentRow(true);
            case TableFocusType.cell:
                if (!this.trySetCellFocus(rowElements, this.columnIndex - 1)
                    && !this.trySetRowSelectionCheckboxFocus(rowElements)) {
                    this.setRowFocusState();
                    this.focusCurrentRow(true);
                }
                return true;
            default:
                break;
        }
        return false;
    }
    onRightArrowPressed() {
        let row;
        let rowElements;
        let headerElements;
        if (this.hasRowOrCellFocusType()) {
            row = this.getCurrentRow();
            rowElements = row?.getFocusableElements();
        }
        else if (this.hasHeaderFocusType()) {
            headerElements = this.getTableHeaderFocusableElements();
        }
        switch (this.focusType) {
            case TableFocusType.headerActions:
                return (this.trySetHeaderActionFocus(headerElements, this.headerActionIndex + 1) || this.trySetColumnHeaderFocus(headerElements, 0));
            case TableFocusType.columnHeader:
                return this.trySetColumnHeaderFocus(headerElements, this.columnIndex + 1);
            case TableFocusType.row:
                if (this.isRowExpanded(row) === false) {
                    this.toggleRowExpanded(row);
                    return true;
                }
                return (this.trySetRowSelectionCheckboxFocus(rowElements)
                    || this.trySetCellFocus(rowElements, 0));
            case TableFocusType.rowSelectionCheckbox:
                return this.trySetCellFocus(rowElements, 0);
            case TableFocusType.cell:
                return this.trySetCellFocus(rowElements, this.columnIndex + 1);
            default:
                break;
        }
        return false;
    }
    onUpArrowPressed() {
        this.onMoveUp(1);
        // Always prevent default - prevents page scroll, and FireFox changing focus if focus is at table extents
        return true;
    }
    onPageUpPressed() {
        return this.onMoveUp(this.virtualizer.pageSize);
    }
    onHomePressed(ctrlKey) {
        if (this.handleHomeEndWithinRow(ctrlKey)) {
            const row = this.getCurrentRow();
            const rowElements = row?.getFocusableElements();
            return (this.trySetRowSelectionCheckboxFocus(rowElements)
                || this.trySetCellFocus(rowElements, 0));
        }
        return this.onMoveUp(0, 0);
    }
    onDownArrowPressed() {
        this.onMoveDown(1);
        // Always prevent default - prevents page scroll, and FireFox changing focus if focus is at table extents
        return true;
    }
    onPageDownPressed() {
        return this.onMoveDown(this.virtualizer.pageSize);
    }
    onEndPressed(ctrlKey) {
        if (this.handleHomeEndWithinRow(ctrlKey)) {
            const row = this.getCurrentRow();
            const rowElements = row?.getFocusableElements();
            return this.trySetCellFocus(rowElements, this.table.visibleColumns.length - 1);
        }
        return this.onMoveDown(0, this.table.tableData.length - 1);
    }
    handleHomeEndWithinRow(ctrlKey) {
        return ((this.focusType === TableFocusType.cell
            || this.focusType === TableFocusType.rowSelectionCheckbox)
            && !ctrlKey);
    }
    onTabPressed(shiftKeyPressed) {
        const activeElement = this.getActiveElement();
        if (activeElement === null || activeElement === this.table) {
            return false;
        }
        const nextFocusState = this.hasRowOrCellFocusType()
            ? this.getNextRowTabStop(shiftKeyPressed)
            : this.getNextHeaderTabStop(shiftKeyPressed);
        if (nextFocusState) {
            this.focusType = nextFocusState.focusType;
            this.rowIndex = nextFocusState.rowIndex ?? this.rowIndex;
            this.columnIndex = nextFocusState.columnIndex ?? this.columnIndex;
            this.headerActionIndex = nextFocusState.headerActionIndex ?? this.headerActionIndex;
            this.cellContentIndex = nextFocusState.cellContentIndex ?? this.cellContentIndex;
            if (this.hasRowOrCellFocusType()) {
                this.focusCurrentRow(!this.focusedViaPointer);
            }
            else {
                this.focusHeaderElement();
            }
            return true;
        }
        this.blurAfterLastTab(activeElement);
        return false;
    }
    getNextRowTabStop(shiftKeyPressed) {
        const row = this.getCurrentRow();
        if (row === undefined) {
            return undefined;
        }
        let startIndex = -1;
        const focusStates = [];
        const rowElements = row.getFocusableElements();
        if (rowElements.selectionCheckbox) {
            focusStates.push({
                focusType: TableFocusType.rowSelectionCheckbox
            });
            if (this.focusType === TableFocusType.rowSelectionCheckbox) {
                startIndex = 0;
            }
        }
        let cellIndex = 0;
        while (cellIndex < rowElements.cells.length) {
            const firstCellTabbableIndex = focusStates.length;
            const cellInfo = rowElements.cells[cellIndex];
            const cellViewTabbableChildren = cellInfo.cell.cellView.tabbableChildren;
            for (let i = 0; i < cellViewTabbableChildren.length; i++) {
                focusStates.push({
                    focusType: TableFocusType.cellContent,
                    columnIndex: cellIndex,
                    cellContentIndex: i
                });
                if (this.focusType === TableFocusType.cellContent
                    && this.columnIndex === cellIndex
                    && this.cellContentIndex === i) {
                    startIndex = focusStates.length - 1;
                }
            }
            if (cellInfo.actionMenuButton) {
                focusStates.push({
                    focusType: TableFocusType.cellActionMenu,
                    columnIndex: cellIndex
                });
                if (this.focusType === TableFocusType.cellActionMenu
                    && this.columnIndex === cellIndex) {
                    startIndex = focusStates.length - 1;
                }
            }
            const lastCellTabbableIndex = focusStates.length - 1;
            if (this.focusType === TableFocusType.cell
                && this.columnIndex === cellIndex) {
                startIndex = shiftKeyPressed
                    ? lastCellTabbableIndex + 1
                    : firstCellTabbableIndex - 1;
            }
            cellIndex += 1;
        }
        if (this.focusType === TableFocusType.row) {
            startIndex = shiftKeyPressed ? focusStates.length : -1;
        }
        const direction = shiftKeyPressed ? -1 : 1;
        return focusStates[startIndex + direction];
    }
    getNextHeaderTabStop(shiftKeyPressed) {
        let startIndex = -1;
        const focusStates = [];
        const headerTabbableElements = this.getTableHeaderFocusableElements().headerActions;
        for (let i = 0; i < headerTabbableElements.length; i++) {
            focusStates.push({
                focusType: TableFocusType.headerActions,
                headerActionIndex: i
            });
        }
        if (this.focusType === TableFocusType.headerActions) {
            startIndex = this.headerActionIndex;
        }
        else {
            // TableFocusType.columnHeader
            startIndex = focusStates.length;
        }
        const direction = shiftKeyPressed ? -1 : 1;
        return focusStates[startIndex + direction];
    }
    blurAfterLastTab(activeElement) {
        // In order to get the desired browser-provided Tab/Shift-Tab behavior of focusing the
        // element before/after the table, the table shouldn't have tabIndex=0 when this event
        // handling ends. However it needs to be tabIndex=0 so we can re-focus the table the next time
        // it's tabbed to, so set tabIndex back to 0 after a rAF.
        // Note: In Chrome this is only needed for Shift-Tab, but in Firefox both Tab and Shift-Tab need this
        // to work as expected.
        this.table.tabIndex = -1;
        window.requestAnimationFrame(() => {
            this.table.tabIndex = 0;
        });
        // Don't explicitly call blur() on activeElement (causes unexpected behavior on Safari / Mac Firefox)
        this.setElementFocusable(activeElement, false);
    }
    onMoveUp(rowDelta, newRowIndex) {
        const coerceRowIndex = rowDelta > 1;
        switch (this.focusType) {
            case TableFocusType.row:
            case TableFocusType.rowSelectionCheckbox:
            case TableFocusType.cell: {
                const scrollOptions = {};
                let rowIndex = this.rowIndex;
                if (newRowIndex !== undefined) {
                    rowIndex = newRowIndex;
                }
                rowIndex -= rowDelta;
                if (coerceRowIndex && rowIndex < 0) {
                    rowIndex = 0;
                }
                if (rowDelta > 1) {
                    scrollOptions.align = 'start';
                }
                if (rowIndex < this.rowIndex && rowIndex >= 0) {
                    return this.scrollToAndFocusRow(rowIndex, scrollOptions);
                }
                if (rowIndex === -1) {
                    const headerElements = this.getTableHeaderFocusableElements();
                    if (this.focusType === TableFocusType.row
                        || this.focusType === TableFocusType.rowSelectionCheckbox) {
                        return (this.trySetHeaderActionFocus(headerElements, 0)
                            || this.trySetColumnHeaderFocus(headerElements, 0));
                    }
                    return this.trySetColumnHeaderFocus(headerElements, this.columnIndex);
                }
                return false;
            }
            default:
                break;
        }
        return false;
    }
    onMoveDown(rowDelta, newRowIndex) {
        const coerceRowIndex = rowDelta > 1;
        switch (this.focusType) {
            case TableFocusType.headerActions: {
                this.setRowFocusState(0);
                return this.scrollToAndFocusRow(0);
            }
            case TableFocusType.columnHeader: {
                this.setCellFocusState(this.columnIndex, 0, false);
                return this.scrollToAndFocusRow(0);
            }
            case TableFocusType.row:
            case TableFocusType.rowSelectionCheckbox:
            case TableFocusType.cell: {
                const scrollOptions = {};
                let rowIndex = this.rowIndex;
                if (newRowIndex !== undefined) {
                    rowIndex = newRowIndex;
                }
                rowIndex += rowDelta;
                if (coerceRowIndex && rowIndex >= this.table.tableData.length) {
                    rowIndex = this.table.tableData.length - 1;
                }
                if (rowDelta > 1) {
                    scrollOptions.align = 'end';
                }
                if (rowIndex > this.rowIndex
                    && rowIndex < this.table.tableData.length) {
                    return this.scrollToAndFocusRow(rowIndex, scrollOptions);
                }
                return false;
            }
            default:
                break;
        }
        return false;
    }
    updateFocusStateFromActiveElement() {
        // If the user is interacting with the table with non-keyboard methods (like mouse), we need to
        // update our focus state based on the current active/focused element
        const { activeElement, row, cell } = this.getActiveElementCellAndRow();
        if (!cell) {
            return row;
        }
        const columnIndex = this.table.visibleColumns.indexOf(cell.column);
        if (cell.actionMenuButton === activeElement) {
            this.setCellActionMenuFocusState(row.resolvedRowIndex, columnIndex, false);
        }
        else {
            const contentIndex = cell.cellView.tabbableChildren.indexOf(activeElement);
            if (contentIndex > -1) {
                this.setCellContentFocusState(contentIndex, row.resolvedRowIndex, columnIndex, false);
            }
        }
        return row;
    }
    getActiveElementCellAndRow() {
        const activeElement = this.getActiveElement();
        if (!activeElement) {
            return {};
        }
        const row = this.getContainingRow(activeElement);
        if (!row) {
            return { activeElement };
        }
        if (row instanceof TableGroupRow) {
            return { activeElement, row };
        }
        const cell = this.getContainingCell(activeElement);
        return { activeElement, row, cell };
    }
    focusElement(element, focusOptions) {
        const previousActiveElement = this.getActiveElement();
        if (previousActiveElement !== element) {
            this.setElementFocusable(element, true);
            this.isCurrentlyFocusingElement = true;
            element.focus(focusOptions);
            this.isCurrentlyFocusingElement = false;
            if (previousActiveElement
                && this.isInTable(previousActiveElement)) {
                this.setElementFocusable(previousActiveElement, false);
            }
        }
    }
    setElementFocusable(element, focusable) {
        if (element === this.table) {
            return;
        }
        element.tabIndex = focusable ? 0 : -1;
    }
    setActionMenuButtonFocused(menuButton, focused) {
        // The action MenuButton needs to be visible in order to be focused, so this CSS class styling
        // handles that (see cell/styles.ts).
        if (focused) {
            menuButton.classList.add('cell-action-menu-focused');
        }
        else {
            menuButton.classList.remove('cell-action-menu-focused');
        }
    }
    setDefaultFocus() {
        const headerElements = this.getTableHeaderFocusableElements();
        if (!this.trySetHeaderActionFocus(headerElements, 0)
            && !this.trySetColumnHeaderFocus(headerElements, 0)
            && !this.scrollToAndFocusRow(0)) {
            this.focusType = TableFocusType.none;
        }
    }
    scrollToAndFocusRow(totalRowIndex, scrollOptions) {
        if (!this.rowIndexIsValid(totalRowIndex)) {
            return false;
        }
        if (!this.hasRowOrCellFocusType()) {
            this.setRowFocusState();
        }
        this.rowIndex = totalRowIndex;
        this.virtualizer.scrollToIndex(totalRowIndex, scrollOptions);
        this.focusCurrentRow(!this.focusedViaPointer);
        return true;
    }
    rowIndexIsValid(rowIndex) {
        return rowIndex >= 0 && rowIndex < this.table.tableData.length;
    }
    focusCurrentRow(allowScroll) {
        const visibleRowIndex = this.getCurrentRowVisibleIndex();
        if (visibleRowIndex < 0) {
            return false;
        }
        const focusedRow = this.table.rowElements[visibleRowIndex];
        let focusRowOnly = false;
        switch (this.focusType) {
            case TableFocusType.row:
                focusRowOnly = true;
                break;
            case TableFocusType.cell:
            case TableFocusType.cellActionMenu:
            case TableFocusType.cellContent:
                focusRowOnly = focusedRow instanceof TableGroupRow;
                break;
            default:
                break;
        }
        const focusOptions = { preventScroll: !allowScroll };
        if (focusRowOnly) {
            this.focusElement(focusedRow, focusOptions);
            return true;
        }
        this.focusRowElement(focusedRow, focusOptions);
        return true;
    }
    focusRowElement(row, focusOptions) {
        const rowElements = row.getFocusableElements();
        let focusableElement;
        switch (this.focusType) {
            case TableFocusType.rowSelectionCheckbox:
                focusableElement = rowElements.selectionCheckbox;
                break;
            case TableFocusType.cell: {
                focusableElement = rowElements.cells[this.columnIndex].cell;
                break;
            }
            case TableFocusType.cellActionMenu: {
                const actionMenuButton = rowElements.cells[this.columnIndex]?.cell.actionMenuButton;
                if (actionMenuButton) {
                    focusableElement = actionMenuButton;
                    this.setActionMenuButtonFocused(actionMenuButton, true);
                }
                break;
            }
            case TableFocusType.cellContent: {
                focusableElement = rowElements.cells[this.columnIndex]?.cell.cellView
                    .tabbableChildren[this.cellContentIndex];
                break;
            }
            default:
                break;
        }
        if (focusableElement) {
            this.focusElement(focusableElement, focusOptions);
        }
    }
    focusHeaderElement() {
        const headerElements = this.getTableHeaderFocusableElements();
        let focusableElement;
        switch (this.focusType) {
            case TableFocusType.headerActions:
                focusableElement = headerElements.headerActions[this.headerActionIndex];
                break;
            case TableFocusType.columnHeader:
                focusableElement = headerElements.columnHeaders[this.columnIndex];
                break;
            default:
                break;
        }
        if (focusableElement) {
            this.focusElement(focusableElement, { preventScroll: this.focusedViaPointer });
            return true;
        }
        return false;
    }
    getCurrentRowVisibleIndex() {
        return this.table.rowElements.findIndex(row => row.resolvedRowIndex === this.rowIndex);
    }
    getTableHeaderFocusableElements() {
        const headerActions = [];
        if (this.table.selectionCheckbox?.getRootNode()
            === this.table.shadowRoot) {
            headerActions.push(this.table.selectionCheckbox);
        }
        if (this.table.showCollapseAll
            && this.table.collapseAllButton?.getRootNode()
                === this.table.shadowRoot) {
            headerActions.push(this.table.collapseAllButton);
        }
        const columnHeaders = [];
        if (this.canFocusColumnHeaders()) {
            this.table.columnHeadersContainer
                .querySelectorAll(tableHeaderTag)
                .forEach(header => columnHeaders.push(header));
        }
        return { headerActions, columnHeaders };
    }
    canFocusColumnHeaders() {
        return (this.table.columns.find(c => !c.columnInternals.sortingDisabled) !== undefined);
    }
    getCurrentRow() {
        return this.table.rowElements[this.getCurrentRowVisibleIndex()];
    }
    isRowExpanded(row) {
        if ((row instanceof TableRow && row.isParentRow)
            || row instanceof TableGroupRow) {
            return row.expanded;
        }
        return undefined;
    }
    toggleRowExpanded(row) {
        if (row instanceof TableGroupRow) {
            row.onGroupExpandToggle();
        }
        else {
            row.onRowExpandToggle();
        }
        this.focusRowElement(row);
    }
    getContainingRow(start) {
        return this.getContainingElement(start, e => this.isResolvedRowType(e));
    }
    getContainingCell(start) {
        return this.getContainingElement(start, e => e instanceof TableCell);
    }
    getContainingElement(start, isElementMatch) {
        let possibleMatch = start;
        while (possibleMatch && possibleMatch !== this.table) {
            if (isElementMatch(possibleMatch)) {
                return possibleMatch;
            }
            possibleMatch = possibleMatch.parentElement
                ?? possibleMatch.parentNode?.host;
        }
        return undefined;
    }
    isInTable(start) {
        let possibleMatch = start;
        while (possibleMatch && possibleMatch !== this.table) {
            possibleMatch = possibleMatch.parentElement
                ?? possibleMatch.parentNode?.host;
        }
        return possibleMatch === this.table;
    }
    getActiveElement() {
        let activeElement = document.activeElement;
        while (activeElement?.shadowRoot?.activeElement) {
            activeElement = activeElement.shadowRoot.activeElement;
            // In some cases, the active element may be a sub-part of a control (example: MenuButton -> ToggleButton -> a div with tabindex=0). Stop at the outer control boundary, so that
            // we can more simply check equality against the elements of getTableHeaderFocusableElements() / row.getFocusableElements().
            // (For rows/cells/cell views, we do need to recurse into them, to get to the appropriate focused controls though)
            if (activeElement instanceof FoundationElement
                && !this.isResolvedRowType(activeElement)
                && !(activeElement instanceof TableCell)
                && !(activeElement instanceof TableCellView)) {
                break;
            }
        }
        return activeElement;
    }
    focusFirstInteractiveElementInCurrentCell(rowElements) {
        if (!rowElements) {
            return false;
        }
        return (this.trySetCellContentFocus(rowElements, 0)
            || this.trySetCellActionMenuFocus(rowElements));
    }
    hasRowOrCellFocusType() {
        switch (this.focusType) {
            case TableFocusType.cell:
            case TableFocusType.cellActionMenu:
            case TableFocusType.cellContent:
            case TableFocusType.row:
            case TableFocusType.rowSelectionCheckbox:
                return true;
            default:
                return false;
        }
    }
    hasHeaderFocusType() {
        switch (this.focusType) {
            case TableFocusType.headerActions:
            case TableFocusType.columnHeader:
                return true;
            default:
                return false;
        }
    }
    trySetRowSelectionCheckboxFocus(rowElements) {
        if (rowElements?.selectionCheckbox) {
            this.focusType = TableFocusType.rowSelectionCheckbox;
            this.focusCurrentRow(!this.focusedViaPointer);
            return true;
        }
        return false;
    }
    trySetColumnHeaderFocus(headerElements, columnIndex) {
        if (columnIndex >= 0
            && columnIndex < headerElements.columnHeaders.length) {
            this.focusType = TableFocusType.columnHeader;
            this.columnIndex = columnIndex;
            this.focusHeaderElement();
            return true;
        }
        return false;
    }
    trySetHeaderActionFocus(headerElements, headerActionIndex) {
        if (headerActionIndex >= 0
            && headerActionIndex < headerElements.headerActions.length) {
            this.focusType = TableFocusType.headerActions;
            this.headerActionIndex = headerActionIndex;
            this.focusHeaderElement();
            return true;
        }
        return false;
    }
    trySetCellFocus(rowElements, columnIndex, rowIndex) {
        if (!rowElements) {
            return false;
        }
        const newColumnIndex = columnIndex ?? this.columnIndex;
        const newRowIndex = rowIndex ?? this.rowIndex;
        if (newColumnIndex >= 0 && newColumnIndex < rowElements.cells.length) {
            this.focusType = TableFocusType.cell;
            this.setRowCellFocusState(newColumnIndex, newRowIndex, true);
            return true;
        }
        return false;
    }
    trySetCellContentFocus(rowElements, cellContentIndex, columnIndex, rowIndex) {
        if (!rowElements) {
            return false;
        }
        const newColumnIndex = columnIndex ?? this.columnIndex;
        const newRowIndex = rowIndex ?? this.rowIndex;
        if (newColumnIndex >= 0
            && newColumnIndex < rowElements.cells.length
            && cellContentIndex >= 0
            && cellContentIndex
                < rowElements.cells[newColumnIndex].cell.cellView
                    .tabbableChildren.length) {
            this.setCellContentFocusState(cellContentIndex, newRowIndex, newColumnIndex, true);
            return true;
        }
        return false;
    }
    trySetCellActionMenuFocus(rowElements, columnIndex, rowIndex) {
        const newColumnIndex = columnIndex ?? this.columnIndex;
        const newRowIndex = rowIndex ?? this.rowIndex;
        if (newColumnIndex >= 0
            && newColumnIndex < rowElements.cells.length
            && rowElements.cells[newColumnIndex].actionMenuButton) {
            this.setCellActionMenuFocusState(newRowIndex, newColumnIndex, true);
            return true;
        }
        return false;
    }
    setCellActionMenuFocusState(rowIndex, columnIndex, focusElement) {
        this.focusType = TableFocusType.cellActionMenu;
        this.setRowCellFocusState(columnIndex, rowIndex, focusElement);
    }
    setCellContentFocusState(cellContentIndex, rowIndex, columnIndex, focusElement) {
        this.focusType = TableFocusType.cellContent;
        this.cellContentIndex = cellContentIndex;
        this.setRowCellFocusState(columnIndex, rowIndex, focusElement);
    }
    setRowFocusState(rowIndex) {
        this.focusType = TableFocusType.row;
        if (rowIndex !== undefined) {
            this.rowIndex = rowIndex;
        }
    }
    setCellFocusState(columnIndex, rowIndex, focusElement) {
        this.focusType = TableFocusType.cell;
        this.setRowCellFocusState(columnIndex, rowIndex, focusElement);
    }
    setRowCellFocusState(columnIndex, rowIndex, focusElement) {
        this.rowIndex = rowIndex;
        this.columnIndex = columnIndex;
        if (focusElement) {
            this.focusCurrentRow(!this.focusedViaPointer);
        }
    }
    isResolvedRowType(row) {
        return row instanceof TableRow || row instanceof TableGroupRow;
    }
}
//# sourceMappingURL=keyboard-navigation-manager.js.map