import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { useIntl, defineMessages } from 'react-intl';
import { Components, Virtuoso, VirtuosoHandle } from 'react-virtuoso';

import { Avatar, Button, Divider, Spinner, Stack, Text } from 'soapbox/components/ui';
import PlaceholderChatMessage from 'soapbox/features/placeholder/components/placeholder-chat-message';
import { useAppSelector, useOwnAccount } from 'soapbox/hooks';
import { IChat, useChatActions, useChatMessages } from 'soapbox/queries/chats';

import ChatMessage from './chat-message';
import ChatMessageListIntro from './chat-message-list-intro';

import type { ChatMessage as ChatMessageEntity } from 'soapbox/types/entities';

const messages = defineMessages({
  today: { id: 'chats.dividers.today', defaultMessage: 'Today' },
  more: { id: 'chats.actions.more', defaultMessage: 'More' },
  delete: { id: 'chats.actions.delete', defaultMessage: 'Delete for both' },
  copy: { id: 'chats.actions.copy', defaultMessage: 'Copy' },
  report: { id: 'chats.actions.report', defaultMessage: 'Report' },
  deleteForMe: { id: 'chats.actions.deleteForMe', defaultMessage: 'Delete for me' },
  blockedBy: { id: 'chat_message_list.blockedBy', defaultMessage: 'You are blocked by' },
  networkFailureTitle: { id: 'chat_message_list.network_failure.title', defaultMessage: 'Whoops!' },
  networkFailureSubtitle: { id: 'chat_message_list.network_failure.subtitle', defaultMessage: 'We encountered a network failure.' },
  networkFailureAction: { id: 'chat_message_list.network_failure.action', defaultMessage: 'Try again' },
});

type TimeFormat = 'today' | 'date';

const timeChange = (prev: ChatMessageEntity, curr: ChatMessageEntity): TimeFormat | null => {
  const prevDate = new Date(prev.created_at).getDate();
  const currDate = new Date(curr.created_at).getDate();
  const nowDate = new Date().getDate();

  if (prevDate !== currDate) {
    return currDate === nowDate ? 'today' : 'date';
  }

  return null;
};

const START_INDEX = 10000;

const List: Components['List'] = React.forwardRef((props, ref) => {
  const { context, ...rest } = props;
  return <div ref={ref} {...rest} className='mb-2' />;
});

const Scroller: Components['Scroller'] = React.forwardRef((props, ref) => {
  const { style, context, ...rest } = props;

  return (
    <div
      {...rest}
      ref={ref}
      style={{
        ...style,
        scrollbarGutter: 'stable',
      }}
    />
  );
});

interface IChatMessageList {
  /** Chat the messages are being rendered from. */
  chat: IChat
}

