// src/components/FastAgentPanel/FastAgentPanel.UIMessageStream.tsx
// Scrollable message container for UIMessages from Agent component
// Supports hierarchical rendering for coordinator/specialized agent delegation

import React, { useEffect, useRef, useMemo, useTransition, useDeferredValue } from 'react';
import { FastAgentUIMessageBubble as UIMessageBubble } from './FastAgentPanel.UIMessageBubble';
import { TypingIndicator } from './TypingIndicator';
import { useSmartAutoScroll } from './hooks/useSmartAutoScroll';
import type { UIMessage } from '@convex-dev/agent/react';
import type { CompanyOption } from './CompanySelectionCard';
import type { PersonOption } from './PeopleSelectionCard';
import type { EventOption } from './EventSelectionCard';
import type { NewsArticleOption } from './NewsSelectionCard';
import type { EntityHoverData } from '@/features/research/components/EntityHoverPreview';

interface UIMessageStreamProps {
  messages: UIMessage[];
  autoScroll?: boolean;
  onMermaidRetry?: (error: string, code: string) => void;
  onRegenerateMessage?: (messageKey: string) => void;
  onDeleteMessage?: (messageId: string) => void;
  onCompanySelect?: (company: CompanyOption) => void;
  onPersonSelect?: (person: PersonOption) => void;
  onEventSelect?: (event: EventOption) => void;
  onNewsSelect?: (article: NewsArticleOption) => void;
  onDocumentSelect?: (documentId: string) => void;
  isQueued?: boolean;
  /** Pre-loaded entity enrichment data for medium-detail hover previews */
  entityEnrichment?: Record<string, EntityHoverData>;
}

// Extended UIMessage type with hierarchical metadata
interface ExtendedUIMessage extends UIMessage {
  id: string; // Enforce string ID to match UIMessage requirement
  _id?: string;
  key?: string;
  role?: 'user' | 'assistant' | 'system';
  parts?: any[];
  text?: string;
  status?: string;
  metadata?: {
    agentRole?: 'coordinator' | 'documentAgent' | 'mediaAgent' | 'secAgent' | 'webAgent';
    parentMessageId?: string;
  };
}

// Message group structure for hierarchical rendering
interface MessageGroup {
  parent: ExtendedUIMessage;
  children: ExtendedUIMessage[];
}

/**
 * UIMessageStream - Scrollable container for UIMessages with auto-scroll
 * Optimized for the Agent component's UIMessage format
 * Supports hierarchical rendering for coordinator agent delegation
 */
