import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
// (C) 2007-2026 GoodData Corporation
import { useCallback, useEffect, useRef, useState, } from "react";
import cx from "classnames";
import { defaultImport } from "default-import";
import { useIntl } from "react-intl";
import DefaultMeasure from "react-measure";
import { ErrorComponent } from "@gooddata/sdk-ui";
import { useFocusWithinContainer } from "../../@ui/hooks/useFocusWithinContainer.js";
import { SELECT_ITEM_ACTION, useListWithActionsKeyboardNavigation, } from "../../@ui/hooks/useListWithActionsKeyboardNavigation.js";
import { ScopedIdStore, useScopedIdStoreValue } from "../../@ui/hooks/useScopedId.js";
import { UiPagedVirtualList } from "../../@ui/UiPagedVirtualList/UiPagedVirtualList.js";
import { DETAILED_ANNOUNCEMENT_THRESHOLD, UiSearchResultsAnnouncement, } from "../../@ui/UiSearchResultsAnnouncement/UiSearchResultsAnnouncement.js";
import { LoadingMask } from "../../LoadingMask/LoadingMask.js";
import { isEnterKey, isEscapeKey, isSpaceKey } from "../../utils/events.js";
import { InvertableSelectAllCheckbox } from "./InvertableSelectAllCheckbox.js";
import { InvertableSelectItem } from "./InvertableSelectItem.js";
import { InvertableSelectNoResultsMatch } from "./InvertableSelectNoResultsMatch.js";
import { InvertableSelectSearchBar } from "./InvertableSelectSearchBar.js";
import { InvertableSelectStatusBar } from "./InvertableSelectStatusBar.js";
import { useInvertableSelect } from "./useInvertableSelect.js";
// There are known compatibility issues between CommonJS (CJS) and ECMAScript modules (ESM).
// In ESM, default exports of CJS modules are wrapped in default properties instead of being exposed directly.
// https://github.com/microsoft/TypeScript/issues/52086#issuecomment-1385978414
const Measure = defaultImport(DefaultMeasure);
const DEFAULT_VISIBLE_ITEMS_COUNT = 10;
/**
 * @internal
 */
