import { type ReactElement } from 'react';
import {
  Controller,
  type ControllerProps,
  useFormContext,
} from 'react-hook-form';

import { maxBy } from 'lodash-es';
import HelpOutlineIcon from '@material-hu/icons/material/HelpOutline';
import Checkbox from '@material-hu/mui/Checkbox';
import Chip from '@material-hu/mui/Chip';
import FormControl, {
  type FormControlProps,
} from '@material-hu/mui/FormControl';
import FormHelperText, {
  type FormHelperTextProps,
} from '@material-hu/mui/FormHelperText';
import InputLabel from '@material-hu/mui/InputLabel';
import ListItemIcon from '@material-hu/mui/ListItemIcon';
import ListItemText from '@material-hu/mui/ListItemText';
import ListSubheader from '@material-hu/mui/ListSubheader';
import MenuItem from '@material-hu/mui/MenuItem';
import Select, {
  type SelectChangeEvent,
  type SelectProps,
} from '@material-hu/mui/Select';
import Stack from '@material-hu/mui/Stack';
import Tooltip from '@material-hu/mui/Tooltip';
import Typography from '@material-hu/mui/Typography';

import { type MUIMenuProps } from '@material-hu/components/design-system/Menu/types';

import { addOrRemove } from 'src/utils/arrayUtils';
import { useLokaliseTranslation } from 'src/utils/i18n';

import CircularProgress from 'src/components/CircularProgress';

export type OptionType = {
  label: string;
  value: any;
  icon?: ReactElement;
  subheader?: boolean;
  disabled?: boolean;
};

type Props = {
  rules?: ControllerProps['rules'];
  name: string;
  label?: string;
  options: OptionType[];
  formControlProps?: FormControlProps;
  renderMenuItems?: () => JSX.Element[];
  loading?: boolean;
  clearable?: boolean;
  adaptWidthToOptions?: boolean;
  limitTags?: number;
  helperText?: string;
  useWatchedValue?: boolean;
  error?: boolean;
  helperTextProps?: FormHelperTextProps;
  onChange?: (value: any) => void;
  disabled?: boolean;
  tooltipOptions?: any[];
  tooltipElement?: ReactElement;
  selectProps?: SelectProps;
  variant?: 'standard' | 'spaced';
  MenuProps?: Partial<MUIMenuProps>;
  showIcons?: boolean;
};

