import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';

import MuiSelect from '@mui/base/SelectUnstyled';
import OptionUnstyled from '@mui/base/OptionUnstyled';

import { bem } from 'utils/bem';
import { Typography, TYPOGRAPHY_TYPE } from 'components/Typography';

import { THEME } from '../../constants'; // TODO: finish config Rollup to work properly with absolute path
import { DefaultTrigger, DefaultDropdown, DefaultPopper, DefaultOption } from './components';
import { SelectOption, SelectProps, SELECT_TYPE } from './types';

import styles from './select.scss';

export const selectClassName = bem(styles, 'select');

export const Select = <T extends SelectOption>({
  className,
  theme = THEME.light,
  value,
  options,
  onChange,
  placeholder,
  components,
  label,
  error,
  rootClassName,
  popperClassName,
  variant = SELECT_TYPE.default,
  isInitiallyOpened = false,
  isSearchable = false,
  isDisabled = false,
}: SelectProps<T>): React.ReactElement => {
  const triggerRef = useRef<HTMLDivElement>(null);
  const popperRef = useRef<HTMLDivElement>(null);
  const dropdownRef = useRef<HTMLDivElement>(null);

  const [isOpened, setIsOpened] = useState<boolean>(isInitiallyOpened);
  const [search, setSearch] = useState<string>('');

  const filteredOptions = useMemo(
    () => (search ? options.filter((option) => option.label.toLowerCase().includes(search.toLowerCase())) : options),
    [options, search],
  );

  const activeOption = useMemo(() => options.find(({ id }) => id === value), [value, options]);

  const openModal = useCallback(() => {
    setIsOpened(true);
  }, []);

  const closeModal = useCallback(() => {
    setIsOpened(false);
  }, []);

  const toggleModal = useCallback(() => {
    if (isOpened) {
      closeModal();
    } else {
      openModal();
    }
  }, [closeModal, isOpened, openModal]);

  const handleChange = useCallback(
    (_: React.MouseEvent | React.KeyboardEvent | React.FocusEvent | null, newValue: number | string | null) => {
      onChange(newValue);
      closeModal();
    },
    [closeModal, onChange],
  );

  const handleSearch = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
    setSearch(event.target.value);
  }, []);

  const resetSearch = useCallback(() => {
    setSearch('');
  }, []);

  const focusDropdown = useCallback(() => {
    dropdownRef.current?.focus();
  }, []);

  const onDropdownOpen = useCallback(
    (isDropdownOpened: boolean) => {
      if (isDropdownOpened) {
        resetSearch();
      }
    },
    [resetSearch],
  );

  useEffect(() => {
    const handleCloseModal = (event: MouseEvent | KeyboardEvent): void => {
      if (!popperRef.current?.contains(event.target as Node) && !triggerRef.current?.contains(event.target as Node)) {
        closeModal();
      }
    };

    document.addEventListener('mousedown', handleCloseModal, true);

    return () => {
      document.removeEventListener('mousedown', handleCloseModal, true);
    };
  }, [closeModal]);

  return (
    <div
      className={selectClassName(
        null,
        { isCompact: variant === SELECT_TYPE.compact, isDisabled, [theme]: true },
        className,
      )}
    >
      {label && <span className={selectClassName('label')}>{label}</span>}

      <MuiSelect<number | string>
        listboxOpen={isOpened}
        value={value}
        onChange={handleChange}
        disabled={isDisabled}
        onListboxOpenChange={onDropdownOpen}
        slots={{
          root: components?.Trigger ?? DefaultTrigger,
          listbox: components?.Dropdown ?? DefaultDropdown,
          popper: DefaultPopper,
        }}
        slotProps={{
          root: () => ({
            ref: triggerRef,
            theme,
            option: activeOption,
            placeholder,
            variant,
            isDisabled,
            toggleModal,
            className: rootClassName,
          }),
          popper: () => ({
            ref: popperRef,
            theme,
            variant,
            isSearchable,
            search,
            handleSearch,
            resetSearch,
            focusDropdown,
            className: popperClassName,
          }),
          listbox: () => ({
            ref: dropdownRef,
          }),
        }}
      >
        {filteredOptions.length ? (
          <>
            {filteredOptions.map((option) => {
              const isActive = option.id === value;
              const OptionComponent = components?.Option ?? DefaultOption;

              return (
                <OptionUnstyled
                  key={option.id}
                  className={selectClassName('option', { isCompact: variant === SELECT_TYPE.compact, isActive })}
                  value={option.id}
                  label={option.label}
                  disabled={option.isDisabled}
                  component="div"
                >
                  <OptionComponent<T>
                    {...option}
                    variant={variant}
                    isActive={isActive}
                    isDisabled={Boolean(option.isDisabled)}
                  />
                </OptionUnstyled>
              );
            })}
          </>
        ) : (
          <div className={selectClassName('empty-container')}>
            <Typography
              className={selectClassName('empty')}
              type={TYPOGRAPHY_TYPE.body3}
            >
              No result found.
              <br />
              Please try editing your query.
            </Typography>
          </div>
        )}
      </MuiSelect>

      {error && <span className={selectClassName('error')}>{error}</span>}
    </div>
  );
};
