// Copyright 1999-2023. Plesk International GmbH. All rights reserved.

import { Children, useState, useRef, isValidElement } from 'react';
import classNames from 'classnames';
import { safeInvoke, EventEmitter } from '../utils';
import { CLS_PREFIX } from '../../constants';
import ComboBoxDropdown, { findNextSafeIndex } from '../ComboBoxDropdown';
import Translate from '../Translate';
import SelectControl from './SelectControl';
import SelectOptionGroup from './SelectOptionGroup';
import SelectOption from './SelectOption';
import { jsx as _jsx } from "react/jsx-runtime";
// eslint-disable-next-line default-param-last
const collect = function (children) {
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
  let group = arguments.length > 2 ? arguments[2] : undefined;
  const groups = [];
  Children.forEach(children, child => {
    if ( /*#__PURE__*/isValidElement(child) && (child.type === SelectOptionGroup || child.type === 'optgroup')) {
      const {
        label,
        children,
        ...props
      } = child.props;
      const group = {
        label: String(label),
        options: [],
        props
      };
      groups.push(group);
      collect(children, options, group);
    } else if ( /*#__PURE__*/isValidElement(child) && (child.type === SelectOption || child.type === 'option')) {
      // eslint-disable-next-line prefer-const
      let {
        value,
        label: propLabel,
        disabled,
        children,
        ...props
      } = child.props;
      if (typeof value !== 'string' && typeof value !== 'number') {
        if (typeof children === 'string' && children) {
          value = children;
        } else {
          throw new Error('The value can only be a number or a string.');
        }
      }
      let label;
      if (propLabel && typeof propLabel === 'string') {
        ({
          label
        } = child.props);
      } else if (typeof child.props.children === 'string') {
        label = child.props.children;
      }
      if (!label) {
        throw new Error('The label can only be a string.');
      }
      const option = {
        value,
        label,
        children,
        disabled: Boolean(disabled),
        group,
        props
      };
      options.push(option);
      if (group) {
        group.options.push(option);
      }
    }
  });
  if (groups.length === 0) {
    groups.push({
      options
    });
  }
  return {
    groups,
    options
  };
};
const getNextValue = (value, prevValue) => {
  if (Array.isArray(prevValue)) {
    return [...prevValue, value];
  }
  return value;
};
/**
 * `Select` component is used for making a choice among a set of related options.
 * @since 2.3.0
 */
