import {useEffect, useState, useMemo, useCallback, memo, useContext, useRef} from "@wordpress/element";
import {__} from "@wordpress/i18n";
import MessageText from "./message/type/text";
import MessageImage from "./message/type/image";
import MessageFile from "./message/type/file";
import MessageActionSubmission from "./message/type/action-submission";
import {getBase64FormatedSize, prepareUserMessage, showWidget, isImage, stripMarkdown} from "../../../../../helpers";
import {MessagesContext} from "./_context/messages";
import Message from "./message";
import {copyToClipboard} from "../../../includes/helpers";
import Action from "./action";
import ButtonRounded from "../../button/rounded";
import RealTimeRelativeTime from "./real-time-relative-time";
import SourcesPopover from "./action/sources-popover";
import useAnimatedBoxShadow from "../../../../hooks/use-animated-box-shadow";

const RENDER_CONTENT_TYPES = ['text', 'attachment', 'action_submission'];

/**
 * Spacer component to fill remaining space and push messages to top
 * Uses margin-top: auto to push content above it to the top
 */
const MessageSpacer = memo(function MessageSpacer({
                                                      messageListRef,
                                                      isTyping,
                                                      messagesCount,
                                                      messages,
                                                      isLiveAgentMode,
                                                      calculatingRef
                                                  }) {
    const spacerRef = useRef(null);
    const shouldCalculateRef = useRef(false);

    /**
     * Check if we should start calculating (when a new user message is added by the user)
     * Only activates when a NEW user message is sent, not when loading existing chats
     * A temp user message (has _clientId but no uuid) is the definitive sign of a newly sent message
     * In live agent mode, never calculate - keep spacer at 0 and scroll to bottom instead
     */
    useEffect(() => {
        if (!messages || messages.length === 0) {
            shouldCalculateRef.current = false;
            return;
        }

        // In live agent mode, never use spacer - always keep it at 0
        if (isLiveAgentMode) {
            shouldCalculateRef.current = false;
            return;
        }

        // Check if there's a temp user message (newly sent, not from existing chat)
        // Temp messages have _clientId but no uuid
        const hasTempUserMessage = messages.some(msg =>
            msg.role === 'user' &&
            msg._clientId &&
            !msg.uuid
        );

        if (hasTempUserMessage) {
            // New user message detected (temp message without uuid) - enable calculation
            shouldCalculateRef.current = true;
        }
    }, [messages, isLiveAgentMode]);

    const calculatingStateUpdateTimeoutRef = useRef(null);

    /**
     * End calculating
     */
    const endCalculating = () => {
        if (calculatingStateUpdateTimeoutRef.current) {
            clearTimeout(calculatingStateUpdateTimeoutRef.current);
            calculatingStateUpdateTimeoutRef.current = null;
        }

        calculatingStateUpdateTimeoutRef.current = setTimeout(() => {
            calculatingRef.current = false;
        }, 300);
    }

    /**
     * Calculate and set spacer height to fill remaining space
     * Formula: container visible height - latest user message height - latest AI message height (only if AI comes after user)
     * Only calculates after user sends a new message
     */
    const calculateSpacerHeight = useCallback(() => {
        calculatingRef.current = true;
        // Don't calculate until user sends a new message
        if (!shouldCalculateRef.current) {
            if (spacerRef.current) {
                spacerRef.current.style.minHeight = '0px';
            }
            endCalculating();
            return;
        }

        const container = messageListRef?.current;
        const spacer = spacerRef?.current;
        if (!container || !spacer) {
            endCalculating();
            return;
        }

        // Ensure container uses flexbox
        if (container.style.display !== 'flex') {
            container.style.display = 'flex';
            container.style.flexDirection = 'column';
        }

        const containerHeight = container.clientHeight;

        // Get all message rows in order (excluding spacer)
        const allMessageRows = container.querySelectorAll('.lbaic-message-row:not(.lbaic-message-spacer)');
        if (allMessageRows.length === 0) {
            spacer.style.minHeight = '0px';
            endCalculating();
            return;
        }

        // Get the latest (last) message row
        const latestMessageRow = allMessageRows[allMessageRows.length - 1];
        const isLatestUser = latestMessageRow.classList.contains('lbaic-message-user');
        const isLatestAI = latestMessageRow.classList.contains('lbaic-message-bot');

        let latestUserMessageHeight = 0;
        let latestAIMessageHeight = 0;

        if (isLatestUser) {
            // Latest message is user - only use user message height
            latestUserMessageHeight = latestMessageRow.offsetHeight;
        } else if (isLatestAI) {
            // Latest message is AI - use both AI and the user message before it (if exists)
            latestAIMessageHeight = latestMessageRow.offsetHeight;

            // Find the user message that comes before this AI message
            for (let i = allMessageRows.length - 2; i >= 0; i--) {
                const prevRow = allMessageRows[i];
                if (prevRow.classList.contains('lbaic-message-user')) {
                    latestUserMessageHeight = prevRow.offsetHeight;
                    break;
                }
            }
        }

        // Calculate needed height: container height - latest user message height - latest AI message height
        // If messages are taller than container, spacer should be 0
        const totalMessagesHeight = latestUserMessageHeight + latestAIMessageHeight;
        const neededHeight = totalMessagesHeight >= containerHeight ? 0 : Math.max(0, containerHeight - totalMessagesHeight - 8);

        spacer.style.minHeight = `${neededHeight}px`;

        endCalculating();
    }, [messageListRef]);

    // Recalculate on mount, when messages change, and when typing state changes
    useEffect(() => {
        // Use requestAnimationFrame to ensure DOM is updated
        const timeout = setTimeout(() => {
            calculateSpacerHeight();
        }, 0);

        return () => clearTimeout(timeout);
    }, [calculateSpacerHeight, isTyping, messagesCount]);

    // Recalculate frequently during streaming to keep up with growing AI message
    useEffect(() => {
        if (!isTyping) {
            // Not streaming, no need for interval
            return;
        }

        // During streaming, recalculate every 100ms to keep up with growing message
        const interval = setInterval(() => {
            calculateSpacerHeight();
        }, 100);

        return () => clearInterval(interval);
    }, [calculateSpacerHeight, isTyping]);

    // Recalculate when streaming ends (isTyping changes from true to false)
    useEffect(() => {
        if (!isTyping) {
            // Streaming just ended, recalculate after a short delay to ensure DOM is updated
            const timeout = setTimeout(() => {
                calculateSpacerHeight();
            }, 100);

            return () => clearTimeout(timeout);
        }
    }, [calculateSpacerHeight, isTyping]);

    // Recalculate on window resize
    useEffect(() => {
        const handleResize = () => {
            calculateSpacerHeight();
        };

        window.addEventListener('resize', handleResize);
        return () => window.removeEventListener('resize', handleResize);
    }, [calculateSpacerHeight]);

    return (
        <div
            ref={spacerRef}
            className="lbaic-message-spacer"
            style={{
                minHeight: '0px',
                width: '100%',
                transition: 'min-height 0.3s ease-out'
            }}
        />
    );
});

