import { forwardRef, useImperativeHandle, useRef, useState } from 'react';
import { matchPath, useParams } from 'react-router-dom';

import {
  type SuggestionOptions,
  type SuggestionProps,
} from '@tiptap/suggestion';
import LinearProgress from '@material-hu/mui/LinearProgress';
import Skeleton from '@material-hu/mui/Skeleton';
import { useTheme } from '@material-hu/mui/styles';
import TableCell from '@material-hu/mui/TableCell';
import TableRow from '@material-hu/mui/TableRow';

import { type Member } from 'src/pages/dashboard/Conversations/types';
import { formatUser } from 'src/pages/dashboard/Conversations/utils';
import { groupsRoutes } from 'src/pages/dashboard/groups/routes';
import { getFullName } from 'src/utils/userUtils';

import InfiniteListWithRef from 'src/components/list/InfiniteListWithRef';

import useGetUsers from '../hooks/useGetUsers';

import { type MentionSuggestion } from './mentionOptions';
import UserItem from './UserItem';

const PATH = groupsRoutes.groups();

export type MentionUserListRef = {
  // For convenience using this MentionUserList from within the
  // mentionSuggestionOptions, we'll match the signature of SuggestionOptions's
  // `onKeyDown` returned in its `render` function
  onKeyDown: NonNullable<
    ReturnType<
      NonNullable<SuggestionOptions<MentionSuggestion>['render']>
    >['onKeyDown']
  >;
};

type MentionNodeAttrs = {
  id: string | null;
  label?: string | null;
};

export type MentionUserListProps = SuggestionProps<MentionSuggestion> & {
  externalUsers?: Member[];
};

const MentionUserList = forwardRef<MentionUserListRef, MentionUserListProps>(
  ({ editor, query, externalUsers, command }, ref) => {
    const [selectedIndex, setSelectedIndex] = useState(0);
    const params = useParams();
    const theme = useTheme();
    const { id: groupId } = params;
    const isGroupPath = !!matchPath(
      {
        path: PATH,
        end: false,
      },
      `/${params['*']}`,
    );
    const { users, processedUsers: items } = useGetUsers({
      editor: editor,
      query: query,
      members: externalUsers,
      isGroupPath,
      groupId,
    });

    const containerRef = useRef<HTMLDivElement>(null);
    const itemRefs = useRef<(HTMLLIElement | null)[]>([]);

    const isExternalUsers = !!externalUsers;
    const SIZES = {
      minWidth: !isExternalUsers ? '230px' : '540px',
      maxHeight: !isExternalUsers ? '312px' : '300px',
    };

    const selectItem = (index: number) => {
      if (index >= (items?.length ?? 0)) {
        // Make sure we actually have enough items to select the given index. For
        // instance, if a user presses "Enter" when there are no options, the index will
        // be 0 but there won't be any items, so just ignore the callback here
        return;
      }

      const mention = isGroupPath ? items![index].user : items![index];
      const label = isExternalUsers
        ? getFullName({
            firstName: mention.first_name,
            lastName: mention.last_name,
          })
        : getFullName(mention);
      const mentionItem: MentionNodeAttrs = {
        id: mention.id.toString(),
        label,
      };
      command(mentionItem);
    };

    const scrollToItem = (index: number) => {
      if (itemRefs.current[index]) {
        itemRefs.current[index]?.scrollIntoView({
          behavior: 'smooth',
          block: 'nearest',
        });
      }
    };

    const downHandler = () => {
      if ((items?.length ?? 0) > 0) {
        const nextIndex = (selectedIndex + 1) % (items?.length ?? 0);
        setSelectedIndex(nextIndex);
        scrollToItem(nextIndex);
      }
    };

    const upHandler = () => {
      if ((items?.length ?? 0) > 0) {
        const prevIndex =
          (selectedIndex - 1 + (items?.length ?? 0)) % (items?.length ?? 0);
        setSelectedIndex(prevIndex);
        scrollToItem(prevIndex);
      }
    };

    const enterHandler = () => selectItem(selectedIndex);

    useImperativeHandle(ref, () => ({
      onKeyDown: ({ event }) => {
        if (event.key === 'ArrowUp') {
          upHandler();
          return true;
        }

        if (event.key === 'ArrowDown') {
          downHandler();
          return true;
        }

        if (event.key === 'Enter') {
          enterHandler();
          return true;
        }

        return false;
      },
    }));

    return (
      <InfiniteListWithRef
        ref={containerRef}
        isSuccess={isExternalUsers ? true : !!users.data}
        isLoading={isExternalUsers ? false : users.isFetching}
        isEmpty={(items?.length ?? 0) === 0}
        fetchNextPage={users.fetchNextPage}
        hasNextPage={!isExternalUsers && (users.hasNextPage ?? false)}
        isFetchingNextPage={isExternalUsers ? false : users.isFetchingNextPage}
        customLoader={<LinearProgress />}
        renderSkeleton={
          <>
            {Array.from({ length: 4 }, (n, i) => (
              <TableRow key={i}>
                <TableCell
                  width="5%"
                  sx={{ paddingY: 0, paddingX: 2 }}
                >
                  <Skeleton
                    animation="wave"
                    height={52}
                    width={32}
                  />
                </TableCell>
                <TableCell>
                  <Skeleton
                    animation="wave"
                    height={25}
                    style={{ marginTop: 2, marginBottom: 2 }}
                  />
                </TableCell>
              </TableRow>
            ))}
          </>
        }
        sx={{
          position: 'relative',
          zIndex: 2,
          overflow: 'auto',
          minHeight: '48px',
          maxHeight: SIZES.maxHeight,
          minWidth: SIZES.minWidth,
          borderRadius: '8px',
          backgroundColor: theme.palette.new.background.elements.default,
          border: '1px solid',
          borderColor: theme.palette.new.border.neutral.default,
        }}
      >
        {items?.map((user, index) => (
          <UserItem
            user={formatUser(user, isExternalUsers, isGroupPath)}
            key={`user-${user.id}`}
            onSelect={() => selectItem(index)}
            selected={index === selectedIndex}
            ref={el => (itemRefs.current[index] = el)}
          />
        ))}
      </InfiniteListWithRef>
    );
  },
);

MentionUserList.displayName = 'MentionUserList';

export default MentionUserList;