export function InvertableSelectVirtualised(props) {
    const { className, height, adaptiveHeight, isSingleSelect = false, items, totalItemsCount, hideTotalItemsCount = false, itemHeight, getItemTitle, getItemKey, isItemQuestionMarkEnabled = () => true, isInverted = true, selectedItems, selectedItemsLimit = Infinity, onSearch, searchString, searchPlaceholder, error, isLoading, nextPageItemPlaceholdersCount, isLoadingNextPage, canSubmitOnKeyDown, onLoadNextPage, onApplyButtonClick, renderError = defaultError, renderLoading = defaultLoading, renderSearchBar = defaultSearchBar, renderNoData = defaultNoData, renderItem = defaultItem, renderStatusBar = defaultStatusBar, renderActions = defaultActions, } = props;
    const representAs = (props.representAs ?? isSingleSelect) ? "listbox" : "grid";
    const { formatMessage } = useIntl();
    const { onSelectAllCheckboxChange, onSelectAllCheckboxToggle, selectOnly, selectItems, deselectItems, selectionState, getIsItemSelected, } = useInvertableSelect(props);
    const [focusedIndex, setFocusedIndex] = useState(0);
    const [hasInitializedFocus, setHasInitializedFocus] = useState(false);
    // Whether the keyboard focus currently lives inside the list. While it does, the grid
    // container must not be a tab stop itself, otherwise Shift+Tab from an item would land on the
    // container (its ancestor tab stop) and get trapped there instead of leaving the list.
    const [isFocusWithin, setIsFocusWithin] = useState(false);
    const firstSelectedItem = items.find((item) => getIsItemSelected(item));
    useEffect(() => {
        if (items.length === 0) {
            setFocusedIndex(-1);
            setHasInitializedFocus(false);
        }
        else if (!hasInitializedFocus) {
            setHasInitializedFocus(true);
            setFocusedIndex(firstSelectedItem ? items.indexOf(firstSelectedItem) : 0);
        }
    }, [items, hasInitializedFocus, getIsItemSelected, setFocusedIndex, firstSelectedItem]);
    // onBlur is unreliable when the focused row unmounts (the list empties, or switches to a
    // loading/error state), so isFocusWithin could stay true and leave the grid container stuck at
    // tabIndex={-1}, blocking keyboard re-entry. Reset it whenever the list of values is not rendered.
    const isListRendered = !isLoading && !error && items.length > 0;
    useEffect(() => {
        if (!isListRendered) {
            setIsFocusWithin(false);
        }
    }, [isListRendered]);
    const handleSelectItemKeyboard = useCallback((item, e) => () => {
        setFocusedIndex(items.indexOf(item));
        if (isSingleSelect) {
            selectOnly(item);
        }
        else {
            const isSelected = getIsItemSelected(item);
            if (e && isSpaceKey(e)) {
                if (isSelected) {
                    deselectItems([item]);
                }
                else {
                    selectItems([item]);
                }
            }
            if (e && isEnterKey(e) && canSubmitOnKeyDown) {
                onApplyButtonClick?.();
            }
        }
    }, [
        items,
        isSingleSelect,
        selectOnly,
        getIsItemSelected,
        canSubmitOnKeyDown,
        deselectItems,
        selectItems,
        onApplyButtonClick,
    ]);
    const handleSelectOnly = useCallback((item) => () => {
        if (isSingleSelect) {
            return undefined;
        }
        selectOnly(item);
    }, [isSingleSelect, selectOnly]);
    const getItemAdditionalActions = useCallback((item) => {
        const questionMark = isItemQuestionMarkEnabled(item) ? ["questionMark"] : [];
        if (isSingleSelect) {
            return [...questionMark];
        }
        return ["only", ...questionMark];
    }, [isItemQuestionMarkEnabled, isSingleSelect]);
    const { onKeyboardNavigation, focusedItem, focusedAction, setFocusedAction } = useListWithActionsKeyboardNavigation({
        items,
        getItemAdditionalActions,
        actionHandlers: {
            selectItem: handleSelectItemKeyboard,
            only: handleSelectOnly,
            questionMark: () => () => { },
        },
        focusedIndex,
        isSimple: true,
        isWrapping: false,
    });
    const scopedIdStoreValue = useScopedIdStoreValue(getItemKey);
    const { containerRef } = useFocusWithinContainer(focusedItem && focusedAction
        ? scopedIdStoreValue.makeId({ item: focusedItem, specifier: focusedAction })
        : "");
    const itemRenderer = useCallback((item) => {
        return renderItem({
            onSelect: () => {
                selectItems([item]);
                setFocusedIndex(items.indexOf(item));
            },
            onDeselect: () => {
                deselectItems([item]);
                setFocusedIndex(items.indexOf(item));
            },
            onSelectOnly: () => {
                selectOnly(item);
                setFocusedIndex(items.indexOf(item));
            },
            item,
            title: getItemTitle(item),
            isSelected: getIsItemSelected(item),
            focusedAction: item === focusedItem ? focusedAction : undefined,
            listRef: containerRef,
            index: items.indexOf(item) + 1,
            itemsCount: items.length,
        });
    }, [
        renderItem,
        getItemTitle,
        getIsItemSelected,
        focusedItem,
        focusedAction,
        containerRef,
        items,
        selectItems,
        deselectItems,
        selectOnly,
    ]);
    const handleSearchBarKeyDown = useCallback((e) => {
        if (isEscapeKey(e) && (searchString?.length ?? 0) > 0) {
            e.stopPropagation();
        }
    }, [searchString]);
    const shouldLoadNextPage = useCallback((lastItemIndex, itemsCount) => {
        return lastItemIndex >= itemsCount - 1;
    }, []);
    const listRef = useRef(null);
    const handleFocus = useCallback((e) => {
        const isNavigatingByKeyboard = document.querySelector(":focus-visible") !== null;
        // Focus is now inside the list (this handler fires for descendants too, as focus
        // bubbles). Drop the container out of the tab order so Shift+Tab can leave the list.
        setIsFocusWithin(true);
        if (e.target.id !== scopedIdStoreValue.containerId) {
            return;
        }
        const elementToFocus = document.getElementById(scopedIdStoreValue.makeId({ item: focusedItem, specifier: SELECT_ITEM_ACTION }));
        if (isNavigatingByKeyboard) {
            listRef.current?.scrollToItem(focusedItem);
        }
        if (!elementToFocus) {
            return;
        }
        setFocusedAction(SELECT_ITEM_ACTION);
        elementToFocus.focus();
    }, [scopedIdStoreValue, focusedItem, setFocusedAction]);
    const handleBlur = useCallback(
    // Select the default action when the focus leaves the list
    (e) => {
        if (containerRef.current?.contains(e.relatedTarget)) {
            return;
        }
        setIsFocusWithin(false);
        setFocusedAction(SELECT_ITEM_ACTION);
    }, [containerRef, setFocusedAction]);
    const selectProps = representAs === "grid"
        ? {
            role: "grid",
            // Only a tab stop when focus is outside the list, so it can catch Tab and
            // redirect to the active row (needed when that row is virtualized out of view).
            // While focus is within, it steps aside so Tab/Shift+Tab can leave the list.
            tabIndex: isFocusWithin ? -1 : 0,
            "aria-rowcount": items.length,
            "aria-multiselectable": !isSingleSelect,
            "aria-label": formatMessage({ id: "attributesDropdown.filterValues" }),
        }
        : {};
    const listProps = representAs === "listbox"
        ? {
            role: "listbox",
            tabIndex: -1,
            "aria-activedescendant": focusedItem && focusedAction
                ? scopedIdStoreValue.makeId({
                    item: focusedItem,
                    specifier: focusedAction,
                })
                : undefined,
            "aria-multiselectable": !isSingleSelect,
            "aria-label": formatMessage({ id: "attributesDropdown.filterValues" }),
        }
        : {};
    return (_jsx(ScopedIdStore, { value: scopedIdStoreValue, children: _jsxs("div", { className: "gd-invertable-select", children: [
                _jsx("div", { className: "gd-invertable-select-search-bar", onKeyDown: handleSearchBarKeyDown, children: renderSearchBar({ onSearch: onSearch ?? (() => { }), searchPlaceholder, searchString }) }), isLoading ? (_jsx("div", { className: "gd-invertable-select-loading", children: renderLoading({ height }) })) : error ? (_jsx("div", { className: "gd-invertable-select-error", children: renderError({ height, error }) })) : (_jsxs(_Fragment, { children: [renderActions({
                            isVisible: items.length > 0,
                            checked: selectionState !== "none",
                            onToggle: onSelectAllCheckboxToggle,
                            onChange: onSelectAllCheckboxChange,
                            isFiltered: (searchString?.length ?? 0) > 0,
                            totalItemsCount: totalItemsCount ?? 0,
                            hideTotalItemsCount,
                            isPartialSelection: selectionState === "partial",
                        }), items.length > 0 && (_jsx(Measure, { client: true, children: ({ measureRef, contentRect }) => {
                                const maxHeight = adaptiveHeight
                                    ? (contentRect?.client?.height ?? 0)
                                    : Math.min(items.length, DEFAULT_VISIBLE_ITEMS_COUNT) *
                                        (itemHeight ?? 0);
                                return isLoading ? (_jsx(LoadingMask, { height: height })) : (_jsx("div", { className: "gd-invertable-select-list", ref: measureRef, children: _jsx("div", { onKeyDown: onKeyboardNavigation, onFocus: handleFocus, onBlur: handleBlur, className: cx("gd-async-list", className || ""), ref: containerRef, id: scopedIdStoreValue.containerId, ...selectProps, children: _jsx(UiPagedVirtualList, { items: items, itemHeight: itemHeight ?? 0, itemsGap: 0, itemPadding: 0, skeletonItemsCount: nextPageItemPlaceholdersCount ?? 0, hasNextPage: (nextPageItemPlaceholdersCount ?? 0) > 0, loadNextPage: onLoadNextPage, isLoading: isLoadingNextPage, maxHeight: maxHeight, scrollToItem: firstSelectedItem, scrollToItemKeyExtractor: getItemKey, tabIndex: -1, focusedItem: focusedItem, focusedAction: focusedAction, shouldLoadNextPage: shouldLoadNextPage, ref: listRef, representAs: representAs, listboxProps: listProps, getIsItemSelected: getIsItemSelected, children: itemRenderer }) }) }));
                            } })), items.length === 0 && (_jsx("div", { className: "gd-invertable-select-no-data", children: renderNoData?.({ height }) }))] })), isSingleSelect ? null : (_jsx("div", { role: "group", className: "gd-invertable-select-status-bar", "aria-label": formatMessage({ id: "attributesDropdown.selectedValues" }), children: renderStatusBar({
                        getItemTitle,
                        isInverted,
                        selectedItems,
                        selectedItemsLimit: selectedItemsLimit ?? 0,
                    }) })), _jsx(UiSearchResultsAnnouncement, { totalResults: searchString && !isLoading ? (totalItemsCount ?? 0) : undefined, resultValues: (totalItemsCount ?? 0) <= DETAILED_ANNOUNCEMENT_THRESHOLD
                        ? items.map(getItemTitle)
                        : undefined })
            ] }) }));
}
function defaultError({ error }) {
    return _jsx(ErrorComponent, { message: error?.message });
}
function defaultLoading({ height }) {
    return _jsx(LoadingMask, { height: height });
}
function defaultSearchBar({ onSearch, searchPlaceholder, searchString, }) {
    return (_jsx(InvertableSelectSearchBar, { searchPlaceholder: searchPlaceholder, onSearch: onSearch, searchString: searchString, isSmall: true }));
}
function defaultNoData() {
    return _jsx(InvertableSelectNoResultsMatch, {});
}
function defaultItem(props) {
    return (_jsx(InvertableSelectItem, { title: props.title, isSelected: props.isSelected, onClick: props.isSelected ? props.onDeselect : props.onSelect, onOnly: props.onSelectOnly, listRef: props.listRef }));
}
function defaultStatusBar({ isInverted, selectedItems, getItemTitle, selectedItemsLimit, }) {
    return (_jsx(InvertableSelectStatusBar, { isInverted: isInverted, selectedItems: selectedItems, getItemTitle: getItemTitle, selectedItemsLimit: selectedItemsLimit ?? 0 }));
}
function defaultActions({ checked, onToggle, onChange, isFiltered, totalItemsCount, isPartialSelection, isVisible, hideTotalItemsCount = false, }) {
    return (_jsx(InvertableSelectAllCheckbox, { isVisible: isVisible, checked: checked, onChange: onChange, onToggle: onToggle, isFiltered: isFiltered, totalItemsCount: totalItemsCount, isPartialSelection: isPartialSelection, hideTotalItemsCount: hideTotalItemsCount }));
}