const MessageContentContainer = memo(function MessageContentContainer({
                                                                          showTitle,
                                                                          icon,
                                                                          iconUrl,
                                                                          title,
                                                                          renderedContent,
                                                                          shouldAnimate,
                                                                          isAiMessage,
                                                                          isLatestMessage
                                                                      }) {
    const {color} = useContext(MessagesContext);
    const containerRef = useAnimatedBoxShadow({
        color: shouldAnimate ? color : null,
        loop: shouldAnimate ? (isAiMessage && isLatestMessage) : false,
        maxWidth: isAiMessage ? 3 : 5,
        delay: 0,
    });

    useEffect(() => {
        if (!shouldAnimate && containerRef.current) {
            containerRef.current.style.boxShadow = '';
        }
    }, [shouldAnimate, containerRef]);

    if (!showTitle && !renderedContent) {
        return null;
    }

    return (
        <div ref={containerRef} className="lbaic-message-container-in">
            {showTitle &&
                <div className='lbaic-message-title'>
                    <div className='lbaic-message-title-icon'>
                        {iconUrl ? (
                            <img src={iconUrl} alt={title || ''}/>
                        ) : icon ? (
                            <svg xmlns='http://www.w3.org/2000/svg' fill='none'>
                                <use href={`#lbaic-chatbot-avatar-${icon}`}/>
                            </svg>
                        ) : null}
                    </div>
                    <span className='lbaic-message-title-in'>{title}</span>
                </div>
            }
            {renderedContent && (
                <div className='lbaic-message'>
                    {renderedContent}
                </div>
            )}
        </div>
    );
});

