import { type RefObject, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useInView } from 'react-intersection-observer';

import {
  alpha,
  autocompleteClasses,
  CircularProgress,
  createFilterOptions,
  FormControl,
  formHelperTextClasses,
  InputAdornment,
  inputBaseClasses,
  Autocomplete as MUIAutocomplete,
  Stack,
  TextField,
  Typography,
  useTheme,
} from '@mui/material';
import {
  IconChevronDown,
  IconCirclePlus,
  IconExclamationCircle,
  IconX,
} from '@tabler/icons-react';
import { debounce } from 'lodash';

import Chip from '../../Chip';
import Title from '../../Title';
import CustomHelperText from '../Base/CustomHelperText';
import CustomLabel from '../Base/CustomLabel';
import { getBorderColor } from '../utils';

import AutocompleteItem from './components/AutocompleteItem';
import VirtualizedListbox from './components/VirtualizedListbox';
import {
  getCreatableInputText,
  getCreatableValue,
  isCreatableOption,
  isEqualText,
} from './constants';
import { type AutocompleteOption, type AutocompleteProps } from './types';

const Autocomplete = <
  TValue extends AutocompleteOption = AutocompleteOption,
  TMultiple extends boolean | undefined = false,
>(
  props: AutocompleteProps<TValue, TMultiple>,
) => {
  const [focused, setFocused] = useState(false);
  const theme = useTheme();
  const { t } = useTranslation('material_hu_only');
  const hasCreateRef = useRef(false);

  const {
    hasError,
    helperText,
    placeholder,
    label,
    fieldRef,
    onCreate,
    onChange,
    onLoadMore,
    hasMoreOptions,
    onInputChange = () => {},
    isServerFiltered = true,
    renderTags,
    noOptionsMessage,
    renderOption,
    loadMoreIndexOffset = 0,
    virtualized = false,
    filterLimit,
    type,
    maxLength,
    startIcon,
    hidePopupIcon = false,
    ...fieldProps
  } = props;

  const [loadMoreRef] = useInView({
    onChange: inView => {
      if (inView) {
        onLoadMore?.();
      }
    },
  });

  const getStatusTextColor = () => {
    if (fieldProps.disabled) {
      return theme.palette.new.text.neutral.disabled;
    }

    return hasError
      ? theme.palette.new.text.feedback.error
      : theme.palette.new.text.neutral.lighter;
  };

  const statusTextColor = getStatusTextColor();

  // Search-style fields (start icon and/or no chevron) match the height and
  // padding of the standard `Inputs/Search` field instead of the shorter
  // default Autocomplete height.
  const isSearchStyle = !!startIcon || hidePopupIcon;

  const hasMultipleSelections =
    !!fieldProps.multiple &&
    Array.isArray(fieldProps.value) &&
    fieldProps.value.length > 0;

  const debouncedOnInputChange = useMemo(
    () => debounce(onInputChange, 500),
    [],
  );

  useEffect(
    () => () => {
      debouncedOnInputChange.cancel();
    },
    [],
  );

  const defaultFilterOptions = createFilterOptions<TValue>(
    filterLimit ? { limit: filterLimit } : undefined,
  );

  const renderTagsFn =
    renderTags ??
    (fieldProps.multiple
      ? (value, getTagProps) =>
          value.map((option, index) => {
            const { key, ...tagProps } = getTagProps({ index });
            return (
              <Chip
                key={key}
                label={option.label}
                sx={{ m: 0.25 }}
                {...tagProps}
              />
            );
          })
      : undefined);

  return (
    <FormControl
      error={hasError}
      fullWidth={fieldProps.fullWidth ?? true}
      disabled={fieldProps.disabled}
      onFocus={() => setFocused(true)}
      onBlur={() => setFocused(false)}
    >
      {label && <CustomLabel label={label} />}
      <MUIAutocomplete
        fullWidth
        selectOnFocus
        handleHomeEndKeys
        clearOnBlur
        {...(virtualized
          ? {
              ListboxComponent: VirtualizedListbox as React.ComponentType<
                React.HTMLAttributes<HTMLElement>
              >,
            }
          : { ListboxProps: { sx: { maxHeight: 240 } } })}
        sx={{
          [`& .${autocompleteClasses.inputRoot} .${autocompleteClasses.endAdornment}`]:
            {
              color: 'inherit',
              right: '16px',
            },
          [`& .${autocompleteClasses.clearIndicator}`]: {
            color: 'inherit',
            padding: '4px',
            marginRight: 0,
          },
          [`& .${autocompleteClasses.popupIndicator}`]: {
            color: 'inherit',
            padding: '4px',
            marginRight: 0,
          },
        }}
        forcePopupIcon={hidePopupIcon ? false : 'auto'}
        popupIcon={
          hidePopupIcon ? null : (
            <IconChevronDown
              style={{ color: 'inherit' }}
              size={20}
            />
          )
        }
        clearIcon={
          <IconX
            style={{ color: 'inherit' }}
            size={20}
          />
        }
        getOptionKey={option => option.value}
        onInputChange={
          isServerFiltered ? debouncedOnInputChange : onInputChange
        }
        isOptionEqualToValue={(option, selected) =>
          option.value === selected.value
        }
        filterOptions={(options, state) => {
          const nextOptions = isServerFiltered
            ? Array.from(options)
            : defaultFilterOptions(options, state);

          if (onCreate) {
            const { inputValue, getOptionLabel } = state;
            const trimmedInput = inputValue
              .trim()
              .slice(0, maxLength || Infinity);

            const isExisting = nextOptions.some(option =>
              isEqualText(inputValue, getOptionLabel(option)),
            );

            if (!isExisting && trimmedInput) {
              hasCreateRef.current = true;
              nextOptions.unshift({
                label: t('hu_inputs.create_option', { label: inputValue }),
                value: getCreatableValue(trimmedInput),
              } as TValue);
            } else {
              hasCreateRef.current = false;
            }
          }

          return nextOptions;
        }}
        renderOption={(...renderArgs) => {
          const [{ key, ...optionProps }, option, state] = renderArgs;
          const { index } = state;
          const isCreatable = isCreatableOption(option.value);

          const content = [
            renderOption?.(...renderArgs) ?? (
              <AutocompleteItem
                key={key}
                {...optionProps}
              >
                <Stack
                  sx={{
                    flexDirection: 'row',
                    alignItems: 'center',
                    gap: 1,
                    width: '100%',
                  }}
                >
                  {isCreatable && (
                    <IconCirclePlus
                      size={20}
                      color={theme.palette.primary.main}
                    />
                  )}
                  <Stack
                    sx={{
                      flexDirection: 'row',
                      justifyContent: 'space-between',
                      flex: 1,
                    }}
                  >
                    <Title
                      title={option.label}
                      description={option.description}
                      variant="S"
                      fontWeight="fontWeightRegular"
                      slotProps={{
                        title: {
                          sx: isCreatable
                            ? {
                                color: theme.palette.primary.main,
                              }
                            : undefined,
                        },
                      }}
                    />
                    {isCreatable && maxLength && (
                      <Typography variant="globalS">
                        {getCreatableInputText(option.value.toString()).length}/
                        {maxLength}
                      </Typography>
                    )}
                  </Stack>
                </Stack>
              </AutocompleteItem>
            ),
          ];

          const optionsLen = hasCreateRef.current
            ? fieldProps.options.length
            : fieldProps.options.length - 1;

          const isLoadMoreVisible =
            index === optionsLen + loadMoreIndexOffset && hasMoreOptions;

          const { className } = optionProps;

          if (isLoadMoreVisible) {
            content.push(
              <AutocompleteItem
                key="load-more"
                ref={loadMoreRef}
                className={className}
                sx={{
                  alignItems: 'center',
                  justifyContent: 'center !important',
                  [`&.${autocompleteClasses.option}:hover`]: {
                    backgroundColor: 'transparent',
                  },
                }}
              >
                <CircularProgress size={24} />
              </AutocompleteItem>,
            );
          }
          return content;
        }}
        disableCloseOnSelect={fieldProps.multiple}
        onChange={(_event, selectedOption, reason) => {
          const nextValue = fieldProps.multiple
            ? (selectedOption as TValue[])?.[
                (selectedOption as TValue[])?.length - 1
              ]?.value
            : (selectedOption as TValue)?.value;
          if (reason === 'selectOption' && isCreatableOption(nextValue)) {
            onCreate?.(getCreatableInputText(nextValue));
          } else {
            onChange?.(selectedOption, reason);
          }
        }}
        getOptionLabel={option =>
          isCreatableOption(option.value)
            ? getCreatableInputText(option.value)
            : option.label
        }
        {...fieldProps}
        ref={fieldRef}
        renderInput={params => (
          <TextField
            variant="outlined"
            type={type}
            {...params}
            inputProps={{
              ...params.inputProps,
              maxLength,
            }}
            error={hasError}
            FormHelperTextProps={{
              component: 'div',
              sx: { display: 'contents' },
            }}
            helperText={
              <CustomHelperText
                component="span"
                helperText={helperText}
                value=""
              />
            }
            placeholder={hasMultipleSelections ? undefined : placeholder}
            sx={{
              [`& .${formHelperTextClasses.root}`]: {
                mt: 1,
                mx: 0,
                color: theme.palette.new.text.neutral.lighter,
              },
              [`.${inputBaseClasses.root}`]: {
                backgroundColor: fieldProps.disabled
                  ? theme.palette.new.background.elements.disabled
                  : theme.palette.new.background.elements.default,
                ...(isSearchStyle && {
                  minHeight: 56,
                  pt: 0,
                  pb: 0,
                  // Horizontal padding only matters when a start icon occupies
                  // the leading space; `hidePopupIcon` alone keeps default padding.
                  ...(startIcon && {
                    pl: 1.75,
                    [`& .${autocompleteClasses.input}`]: {
                      pl: 0,
                    },
                  }),
                }),
              },
              [`.${inputBaseClasses.root} input::placeholder`]: {
                color: theme.palette.new.text.neutral.lighter,
                opacity: 1,
              },
              [`.${inputBaseClasses.root} fieldset`]: {
                transition: 'border 125ms ease',
                borderWidth: '1px !important',
                borderColor: `${getBorderColor(theme, focused, hasError, false, !!(fieldProps.value && (Array.isArray(fieldProps.value) ? fieldProps.value.length > 0 : true)))} !important`,
              },
            }}
            InputProps={{
              ...params.InputProps,
              startAdornment: startIcon ? (
                <>
                  <InputAdornment
                    position="start"
                    sx={{
                      alignSelf: 'center',
                      height: 3,
                      mr: 1,
                      color: theme.palette.new.text.neutral.lighter,
                    }}
                  >
                    {startIcon}
                  </InputAdornment>
                  {params.InputProps.startAdornment ?? null}
                </>
              ) : (
                params.InputProps.startAdornment
              ),
              endAdornment: (
                <InputAdornment
                  position="end"
                  sx={{ color: 'inherit' }}
                >
                  {params.InputProps.endAdornment}
                  {fieldProps.loading && (
                    <CircularProgress
                      sx={{ color: 'inherit' }}
                      size={20}
                    />
                  )}
                  {hasError && (
                    <IconExclamationCircle
                      size={20}
                      style={{
                        color: statusTextColor,
                      }}
                    />
                  )}
                </InputAdornment>
              ),
              sx: {
                ...(fieldProps.disabled && {
                  bgcolor: alpha(theme.palette.new.border.neutral.default, 0.5),
                  border: `1px solid ${theme.palette.new.border.neutral.default}`,
                }),
                color: theme.palette.new.text.neutral.default,
                fontSize: 'globalS',
                transition: 'border 0.3s ease',
              },
            }}
            InputLabelProps={{
              shrink:
                (params.inputProps.ref as RefObject<HTMLInputElement>)
                  .current === document.activeElement ||
                !!params.inputProps.value ||
                fieldProps.loading,
            }}
          />
        )}
        noOptionsText={
          <Title
            variant="S"
            title={noOptionsMessage?.title || t('hu_inputs.no_results_found')}
            description={
              noOptionsMessage
                ? noOptionsMessage.description
                : t('hu_inputs.select_option_in_list')
            }
          />
        }
        loadingText={
          <Title
            variant="S"
            title={t('hu_inputs.loading')}
          />
        }
        renderTags={renderTagsFn}
        getLimitTagsText={more => (
          <Chip
            disabled={fieldProps.disabled}
            label={`+${more}`}
            sx={{ m: 0.25 }}
          />
        )}
      />
    </FormControl>
  );
};

export type { AutocompleteProps };

export default Autocomplete;
