import React, { useState, useRef, useEffect, useCallback, Children, useMemo } from 'react';
import { debounce } from '../../utilities/debounce.js';
import { useToggle } from '../../utilities/use-toggle.js';
import { Key } from '../../types.js';
import { scrollable } from '../shared.js';
import styles from './Listbox.scss.js';
import { useComboboxListbox } from '../../utilities/combobox/hooks.js';
import { scrollOptionIntoView } from '../../utilities/listbox/utilities.js';
import { ListboxContext, WithinListboxContext } from '../../utilities/listbox/context.js';
import { TextOption } from './components/TextOption/TextOption.js';
import { Loading } from './components/Loading/Loading.js';
import { Section } from './components/Section/Section.js';
import { Header } from './components/Header/Header.js';
import { Action } from './components/Action/Action.js';
import { useUniqueId } from '../../utilities/unique-id/hooks.js';
import { KeypressListener } from '../KeypressListener/KeypressListener.js';
import { Text } from '../Text/Text.js';
import { Option } from './components/Option/Option.js';

let AutoSelection;

(function (AutoSelection) {
  AutoSelection["FirstSelected"] = "FIRST_SELECTED";
  AutoSelection["First"] = "FIRST";
  AutoSelection["None"] = "NONE";
})(AutoSelection || (AutoSelection = {}));