const Select = _ref => {
  let {
    id,
    name,
    size,
    baseClassName = `${CLS_PREFIX}select`,
    className,
    children,
    defaultValue,
    value: propValue,
    onChange,
    disabled = false,
    searchable = false,
    clearable = false,
    multiple = false,
    placeholder,
    ...props
  } = _ref;
  const [opened, setOpened] = useState(false);
  const controlRef = useRef(null);
  const emitterRef = useRef(new EventEmitter());
  let [value, setValue] = useState(() => {
    if (typeof defaultValue === 'undefined') {
      return multiple ? [] : undefined;
    }
    return defaultValue;
  });
  if (typeof propValue !== 'undefined') {
    value = propValue;
    setValue = () => {};
  }
  const [filterValue, setFilterValue] = useState('');
  const [highlightedIndex, setHighlightedIndex] = useState(0);
  let {
    groups,
    options
  } = collect(children);
  let current;
  if (Array.isArray(value)) {
    const _current = [];
    value.forEach(v => {
      const option = options.find(o => o.value === v);
      if (option) {
        _current.push(option);
      }
    });
    current = _current;
  } else {
    const _current = options.find(o => o.value === value);
    current = _current;
  }
  const groupFns = [];
  const optionFns = [];
  // eslint-disable-next-line func-style
  function createInclude(fns) {
    return item => fns.every(fn => fn(item));
  }
  if (Array.isArray(value) && value.length > 0) {
    const v = value;
    groupFns.push(() => false);
    optionFns.push(option => v.indexOf(option.value) === -1);
  }
  if (filterValue) {
    const lowerFilterValue = filterValue.toLowerCase();
    groupFns.push(group => !!group.label && group.label.toLowerCase().includes(lowerFilterValue));
    optionFns.push(option => option.label.toLowerCase().includes(lowerFilterValue));
  }
  if (groupFns.length || optionFns.length) {
    const includeGroup = createInclude(groupFns);
    const includeOption = createInclude(optionFns);
    options = [];
    groups = groups.filter(group => {
      if (includeGroup(group)) {
        options = [...options, ...group.options];
        return true;
      }
      group.options = group.options.filter(option => {
        if (includeOption(option)) {
          options.push(option);
          return true;
        }
        return false;
      });
      return group.options.length > 0;
    });
  }
  if (highlightedIndex && highlightedIndex > options.length - 1) {
    setHighlightedIndex(0);
  }
  const handleFilterChange = nextFilterValue => {
    handleOpen();
    setFilterValue(nextFilterValue);
  };
  const focusHighlightedIndex = index => {
    emitterRef.current.emit('focus', index);
  };
  const handleHighlightChange = diff => {
    if (!opened) {
      handleOpen();
      return;
    }
    const nextIndex = findNextSafeIndex(highlightedIndex, diff, options);
    if (nextIndex !== -1) {
      setHighlightedIndex(nextIndex);
      focusHighlightedIndex(nextIndex);
    }
  };
  const handleHighlightConfirm = index => {
    if (!opened && index === undefined) {
      return;
    }
    const option = options[index === undefined ? highlightedIndex : index];
    if (!option) {
      return;
    }
    if (option.disabled) {
      return;
    }
    const nextValue = getNextValue(option.value, value);
    setValue(nextValue);
    setFilterValue('');
    if (!multiple) {
      handleClose();
    }
    safeInvoke(onChange, nextValue);
  };
  const handleHighlightSearch = query => {
    const lowerQuery = query.toLowerCase();
    const index = options.findIndex(o => !o.disabled && o.label.toLowerCase().includes(lowerQuery));
    if (index !== -1) {
      setHighlightedIndex(index);
      focusHighlightedIndex(index);
      if (!opened) {
        handleHighlightConfirm(index);
      }
    }
  };
  const handleOpen = () => {
    setOpened(true);
  };
  const handleClose = () => {
    setOpened(false);
    setHighlightedIndex(0);
  };
  const handleSelect = optionValue => {
    const nextValue = getNextValue(optionValue, value);
    setValue(nextValue);
    setFilterValue('');
    if (!multiple) {
      handleClose();
    }
    safeInvoke(onChange, nextValue);
  };
  const handleHighlightedIndexChange = index => {
    setHighlightedIndex(index);
  };
  const handleValueRemove = valueToRemove => {
    if (Array.isArray(value)) {
      let nextValue;
      if (valueToRemove === undefined) {
        nextValue = [...value];
        nextValue.pop();
      } else {
        nextValue = value.filter(v => v !== valueToRemove);
      }
      setValue(nextValue);
      safeInvoke(onChange, nextValue);
    } else {
      setValue(undefined);
      safeInvoke(onChange, undefined);
    }
  };
  const handleClear = () => {
    const nextValue = Array.isArray(value) ? [] : undefined;
    setFilterValue('');
    setValue(nextValue);
    safeInvoke(onChange, nextValue);
  };
  const control = /*#__PURE__*/_jsx(SelectControl, {
    id: id,
    name: name,
    current: current,
    filterValue: filterValue,
    searchable: searchable,
    clearable: clearable,
    multiple: multiple,
    disabled: disabled,
    onFilterChange: handleFilterChange,
    onHighlightChange: handleHighlightChange,
    onHighlightConfirm: handleHighlightConfirm,
    onHighlightSearch: handleHighlightSearch,
    onOpen: handleOpen,
    onClose: handleClose,
    onClear: handleClear,
    onValueRemove: handleValueRemove,
    placeholder: placeholder,
    ref: controlRef
  });
  return /*#__PURE__*/_jsx(ComboBoxDropdown, {
    className: classNames(baseClassName, className, {
      [`${baseClassName}--size-${size}`]: size
    }),
    control: control,
    controlRef: controlRef,
    opened: opened && !disabled,
    disabled: disabled,
    groups: groups,
    options: options,
    onSelect: handleSelect,
    highlightedIndex: highlightedIndex,
    onHighlightedIndexChange: handleHighlightedIndexChange,
    onClose: handleClose,
    noOptions: /*#__PURE__*/_jsx(Translate, {
      content: "Select.noOptions",
      fallback: "No options"
    }),
    emitter: emitterRef.current,
    value: value,
    ...props
  });
};
export default Select;