import { useCallback, useRef, useState } from "preact/compat";
import DropdownMenu from "./DropdownMenu";
import debounce from "../utilities/debounce";
import describeField from "../utilities/describeField";
import classNames from 'classnames';
import cleanFieldProps from "../utilities/cleanFieldProps";
import mergeRefs from "../utilities/mergeRefs";
import useClickOutsideHandler from "../utilities/useClickOutsideHandler";
import useId from "../utilities/useId";
import useAutofocus from "../utilities/useAutoFocus";
import { Label } from "../Label";
import { SvgIcon } from "../Icons";
import { getFirstOptionValue, isOptGroup, parseChildren, validateProps } from "./utils";
import { Item, Section, useSelectState } from "../react-aria"; // from react-stately
import { HiddenSelect, useButton, useSelect } from "../react-aria"; // from react-aria
import { useLabelProps } from "../Label/useLabelProps";
import { useHint } from "../Hint/useHint";
import { useInlineError } from "../InlineError/useInlineError";
import { jsx as _jsx } from "preact/compat/jsx-runtime";
import { createElement as _createElement } from "preact/compat";
import { jsxs as _jsxs } from "preact/compat/jsx-runtime";
const caretIcon = /*#__PURE__*/_jsx(SvgIcon, {
  title: "",
  viewBox: "0 0 448 512",
  className: "ds-u-font-size--sm",
  children: /*#__PURE__*/_jsx("path", {
    d: "M212.7 148.7c6.2-6.2 16.4-6.2 22.6 0l160 160c6.2 6.2 6.2 16.4 0 22.6s-16.4 6.2-22.6 0L224 182.6 75.3 331.3c-6.2 6.2-16.4 6.2-22.6 0s-6.2-16.4 0-22.6l160-160z"
  })
});
/**
 * For information about how and when to use this component,
 * [refer to its full documentation page](https://design.cms.gov/components/dropdown/).
 */
