import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { Virtuoso, } from 'react-virtuoso';
import { GiphyPreviewMessage as DefaultGiphyPreviewMessage } from './GiphyPreviewMessage';
import { useLastReadData } from './hooks';
import { useGiphyPreview, useMessageSetKey, useNewMessageNotification, usePrependedMessagesCount, useScrollToBottomOnNewMessage, useShouldForceScrollToBottom, useUnreadMessagesNotificationVirtualized, } from './hooks/VirtualizedMessageList';
import { useMarkRead } from './hooks/useMarkRead';
import { MessageNotification as DefaultMessageNotification } from './MessageNotification';
import { MessageListNotifications as DefaultMessageListNotifications } from './MessageListNotifications';
import { MessageListMainPanel } from './MessageListMainPanel';
import { getGroupStyles, getLastReceived, processMessages, } from './utils';
import { MessageSimple } from '../Message';
import { UnreadMessagesNotification as DefaultUnreadMessagesNotification } from './UnreadMessagesNotification';
import { calculateFirstItemIndex, calculateItemIndex, EmptyPlaceholder, Header, Item, makeItemsRenderedHandler, messageRenderer, } from './VirtualizedMessageListComponents';
import { UnreadMessagesSeparator as DefaultUnreadMessagesSeparator } from '../MessageList';
import { DateSeparator as DefaultDateSeparator } from '../DateSeparator';
import { EventComponent as DefaultMessageSystem } from '../EventComponent';
import { useChannelActionContext, } from '../../context/ChannelActionContext';
import { useChannelStateContext, } from '../../context/ChannelStateContext';
import { useChatContext } from '../../context/ChatContext';
import { useComponentContext } from '../../context/ComponentContext';
import { DEFAULT_NEXT_CHANNEL_PAGE_SIZE } from '../../constants/limits';
function captureResizeObserverExceededError(e) {
    if (e.message === 'ResizeObserver loop completed with undelivered notifications.' ||
        e.message === 'ResizeObserver loop limit exceeded') {
        e.stopImmediatePropagation();
    }
}
function useCaptureResizeObserverExceededError() {
    useEffect(() => {
        window.addEventListener('error', captureResizeObserverExceededError);
        return () => {
            window.removeEventListener('error', captureResizeObserverExceededError);
        };
    }, []);
}
function fractionalItemSize(element) {
    return element.getBoundingClientRect().height;
}
function findMessageIndex(messages, id) {
    return messages.findIndex((message) => message.id === id);
}
function calculateInitialTopMostItemIndex(messages, highlightedMessageId) {
    if (highlightedMessageId) {
        const index = findMessageIndex(messages, highlightedMessageId);
        if (index !== -1) {
            return { align: 'center', index };
        }
    }
    return messages.length - 1;
}
const VirtualizedMessageListWithContext = (props) => {
    const { additionalMessageInputProps, additionalVirtuosoProps = {}, channel, channelUnreadUiState, closeReactionSelectorOnClick, customMessageActions, customMessageRenderer, defaultItemHeight, disableDateSeparator = true, formatDate, groupStyles, hasMoreNewer, head, hideDeletedMessages = false, hideNewMessageSeparator = false, highlightedMessageId, jumpToLatestMessage, loadingMore, loadMore, loadMoreNewer, maxTimeBetweenGroupedMessages, Message: MessageUIComponentFromProps, messageActions, messageLimit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE, messages, notifications, 
    // TODO: refactor to scrollSeekPlaceHolderConfiguration and components.ScrollSeekPlaceholder, like the Virtuoso Component
    overscan = 0, read, returnAllReadData = false, reviewProcessedMessage, scrollSeekPlaceHolder, scrollToLatestMessageOnFocus = false, separateGiphyPreview = false, shouldGroupByUser = false, showUnreadNotificationAlways, reactionDetailsSort, sortReactionDetails, sortReactions, stickToBottomScrollBehavior = 'smooth', suppressAutoscroll, threadList, } = props;
    const { components: virtuosoComponentsFromProps, ...overridingVirtuosoProps } = additionalVirtuosoProps;
    // Stops errors generated from react-virtuoso to bubble up
    // to Sentry or other tracking tools.
    useCaptureResizeObserverExceededError();
    const { DateSeparator = DefaultDateSeparator, GiphyPreviewMessage = DefaultGiphyPreviewMessage, MessageListNotifications = DefaultMessageListNotifications, MessageNotification = DefaultMessageNotification, MessageSystem = DefaultMessageSystem, UnreadMessagesNotification = DefaultUnreadMessagesNotification, UnreadMessagesSeparator = DefaultUnreadMessagesSeparator, VirtualMessage: MessageUIComponentFromContext = MessageSimple, TypingIndicator, } = useComponentContext('VirtualizedMessageList');
    const MessageUIComponent = MessageUIComponentFromProps || MessageUIComponentFromContext;
    const { client, customClasses } = useChatContext('VirtualizedMessageList');
    const virtuoso = useRef(null);
    const lastRead = useMemo(() => channel.lastRead?.(), [channel]);
    const { show: showUnreadMessagesNotification, toggleShowUnreadMessagesNotification, } = useUnreadMessagesNotificationVirtualized({
        lastRead: channelUnreadUiState?.last_read,
        showAlways: !!showUnreadNotificationAlways,
        unreadCount: channelUnreadUiState?.unread_messages ?? 0,
    });
    const { giphyPreviewMessage, setGiphyPreviewMessage } = useGiphyPreview(separateGiphyPreview);
    const processedMessages = useMemo(() => {
        if (typeof messages === 'undefined') {
            return [];
        }
        if (disableDateSeparator &&
            !hideDeletedMessages &&
            hideNewMessageSeparator &&
            !separateGiphyPreview) {
            return messages;
        }
        return processMessages({
            enableDateSeparator: !disableDateSeparator,
            hideDeletedMessages,
            hideNewMessageSeparator,
            lastRead,
            messages,
            reviewProcessedMessage,
            setGiphyPreviewMessage,
            userId: client.userID || '',
        });
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [
        disableDateSeparator,
        hideDeletedMessages,
        hideNewMessageSeparator,
        lastRead,
        messages,
        messages?.length,
        client.userID,
    ]);
    // get the mapping of own messages to array of users who read them
    const ownMessagesReadByOthers = useLastReadData({
        messages: processedMessages,
        read,
        returnAllReadData,
        userID: client.userID,
    });
    const lastReceivedMessageId = useMemo(() => getLastReceived(processedMessages), [
        processedMessages,
    ]);
    const groupStylesFn = groupStyles || getGroupStyles;
    const messageGroupStyles = useMemo(() => processedMessages.reduce((acc, message, i) => {
        const style = groupStylesFn(message, processedMessages[i - 1], processedMessages[i + 1], !shouldGroupByUser, maxTimeBetweenGroupedMessages);
        if (style)
            acc[message.id] = style;
        return acc;
    }, {}), 
    // processedMessages were incorrectly rebuilt with a new object identity at some point, hence the .length usage
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [maxTimeBetweenGroupedMessages, processedMessages.length, shouldGroupByUser, groupStylesFn]);
    const { atBottom, isMessageListScrolledToBottom, newMessagesNotification, setIsMessageListScrolledToBottom, setNewMessagesNotification, } = useNewMessageNotification(processedMessages, client.userID, hasMoreNewer);
    useMarkRead({
        isMessageListScrolledToBottom,
        messageListIsThread: !!threadList,
        unreadCount: channelUnreadUiState?.unread_messages ?? 0,
        wasMarkedUnread: !!channelUnreadUiState?.first_unread_message_id,
    });
    const scrollToBottom = useCallback(async () => {
        if (hasMoreNewer) {
            await jumpToLatestMessage();
            return;
        }
        if (virtuoso.current) {
            virtuoso.current.scrollToIndex(processedMessages.length - 1);
        }
        setNewMessagesNotification(false);
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [
        virtuoso,
        processedMessages,
        setNewMessagesNotification,
        // processedMessages were incorrectly rebuilt with a new object identity at some point, hence the .length usage
        processedMessages.length,
        hasMoreNewer,
        jumpToLatestMessage,
    ]);
    useScrollToBottomOnNewMessage({ messages, scrollToBottom, scrollToLatestMessageOnFocus });
    const numItemsPrepended = usePrependedMessagesCount(processedMessages, !disableDateSeparator);
    const { messageSetKey } = useMessageSetKey({ messages });
    const shouldForceScrollToBottom = useShouldForceScrollToBottom(processedMessages, client.userID);
    const handleItemsRendered = useMemo(() => makeItemsRenderedHandler([toggleShowUnreadMessagesNotification], processedMessages), [processedMessages, toggleShowUnreadMessagesNotification]);
    const followOutput = (isAtBottom) => {
        if (hasMoreNewer || suppressAutoscroll) {
            return false;
        }
        if (shouldForceScrollToBottom()) {
            return isAtBottom ? stickToBottomScrollBehavior : 'auto';
        }
        // a message from another user has been received - don't scroll to bottom unless already there
        return isAtBottom ? stickToBottomScrollBehavior : false;
    };
    const computeItemKey = useCallback((index, _, { numItemsPrepended, processedMessages }) => processedMessages[calculateItemIndex(index, numItemsPrepended)].id, []);
    const atBottomStateChange = (isAtBottom) => {
        atBottom.current = isAtBottom;
        setIsMessageListScrolledToBottom(isAtBottom);
        if (isAtBottom) {
            loadMoreNewer?.(messageLimit);
            setNewMessagesNotification?.(false);
        }
    };
    const atTopStateChange = (isAtTop) => {
        if (isAtTop) {
            loadMore?.(messageLimit);
        }
    };
    useEffect(() => {
        let scrollTimeout;
        if (highlightedMessageId) {
            const index = findMessageIndex(processedMessages, highlightedMessageId);
            if (index !== -1) {
                scrollTimeout = setTimeout(() => {
                    virtuoso.current?.scrollToIndex({ align: 'center', index });
                }, 0);
            }
        }
        return () => {
            clearTimeout(scrollTimeout);
        };
    }, [highlightedMessageId, processedMessages]);
    if (!processedMessages)
        return null;
    return (React.createElement(React.Fragment, null,
        React.createElement(MessageListMainPanel, null,
            !threadList && showUnreadMessagesNotification && (React.createElement(UnreadMessagesNotification, { unreadCount: channelUnreadUiState?.unread_messages })),
            React.createElement("div", { className: customClasses?.virtualizedMessageList || 'str-chat__virtual-list' },
                React.createElement(Virtuoso, { atBottomStateChange: atBottomStateChange, atBottomThreshold: 100, atTopStateChange: atTopStateChange, atTopThreshold: 100, className: 'str-chat__message-list-scroll', components: {
                        EmptyPlaceholder,
                        Header,
                        Item,
                        ...virtuosoComponentsFromProps,
                    }, computeItemKey: computeItemKey, context: {
                        additionalMessageInputProps,
                        closeReactionSelectorOnClick,
                        customClasses,
                        customMessageActions,
                        customMessageRenderer,
                        DateSeparator,
                        firstUnreadMessageId: channelUnreadUiState?.first_unread_message_id,
                        formatDate,
                        head,
                        lastReadDate: channelUnreadUiState?.last_read,
                        lastReadMessageId: channelUnreadUiState?.last_read_message_id,
                        lastReceivedMessageId,
                        loadingMore,
                        Message: MessageUIComponent,
                        messageActions,
                        messageGroupStyles,
                        MessageSystem,
                        numItemsPrepended,
                        ownMessagesReadByOthers,
                        processedMessages,
                        reactionDetailsSort,
                        shouldGroupByUser,
                        sortReactionDetails,
                        sortReactions,
                        threadList,
                        unreadMessageCount: channelUnreadUiState?.unread_messages,
                        UnreadMessagesSeparator,
                        virtuosoRef: virtuoso,
                    }, firstItemIndex: calculateFirstItemIndex(numItemsPrepended), followOutput: followOutput, increaseViewportBy: { bottom: 200, top: 0 }, initialTopMostItemIndex: calculateInitialTopMostItemIndex(processedMessages, highlightedMessageId), itemContent: messageRenderer, itemSize: fractionalItemSize, itemsRendered: handleItemsRendered, key: messageSetKey, overscan: overscan, ref: virtuoso, style: { overflowX: 'hidden' }, totalCount: processedMessages.length, ...overridingVirtuosoProps, ...(scrollSeekPlaceHolder ? { scrollSeek: scrollSeekPlaceHolder } : {}), ...(defaultItemHeight ? { defaultItemHeight } : {}) })),
            TypingIndicator && React.createElement(TypingIndicator, null)),
        React.createElement(MessageListNotifications, { hasNewMessages: newMessagesNotification, isMessageListScrolledToBottom: isMessageListScrolledToBottom, isNotAtLatestMessageSet: hasMoreNewer, MessageNotification: MessageNotification, notifications: notifications, scrollToBottom: scrollToBottom, threadList: threadList, unreadCount: threadList ? undefined : channelUnreadUiState?.unread_messages }),
        giphyPreviewMessage && React.createElement(GiphyPreviewMessage, { message: giphyPreviewMessage })));
};
/**
 * The VirtualizedMessageList component renders a list of messages in a virtualized list.
 * It is a consumer of the React contexts set in [Channel](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Channel/Channel.tsx).
 */
export function VirtualizedMessageList(props) {
    const { jumpToLatestMessage, loadMore, loadMoreNewer, } = useChannelActionContext('VirtualizedMessageList');
    const { channel, channelUnreadUiState, hasMore, hasMoreNewer, highlightedMessageId, loadingMore, loadingMoreNewer, messages: contextMessages, notifications, read, suppressAutoscroll, } = useChannelStateContext('VirtualizedMessageList');
    const messages = props.messages || contextMessages;
    return (React.createElement(VirtualizedMessageListWithContext, { channel: channel, channelUnreadUiState: props.channelUnreadUiState ?? channelUnreadUiState, hasMore: !!hasMore, hasMoreNewer: !!hasMoreNewer, highlightedMessageId: highlightedMessageId, jumpToLatestMessage: jumpToLatestMessage, loadingMore: !!loadingMore, loadingMoreNewer: !!loadingMoreNewer, loadMore: loadMore, loadMoreNewer: loadMoreNewer, messages: messages, notifications: notifications, read: read, suppressAutoscroll: suppressAutoscroll, ...props }));
}