// Memoized individual message item component
const MessageItem = memo(function MessageItem({
                                                  message,
                                                  index,
                                                  messagesArray,
                                                  chatbot,
                                                  setIsStreamTyping,
                                                  messageListRef,
                                                  chatbotErrorCallback,
                                                  userErrorCallback,
                                                  assistantReloadCallback,
                                                  isLastAssistantMessage
                                              }) {
    if (!message.content?.length && !message.hasError) {
        return null;
    }

    // Check if message has any renderable content types
    const hasRenderableContent = message.content?.length > 0 && message.content.some(item => RENDER_CONTENT_TYPES.includes(item.type));

    // Don't render messages that don't have renderable content and no errors
    // (e.g., messages with only action_cancellation, parameter, or audio types)
    // This prevents empty message rows from appearing when there's only a timestamp
    if (!hasRenderableContent && !message.hasError) {
        return null;
    }

    const isAiMessage = message.role === 'assistant';
    // Use agent info if available (for live agent messages), otherwise use chatbot info
    const hasAgent = isAiMessage && message.agent;
    const icon = isAiMessage && !hasAgent ? chatbot.utility.avatar : false;
    const iconUrl = hasAgent ? (message.agent.avatar_urls?.['24'] || message.agent.avatar_urls?.['48'] || message.agent.avatar_urls?.['96']) : null;
    const title = isAiMessage ? (hasAgent ? message.agent.name : chatbot.utility.title) : false;
    // Check if avatar visibility is enabled (defaults to true if not set)
    const avatarVisibility = Boolean(chatbot.utility.avatar_visibility);

    // Check if this message is the latest
    const isLatestMessage = index === messagesArray.length - 1;

    // Prepare actions for AI messages - memoized to prevent recreation
    const actions = useMemo(() => {
        const acts = [];
        if (isAiMessage && !message.hasError && chatbot.utility.copy && message.content) {
            // Get text content(s) for copy action
            const textContent = message.content.map(c => {
                if (c.type === 'text') {
                    return c.text?.value || '';
                }

                return '';
            }).join("\n");

            if (textContent) {
                // Strip markdown formatting from text before copying
                const plainText = stripMarkdown(textContent);
                acts.push({
                    icon: 'copy',
                    iconDone: 'check',
                    tooltip: __("Copy", 'limb-chatbot'),
                    onClick: async () => await copyToClipboard(plainText)
                });
            }
        }
        // Check if message contains action_submission content
        const hasActionSubmission = message.content?.some(item => item.type === 'action_submission');
        // Check if message contains slack_connection or live_agent_disconnection content
        const hasLiveAgentContent = message.content?.some(item =>
            item.type === 'slack_connection' || item.type === 'live_agent_disconnection'
        );
        // Add Reset button next to Copy when Reload is enabled (but not for action_submission, live agent messages, or slack/disconnection messages)
        if (isAiMessage && isLastAssistantMessage && !message.hasError && !hasActionSubmission && !hasLiveAgentContent && !message.agent && chatbot.utility?.reload && typeof assistantReloadCallback === 'function') {
            acts.push({
                icon: 'refresh',
                tooltip: __("Retry", 'limb-chatbot'),
                onClick: async () => {
                    try {
                        await assistantReloadCallback(message);
                        return {success: true};
                    } catch (e) {
                        return {success: false, error: e};
                    }
                },
            });
        }
        // Add Sources button for AI messages with sources
        if (isAiMessage && chatbot.utility?.show_sources && !message.hasError && message.sources && Array.isArray(message.sources) && message.sources.length > 0) {
            const sourcesCount = message.sources.length;
            const arrowPos = !message.created_at && acts.length === 0 ? 'left' : 'center';
            acts.push({
                customRender: () => (
                    <SourcesPopover sources={message.sources} arrowPosition={arrowPos}>
                        <button
                            className="lbaic-button lbaic-button-rounded lbaic-button-sources"
                            type="button"
                        >
                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" className="lbaic-button-sources-icon"
                                 aria-hidden="true">
                                <use href="#lbaic-book-open"></use>
                            </svg>
                            <span className="lbaic-button-sources-count">{sourcesCount}</span>
                        </button>
                    </SourcesPopover>
                ),
            });
        }
        return acts;
    }, [assistantReloadCallback, chatbot.utility?.copy, chatbot.utility?.reload, isAiMessage, isLastAssistantMessage, message]);

    // Prepare errors - memoized to prevent recreation
    const errors = useMemo(() => {
        return message.hasError ? [{
            message: message.errorMessage || __("Failed to send", 'limb-chatbot'),
            callback: {
                args: message.role === 'user' ? [message] : [],
            },
        }] : [];
    }, [message.hasError, message.errorMessage, message.role, message]);

    // Check if message has parameter type content
    const hasParameterContent = message.content?.some(item => item.type === 'parameter');

    /**
     * Render message content item
     */
    const renderContentItem = (contentItem, contentIndex) => {
        if (contentItem.type === 'text') {
            if (!contentItem.text.value) {
                return null;
            }

            return (
                <MessageText
                    key={contentIndex}
                    message={contentItem.text.value}
                    stream={message.stream}
                    setIsStreamTyping={setIsStreamTyping}
                    messageListRef={messageListRef}
                    messageShowCheckIcon={message.showCheckIcon && !message.hasError}
                />
            );
        } else if (contentItem.type === 'attachment') {
            if (!contentItem.attachment.value) {
                return null;
            }
            if (isImage(contentItem.attachment.mime_type)) {
                return (
                    <MessageImage
                        key={contentIndex}
                        src={contentItem.attachment.value}
                    />
                );
            } else {
                return (
                    <MessageFile
                        key={contentIndex}
                        name={`file-${contentIndex}`}
                        size={getBase64FormatedSize(contentItem.attachment.value)}
                    />
                );
            }
        } else if (contentItem.type === 'action_submission') {
            if (!contentItem.action_submission) {
                return null;
            }

            return (
                <MessageActionSubmission
                    key={contentIndex}
                    actionSubmission={contentItem.action_submission}
                />
            );
        } else if (contentItem.type === 'audio') {
            // TODO: Add audio component
            return null;
        }
        return null;
    };

    const hasActions = actions.length > 0 || Boolean(message.created_at);
    const hasContentToRender = message.content?.length > 0 && message.content.some(item => {
        if (!RENDER_CONTENT_TYPES.includes(item.type)) {
            return false;
        }
        if ('value' in item[item.type]) {
            return Boolean(item[item.type]?.value);
        }

        return true;
    });

    return (
        <div className={`lbaic-message-row lbaic-message-${message.role === 'user' ? 'user' : 'bot'}${message.isWidgetMessage ? ' lbaic-no-animation' : ''}`}>
            {hasContentToRender && (
                <div className="lbaic-message-body">
                    <div className="lbaic-message-body-in">
                        <div className="lbaic-message-container">
                            {message.content.map((contentItem, contentIndex) => {
                                if (!RENDER_CONTENT_TYPES.includes(contentItem.type)) {
                                    return null;
                                }
                                // Content to be rendered
                                const renderedContent = renderContentItem(contentItem, contentIndex);

                                let shouldAnimate = false;
                                if (contentItem.type === 'text') {
                                    const textValue = contentItem.text?.value || '';
                                    const hasUuid = Boolean(message.uuid);
                                    const shouldAnimateAI = hasUuid && isLatestMessage && isAiMessage && hasParameterContent && textValue.length > 0;
                                    const shouldAnimateUser = hasUuid && isLatestMessage && !isAiMessage && message.showCheckIcon && !message.hasError && textValue.length > 0;
                                    shouldAnimate = shouldAnimateAI || shouldAnimateUser;
                                }

                                return (
                                    <MessageContentContainer
                                        key={contentIndex}
                                        showTitle={contentIndex === 0 && isAiMessage && avatarVisibility && (icon || iconUrl || title)}
                                        icon={icon}
                                        iconUrl={iconUrl}
                                        title={title}
                                        renderedContent={renderedContent}
                                        shouldAnimate={shouldAnimate}
                                        isAiMessage={isAiMessage}
                                        isLatestMessage={isLatestMessage}
                                    />
                                )
                            })}
                        </div>
                        {hasActions && (
                            <div className='lbaic-message-actions'>
                                {isAiMessage && !avatarVisibility && title && (
                                    <div className='lbaic-message-actions-in'>
                                        <span className='lbaic-message-agent-name'>{title}</span>
                                    </div>
                                )}
                                {message.created_at?.length > 0 && (
                                    <div className='lbaic-message-actions-in'>
                                        <RealTimeRelativeTime
                                            dateString={message.created_at}
                                            className='lbaic-message-time'
                                            tooltipProps={{
                                                arrowPosition: message.role === 'user' ? 'right' : (avatarVisibility ? 'left' : 'center')
                                            }}
                                        />
                                    </div>
                                )}
                                {actions.length > 0 &&
                                    actions.map((action, actionIndex) => (
                                        <Action
                                            key={actionIndex}
                                            action={action}
                                            tooltipArrowPosition={!message.created_at && actionIndex === 0 ? 'left' : 'center'}
                                        />
                                    ))
                                }
                            </div>
                        )}
                    </div>
                </div>
            )}
            {errors.length > 0 &&
                errors.map((error, errorIndex) => {
                    const args = error.callback?.args?.length > 0 ? error.callback.args : [];
                    const errorCallback = (...callbackArgs) => {
                        if (message.role === 'user') {
                            // Resend the user message
                            if (typeof userErrorCallback === 'function') {
                                userErrorCallback(...callbackArgs);
                            }
                        } else {
                            // Run the chat
                            if (typeof chatbotErrorCallback === 'function') {
                                chatbotErrorCallback(...callbackArgs);
                            }
                        }
                    };

                    return (
                        <div key={errorIndex} className='lbaic-message-error'>
                            <p className='lbaic-message-error-in'
                               dangerouslySetInnerHTML={{__html: error.message}}></p>
                            <ButtonRounded
                                className='lbaic-message-error-action'
                                icon='chat-resend'
                                onClick={() => errorCallback(...args)}
                            />
                        </div>
                    );
                })
            }
        </div>
    );
}, (prevProps, nextProps) => {
    // Custom comparison function for better memoization
    // Check if the message content actually changed
    const prevMsg = prevProps.message;
    const nextMsg = nextProps.message;

    // If messages are the same reference and nothing else changed, no need to re-render
    if (prevMsg === nextMsg &&
        prevProps.index === nextProps.index &&
        prevProps.messagesArray.length === nextProps.messagesArray.length &&
        prevProps.chatbotErrorCallback === nextProps.chatbotErrorCallback &&
        prevProps.userErrorCallback === nextProps.userErrorCallback &&
        prevProps.assistantReloadCallback === nextProps.assistantReloadCallback &&
        prevProps.isLastAssistantMessage === nextProps.isLastAssistantMessage) {
        return true;
    }

    // Compare key message properties
    const messageUnchanged = (
        prevMsg.uuid === nextMsg.uuid &&
        prevMsg.role === nextMsg.role &&
        prevMsg.hasError === nextMsg.hasError &&
        prevMsg.errorMessage === nextMsg.errorMessage &&
        prevMsg.stream === nextMsg.stream &&
        prevMsg.showCheckIcon === nextMsg.showCheckIcon &&
        JSON.stringify(prevMsg.content) === JSON.stringify(nextMsg.content) &&
        // Compare agent info for live agent messages
        prevMsg.agent?.id === nextMsg.agent?.id &&
        prevMsg.agent?.name === nextMsg.agent?.name
    );

    // Calculate if this message WAS the latest and if it IS the latest
    const prevIsLatest = prevProps.index === prevProps.messagesArray.length - 1;
    const nextIsLatest = nextProps.index === nextProps.messagesArray.length - 1;
    const isLatestStatusChanged = prevIsLatest !== nextIsLatest;

    // Only care about isLatestStatusChanged for AI messages with parameters
    // (they need to stop/start animation based on latest status)
    const isAiMessage = nextMsg.role === 'assistant';
    const hasParameterContent = nextMsg.content?.some(item => item.type === 'parameter');
    const shouldCareAboutLatestStatus = isAiMessage && hasParameterContent && nextMsg.uuid;

    // Check if position-related props that affect rendering changed
    const positionUnchanged = (
        prevProps.index === nextProps.index &&
        // Only care if previous message changed (affects avatar display)
        (prevProps.index === 0 || prevProps.messagesArray[prevProps.index - 1] === nextProps.messagesArray[nextProps.index - 1])
    );

    // Check if chatbot settings changed
    const chatbotUnchanged = (
        prevProps.chatbot.utility.avatar === nextProps.chatbot.utility.avatar &&
        prevProps.chatbot.utility.title === nextProps.chatbot.utility.title &&
        prevProps.chatbot.utility.copy === nextProps.chatbot.utility.copy &&
        prevProps.chatbot.utility.avatar_visibility === nextProps.chatbot.utility.avatar_visibility
    );

    // Check if callbacks changed
    const callbacksUnchanged = (
        prevProps.chatbotErrorCallback === nextProps.chatbotErrorCallback &&
        prevProps.userErrorCallback === nextProps.userErrorCallback &&
        prevProps.assistantReloadCallback === nextProps.assistantReloadCallback &&
        prevProps.isLastAssistantMessage === nextProps.isLastAssistantMessage
    );

    // Re-render if:
    // 1. Message content changed
    // 2. Position/context changed
    // 3. "Latest message" status changed (but ONLY for AI messages with parameters)
    // 4. Chatbot settings changed
    // 5. Callbacks changed
    if (!messageUnchanged ||
        !positionUnchanged ||
        (shouldCareAboutLatestStatus && isLatestStatusChanged) ||
        !chatbotUnchanged ||
        !callbacksUnchanged) {
        return false; // Props changed, re-render
    }

    return true; // Nothing changed, skip re-render
});