/** Scrollable list of chat messages. */
const ChatMessageList: React.FC<IChatMessageList> = ({ chat }) => {
  const intl = useIntl();
  const { account } = useOwnAccount();

  const myLastReadMessageDateString = chat.latest_read_message_by_account?.find((latest) => latest.id === account?.id)?.date;
  const myLastReadMessageTimestamp = myLastReadMessageDateString ? new Date(myLastReadMessageDateString) : null;

  const node = useRef<VirtuosoHandle>(null);
  const [firstItemIndex, setFirstItemIndex] = useState(START_INDEX - 20);

  const { markChatAsRead } = useChatActions(chat.id);
  const {
    data: chatMessages,
    fetchNextPage,
    hasNextPage,
    isError,
    isFetching,
    isFetchingNextPage,
    isLoading,
    refetch,
  } = useChatMessages(chat);

  const formattedChatMessages = chatMessages || [];

  const isBlocked = useAppSelector((state) => state.getIn(['relationships', chat.account.id, 'blocked_by']));

  const lastChatMessage = chatMessages ? chatMessages[chatMessages.length - 1] : null;

  useEffect(() => {
    if (!chatMessages) {
      return;
    }

    const nextFirstItemIndex = START_INDEX - chatMessages.length;
    setFirstItemIndex(nextFirstItemIndex);
  }, [lastChatMessage]);

  const buildCachedMessages = () => {
    if (!chatMessages) {
      return [];
    }

    const currentYear = new Date().getFullYear();

    return chatMessages.reduce((acc: any, curr: any, idx: number) => {
      const lastMessage = formattedChatMessages[idx - 1];

      const messageDate = new Date(curr.created_at);

      if (lastMessage) {
        switch (timeChange(lastMessage, curr)) {
          case 'today':
            acc.push({
              type: 'divider',
              text: intl.formatMessage(messages.today),
            });
            break;
          case 'date':
            acc.push({
              type: 'divider',
              text: intl.formatDate(messageDate, {
                weekday: 'short',
                hour: 'numeric',
                minute: '2-digit',
                month: 'short',
                day: 'numeric',
                year: messageDate.getFullYear() !== currentYear ? '2-digit' : undefined,
              }),
            });
            break;
        }
      }

      acc.push(curr);
      return acc;
    }, []);
  };
  const cachedChatMessages = buildCachedMessages();

  const initialScrollPositionProps = useMemo(() => {
    if (process.env.NODE_ENV === 'test') {
      return {};
    }

    return {
      initialTopMostItemIndex: cachedChatMessages.length - 1,
      firstItemIndex: Math.max(0, firstItemIndex),
    };
  }, [cachedChatMessages.length, firstItemIndex]);

  const handleStartReached = useCallback(() => {
    if (hasNextPage && !isFetching) {
      fetchNextPage();
    }
    return false;
  }, [firstItemIndex, hasNextPage, isFetching]);

  const renderDivider = (key: React.Key, text: string) => <Divider key={key} text={text} textSize='xs' />;

  useEffect(() => {
    const lastMessage = formattedChatMessages[formattedChatMessages.length - 1];
    if (!lastMessage) {
      return;
    }

    const lastMessageId = lastMessage.id;
    const isMessagePending = lastMessage.pending;
    const isAlreadyRead = myLastReadMessageTimestamp ? myLastReadMessageTimestamp >= new Date(lastMessage.created_at) : false;

    /**
     * Only "mark the message as read" if..
     * 1) it is not pending and
     * 2) it has not already been read
    */
    if (!isMessagePending && !isAlreadyRead) {
      markChatAsRead(lastMessageId);
    }
  }, [formattedChatMessages.length]);

  if (isBlocked) {
    return (
      <Stack alignItems='center' justifyContent='center' className='h-full grow'>
        <Stack alignItems='center' space={2}>
          <Avatar src={chat.account.avatar} size={75} />
          <Text align='center'>
            <>
              <Text tag='span'>{intl.formatMessage(messages.blockedBy)}</Text>
              {' '}
              <Text tag='span' theme='primary'>@{chat.account.acct}</Text>
            </>
          </Text>
        </Stack>
      </Stack>
    );
  }

  if (isError) {
    return (
      <Stack alignItems='center' justifyContent='center' className='h-full grow'>
        <Stack space={4}>
          <Stack space={1}>
            <Text size='lg' weight='bold' align='center'>
              {intl.formatMessage(messages.networkFailureTitle)}
            </Text>
            <Text theme='muted' align='center'>
              {intl.formatMessage(messages.networkFailureSubtitle)}
            </Text>
          </Stack>

          <div className='mx-auto'>
            <Button theme='primary' onClick={() => refetch()}>
              {intl.formatMessage(messages.networkFailureAction)}
            </Button>
          </div>
        </Stack>
      </Stack>
    );
  }

  if (isLoading) {
    return (
      <div className='flex grow flex-col justify-end pb-4'>
        <div className='px-4'>
          <PlaceholderChatMessage isMyMessage />
          <PlaceholderChatMessage />
          <PlaceholderChatMessage isMyMessage />
          <PlaceholderChatMessage isMyMessage />
          <PlaceholderChatMessage />
        </div>
      </div>
    );
  }

  return (
    <div className='flex h-full grow flex-col space-y-6'>
      <div className='flex grow flex-col justify-end'>
        <Virtuoso
          ref={node}
          alignToBottom
          {...initialScrollPositionProps}
          data={cachedChatMessages}
          startReached={handleStartReached}
          followOutput='auto'
          itemContent={(index, chatMessage) => {
            if (chatMessage.type === 'divider') {
              return renderDivider(index, chatMessage.text);
            } else {
              return <ChatMessage chat={chat} chatMessage={chatMessage} />;
            }
          }}
          components={{
            List,
            Scroller,
            Header: () => {
              if (hasNextPage || isFetchingNextPage) {
                return <Spinner withText={false} />;
              }

              if (!hasNextPage && !isLoading) {
                return <ChatMessageListIntro />;
              }

              return null;
            },
          }}
        />
      </div>
    </div>
  );
};

export default ChatMessageList;