import {
  Fragment,
  type RefObject,
  type SyntheticEvent,
  useEffect,
  useId,
  useMemo,
  useState,
} from 'react';
import { Controller, type FieldError, useFormContext } from 'react-hook-form';
import { useInView } from 'react-intersection-observer';
import { useInfiniteQuery, useMutation, useQueryClient } from 'react-query';

import debounce from 'lodash/debounce';
import Add from '@material-hu/icons/material/Add';
import Autocomplete, {
  autocompleteClasses,
} from '@material-hu/mui/Autocomplete';
import Box from '@material-hu/mui/Box';
import CircularProgress from '@material-hu/mui/CircularProgress';
import { formLabelClasses } from '@material-hu/mui/FormLabel';
import TextField from '@material-hu/mui/TextField';
import Typography from '@material-hu/mui/Typography';
import { type FilterOptionsState } from '@material-hu/mui/useAutocomplete';

import { type MUIAutocompleteProps } from '@material-hu/components/design-system/Inputs/Autocomplete/types';

import { pxKeys } from 'src/pages/dashboard/PeopleExperience/queries';
import {
  createTopic,
  getDimensions,
} from 'src/pages/dashboard/PeopleExperience/services';
import {
  type TopicOption,
  type TopicPayload,
} from 'src/pages/dashboard/PeopleExperience/types';
import { useLokaliseTranslation } from 'src/utils/i18n';

import { isEqualText } from './utils';

const LoadingIndicator = () => {
  return <CircularProgress size={24} />;
};

type TopicsAutocompleteProps = Partial<
  MUIAutocompleteProps<TopicOption, false, false, false>
> & {
  error?: FieldError;
  fieldRef?: TopicsAutocompleteProps['ref'];
};

