import React, { ComponentProps, CSSProperties, forwardRef, useEffect, useLayoutEffect, useRef, useState } from 'react';
import classNames from 'clsx';
// eslint-disable-next-line no-restricted-imports
import { Autocomplete, autocompleteClasses, Checkbox, createFilterOptions, inputAdornmentClasses } from '@mui/material';

import { largeSize, mediumSize, smallSize, xSmallSize } from '@/mui/components/constants';
import { SIZE_LARGE, SIZE_MEDIUM, SIZE_SMALL, SIZE_X_SMALL, SPACING } from '@/mui/constants';

import { autoSuggestHighlight } from '@/mui/helpers';

import { Box, Button, Chip } from '@/mui/components';
import ListItem from '@/mui/components/ListItem/ListItem';
import ListItemIcon from '@/mui/components/ListItemIcon/ListItemIcon';
import ListItemText from '@/mui/components/ListItemText/ListItemText';
import Stack from '@/mui/components/Stack/Stack';

import styles from './MultiAutocomplete.module.scss';

const SELECT_ALL_KEY = '$$selectAllOptions';

type BaseOption = { label: string; value: string; [SELECT_ALL_KEY]?: boolean };

type Props = Omit<ComponentProps<typeof Autocomplete>, 'options' | 'value'> & {
  value: BaseOption[];
  options: BaseOption[];
  autoSuggest?: boolean;
};

const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;

const fixedHeight = {
  [SIZE_X_SMALL]: xSmallSize,
  [SIZE_SMALL]: smallSize,
  [SIZE_MEDIUM]: mediumSize,
  [SIZE_LARGE]: largeSize,
};

const MultiAutocomplete = forwardRef<HTMLElement, Props>(
  (
    {
      disableCloseOnSelect = true,
      autoSuggest = true,
      value,
      options,
      size = 'small',
      onChange,
      onOpen,
      onClose,
      classes = {},
      className,
      renderInput,
      ...props
    },
    ref,
  ) => {
    const autocompleteRef = useRef<HTMLElement>(null);
    const [adornmentWidth, setAdornmentWidth] = useState(0);
    const [inFocus, setInFocus] = useState(false);
    const $tagsContainer = useRef<HTMLDivElement>(null);

    useIsomorphicLayoutEffect(() => {
      const checkWidth = () => {
        const aWidth =
          (
            autocompleteRef.current?.querySelector(`.${inputAdornmentClasses.positionEnd}`) ||
            autocompleteRef.current?.querySelector(`.${autocompleteClasses.endAdornment}`)
          )?.clientWidth || 0;

        setAdornmentWidth(aWidth + SPACING * 2);
      };

      checkWidth();

      const timer = window.setInterval(() => checkWidth(), 1000);

      return () => {
        clearInterval(timer);
      };
    }, [autocompleteRef.current]);

    const allSelected = options.every((option) => value.some((selected) => selected.value === option.value));

    return (
      <Autocomplete
        style={
          {
            '--internal-height': `${fixedHeight[size]}`,
            '--internal-adornment-width': `${adornmentWidth}px`,
          } as CSSProperties
        }
        ref={(node) => {
          if (typeof ref === 'function') {
            ref(node as HTMLElement);
          } else if (ref) {
            ref.current = node as HTMLElement;
          }
          autocompleteRef.current = node as HTMLElement;
        }}
        classes={{
          ...classes,
          input: styles.Input,
          inputRoot: classNames(styles.InputRoot, {
            [styles.InputRoot___blur]: !inFocus,
          }),
        }}
        renderValue={(values$, getTagProps) => {
          const values = values$ as BaseOption[];

          return (
            <Box
              className={classNames(styles.TagsRoot, {
                [styles.TagsRoot___blur]: !inFocus,
              })}
              ref={$tagsContainer}
            >
              {values.map((value$, index) => {
                const showTag = inFocus || (!inFocus && !index);

                return showTag ? (
                  <Chip {...getTagProps({ index })} key={index} label={value$.label} size={size} />
                ) : undefined;
              })}
              {!inFocus && values.length > 1 && <Chip variant="text" label={`+${values.length - 1}`} size={size} />}
            </Box>
          );
        }}
        options={options}
        multiple
        value={value}
        disableCloseOnSelect={disableCloseOnSelect}
        size={size}
        className={classNames(styles.MultiAutocomplete, className)}
        filterOptions={(options$, params) => {
          const filter = createFilterOptions();
          const filtered = filter(options$, params);

          return !options$.length
            ? [...filtered]
            : [...filtered, { label: 'Select All...', value: '', [SELECT_ALL_KEY]: true }];
        }}
        onChange={(event, newOptions$, reason, ...args) => {
          let newOptions = newOptions$ as BaseOption[];

          if (newOptions.find((option) => option[SELECT_ALL_KEY])) {
            newOptions = [...(options as BaseOption[])];
          }

          if (reason === 'selectOption' && $tagsContainer.current) {
            $tagsContainer.current.scrollTop = $tagsContainer.current.scrollHeight;
          }

          if (onChange) {
            onChange(event, newOptions, reason, ...args);
          }
        }}
        getOptionLabel={(option$) => (option$ as BaseOption).label}
        isOptionEqualToValue={(option, v) => (option as BaseOption).value === (v as BaseOption).value}
        renderOption={(props$, option$, { inputValue, selected }) => {
          const option = option$ as BaseOption;

          return (
            <ListItem
              {...props$}
              key={`${option.label}:${option.value}:${option}`}
              className={classNames(styles.ListItem, {
                [styles.ListItem___all]: option[SELECT_ALL_KEY],
              })}
            >
              {option[SELECT_ALL_KEY] ? (
                <Stack alignItems="center" fullWidth justifyContent="center">
                  <Button size="small" disabled={allSelected} fullWidth>
                    Select All
                  </Button>
                </Stack>
              ) : (
                <Stack direction="row" alignItems="center" spacing={0} fullWidth>
                  <ListItemIcon>
                    <Checkbox checked={selected} />
                  </ListItemIcon>
                  <ListItemText
                    primary={
                      autoSuggest
                        ? autoSuggestHighlight(inputValue, option.label as string).map((part) => {
                            const Tag = part.highlight ? 'b' : React.Fragment;

                            return <Tag key={part.id}>{part.text}</Tag>;
                          })
                        : option.label
                    }
                    slotProps={{
                      primary: {
                        variant: 'body2',
                      },
                    }}
                  />
                </Stack>
              )}
            </ListItem>
          );
        }}
        renderInput={renderInput}
        onOpen={(...args) => {
          setInFocus(true);

          if (onOpen) {
            onOpen(...args);
          }
        }}
        onClose={(...args) => {
          setInFocus(false);

          if (onClose) {
            onClose(...args);
          }
        }}
        {...props}
      />
    );
  },
);

export default MultiAutocomplete;