export function UIMessageStream({
  messages,
  autoScroll = true,
  onMermaidRetry,
  onRegenerateMessage,
  onDeleteMessage,
  onCompanySelect,
  onPersonSelect,
  onEventSelect,
  onNewsSelect,
  onDocumentSelect,
  isQueued = false,
  entityEnrichment,
}: UIMessageStreamProps) {
  const scrollRef = useRef<HTMLDivElement>(null);
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const [_isPending, startTransition] = useTransition();

  // Use smart auto-scroll that respects user scroll position
  const { autoScroll: smartAutoScroll, reset: resetScroll } = useSmartAutoScroll(
    scrollRef as React.RefObject<HTMLDivElement>,
    { nearBottomThreshold: 80, enableLogging: false }
  );

  // Defer heavy message filtering/grouping to prevent blocking UI
  const deferredMessages = useDeferredValue(messages);

  // Smart auto-scroll when new messages arrive
  useEffect(() => {
    if (!autoScroll) return;
    smartAutoScroll();
  }, [messages.length, autoScroll, smartAutoScroll]);

  // Reset scroll state when starting new conversation
  useEffect(() => {
    resetScroll();
  }, [resetScroll]);

  // Filter out empty messages and agent-generated sub-query messages before processing
  // Use deferredMessages to prevent blocking UI during heavy filtering
  const filteredMessages = useMemo(() => {
    // First pass: identify delegation patterns
    const delegationIndices = new Set<number>();

    deferredMessages.forEach((msg, idx) => {
      // Check if this message has delegation tool calls
      const hasDelegationTools = msg.parts?.some((p: any) =>
        p.type === 'tool-call' && p.toolName?.startsWith('delegateTo')
      );

      if (hasDelegationTools) {
        // Mark the next user messages as agent-generated sub-queries
        // These are created by specialized agents when they call generateText()
        for (let i = idx + 1; i < deferredMessages.length; i++) {
          const nextMsg = deferredMessages[i];

          // Stop when we hit an assistant message (the response to the delegation)
          if (nextMsg.role === 'assistant') break;

          // Mark user messages between delegation and response as agent-generated
          if (nextMsg.role === 'user') {
            delegationIndices.add(i);
          }
        }
      }
    });

    return (deferredMessages as ExtendedUIMessage[]).filter((msg: ExtendedUIMessage, idx: number) => {
      // Filter out ONLY agent-generated sub-query messages (between delegation and response)
      if (delegationIndices.has(idx)) {
        return false;
      }

      // CRITICAL: Always keep real user messages (actual user input)
      if (msg.role === 'user') {
        return true;
      }

      // For assistant messages, be lenient - keep if has ANY content
      if (msg.role === 'assistant') {
        const hasText = msg.text && msg.text.trim().length > 0;
        const hasParts = msg.parts && msg.parts.length > 0;
        const hasToolCalls = msg.parts?.some((p: any) => p.type.startsWith('tool-'));

        // Keep if has text, parts, or tool calls
        return hasText || hasParts || hasToolCalls;
      }

      // Keep all other message types (system, etc.)
      return true;
    });
  }, [deferredMessages]);

  // Infer hierarchy from tool calls (Option 3 approach)
  // When a coordinator message has delegation tool calls, the next N messages are likely children
  const groupedMessages = useMemo(() => {
    const extendedMessages = filteredMessages as ExtendedUIMessage[];
    const groups: MessageGroup[] = [];
    const processedIndices = new Set<number>();
    const seenMessageIds = new Set<string>(); // Track message IDs to prevent duplicates

    extendedMessages.forEach((msg, idx) => {
      // Skip if already processed as a child
      if (processedIndices.has(idx)) return;

      // Skip if we've already seen this message ID
      const messageId = msg._id || msg.key;
      if (messageId && seenMessageIds.has(messageId)) {
        return;
      }
      if (messageId) {
        seenMessageIds.add(messageId);
      }

      // Check if this message has delegation tool calls
      const delegationToolCalls = msg.parts?.filter((p: any) =>
        p.type === 'tool-call' &&
        p.toolName?.startsWith('delegateTo')
      ) || [];

      if (delegationToolCalls.length > 0) {
        // This is a coordinator message with delegations
        const children: ExtendedUIMessage[] = [];

        // Collect the next N assistant messages as children (where N = number of delegation calls)
        let childrenFound = 0;
        for (let i = idx + 1; i < extendedMessages.length && childrenFound < delegationToolCalls.length; i++) {
          const nextMsg = extendedMessages[i];

          // Only include assistant messages (not user messages)
          if (nextMsg.role === 'assistant') {
            // Skip if we've already seen this child message
            const childId = nextMsg._id || nextMsg.key;
            if (childId && seenMessageIds.has(childId)) {
              processedIndices.add(i);
              childrenFound++;
              continue;
            }
            if (childId) {
              seenMessageIds.add(childId);
            }

            // Infer agent role from the corresponding delegation tool call
            const delegationTool = delegationToolCalls[childrenFound];
            const toolName = (delegationTool as any).toolName || '';

            // Map delegation tool name to agent role
            let agentRole: 'coordinator' | 'documentAgent' | 'mediaAgent' | 'secAgent' | 'webAgent' | undefined = undefined;
            if (toolName === 'delegateToDocumentAgent') agentRole = 'documentAgent';
            else if (toolName === 'delegateToMediaAgent') agentRole = 'mediaAgent';
            else if (toolName === 'delegateToSECAgent') agentRole = 'secAgent';
            else if (toolName === 'delegateToWebAgent') agentRole = 'webAgent';

            // Add inferred metadata to the child message
            const childWithMetadata: ExtendedUIMessage = {
              ...nextMsg,
              metadata: {
                ...nextMsg.metadata,
                agentRole,
                parentMessageId: msg._id,
              },
            };

            children.push(childWithMetadata);
            processedIndices.add(i);
            childrenFound++;
          }
        }

        // Mark the parent message as coordinator
        const parentWithMetadata: ExtendedUIMessage = {
          ...msg,
          metadata: {
            ...msg.metadata,
            agentRole: 'coordinator',
          },
        };

        groups.push({ parent: parentWithMetadata, children });
      } else {
        // Regular message without delegations
        groups.push({ parent: msg, children: [] });
      }
    });

    return groups;
  }, [filteredMessages]);

  // Deduplication: Check if a message appears in a previous group
  const isDuplicate = (message: ExtendedUIMessage, currentGroupIndex: number): boolean => {
    // Check if this message appears in a previous group
    const messageId = message._id || message.key;
    if (!messageId) return false;

    // Check if this message ID appears in any earlier group
    for (let i = 0; i < currentGroupIndex; i++) {
      const group = groupedMessages[i];
      const parentId = group.parent._id || group.parent.key;
      if (parentId === messageId) {
        return true;
      }

      const childMatch = group.children.some(child => {
        const childId = child._id || child.key;
        return childId === messageId;
      });
      if (childMatch) {
        return true;
      }
    }

    return false;
  };

  return (
    <div
      ref={scrollRef}
      className="flex-1 overflow-y-auto p-6"
      style={{ maxHeight: '100%' }}
    >
      {messages.length === 0 ? (
        <div className="flex items-center justify-center h-full text-content-secondary">
          <p>No messages yet. Start a conversation!</p>
        </div>
      ) : groupedMessages.length === 0 ? (
        <div className="text-content-secondary text-center p-4">
          <p>No messages to display. Check console for filtering details.</p>
          <p className="text-xs mt-2">Raw messages: {messages.length}, Filtered: {filteredMessages.length}, Grouped: {groupedMessages.length}</p>
          <details className="text-xs mt-4 text-left">
            <summary>Debug Info</summary>
            <pre className="bg-surface-hover p-2 rounded mt-2 overflow-auto max-h-40 text-xs">
              {JSON.stringify({
                messagesCount: messages.length,
                filteredCount: filteredMessages.length,
                groupedCount: groupedMessages.length,
                firstMessage: messages[0] ? { role: messages[0].role, text: messages[0].text?.substring(0, 50) } : null,
              }, null, 2)}
            </pre>
          </details>
        </div>
      ) : (
        <div className="flex flex-col gap-4">
          {groupedMessages.map((group, groupIdx) => {
            const isParent = group.children.length > 0;
            const parentDuplicate = isDuplicate(group.parent, groupIdx);

            return (
              <div key={group.parent.key || group.parent._id} className="message-group">
                {/* Render parent message (user or coordinator) */}
                {!parentDuplicate && (
                  <UIMessageBubble
                    message={group.parent}
                    onMermaidRetry={onMermaidRetry}
                    onRegenerateMessage={onRegenerateMessage && group.parent.key ? () => onRegenerateMessage(group.parent.key as string) : undefined}
                    onDeleteMessage={
                      onDeleteMessage && group.parent.id
                        ? () => onDeleteMessage(group.parent.id)
                        : undefined
                    }
                    onCompanySelect={onCompanySelect}
                    onPersonSelect={onPersonSelect}
                    onEventSelect={onEventSelect}
                    onNewsSelect={onNewsSelect}
                    onDocumentSelect={onDocumentSelect}
                    isParent={isParent}
                    agentRole={group.parent.metadata?.agentRole}
                    entityEnrichment={entityEnrichment}
                  />
                )}

                {/* Render child messages (specialized agents) with indentation */}
                {group.children.length > 0 && (
                  <div className="ml-8 border-l-2 border-purple-200 pl-4 space-y-3 mt-2">
                    {group.children.map((child) => {
                      const childDuplicate = isDuplicate(child, groupIdx);
                      if (childDuplicate) return null;

                      return (
                        <UIMessageBubble
                          key={child.key || child._id}
                          message={child}
                          onMermaidRetry={onMermaidRetry}
                          onRegenerateMessage={onRegenerateMessage && child.key ? () => onRegenerateMessage(child.key as string) : undefined}
                          onDeleteMessage={
                            onDeleteMessage && child.id
                              ? () => onDeleteMessage(child.id)
                              : undefined
                          }
                          onCompanySelect={onCompanySelect}
                          onPersonSelect={onPersonSelect}
                          onEventSelect={onEventSelect}
                          onNewsSelect={onNewsSelect}
                          onDocumentSelect={onDocumentSelect}
                          isChild={true}
                          agentRole={child.metadata?.agentRole}
                          entityEnrichment={entityEnrichment}
                        />
                      );
                    })}
                  </div>
                )}
              </div>
            );
          })}

          {/* Scroll anchor for smooth scroll anchoring */}
          <div ref={messagesEndRef} className="h-0" />

          {/* Show typing indicator ONLY when:
              1. Last message is user and no assistant response yet (agent intercepting)
              2. Last message is streaming assistant with NO parts/tools yet (truly empty)

              NOTE: If streaming message has ANY parts (tool calls, reasoning), render the message bubble
              so user can see agent progress in real-time
          */}
          {(() => {
            const lastMessage = filteredMessages[filteredMessages.length - 1];

            // Helper function to extract intent from user query
            const extractIntent = (query: string): string => {
              const lowerQuery = query.toLowerCase().trim();

              // Multi-entity research patterns
              if (lowerQuery.includes('compile information') || lowerQuery.includes('research')) {
                // Extract entities mentioned
                const entities: string[] = [];

                // Company/product patterns
                const companyMatch = lowerQuery.match(/(?:about|on|for)\s+([a-z0-9.-]+(?:\.[a-z]{2,})?)/i);
                if (companyMatch) entities.push(companyMatch[1]);

                // Person patterns
                const personMatch = lowerQuery.match(/([A-Z][a-z]+\s+[A-Z][a-z]+)(?:\s+(?:the\s+)?founder)?/);
                if (personMatch) entities.push(personMatch[1]);

                if (entities.length > 0) {
                  return `researching ${entities.join(', ')}`;
                }
                return 'compiling research';
              }

              // Search patterns
              if (lowerQuery.startsWith('search') || lowerQuery.startsWith('find')) {
                const searchMatch = lowerQuery.match(/(?:search|find)\s+(?:for\s+)?(?:me\s+)?(.+)/i);
                if (searchMatch) {
                  const subject = searchMatch[1].substring(0, 40);
                  return `searching for ${subject}`;
                }
                return 'searching';
              }

              // Image/video patterns
              if (lowerQuery.includes('images') || lowerQuery.includes('pictures') || lowerQuery.includes('photos')) {
                return 'finding images';
              }
              if (lowerQuery.includes('videos')) {
                return 'finding videos';
              }

              // Document patterns
              if (lowerQuery.includes('document') || lowerQuery.includes('file')) {
                return 'finding documents';
              }

              // SEC filing patterns
              if (lowerQuery.includes('10-k') || lowerQuery.includes('10-q') || lowerQuery.includes('sec filing')) {
                return 'finding SEC filings';
              }

              // News patterns
              if (lowerQuery.includes('news')) {
                return 'finding news';
              }

              // Default: use first few words
              const words = query.split(' ').slice(0, 5).join(' ');
              return words.length > 40 ? words.substring(0, 40) + '...' : words;
            };

            // Case 0: Queue processing
            if (isQueued) {
              const recentUserMessage = [...(filteredMessages as ExtendedUIMessage[])].reverse().find((msg: ExtendedUIMessage) => msg.role === 'user');
              const intent = recentUserMessage?.text ? extractIntent(recentUserMessage.text) : 'your request';
              return (
                <div className="flex flex-col gap-2">
                  <TypingIndicator message={`Waiting for available agent to handle ${intent}...`} />
                  <div className="text-xs text-content-muted pl-12 motion-safe:animate-pulse">
                    Position in queue: 1 (Estimated wait: &lt; 5s)
                  </div>
                </div>
              );
            }

            // Case 1: User message with no assistant response yet (agent intercepting)
            if (lastMessage && (lastMessage as ExtendedUIMessage).role === 'user') {
              const extLast = lastMessage as ExtendedUIMessage;
              // Check if there's an assistant message after this user message
              const hasResponse = (filteredMessages as ExtendedUIMessage[]).some((msg: ExtendedUIMessage, idx: number) => {
                const lastIdx = (filteredMessages as ExtendedUIMessage[]).findIndex((m: ExtendedUIMessage) =>
                  m.key === extLast.key || m._id === extLast._id
                );
                return idx > lastIdx && msg.role === 'assistant';
              });

              if (!hasResponse) {
                const intent = extLast.text ? extractIntent(extLast.text) : 'your request';
                return <TypingIndicator message={`Helping you with ${intent}...`} />;
              }
            }

            // Case 2: Streaming assistant message with NO parts/tools yet (truly empty)
            // If it has parts (tool calls, reasoning), the message bubble will show them
            const extLastMessage = lastMessage as ExtendedUIMessage | undefined;
            if (extLastMessage &&
              extLastMessage.role === 'assistant' &&
              extLastMessage.status === 'streaming' &&
              (!extLastMessage.text || extLastMessage.text.trim().length === 0) &&
              (!extLastMessage.parts || extLastMessage.parts.length === 0)) {

              // Find the most recent user message to extract intent
              const recentUserMessage = [...(filteredMessages as ExtendedUIMessage[])].reverse().find((msg: ExtendedUIMessage) => msg.role === 'user');
              const intent = recentUserMessage?.text ? extractIntent(recentUserMessage.text) : 'your request';

              return <TypingIndicator message={`Helping you with ${intent}...`} />;
            }

            return null;
          })()}

          <div ref={messagesEndRef} />
        </div>
      )}
    </div>
  );
}