const TopicsAutocomplete = ({
  onBlur,
  onChange,
  value: formValue,
  disabled,
  error,
  fieldRef,
}: TopicsAutocompleteProps) => {
  const { t } = useLokaliseTranslation('people_experience');
  const id = useId();
  const [searchQuery, setSearchQuery] = useState('');
  const [value, setValue] = useState<TopicOption | null>(formValue ?? null);

  const [triggerRef, isTriggerVisible] = useInView({
    threshold: 0,
  });

  const searchText = searchQuery.trim();

  const { data, isLoading, isFetching, hasNextPage, fetchNextPage } =
    useInfiniteQuery(
      pxKeys.dimensions({ q: searchText }),
      ({ pageParam = 0 }) =>
        getDimensions({ q: searchText, page: pageParam + 1 }),
      {
        getNextPageParam: lastPage =>
          lastPage.data.page === lastPage.data.totalPages
            ? undefined
            : lastPage.data.page,
        keepPreviousData: true,
      },
    );

  const allItems = data?.pages.flatMap(page => page.data.items) ?? [];

  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: (newTopic: TopicPayload) => createTopic(newTopic),
    onSuccess: () => {
      queryClient.invalidateQueries(pxKeys.dimensions({ q: searchQuery }));
    },
  });

  const debouncedSetSearchQuery = useMemo(
    () => debounce(setSearchQuery, 300),
    [],
  );

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

  useEffect(() => {
    if (isTriggerVisible) {
      fetchNextPage();
    }
  }, [isTriggerVisible, hasNextPage]);

  const handleChange = (
    event: SyntheticEvent,
    nextValue: TopicOption | null,
  ) => {
    if (!nextValue) {
      setSearchQuery('');
    }

    if (nextValue?.inputValue) {
      const newTopic = { title: nextValue.inputValue };
      mutation.mutate(newTopic, {
        onSuccess: res => {
          setValue(res.data);
          onChange?.(event, res.data, 'selectOption');
        },
      });
    } else {
      setValue(nextValue);
      onChange?.(event, nextValue, 'selectOption');
    }
  };

  const filterOptions = (
    options: TopicOption[],
    params: FilterOptionsState<TopicOption>,
  ) => {
    const filtered = Array.from(options);
    const { inputValue } = params;
    const trimmedInput = inputValue.trim();

    const isExisting = filtered.some(option =>
      isEqualText(trimmedInput, option.title!),
    );

    filtered.unshift({
      id: -1,
      inputValue: trimmedInput,
      title: t('CREATE_DIMENSION', { dimension: trimmedInput || ' ' }),
      isCreate: true,
      isDisabled: !trimmedInput || isExisting || isFetching,
    });

    return filtered;
  };

  const isDisabled = disabled || mutation.isLoading;

  return (
    <Autocomplete
      selectOnFocus
      clearOnBlur
      handleHomeEndKeys
      ref={fieldRef}
      id={id}
      options={allItems}
      value={value}
      loading={isLoading}
      loadingText={<LoadingIndicator />}
      sx={{ flex: 1 }}
      renderInput={params => (
        <TextField
          {...params}
          label={t('SELECT_DIMENSION')}
          error={!!error}
          helperText={error?.message ?? t('CREATE_OR_SELECT_DIMENSION')}
          InputProps={{
            ...params.InputProps,
            endAdornment: (
              <Fragment>
                {mutation.isLoading ||
                  (isFetching && (
                    <CircularProgress
                      color="inherit"
                      size={20}
                    />
                  ))}
                {params.InputProps.endAdornment}
              </Fragment>
            ),
          }}
          InputLabelProps={{
            shrink:
              (params.inputProps.ref as RefObject<HTMLInputElement>).current ===
                document.activeElement ||
              !!params.inputProps.value ||
              mutation.isLoading,
          }}
          sx={{
            '& .MuiInputBase-root': {
              backgroundColor: theme =>
                theme.palette.new.background.elements.default,
            },
          }}
        />
      )}
      renderOption={(props, option, state) => {
        const { key: _key, ...listItemProps } = props;
        const { title, isCreate } = option;
        const { index } = state;

        const shouldRenderInfiniteTrigger =
          index === allItems.length && hasNextPage;

        const result = [
          <Box
            key={option.id}
            component="li"
            sx={{
              padding: 1,
              ...(isCreate
                ? {
                    bgcolor: theme => theme.palette.new.background.layout.brand,
                    borderRadius: 1.5,
                    color: theme => theme.palette.new.text.neutral.brand,
                    display: 'flex',
                    alignItems: 'center',
                    gap: 0.5,
                    mx: 1,
                    p: 2,
                    mb: 1,
                    '&:hover': {
                      bgcolor: theme =>
                        theme.palette.new.background.layout.brand,
                    },
                    [`&.${autocompleteClasses.option}.${formLabelClasses.focused}`]:
                      {
                        bgcolor: theme =>
                          theme.palette.new.background.layout.brand,
                      },
                  }
                : {}),
            }}
            {...listItemProps}
          >
            {isCreate && <Add fontSize="small" />}
            <Typography
              variant={isCreate ? 'body2' : 'body1'}
              sx={
                isCreate
                  ? { fontWeight: theme => theme.typography.fontWeightBold }
                  : {}
              }
            >
              {title}
            </Typography>
          </Box>,
        ];

        if (shouldRenderInfiniteTrigger) {
          result.push(
            <Box
              component="li"
              key="loadmore-trigger"
              ref={triggerRef}
              sx={{
                width: '100%',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                py: 1,
              }}
            >
              <LoadingIndicator />
            </Box>,
          );
        }

        return result;
      }}
      getOptionLabel={option => option.inputValue ?? option.title!}
      isOptionEqualToValue={(option, currentValue) =>
        option.title === currentValue.title
      }
      filterOptions={filterOptions}
      onInputChange={(_event, nextValue) => debouncedSetSearchQuery(nextValue)}
      onChange={handleChange}
      onBlur={onBlur}
      noOptionsText={t('NO_OPTIONS')}
      ListboxProps={{
        sx: { maxHeight: 240 },
      }}
      disabled={isDisabled}
      getOptionDisabled={option => option.isDisabled!}
      getOptionKey={option => option.id!}
    />
  );
};

type TopicsFormAutocompleteProps = {
  name: string;
  disabled?: boolean;
};

const TopicsFormAutocomplete = ({
  name,
  disabled,
}: TopicsFormAutocompleteProps) => {
  const { control } = useFormContext();
  const { t } = useLokaliseTranslation('people_experience');
  return (
    <Controller
      name={name}
      control={control}
      rules={{
        required: t('REQUIRED_FIELD'),
      }}
      render={({
        field: { ref, onChange, ...field },
        fieldState: { error },
      }) => (
        <TopicsAutocomplete
          {...field}
          error={error}
          fieldRef={ref}
          onChange={(_, value) => onChange(value)}
          disabled={disabled}
        />
      )}
    />
  );
};

TopicsAutocomplete.displayName = 'TopicsAutocomplete';

export default TopicsFormAutocomplete;