const OPTION_SELECTOR = '[data-listbox-option]';
const OPTION_VALUE_ATTRIBUTE = 'data-listbox-option-value';
const OPTION_ACTION_ATTRIBUTE = 'data-listbox-option-action';
const OPTION_FOCUS_ATTRIBUTE = 'data-focused';
function Listbox({
  children,
  autoSelection = AutoSelection.FirstSelected,
  enableKeyboardControl,
  accessibilityLabel,
  customListId,
  onSelect,
  onActiveOptionChange
}) {
  const [loading, setLoading] = useState();
  const [activeOption, setActiveOption] = useState();
  const [lazyLoading, setLazyLoading] = useState(false);
  const [currentOptions, setCurrentOptions] = useState([]);
  const {
    value: keyboardEventsEnabled,
    setTrue: enableKeyboardEvents,
    setFalse: disableKeyboardEvents
  } = useToggle(Boolean(enableKeyboardControl));
  const uniqueId = useUniqueId('Listbox');
  const listId = customListId || uniqueId;
  const scrollableRef = useRef(null);
  const listboxRef = useRef(null);
  const {
    listboxId,
    textFieldLabelId,
    textFieldFocused,
    willLoadMoreOptions,
    setActiveOptionId,
    setListboxId,
    onOptionSelected,
    onKeyToBottom
  } = useComboboxListbox();
  const inCombobox = Boolean(setActiveOptionId);
  useEffect(() => {
    if (setListboxId && !listboxId) {
      setListboxId(listId);
    }
  }, [setListboxId, listboxId, listId]);
  const getNavigableOptions = useCallback(() => {
    if (!listboxRef.current) {
      return [];
    }

    return [...new Set(listboxRef.current.querySelectorAll(OPTION_SELECTOR))];
  }, []);
  const getFirstNavigableOption = useCallback(currentOptions => {
    const hasSelectedOptions = currentOptions.some(option => option.getAttribute('aria-selected') === 'true');
    let elementIndex = 0;
    const element = currentOptions.find((option, index) => {
      const isInteractable = option.getAttribute('aria-disabled') !== 'true';
      let isFirstNavigableOption;

      if (hasSelectedOptions && autoSelection === AutoSelection.FirstSelected) {
        const isSelected = option.getAttribute('aria-selected') === 'true';
        isFirstNavigableOption = isSelected && isInteractable;
      } else {
        isFirstNavigableOption = isInteractable;
      }

      if (isFirstNavigableOption) elementIndex = index;
      return isFirstNavigableOption;
    });
    if (!element) return;
    return {
      element,
      index: elementIndex
    };
  }, [autoSelection]);
  const handleScrollIntoView = useCallback(option => {
    const {
      current: scrollable
    } = scrollableRef;

    if (scrollable) {
      scrollOptionIntoView(option.element, scrollable);
    }
  }, []);
  const handleScrollIntoViewDebounced = debounce(handleScrollIntoView, 50);
  const handleKeyToBottom = useCallback(() => {
    if (onKeyToBottom) {
      setLazyLoading(true);
      return Promise.resolve(onKeyToBottom());
    }
  }, [onKeyToBottom]);
  const handleChangeActiveOption = useCallback(nextOption => {
    if (!nextOption) return setActiveOption(undefined);
    activeOption === null || activeOption === void 0 ? void 0 : activeOption.element.removeAttribute(OPTION_FOCUS_ATTRIBUTE);
    nextOption.element.setAttribute(OPTION_FOCUS_ATTRIBUTE, 'true');
    handleScrollIntoViewDebounced(nextOption);
    setActiveOption(nextOption);
    setActiveOptionId === null || setActiveOptionId === void 0 ? void 0 : setActiveOptionId(nextOption.domId);
    onActiveOptionChange === null || onActiveOptionChange === void 0 ? void 0 : onActiveOptionChange(nextOption.value, nextOption.domId);
  }, [activeOption, setActiveOptionId, onActiveOptionChange, handleScrollIntoViewDebounced]);
  const getFormattedOption = useCallback((element, index) => {
    return {
      element,
      index,
      domId: element.id,
      value: element.getAttribute(OPTION_VALUE_ATTRIBUTE) || '',
      disabled: element.getAttribute('aria-disabled') === 'true',
      isAction: element.getAttribute(OPTION_ACTION_ATTRIBUTE) === 'true'
    };
  }, []);
  const resetActiveOption = useCallback(() => {
    var _nextOption, _nextOption2, _nextOption3;

    let nextOption;
    const nextOptions = getNavigableOptions();
    const nextActiveOption = getFirstNavigableOption(nextOptions);

    if (nextOptions.length === 0 && currentOptions.length > 0) {
      setCurrentOptions(nextOptions);
      handleChangeActiveOption();
      return;
    }

    if (nextActiveOption) {
      const {
        element,
        index
      } = nextActiveOption;
      nextOption = getFormattedOption(element, index);
    }

    const optionIsAlreadyActive = activeOption !== undefined && ((_nextOption = nextOption) === null || _nextOption === void 0 ? void 0 : _nextOption.domId) === (activeOption === null || activeOption === void 0 ? void 0 : activeOption.domId);
    const actionContentHasUpdated = (activeOption === null || activeOption === void 0 ? void 0 : activeOption.isAction) && ((_nextOption2 = nextOption) === null || _nextOption2 === void 0 ? void 0 : _nextOption2.isAction) && ((_nextOption3 = nextOption) === null || _nextOption3 === void 0 ? void 0 : _nextOption3.value) !== (activeOption === null || activeOption === void 0 ? void 0 : activeOption.value);
    const currentValues = currentOptions.map(option => option.getAttribute(OPTION_VALUE_ATTRIBUTE));
    const nextValues = nextOptions.map(option => option.getAttribute(OPTION_VALUE_ATTRIBUTE));
    const listIsUnchanged = nextValues.length === currentValues.length && nextValues.every((value, index) => {
      return currentValues[index] === value;
    });
    const listIsAppended = currentValues.length !== 0 && nextValues.length > currentValues.length && currentValues.every((value, index) => {
      return nextValues[index] === value;
    });

    if (listIsUnchanged) {
      if (optionIsAlreadyActive && actionContentHasUpdated) {
        setCurrentOptions(nextOptions);
        handleChangeActiveOption(nextOption);
      }

      return;
    }

    if (listIsAppended) {
      setCurrentOptions(nextOptions);
      return;
    }

    setCurrentOptions(nextOptions);

    if (lazyLoading) {
      setLazyLoading(false);
      return;
    }

    handleChangeActiveOption(nextOption);
  }, [lazyLoading, currentOptions, activeOption, getFirstNavigableOption, getNavigableOptions, getFormattedOption, handleChangeActiveOption]);
  useEffect(() => {
    if (autoSelection !== AutoSelection.None && !loading && children && Children.count(children) > 0) {
      resetActiveOption();
    }
  }, [children, autoSelection, activeOption, loading, resetActiveOption]);
  useEffect(() => {
    if (listboxRef.current) {
      scrollableRef.current = listboxRef.current.closest(scrollable.selector);
    }
  }, []);
  useEffect(() => {
    if (enableKeyboardControl && !keyboardEventsEnabled) {
      enableKeyboardEvents();
    }
  }, [enableKeyboardControl, keyboardEventsEnabled, enableKeyboardEvents]);
  const onOptionSelect = useCallback(option => {
    handleChangeActiveOption(option);
    if (onOptionSelected) onOptionSelected();
    if (onSelect) onSelect(option.value);
  }, [handleChangeActiveOption, onSelect, onOptionSelected]);
  const getNextIndex = useCallback((currentIndex, lastIndex, direction) => {
    let nextIndex;

    if (direction === 'down') {
      if (currentIndex === lastIndex) {
        nextIndex = willLoadMoreOptions ? currentIndex + 1 : 0;
      } else {
        nextIndex = currentIndex + 1;
      }
    } else {
      nextIndex = currentIndex === 0 ? lastIndex : currentIndex - 1;
    }

    return nextIndex;
  }, [willLoadMoreOptions]);
  const getNextValidOption = useCallback(async key => {
    const lastIndex = currentOptions.length - 1;
    let currentIndex = (activeOption === null || activeOption === void 0 ? void 0 : activeOption.index) || 0;
    let nextIndex = 0;
    let element = activeOption === null || activeOption === void 0 ? void 0 : activeOption.element;
    let totalOptions = -1;

    if (!activeOption && autoSelection === AutoSelection.None) {
      const nextOptions = getNavigableOptions();
      const nextActiveOption = getFirstNavigableOption(nextOptions);
      setCurrentOptions(nextOptions);
      return {
        element: nextActiveOption === null || nextActiveOption === void 0 ? void 0 : nextActiveOption.element,
        nextIndex: (nextActiveOption === null || nextActiveOption === void 0 ? void 0 : nextActiveOption.index) || 0
      };
    }

    while (totalOptions++ < lastIndex) {
      var _element;

      nextIndex = getNextIndex(currentIndex, lastIndex, key);
      element = currentOptions[nextIndex];
      const triggerLazyLoad = nextIndex >= lastIndex;
      const isDisabled = ((_element = element) === null || _element === void 0 ? void 0 : _element.getAttribute('aria-disabled')) === 'true';

      if (triggerLazyLoad && willLoadMoreOptions) {
        await handleKeyToBottom();
      }

      if (isDisabled) {
        currentIndex = nextIndex;
        element = undefined;
        continue;
      }

      break;
    }

    return {
      element,
      nextIndex
    };
  }, [autoSelection, currentOptions, activeOption, willLoadMoreOptions, getNextIndex, handleKeyToBottom, getFirstNavigableOption, getNavigableOptions]);
  const handleArrow = useCallback(async (type, event) => {
    event.preventDefault();
    const {
      element,
      nextIndex
    } = await getNextValidOption(type);
    if (!element) return;
    const nextOption = getFormattedOption(element, nextIndex);
    handleChangeActiveOption(nextOption);
  }, [getFormattedOption, getNextValidOption, handleChangeActiveOption]);
  const handleDownArrow = useCallback(event => {
    handleArrow('down', event);
  }, [handleArrow]);
  const handleUpArrow = useCallback(event => {
    handleArrow('up', event);
  }, [handleArrow]);
  const handleEnter = useCallback(event => {
    event.preventDefault();
    event.stopPropagation();

    if (activeOption) {
      onOptionSelect(activeOption);
    }
  }, [activeOption, onOptionSelect]);
  const handleFocus = useCallback(() => {
    if (enableKeyboardControl) return;
    enableKeyboardEvents();
  }, [enableKeyboardControl, enableKeyboardEvents]);
  const handleBlur = useCallback(event => {
    event.stopPropagation();

    if (keyboardEventsEnabled) {
      const nextActiveOption = getFirstNavigableOption(currentOptions);

      if (nextActiveOption) {
        const {
          element,
          index
        } = nextActiveOption;
        const nextOption = getFormattedOption(element, index);
        handleChangeActiveOption(nextOption);
      }
    }

    if (enableKeyboardControl) return;
    disableKeyboardEvents();
  }, [enableKeyboardControl, currentOptions, keyboardEventsEnabled, disableKeyboardEvents, getFirstNavigableOption, getFormattedOption, handleChangeActiveOption]);
  const listeners = keyboardEventsEnabled || textFieldFocused ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(KeypressListener, {
    keyEvent: "keydown",
    keyCode: Key.DownArrow,
    handler: handleDownArrow
  }), /*#__PURE__*/React.createElement(KeypressListener, {
    keyEvent: "keydown",
    keyCode: Key.UpArrow,
    handler: handleUpArrow
  }), /*#__PURE__*/React.createElement(KeypressListener, {
    keyEvent: "keydown",
    keyCode: Key.Enter,
    handler: handleEnter
  })) : null;
  const listboxContext = useMemo(() => ({
    onOptionSelect,
    setLoading
  }), [onOptionSelect]);
  return /*#__PURE__*/React.createElement(React.Fragment, null, listeners, /*#__PURE__*/React.createElement(Text, {
    as: "span",
    visuallyHidden: true
  }, /*#__PURE__*/React.createElement("div", {
    "aria-live": "polite"
  }, loading ? loading : null)), /*#__PURE__*/React.createElement(ListboxContext.Provider, {
    value: listboxContext
  }, /*#__PURE__*/React.createElement(WithinListboxContext.Provider, {
    value: true
  }, children ? /*#__PURE__*/React.createElement("ul", {
    tabIndex: 0,
    role: "listbox",
    className: styles.Listbox,
    "aria-label": inCombobox ? undefined : accessibilityLabel,
    "aria-labelledby": textFieldLabelId,
    "aria-busy": Boolean(loading),
    "aria-activedescendant": activeOption && activeOption.domId,
    id: listId,
    onFocus: inCombobox ? undefined : handleFocus,
    onBlur: inCombobox ? undefined : handleBlur,
    ref: listboxRef
  }, children) : null)));
}
Listbox.Option = Option;
Listbox.TextOption = TextOption;
Listbox.Loading = Loading;
Listbox.Section = Section;
Listbox.Header = Header;
Listbox.Action = Action;

export { AutoSelection, Listbox };