const Messages = memo(function Messages({
                                            chatbot,
                                            messages,
                                            sendMessage,
                                            isTyping,
                                            setIsStreamTyping,
                                            chatbotErrorCallback,
                                            userErrorCallback,
                                            assistantReloadCallback,
                                            messageListRef,
                                            onLastMessageBotError,
                                            hasMore,
                                            isLoadingMore,
                                            onLastMessageParameter,
                                            isLiveAgentMode,
                                            messageSpacerCalculatingRef,
                                            chatScreen,
                                            onWidgetsLoadingChange
                                        }) {
    const widgets = useMemo(() => {
        if (!chatbot.utility.widgets?.length) {
            return [];
        }

        return chatbot.utility.widgets.filter(widget =>
            widget.published
            && showWidget(widget.locations, {screen: 'new_chat'})
        )
    }, [chatbot.utility.widgets]);

    // Track typing state for first message group
    const [showTypingIndicator, setShowTypingIndicator] = useState(false);
    const [showWidgets, setShowWidgets] = useState(false);
    const [typingShown, setTypingShown] = useState(false);
    const hasMessageWidgetRef = useRef(null);

    /**
     * Group consecutive widget items by type
     *
     * @param {object[]} widgets Widget items array
     * @return {object[]} Grouped items array
     */
    const groupWidgetItems = useCallback((widgets) => {
        const groups = [];
        let currentGroup = null;

        widgets.forEach((widget) => {
            if (widget.type === 'message' || widget.type === 'prompt') {
                // Group consecutive message widgets
                if (currentGroup && currentGroup.type === widget.type) {
                    currentGroup.items.push(widget);
                } else {
                    currentGroup = {type: widget.type, items: [widget]};
                    groups.push(currentGroup);
                }
            } else {
                // Other types are added individually
                groups.push({type: widget.type, items: [widget]});
                currentGroup = null;
            }
        });

        return groups;
    }, []);

    /**
     * Render widget
     *
     * @param {object} widgetItem Widget item
     * @param {number} index Widget item index
     * @param {object[]} widgetItems Widget items array
     * @return {JSX.Element|null}
     */
    const renderWidget = useCallback((widgetItem, index, widgetItems) => {
        if (widgetItem.type === 'prompt') {
            if (!messages.length) {
                // Render prompt
                return (
                    widgetItem.data.content?.map((contentItem, i) => {
                        // Use Message field if available, otherwise fallback to Content for backward compatibility
                        const messageToSend = widgetItem.data?.message || contentItem[contentItem.type]?.value || '';
                        // Use Content as label
                        const labelText = contentItem[contentItem.type]?.value || '';

                        return (
                            <button
                                key={widgetItem.id + i}
                                className='lbaic-message-fast-inner lbaic-fade-in-up'
                                onClick={() => {
                                    // Send the message
                                    sendMessage(prepareUserMessage({
                                        [contentItem.type]: messageToSend,
                                    }));
                                }}
                            >
                                {contentItem.type === 'text' &&
                                    <span
                                        className='lbaic-message-fast-label'>{labelText}</span>}
                            </button>
                        );
                    })
                );
            }
            // Show only when messages don't exist
            return null;
        } else if (widgetItem.type === 'message') {
            // Render message
            const showAvatarTitle = index === 0 || widgetItems[index - 1].type !== 'message';
            const avatarVisibility = Boolean(chatbot.utility.avatar_visibility);
            return <Message
                key={widgetItem.id}
                role="assistant"
                icon={showAvatarTitle && avatarVisibility && chatbot.utility.avatar}
                title={showAvatarTitle && avatarVisibility && chatbot.utility.title}
                agentName={showAvatarTitle && !avatarVisibility && chatbot.utility.title ? chatbot.utility.title : undefined}
                time={new Date().toISOString()}
                actions={[]}
                errors={[]}
            >
                {widgetItem.data.content?.map((contentItem, i) => {
                    if (contentItem.type === 'text') {
                        return <MessageText
                            key={i}
                            message={contentItem[contentItem.type]?.value}
                            role="assistant"
                            messageUuid={null}
                        />
                    }
                    return null;
                })}
            </Message>
        } else {
            return null;
        }
    }, [chatbot.utility.avatar, chatbot.utility.title, chatbot.utility.avatar_visibility, messages.length, sendMessage]);

    /**
     * Render grouped widget items
     *
     * @param {object} group Grouped widget items
     * @param {number} groupIndex Group index
     * @return {JSX.Element|null}
     */
    const renderWidgetGroup = useCallback((group, groupIndex) => {
        if (group.type === 'message') {
            // Render grouped message widgets
            const avatarVisibility = Boolean(chatbot.utility.avatar_visibility);

            return (
                <div key={`message-group-${groupIndex}`}
                     className="lbaic-message-row lbaic-message-bot lbaic-message-widget">
                    <div className="lbaic-message-body">
                        <div className="lbaic-message-body-in">
                            <div className="lbaic-message-container lbaic-fade-in-up">
                                {group.items.map((widgetItem, itemIndex) => (
                                    <div key={widgetItem.id} className="lbaic-message-container-in">
                                        {!!(itemIndex === 0 && avatarVisibility && (chatbot.utility.avatar || chatbot.utility.title)) &&
                                            <div className='lbaic-message-title'>
                                                <div className='lbaic-message-title-icon'>
                                                    <svg xmlns='http://www.w3.org/2000/svg' fill='none'>
                                                        <use href={`#lbaic-chatbot-avatar-${chatbot.utility.avatar}`}/>
                                                    </svg>
                                                </div>
                                                <span className='lbaic-message-title-in'>{chatbot.utility.title}</span>
                                            </div>
                                        }
                                        <div className='lbaic-message'>
                                            {widgetItem.data.content?.map((contentItem, i) => {
                                                if (contentItem.type === 'text') {
                                                    return <MessageText
                                                        key={i}
                                                        message={contentItem[contentItem.type]?.value}
                                                        role="assistant"
                                                        messageUuid={null}
                                                    />
                                                }
                                                return null;
                                            })}
                                        </div>
                                    </div>
                                ))}
                            </div>
                        </div>
                    </div>
                </div>
            );
        } else if (group.type === 'prompt') {
            // Render single prompt widget
            return (
                <div
                    key={`prompt-group-${groupIndex}`}
                    className="lbaic-message-fast-row"
                >
                    <div className="lbaic-message-fast-row-in">
                        {group.items.map((item, idx) => renderWidget(item, idx, group.items))}
                    </div>
                </div>
            );
        }

        return null;
    }, [chatbot.utility.avatar, chatbot.utility.title, chatbot.utility.avatar_visibility, renderWidget]);

    // Check if the last message is from user and not bot
    const lastMessageIsFromUser = messages.length > 0 && messages[messages.length - 1].role === 'user' && Boolean(messages[messages.length - 1].uuid);

    // Check if the last user message is a live agent disconnection message
    // (only contains live_agent_disconnection content, no other content types)
    const lastMessageIsDisconnection = lastMessageIsFromUser && messages[messages.length - 1].content?.length > 0 &&
        messages[messages.length - 1].content.every(item => item.type === 'live_agent_disconnection');

    // Check if the last message is a bot error message
    const lastMessageIsBotError = messages.length > 0 &&
        messages[messages.length - 1].role === 'assistant' &&
        messages[messages.length - 1].hasError === true;

    // Also check if we're showing the artificial error message (when last message is from user, skip in live agent mode or if it's a disconnection message)
    const showingArtificialError = lastMessageIsFromUser && !isTyping && !isLiveAgentMode && !lastMessageIsDisconnection;

    // Notify parent component about bot error state
    useEffect(() => {
        if (typeof onLastMessageBotError === 'function') {
            onLastMessageBotError(lastMessageIsBotError || showingArtificialError);
        }
    }, [lastMessageIsBotError, showingArtificialError, onLastMessageBotError]);

    // Detect and notify parent about parameter in last message
    useEffect(() => {
        if (typeof onLastMessageParameter === 'function') {
            // Get the last message that is from assistant
            const lastAssistantMessage = messages.length > 0 && messages[messages.length - 1].role === 'assistant'
                ? messages[messages.length - 1]
                : null;

            // Find parameter content item in last assistant message
            const parameterContentItem = lastAssistantMessage?.content?.find(item => item.type === 'parameter') || null;
            const parameter = parameterContentItem?.parameter || null;

            // Attach has_callback to parameter if it exists
            if (parameter && parameterContentItem && 'has_callback' in parameterContentItem) {
                parameter.has_callback = parameterContentItem.has_callback;
            }

            onLastMessageParameter(parameter);
        }
    }, [messages, onLastMessageParameter]);

    // Check if avatar visibility is enabled (defaults to true if not set)
    const avatarVisibility = Boolean(chatbot.utility.avatar_visibility);

    // Memoize the context value to prevent unnecessary re-renders
    const contextValue = useMemo(() => ({
        chatbotErrorCallback: chatbotErrorCallback,
        userErrorCallback: userErrorCallback,
        color: chatbot.utility.color
    }), [chatbotErrorCallback, userErrorCallback, chatbot.utility.color]);

    // Memoize grouped widgets
    const groupedWidgets = useMemo(() => {
        return groupWidgetItems(widgets);
    }, [widgets, groupWidgetItems]);

    // Memoize hasMessageWidget to prevent unnecessary effect re-runs
    const hasMessageWidget = useMemo(() => {
        return groupedWidgets.some(item => item.type === 'message');
    }, [groupedWidgets]);

    // True when on new chat screen with message widgets and typing indicator is still showing (widgets not yet rendered)
    const widgetsLoading = !messages?.length && hasMessageWidget && showTypingIndicator;

    // Notify parent when widgets loading state changes (so footer send can be disabled)
    useEffect(() => {
        if (typeof onWidgetsLoadingChange === 'function') {
            onWidgetsLoadingChange(widgetsLoading);
        }
    }, [widgetsLoading, onWidgetsLoadingChange]);

    // Handle typing effect for first message group (only in 'chat' screen)
    useEffect(() => {
        if (chatScreen !== 'chat') {
            setTypingShown(false);
            setShowTypingIndicator(false);
            setShowWidgets(false);
            hasMessageWidgetRef.current = false;
            return;
        }

        if (!groupedWidgets.length) {
            setShowTypingIndicator(false);
            setShowWidgets(false);
            hasMessageWidgetRef.current = null;
            return;
        }

        // If typing has already been shown, just update visibility
        if (typingShown) {
            setShowTypingIndicator(false);
            setShowWidgets(groupedWidgets.length > 0);
            return;
        }

        // Only trigger typing if chatScreen is "chat" and has message widgets
        if (chatScreen === 'chat' && hasMessageWidget && !hasMessageWidgetRef.current) {
            hasMessageWidgetRef.current = true;
            // Show typing indicator first
            setShowTypingIndicator(true);
            setShowWidgets(false);

            // After 2 seconds, show all widgets
            const typingTimer = setTimeout(() => {
                setShowTypingIndicator(false);
                setShowWidgets(true);
                setTypingShown(true);
            }, 2000);

            return () => {
                clearTimeout(typingTimer);
            }
        } else if (!hasMessageWidget) {
            // If no message widgets or not in 'chat' screen, show widgets directly
            hasMessageWidgetRef.current = false;
            setShowTypingIndicator(false);
            setShowWidgets(true);
        }
    }, [groupedWidgets.length, hasMessageWidget, typingShown, chatScreen]);

    return <>
        {!messages?.length && (
            <>
                {/* Typing Indicator - Show before widgets if first group is message */}
                {showTypingIndicator && hasMessageWidget && (
                    <div className="lbaic-message-row lbaic-message-bot lbaic-message-widget">
                        <div className="lbaic-message-body">
                            <div className="lbaic-message-body-in">
                                <div className="lbaic-message-container">
                                    <div className="lbaic-message-container-in">
                                        {chatbot.utility.avatar_visibility && (chatbot.utility.avatar || chatbot.utility.title) && (
                                            <div className='lbaic-message-title'>
                                                <div className='lbaic-message-title-icon'>
                                                    <svg xmlns='http://www.w3.org/2000/svg' fill='none'>
                                                        <use href={`#lbaic-chatbot-avatar-${chatbot.utility.avatar}`}/>
                                                    </svg>
                                                </div>
                                                <span className='lbaic-message-title-in'>{chatbot.utility.title}</span>
                                            </div>
                                        )}
                                        <div className='lbaic-message'>
                                            <div className='lbaic-message-widget-typing'>
                                                <div className='lbaic-message-widget-typing-dot'></div>
                                                <div className='lbaic-message-widget-typing-dot'></div>
                                                <div className='lbaic-message-widget-typing-dot'></div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                )}

                {/* Widgets - Show after typing indicator */}
                {showWidgets && groupedWidgets.map((group, index) => renderWidgetGroup(group, index))}
            </>
        )}
        {hasMore && isLoadingMore && (
            <div className="lbaic-message-load-more">
                <div className="lbaic-message-load-more-in">
                    <span className="lbaic-message-load-more-label">{__('Loading', 'limb-chatbot')}...</span>
                </div>
            </div>
        )}
        <MessagesContext.Provider value={contextValue}>
            {messages.map((message, i) => (
                <MessageItem
                    key={message._clientId || message.uuid || 'message-' + i}
                    message={message}
                    index={i}
                    messagesArray={messages}
                    chatbot={chatbot}
                    setIsStreamTyping={setIsStreamTyping}
                    messageListRef={messageListRef}
                    chatbotErrorCallback={chatbotErrorCallback}
                    userErrorCallback={userErrorCallback}
                    assistantReloadCallback={assistantReloadCallback}
                    isLastAssistantMessage={i === messages.length - 1}
                />
            ))}
            {/* Add error row on bot side when last message is from user (skip in live agent mode or if it's a disconnection message) */}
            {(lastMessageIsFromUser && !isTyping && !isLiveAgentMode && !lastMessageIsDisconnection) && (
                <Message
                    key="error-rerun-message"
                    role="assistant"
                    icon={avatarVisibility && chatbot.utility.avatar}
                    title={avatarVisibility && chatbot.utility.title}
                    agentName={!avatarVisibility && chatbot.utility.title ? chatbot.utility.title : undefined}
                    actions={[]}
                    errors={[{
                        message: __("Failed to reply.", 'limb-chatbot'),
                        callback: {
                            args: [],
                        },
                    }]}
                >
                    <MessageText message="" role="assistant"/>
                </Message>
            )}
            {/* Spacer to push messages to top and fill remaining space */}
            {messages.length > 0 && (
                <MessageSpacer
                    messageListRef={messageListRef}
                    isTyping={isTyping}
                    messagesCount={messages.length}
                    messages={messages}
                    isLiveAgentMode={isLiveAgentMode}
                    calculatingRef={messageSpacerCalculatingRef}
                />
            )}
        </MessagesContext.Provider>
    </>
});

export default Messages;