export const Dropdown = props => {
  validateProps(props);
  const id = useId('dropdown__button--', props.id);
  const buttonContentId = `${id}__button-content`;
  const menuId = `${id}__menu`;

  // Draw out certain props that we don't want to pass through as attributes
  const {
    autoFocus,
    children,
    className,
    fieldClassName,
    onBlur: userOnBlur,
    onChange,
    options,
    size,
    defaultValue,
    value,
    inputRef,
    inversed,
    getA11yStatusMessage,
    getA11ySelectionMessage,
    ...extraProps
  } = props;
  const optionsAndGroups = options ?? parseChildren(children);
  const renderReactStatelyItem = item => {
    const {
      label,
      value,
      ...extraAttrs
    } = item;
    return /*#__PURE__*/_createElement(Item, {
      ...extraAttrs,
      key: value
    }, label);
  };
  const reactStatelyItems = optionsAndGroups.map((item, index) => {
    if (isOptGroup(item)) {
      const {
        label,
        options,
        ...extraAttrs
      } = item;
      return /*#__PURE__*/_createElement(Section, {
        ...extraAttrs,
        key: `group-${index}`,
        title: label
      }, options.map(renderReactStatelyItem));
    } else {
      return renderReactStatelyItem(item);
    }
  });
  const isControlled = value !== undefined;
  let fallbackValue = defaultValue;
  if (!isControlled && fallbackValue === undefined) {
    fallbackValue = getFirstOptionValue(optionsAndGroups);
  }
  const [internalValueState, setInternalValueState] = useState(fallbackValue);
  const selectedKey = isControlled ? value : internalValueState;
  const onSelectionChange = value => {
    triggerRef.current?.focus?.();
    if (onChange) {
      // Try to support the old API that passed an event object
      const target = {
        value,
        name: props.name
      };
      onChange({
        target,
        currentTarget: target
      });
    }
    if (!isControlled) {
      setInternalValueState(value);
    }
  };
  const state = useSelectState({
    ...props,
    children: reactStatelyItems,
    selectedKey,
    onSelectionChange
  });
  const {
    errorId,
    topError,
    bottomError,
    invalid
  } = useInlineError({
    ...props,
    id
  });
  const {
    hintId,
    hintElement
  } = useHint({
    ...props,
    id
  });
  const onBlur = useCallback(
  // The active element is always the document body during a focus transition,
  // so in order to check if the newly focused element is one of our other date
  // inputs, we're going to have to wait a bit. We also have an issue with
  // tabbing out firing two blur events, so debounce during that time too. In
  // order for the debounce to work, we need to wrap this in a useCallback so
  // don't create a new one on each render.
  debounce(event => {
    // Only call the user's onBlur handler if focus leaves the whole component
    if (!wrapperRef.current?.contains(document.activeElement)) {
      userOnBlur?.(event);
      state.setOpen(false);
    }
  }, 20), [userOnBlur, state]);
  const triggerRef = useRef();
  const useSelectProps = useSelect({
    ...props,
    onBlur,
    isDisabled: props.disabled
  }, state, triggerRef);
  const useButtonProps = useButton(useSelectProps.triggerProps, triggerRef);
  const labelProps = {
    ...useSelectProps.labelProps,
    ...useLabelProps({
      ...props,
      id
    }),
    fieldId: id
  };
  const buttonProps = {
    ...useButtonProps.buttonProps,
    ...cleanFieldProps(extraProps),
    id,
    name: undefined,
    className: classNames('ds-c-dropdown__button', 'ds-c-field', props.errorMessage && 'ds-c-field--error', inversed && 'ds-c-field--inverse', size && `ds-c-field--${size}`, fieldClassName),
    ref: mergeRefs([triggerRef, inputRef, useAutofocus(props.autoFocus)]),
    'aria-controls': menuId,
    'aria-labelledby': `${buttonContentId} ${labelProps.id}`,
    'aria-invalid': invalid,
    'aria-describedby': describeField({
      ...props,
      hintId,
      errorId
    })
    // TODO: Someday we may want to add this `combobox` role back to the button, but right
    // now desktop VoiceOver has an issue. It seems to interpret the selected value in the
    // button as user input that needs to be checked for spelling (default setting). It
    // therefore announces anything it deems misspelled as such. The `react-aria` authors
    // likely ran into the same issue, since they leave it off for `useSelect` buttons.
    // Adding the combobox role in the future can help because screen reader users are more
    // familiar with the combobox pattern.
    // role: 'combobox',
  };

  const wrapperRef = useRef();
  useClickOutsideHandler([wrapperRef], () => state.setOpen(false));
  return /*#__PURE__*/_jsxs("div", {
    className: classNames('ds-c-dropdown', className, state.isOpen && 'ds-c-dropdown--open'),
    ref: wrapperRef,
    children: [/*#__PURE__*/_jsx(Label, {
      ...labelProps
    }), hintElement, topError, /*#__PURE__*/_jsx(HiddenSelect, {
      isDisabled: props.disabled,
      state: state,
      triggerRef: triggerRef,
      label: props.label,
      name: props.name
    }), /*#__PURE__*/_jsxs("button", {
      ...buttonProps,
      children: [/*#__PURE__*/_jsx("span", {
        id: buttonContentId,
        className: "ds-c-dropdown__label-text",
        children: state.selectedItem ? state.selectedItem.rendered : ''
      }), /*#__PURE__*/_jsx("span", {
        className: "ds-c-dropdown__caret",
        children: caretIcon
      })]
    }), state.isOpen && /*#__PURE__*/_jsx(DropdownMenu, {
      ...useSelectProps.menuProps,
      componentClass: "ds-c-dropdown",
      labelId: labelProps.id,
      menuId: menuId,
      rootId: id,
      size: size,
      state: state,
      triggerRef: triggerRef
    }), bottomError]
  });
};
export default Dropdown;