function FormSelect({
  name,
  rules,
  label,
  options,
  selectProps,
  formControlProps,
  renderMenuItems,
  loading = false,
  clearable = false,
  adaptWidthToOptions = false,
  tooltipElement,
  tooltipOptions,
  limitTags = 5,
  helperText,
  helperTextProps,
  useWatchedValue = false,
  error: errorProp,
  onChange: onChangeProp = () => null,
  disabled = false,
  variant = 'standard',
  MenuProps,
  showIcons = false,
}: Props) {
  const { control, setValue, getFieldState, watch } = useFormContext();
  const { t } = useLokaliseTranslation('general');

  const { error } = getFieldState(name);

  // This is needed whenever we need the value to refresh on every render
  // which is not the default behavior of Controller - avoid using for better perfomance
  const fieldValue = useWatchedValue ? watch(name) : undefined;

  const maxLabelOption = maxBy(options, option => option.label.length);
  const customFormControlSxProp = {
    width: adaptWidthToOptions
      ? `${(maxLabelOption?.label?.length ?? 0) + 7}ch`
      : undefined,
    ...formControlProps?.sx,
  };

  const handleChange =
    (onChange: (...event: any[]) => void) =>
    (event: SelectChangeEvent<unknown>) => {
      onChange(event);
      onChangeProp(event);
    };

  return (
    <FormControl
      {...formControlProps}
      sx={customFormControlSxProp}
      error={!!error || errorProp}
    >
      <Controller
        control={control}
        name={name}
        rules={rules}
        render={({ field: { ref, onChange, ...field } }) => (
          <>
            <InputLabel disabled={disabled}>{label}</InputLabel>
            <Select
              label={label}
              {...field}
              onChange={handleChange(onChange)}
              disabled={disabled}
              value={useWatchedValue ? fieldValue : field.value}
              IconComponent={
                loading
                  ? () => (
                      <CircularProgress
                        centered
                        size={15}
                        sx={{ mr: 2 }}
                      />
                    )
                  : undefined
              }
              error={!!error || errorProp}
              renderValue={
                selectProps?.multiple
                  ? (value: unknown) => {
                      const selected = value as any[];
                      const limitedSelected = selected.slice(0, limitTags);
                      const more = selected.length - limitedSelected.length;
                      return (
                        <Stack
                          direction="row"
                          sx={{
                            flexWrap: 'wrap',
                            gap: 0.5,
                            alignItems: 'center',
                          }}
                        >
                          {limitedSelected.map(ls => (
                            <Chip
                              key={ls}
                              label={
                                options.find(o => o.value === ls)?.label ?? ''
                              }
                              onDelete={() => {
                                setValue(name, addOrRemove(selected, ls), {
                                  shouldDirty: true,
                                  shouldValidate: true,
                                });
                              }}
                              onMouseDown={event => {
                                event.stopPropagation();
                              }}
                            />
                          ))}
                          {more ? `+${more}` : null}
                        </Stack>
                      );
                    }
                  : undefined
              }
              sx={{
                ...(!showIcons && {
                  '& .MuiListItemIcon-root': {
                    display: 'none',
                  },
                }),
                '& .MuiListItemText-root': {
                  my: 0,
                },
              }}
              MenuProps={{
                sx: {
                  ...(variant === 'spaced' && {
                    '& .MuiList-root': {
                      display: 'flex',
                      flexDirection: 'column',
                      gap: 1.5,
                      p: 2,
                      '& .MuiListSubheader-root': {
                        p: 0,
                      },
                      '& .MuiMenuItem-root': {
                        p: 1,
                        gap: 1,
                        borderRadius: '12px',
                      },
                    },
                  }),
                },
                ...MenuProps,
              }}
              {...selectProps}
              inputRef={ref}
            >
              {clearable && (
                <MenuItem value="">
                  <em>{t('none')}</em>
                </MenuItem>
              )}
              {renderMenuItems && renderMenuItems()}
              {!renderMenuItems &&
                options.map(option => {
                  const value = useWatchedValue ? fieldValue : field.value;

                  const withTooltip =
                    tooltipOptions?.includes(option.value) &&
                    value !== option.value;

                  if (option.subheader) {
                    return (
                      <ListSubheader key={option.value}>
                        <Typography variant="subtitle2">
                          {option.label}
                        </Typography>
                      </ListSubheader>
                    );
                  }

                  if (withTooltip) {
                    return (
                      <MenuItem
                        key={option.value}
                        sx={{ alignItems: 'center' }}
                        value={option.value}
                      >
                        <>
                          {option.icon && (
                            <ListItemIcon>{option.icon}</ListItemIcon>
                          )}
                          <ListItemText
                            sx={{
                              '.MuiListItemText-primary': {
                                display: 'flex',
                                alignItems: 'center',
                              },
                            }}
                          >
                            {option.label}
                            <Tooltip
                              title={tooltipElement || ''}
                              placement="right-end"
                            >
                              <HelpOutlineIcon
                                fontSize="small"
                                sx={{ ml: 1 }}
                              />
                            </Tooltip>
                          </ListItemText>
                        </>
                      </MenuItem>
                    );
                  }

                  return (
                    <MenuItem
                      key={option.value}
                      sx={{ alignItems: 'center' }}
                      value={option.value}
                      disabled={option.disabled}
                    >
                      {selectProps?.multiple && (
                        <Checkbox checked={value?.includes(option.value)} />
                      )}
                      {option.icon && (
                        <ListItemIcon>{option.icon}</ListItemIcon>
                      )}
                      <ListItemText>{option.label}</ListItemText>
                    </MenuItem>
                  );
                })}
            </Select>
            {error && <FormHelperText error>{error?.message}</FormHelperText>}
            {helperText && (
              <FormHelperText {...helperTextProps}>{helperText}</FormHelperText>
            )}
          </>
        )}
      />
    </FormControl>
  );
}

export default FormSelect;
