'use client';

import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { Button } from '@/components/ui/button';
import {
  ArrowDown, CheckCircle, CircleDashed, AlertTriangle, Info, File, ChevronRight
} from 'lucide-react';
import { addUserMessage, getMessages, startAgent, stopAgent, getAgentRuns, getProject, getThread, updateProject, Project, Message as BaseApiMessageType } from '@/lib/api';
import { toast } from 'sonner';
import { Skeleton } from "@/components/ui/skeleton";
import { ChatInput } from '@/components/thread/chat-input';
import { FileViewerModal } from '@/components/thread/file-viewer-modal';
import { SiteHeader } from "@/components/thread/thread-site-header"
import { ToolCallSidePanel, ToolCallInput } from "@/components/thread/tool-call-side-panel";
import { useSidebar } from "@/components/ui/sidebar";
import { useAgentStream } from '@/hooks/useAgentStream';
import { Markdown } from '@/components/ui/markdown';
import { cn } from "@/lib/utils";
import { useIsMobile } from "@/hooks/use-mobile";
import { BillingErrorAlert } from '@/components/billing/BillingErrorAlert';
import { SUBSCRIPTION_PLANS } from '@/components/billing/PlanComparison';
import { createClient } from '@/lib/supabase/client';

import { UnifiedMessage, ParsedContent, ParsedMetadata, ThreadParams } from '@/components/thread/types';
import { getToolIcon, extractPrimaryParam, safeJsonParse } from '@/components/thread/utils';

// Define the set of tags whose raw XML should be hidden during streaming
const HIDE_STREAMING_XML_TAGS = new Set([
  'execute-command',
  'create-file',
  'delete-file',
  'full-file-rewrite',
  'str-replace',
  'browser-click-element',
  'browser-close-tab',
  'browser-drag-drop',
  'browser-get-dropdown-options',
  'browser-go-back',
  'browser-input-text',
  'browser-navigate-to',
  'browser-scroll-down',
  'browser-scroll-to-text',
  'browser-scroll-up',
  'browser-select-dropdown-option',
  'browser-send-keys',
  'browser-switch-tab',
  'browser-wait',
  'deploy',
  'ask',
  'complete',
  'crawl-webpage',
  'web-search'
]);

// Extend the base Message type with the expected database fields
interface ApiMessageType extends BaseApiMessageType {
  message_id?: string;
  thread_id?: string;
  is_llm_message?: boolean;
  metadata?: string;
  created_at?: string;
  updated_at?: string;
}

// Add a simple interface for streaming tool calls
interface StreamingToolCall {
  id?: string;
  name?: string;
  arguments?: string;
  index?: number;
  xml_tag_name?: string;
}

// Render Markdown content while preserving XML tags that should be displayed as tool calls
function renderMarkdownContent(content: string, handleToolClick: (assistantMessageId: string | null, toolName: string) => void, messageId: string | null, fileViewerHandler?: (filePath?: string) => void) {
  const xmlRegex = /<(?!inform\b)([a-zA-Z\-_]+)(?:\s+[^>]*)?>(?:[\s\S]*?)<\/\1>|<(?!inform\b)([a-zA-Z\-_]+)(?:\s+[^>]*)?\/>/g;
  let lastIndex = 0;
  const contentParts: React.ReactNode[] = [];
  let match;

  // If no XML tags found, just return the full content as markdown
  if (!content.match(xmlRegex)) {
    return <Markdown className="text-sm prose prose-sm dark:prose-invert chat-markdown max-w-none break-words">{content}</Markdown>;
  }

  while ((match = xmlRegex.exec(content)) !== null) {
    // Add text before the tag as markdown
    if (match.index > lastIndex) {
      const textBeforeTag = content.substring(lastIndex, match.index);
      contentParts.push(
        <Markdown key={`md-${lastIndex}`} className="text-sm prose prose-sm dark:prose-invert chat-markdown max-w-none inline-block mr-1 break-words">{textBeforeTag}</Markdown>
      );
    }

    const rawXml = match[0];
    const toolName = match[1] || match[2];
    const IconComponent = getToolIcon(toolName);
    const paramDisplay = extractPrimaryParam(toolName, rawXml);
    const toolCallKey = `tool-${match.index}`;

    if (toolName === 'ask') {
      // Extract attachments from the XML attributes
      const attachmentsMatch = rawXml.match(/attachments=["']([^"']*)["']/i);
      const attachments = attachmentsMatch
        ? attachmentsMatch[1].split(',').map(a => a.trim())
        : [];

      // Extract content from the ask tag
      const contentMatch = rawXml.match(/<ask[^>]*>([\s\S]*?)<\/ask>/i);
      const askContent = contentMatch ? contentMatch[1] : '';

      // Render <ask> tag content with attachment UI (using the helper)
      contentParts.push(
        <div key={`ask-${match.index}`} className="space-y-3">
          <Markdown className="text-sm prose prose-sm dark:prose-invert chat-markdown max-w-none break-words [&>:first-child]:mt-0 prose-headings:mt-3">{askContent}</Markdown>
          {renderAttachments(attachments, fileViewerHandler)}
        </div>
      );
    } else {
      // Render tool button as a clickable element
      contentParts.push(
        <button
          key={toolCallKey}
          onClick={() => handleToolClick(messageId, toolName)}
          className="inline-flex items-center gap-1.5 py-1 px-2.5 my-1 text-xs text-muted-foreground bg-muted hover:bg-muted/80 rounded-md transition-colors cursor-pointer border border-border"
        >
          <IconComponent className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
          <span className="font-mono text-xs text-foreground">{toolName}</span>
          {paramDisplay && <span className="ml-1 text-muted-foreground truncate max-w-[200px]" title={paramDisplay}>{paramDisplay}</span>}
        </button>
      );
    }
    lastIndex = xmlRegex.lastIndex;
  }

  // Add text after the last tag
  if (lastIndex < content.length) {
    contentParts.push(
      <Markdown key={`md-${lastIndex}`} className="text-sm prose prose-sm dark:prose-invert chat-markdown max-w-none break-words">{content.substring(lastIndex)}</Markdown>
    );
  }

  return contentParts;
}

// Helper function to render attachments (now deduplicated)
function renderAttachments(attachments: string[], fileViewerHandler?: (filePath?: string) => void) {
  if (!attachments || attachments.length === 0) return null;

  // Deduplicate attachments using Set for simplicity
  const uniqueAttachments = [...new Set(attachments)];

  return (
    <div className="mt-6">
      <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
        {uniqueAttachments.map((attachment, idx) => {
          const extension = attachment.split('.').pop()?.toLowerCase();
          const filename = attachment.split('/').pop() || 'file';

          // Define file size (placeholder logic)
          const fileSize =
            extension === 'html' ? '52.68 KB' :
            attachment.includes('itinerary') ? '4.14 KB' :
            attachment.includes('proposal') ? '6.20 KB' :
            attachment.includes('todo') ? '1.89 KB' :
            attachment.includes('research') ? '3.75 KB' :
            `${(attachment.length % 5 + 1).toFixed(2)} KB`; // Use length for pseudo-stable size

          // Get file type display
          const fileType = extension === 'html' ? 'Code' : 'Text';

          return (
            <button
              key={`attachment-${idx}`}
              onClick={() => fileViewerHandler?.(attachment)}
              className="group flex items-center gap-3 p-4 rounded-md bg-muted/10 hover:bg-muted/20 transition-colors"
            >
              <div className="flex items-center justify-center">
                <File className="h-5 w-5 text-muted-foreground" />
              </div>
              <div className="flex-1 min-w-0 text-left">
                <div className="text-sm font-medium text-foreground truncate">
                  {filename}
                </div>
                <div className="text-xs text-muted-foreground flex items-center gap-1">
                  <span>{fileType}</span>
                  <span>·</span>
                  <span>{fileSize}</span>
                </div>
              </div>
            </button>
          );
        })}
      </div>
    </div>
  );
}

export default function ThreadPage({ params }: { params: Promise<ThreadParams> }) {
  const unwrappedParams = React.use(params);
  const threadId = unwrappedParams.threadId;
  const isMobile = useIsMobile();
  
  const router = useRouter();
  const [messages, setMessages] = useState<UnifiedMessage[]>([]);
  const [newMessage, setNewMessage] = useState('');
  const [isLoading, setIsLoading] = useState(true);
  const [isSending, setIsSending] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [agentRunId, setAgentRunId] = useState<string | null>(null);
  const [agentStatus, setAgentStatus] = useState<'idle' | 'running' | 'connecting' | 'error'>('idle');
  const [isSidePanelOpen, setIsSidePanelOpen] = useState(false);
  const [toolCalls, setToolCalls] = useState<ToolCallInput[]>([]);
  const [currentToolIndex, setCurrentToolIndex] = useState<number>(0);
  const [autoOpenedPanel, setAutoOpenedPanel] = useState(false);
  const [initialPanelOpenAttempted, setInitialPanelOpenAttempted] = useState(false);
  
  // Billing alert state
  const [showBillingAlert, setShowBillingAlert] = useState(false);
  const [billingData, setBillingData] = useState<{
    currentUsage?: number;
    limit?: number;
    message?: string;
    accountId?: string;
  }>({});

  const messagesEndRef = useRef<HTMLDivElement>(null);
  const messagesContainerRef = useRef<HTMLDivElement>(null);
  const latestMessageRef = useRef<HTMLDivElement>(null);
  const [showScrollButton, setShowScrollButton] = useState(false);
  const [userHasScrolled, setUserHasScrolled] = useState(false);
  const hasInitiallyScrolled = useRef<boolean>(false);

  const [project, setProject] = useState<Project | null>(null);
  const [sandboxId, setSandboxId] = useState<string | null>(null);
  const [fileViewerOpen, setFileViewerOpen] = useState(false);
  const [projectName, setProjectName] = useState<string>('');
  const [fileToView, setFileToView] = useState<string | null>(null);

  const initialLoadCompleted = useRef<boolean>(false);
  const messagesLoadedRef = useRef(false);
  const agentRunsCheckedRef = useRef(false);
  const previousAgentStatus = useRef<typeof agentStatus>('idle');
  const pollingIntervalRef = useRef<NodeJS.Timeout | null>(null); // POLLING FOR MESSAGES

  const handleProjectRenamed = useCallback((newName: string) => {
    setProjectName(newName);
  }, []);

  const { state: leftSidebarState, setOpen: setLeftSidebarOpen } = useSidebar();
  const initialLayoutAppliedRef = useRef(false);

  const userClosedPanelRef = useRef(false);

  // Replace both useEffect hooks with a single one that respects user closing
  useEffect(() => {
    if (initialLoadCompleted.current && !initialPanelOpenAttempted) {
      // Only attempt to open panel once on initial load
      setInitialPanelOpenAttempted(true);
      
      // Open the panel with tool calls if available
      if (toolCalls.length > 0) {
        setIsSidePanelOpen(true);
        setCurrentToolIndex(toolCalls.length - 1);
      } else {
        // Only if there are messages but no tool calls yet
        if (messages.length > 0) {
          setIsSidePanelOpen(true);
        }
      }
    }
  }, [initialPanelOpenAttempted, messages, toolCalls]);

  const toggleSidePanel = useCallback(() => {
    setIsSidePanelOpen(prevIsOpen => {
      const newState = !prevIsOpen;
      if (!newState) {
        userClosedPanelRef.current = true;
      }
      if (newState) {
        // Close left sidebar when opening side panel
        setLeftSidebarOpen(false);
      }
      return newState;
    });
  }, [setLeftSidebarOpen]);

  const handleSidePanelNavigate = useCallback((newIndex: number) => {
    setCurrentToolIndex(newIndex);
  }, []);

  useEffect(() => {
    if (!initialLayoutAppliedRef.current) {
      setLeftSidebarOpen(false);
      initialLayoutAppliedRef.current = true;
    }
  }, [setLeftSidebarOpen]);

  // Update keyboard shortcut handlers to manage both panels
  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      // CMD+I for ToolCall SidePanel
      if ((event.metaKey || event.ctrlKey) && event.key === 'i') {
        event.preventDefault();
        // If side panel is already open, just close it
        if (isSidePanelOpen) {
          setIsSidePanelOpen(false);
          userClosedPanelRef.current = true;
        } else {
          // Open side panel and ensure left sidebar is closed
          setIsSidePanelOpen(true);
          setLeftSidebarOpen(false);
        }
      }
      
      // CMD+B for Left Sidebar
      if ((event.metaKey || event.ctrlKey) && event.key === 'b') {
        event.preventDefault();
        // If left sidebar is expanded, collapse it
        if (leftSidebarState === 'expanded') {
          setLeftSidebarOpen(false);
        } else {
          // Otherwise expand the left sidebar and close the side panel
          setLeftSidebarOpen(true);
          if (isSidePanelOpen) {
            setIsSidePanelOpen(false);
            userClosedPanelRef.current = true;
          }
        }
      }
    };
    
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [toggleSidePanel, isSidePanelOpen, leftSidebarState, setLeftSidebarOpen]);

  const handleNewMessageFromStream = useCallback((message: UnifiedMessage) => {
    // Log the ID of the message received from the stream
    console.log(`[STREAM HANDLER] Received message: ID=${message.message_id}, Type=${message.type}`);
    if (!message.message_id) {
        console.warn(`[STREAM HANDLER] Received message is missing ID: Type=${message.type}, Content=${message.content?.substring(0, 50)}...`);
    }
    
    setMessages(prev => {
      const messageExists = prev.some(m => m.message_id === message.message_id);
      if (messageExists) {
        return prev.map(m => m.message_id === message.message_id ? message : m);
      } else {
        return [...prev, message];
      }
    });

    // If we received a tool message, refresh the tool panel
    if (message.type === 'tool') {
      setAutoOpenedPanel(false);
    }
  }, []);

  const handleStreamStatusChange = useCallback((hookStatus: string) => {
    console.log(`[PAGE] Hook status changed: ${hookStatus}`);
    switch(hookStatus) {
      case 'idle':
      case 'completed':
      case 'stopped':
      case 'agent_not_running':
      case 'error':
      case 'failed':
        setAgentStatus('idle');
        setAgentRunId(null);
        // Reset auto-opened state when agent completes to trigger tool detection
        setAutoOpenedPanel(false);
        
        // After terminal states, we should scroll to bottom to show latest messages
        // The hook will already have refetched messages by this point
        if (['completed', 'stopped', 'agent_not_running', 'error', 'failed'].includes(hookStatus)) {
          scrollToBottom('smooth');
        }
        break;
      case 'connecting':
        setAgentStatus('connecting');
        break;
      case 'streaming':
        setAgentStatus('running');
        break;
    }
  }, []);

  const handleStreamError = useCallback((errorMessage: string) => {
    console.error(`[PAGE] Stream hook error: ${errorMessage}`);
    if (!errorMessage.toLowerCase().includes('not found') && 
        !errorMessage.toLowerCase().includes('agent run is not running')) {
        toast.error(`Stream Error: ${errorMessage}`);
    }
  }, []);
  
  const handleStreamClose = useCallback(() => {
      console.log(`[PAGE] Stream hook closed with final status: ${agentStatus}`);
  }, [agentStatus]);

  const {
    status: streamHookStatus,
    textContent: streamingTextContent,
    toolCall: streamingToolCall,
    error: streamError,
    agentRunId: currentHookRunId,
    startStreaming,
    stopStreaming,
  } = useAgentStream({
    onMessage: handleNewMessageFromStream,
    onStatusChange: handleStreamStatusChange,
    onError: handleStreamError,
    onClose: handleStreamClose,
  }, threadId, setMessages);

  useEffect(() => {
    if (agentRunId && agentRunId !== currentHookRunId) {
      console.log(`[PAGE] Target agentRunId set to ${agentRunId}, initiating stream...`);
      startStreaming(agentRunId);
    }
  }, [agentRunId, startStreaming, currentHookRunId]);

  useEffect(() => {
    let isMounted = true;

    async function loadData() {
      if (!initialLoadCompleted.current) setIsLoading(true);
      setError(null);
      
      try {
        if (!threadId) throw new Error('Thread ID is required');

        const threadData = await getThread(threadId).catch(err => { 
          throw new Error('Failed to load thread data: ' + err.message); 
        });
        
        if (!isMounted) return;
        
        console.log('[PAGE] Thread data loaded:', threadData);
        
        if (threadData?.project_id) {
          console.log('[PAGE] Getting project data for project_id:', threadData.project_id);
          const projectData = await getProject(threadData.project_id);
          if (isMounted && projectData) {
            console.log('[PAGE] Project data loaded:', projectData);
            console.log('[PAGE] Project ID:', projectData.id);
            console.log('[PAGE] Project sandbox data:', projectData.sandbox);
            
            // Set project data
            setProject(projectData);
            
            // Make sure sandbox ID is set correctly
            if (typeof projectData.sandbox === 'string') {
              setSandboxId(projectData.sandbox);
            } else if (projectData.sandbox?.id) {
              setSandboxId(projectData.sandbox.id);
            }
            
            setProjectName(projectData.name || '');
          }
        }

        if (!messagesLoadedRef.current) {
          const messagesData = await getMessages(threadId);
          if (isMounted) {
            // Log raw messages fetched from API
            console.log('[PAGE] Raw messages fetched:', messagesData);
            
            // Map API message type to UnifiedMessage type
            const unifiedMessages = (messagesData || [])
              .filter(msg => msg.type !== 'status')
              .map((msg: ApiMessageType, index: number) => {
                console.log(`[MAP ${index}] Processing raw message:`, msg);
                const messageId = msg.message_id;
                console.log(`[MAP ${index}] Accessed msg.message_id:`, messageId);
                if (!messageId && msg.type !== 'status') { 
                  console.warn(`[MAP ${index}] Non-status message fetched from API is missing ID: Type=${msg.type}`);
                }
                const threadIdMapped = msg.thread_id || threadId;
                console.log(`[MAP ${index}] Accessed msg.thread_id (using fallback):`, threadIdMapped);
                const typeMapped = (msg.type || 'system') as UnifiedMessage['type'];
                console.log(`[MAP ${index}] Accessed msg.type (using fallback):`, typeMapped);
                const isLlmMessageMapped = Boolean(msg.is_llm_message);
                console.log(`[MAP ${index}] Accessed msg.is_llm_message:`, isLlmMessageMapped);
                const contentMapped = msg.content || '';
                console.log(`[MAP ${index}] Accessed msg.content (using fallback):`, contentMapped.substring(0, 50) + '...');
                const metadataMapped = msg.metadata || '{}';
                console.log(`[MAP ${index}] Accessed msg.metadata (using fallback):`, metadataMapped);
                const createdAtMapped = msg.created_at || new Date().toISOString();
                console.log(`[MAP ${index}] Accessed msg.created_at (using fallback):`, createdAtMapped);
                const updatedAtMapped = msg.updated_at || new Date().toISOString();
                console.log(`[MAP ${index}] Accessed msg.updated_at (using fallback):`, updatedAtMapped);

                return {
                  message_id: messageId || null, 
                  thread_id: threadIdMapped,
                  type: typeMapped, 
                  is_llm_message: isLlmMessageMapped,
                  content: contentMapped,
                  metadata: metadataMapped,
                  created_at: createdAtMapped,
                  updated_at: updatedAtMapped
                };
            });
            
            setMessages(unifiedMessages); // Set the filtered and mapped messages
            console.log('[PAGE] Loaded Messages (excluding status, keeping browser_state):', unifiedMessages.length)
            
            // Debug loaded messages
            const assistantMessages = unifiedMessages.filter(m => m.type === 'assistant');
            const toolMessages = unifiedMessages.filter(m => m.type === 'tool');
            
            console.log('[PAGE] Assistant messages:', assistantMessages.length);
            console.log('[PAGE] Tool messages:', toolMessages.length);
            
            // Check if tool messages have associated assistant messages
            toolMessages.forEach(toolMsg => {
              try {
                const metadata = JSON.parse(toolMsg.metadata);
                if (metadata.assistant_message_id) {
                  const hasAssociated = assistantMessages.some(
                    assMsg => assMsg.message_id === metadata.assistant_message_id
                  );
                  console.log(`[PAGE] Tool message ${toolMsg.message_id} references assistant ${metadata.assistant_message_id} - found: ${hasAssociated}`);
                }
              } catch (e) {
                console.error("Error parsing tool message metadata:", e);
              }
            });
            
            messagesLoadedRef.current = true;
            if (!hasInitiallyScrolled.current) {
              scrollToBottom('auto');
              hasInitiallyScrolled.current = true;
            }
          }
        }

        if (!agentRunsCheckedRef.current && isMounted) {
          try {
            console.log('[PAGE] Checking for active agent runs...');
            const agentRuns = await getAgentRuns(threadId);
            agentRunsCheckedRef.current = true;

            const activeRun = agentRuns.find(run => run.status === 'running');
            if (activeRun && isMounted) {
              console.log('[PAGE] Found active run on load:', activeRun.id);
              setAgentRunId(activeRun.id);
            } else {
              console.log('[PAGE] No active agent runs found');
              if (isMounted) setAgentStatus('idle');
            }
          } catch (err) {
            console.error('[PAGE] Error checking for active runs:', err);
            agentRunsCheckedRef.current = true;
            if (isMounted) setAgentStatus('idle');
          }
        }
          
        initialLoadCompleted.current = true;

      } catch (err) {
        console.error('Error loading thread data:', err);
        if (isMounted) {
          const errorMessage = err instanceof Error ? err.message : 'Failed to load thread';
          setError(errorMessage);
          toast.error(errorMessage);
        }
      } finally {
        if (isMounted) setIsLoading(false);
      }
    }
    
    loadData();

    return () => {
      isMounted = false;
    };
  }, [threadId]);

  const handleSubmitMessage = useCallback(async (message: string, options?: { model_name?: string; enable_thinking?: boolean }) => {
    if (!message.trim()) return;
    setIsSending(true);

    const optimisticUserMessage: UnifiedMessage = {
      message_id: `temp-${Date.now()}`,
      thread_id: threadId,
      type: 'user',
      is_llm_message: false,
      content: message,
      metadata: '{}',
      created_at: new Date().toISOString(),
      updated_at: new Date().toISOString(),
    };

    setMessages(prev => [...prev, optimisticUserMessage]);
    setNewMessage('');
    scrollToBottom('smooth');

    try {
      const results = await Promise.allSettled([
        addUserMessage(threadId, message),
        startAgent(threadId, options)
      ]);

      if (results[0].status === 'rejected') {
        console.error("Failed to send message:", results[0].reason);
        throw new Error(`Failed to send message: ${results[0].reason?.message || results[0].reason}`);
      }

      if (results[1].status === 'rejected') {
        console.error("Failed to start agent:", results[1].reason);
        throw new Error(`Failed to start agent: ${results[1].reason?.message || results[1].reason}`);
      }

      const agentResult = results[1].value;
      setAgentRunId(agentResult.agent_run_id);

    } catch (err) {
      console.error('Error sending message or starting agent:', err);
      toast.error(err instanceof Error ? err.message : 'Operation failed');
      setMessages(prev => prev.filter(m => m.message_id !== optimisticUserMessage.message_id));
    } finally {
      setIsSending(false);
    }
  }, [threadId]);

  const handleStopAgent = useCallback(async () => {
    console.log(`[PAGE] Requesting agent stop via hook.`);
    setAgentStatus('idle');
    
    // First stop the streaming and let the hook handle refetching
    await stopStreaming();
    
    // We don't need to refetch messages here since the hook will do that
    // The centralizing of refetching in the hook simplifies this logic
  }, [stopStreaming]);

  const handleScroll = () => {
    if (!messagesContainerRef.current) return;
    const { scrollTop, scrollHeight, clientHeight } = messagesContainerRef.current;
    const isScrolledUp = scrollHeight - scrollTop - clientHeight > 100;
    setShowScrollButton(isScrolledUp);
    setUserHasScrolled(isScrolledUp);
  };

  const scrollToBottom = (behavior: ScrollBehavior = 'smooth') => {
    messagesEndRef.current?.scrollIntoView({ behavior });
  };

  useEffect(() => {
    const lastMsg = messages[messages.length - 1];
    const isNewUserMessage = lastMsg?.type === 'user';
    if ((isNewUserMessage || agentStatus === 'running') && !userHasScrolled) {
      scrollToBottom('smooth');
    }
  }, [messages, agentStatus, userHasScrolled, scrollToBottom]);

  useEffect(() => {
    if (!latestMessageRef.current || messages.length === 0) return;
    const observer = new IntersectionObserver(
      ([entry]) => setShowScrollButton(!entry?.isIntersecting),
      { root: messagesContainerRef.current, threshold: 0.1 }
    );
    observer.observe(latestMessageRef.current);
    return () => observer.disconnect();
  }, [messages, streamingTextContent, streamingToolCall, setShowScrollButton]);

  const handleScrollButtonClick = () => {
    scrollToBottom('smooth');
    setUserHasScrolled(false);
  };

  useEffect(() => {
    console.log(`[PAGE] 🔄 Page AgentStatus: ${agentStatus}, Hook Status: ${streamHookStatus}, Target RunID: ${agentRunId || 'none'}, Hook RunID: ${currentHookRunId || 'none'}`);
    
    // If the stream hook reports completion/stopping but our UI hasn't updated
    if ((streamHookStatus === 'completed' || streamHookStatus === 'stopped' || 
         streamHookStatus === 'agent_not_running' || streamHookStatus === 'error') && 
        (agentStatus === 'running' || agentStatus === 'connecting')) {
      console.log('[PAGE] Detected hook completed but UI still shows running, updating status');
      setAgentStatus('idle');
      setAgentRunId(null);
      setAutoOpenedPanel(false);
    }
  }, [agentStatus, streamHookStatus, agentRunId, currentHookRunId]);

  const handleOpenFileViewer = useCallback((filePath?: string) => {
    if (filePath) {
      setFileToView(filePath);
    } else {
      setFileToView(null);
    }
    setFileViewerOpen(true);
  }, []);

  // Automatically detect and populate tool calls from messages
  useEffect(() => {
    // Calculate historical tool pairs regardless of panel state
    const historicalToolPairs: ToolCallInput[] = [];
    const assistantMessages = messages.filter(m => m.type === 'assistant' && m.message_id);
    
    assistantMessages.forEach(assistantMsg => {
      const resultMessage = messages.find(toolMsg => {
        if (toolMsg.type !== 'tool' || !toolMsg.metadata || !assistantMsg.message_id) return false;
        try {
          const metadata = JSON.parse(toolMsg.metadata);
          return metadata.assistant_message_id === assistantMsg.message_id;
        } catch (e) {
          return false;
        }
      });

      if (resultMessage) {
        // Determine tool name from assistant message content
        let toolName = 'unknown';
        try {
          // Try to extract tool name from content
          const xmlMatch = assistantMsg.content.match(/<([a-zA-Z\-_]+)(?:\s+[^>]*)?>|<([a-zA-Z\-_]+)(?:\s+[^>]*)?\/>/);
          if (xmlMatch) {
            toolName = xmlMatch[1] || xmlMatch[2] || 'unknown';
          } else {
            // Fallback to checking for tool_calls JSON structure
            const assistantContentParsed = safeJsonParse<{ tool_calls?: { name: string }[] }>(assistantMsg.content, {});
            if (assistantContentParsed.tool_calls && assistantContentParsed.tool_calls.length > 0) {
              toolName = assistantContentParsed.tool_calls[0].name || 'unknown';
            }
          }
        } catch {}

        // Skip adding <ask> tags to the tool calls
        if (toolName === 'ask' || toolName === 'complete') {
          return;
        }

        let isSuccess = true;
        try {
          const toolContent = resultMessage.content?.toLowerCase() || '';
          isSuccess = !(toolContent.includes('failed') || 
                         toolContent.includes('error') || 
                         toolContent.includes('failure'));
        } catch {}

        historicalToolPairs.push({
          assistantCall: {
            name: toolName,
            content: assistantMsg.content,
            timestamp: assistantMsg.created_at
          },
          toolResult: {
            content: resultMessage.content,
            isSuccess: isSuccess,
            timestamp: resultMessage.created_at
          }
        });
      }
    });

    // Always update the toolCalls state
    setToolCalls(historicalToolPairs);
    
    // Logic to open/update the panel index
    if (historicalToolPairs.length > 0) {
      // If the panel is open (or was just auto-opened) and the user didn't close it
      if (isSidePanelOpen && !userClosedPanelRef.current) {
          // Always jump to the latest tool call index
          setCurrentToolIndex(historicalToolPairs.length - 1);
      } else if (!isSidePanelOpen && !autoOpenedPanel && !userClosedPanelRef.current) {
          // Auto-open the panel only the first time tools are detected
          setCurrentToolIndex(historicalToolPairs.length - 1);
          setIsSidePanelOpen(true);
          setAutoOpenedPanel(true); 
      }
    }
  }, [messages, isSidePanelOpen, autoOpenedPanel]); // Rerun when messages or panel state changes

  // Reset auto-opened state when panel is closed
  useEffect(() => {
    if (!isSidePanelOpen) {
      setAutoOpenedPanel(false);
    }
  }, [isSidePanelOpen]);

  // Process the assistant call data
  const toolViewAssistant = useCallback((assistantContent?: string, toolContent?: string) => {
    // This needs to stay simple as it's meant for the side panel tool call view
    if (!assistantContent) return null;
    
    return (
      <div className="space-y-1">
        <div className="text-xs font-medium text-muted-foreground">Assistant Message</div>
        <div className="rounded-md border bg-muted/50 p-3">
          <Markdown className="text-xs prose prose-xs dark:prose-invert chat-markdown max-w-none">{assistantContent}</Markdown>
        </div>
      </div>
    );
  }, []);

  // Process the tool result data
  const toolViewResult = useCallback((toolContent?: string, isSuccess?: boolean) => {
    if (!toolContent) return null;
    
    return (
      <div className="space-y-1">
        <div className="flex justify-between items-center">
          <div className="text-xs font-medium text-muted-foreground">Tool Result</div>
          <div className={`px-2 py-0.5 rounded-full text-xs ${
            isSuccess 
              ? 'bg-green-50 text-green-700 dark:bg-green-900 dark:text-green-300' 
              : 'bg-red-50 text-red-700 dark:bg-red-900 dark:text-red-300'
          }`}>
            {isSuccess ? 'Success' : 'Failed'}
          </div>
        </div>
        <div className="rounded-md border bg-muted/50 p-3">
          <Markdown className="text-xs prose prose-xs dark:prose-invert chat-markdown max-w-none">{toolContent}</Markdown>
        </div>
      </div>
    );
  }, []);

  // Update handleToolClick to respect user closing preference and navigate correctly
  const handleToolClick = useCallback((clickedAssistantMessageId: string | null, clickedToolName: string) => {
    // Explicitly ignore ask tags from opening the side panel
    if (clickedToolName === 'ask') {
      return;
    }
    
    if (!clickedAssistantMessageId) {
      console.warn("Clicked assistant message ID is null. Cannot open side panel.");
      toast.warning("Cannot view details: Assistant message ID is missing.");
      return;
    }

    // Reset user closed state when explicitly clicking a tool
    userClosedPanelRef.current = false;

    console.log("[PAGE] Tool Click Triggered. Assistant Message ID:", clickedAssistantMessageId, "Tool Name:", clickedToolName);

    // Find the index of the tool call associated with the clicked assistant message
    const toolIndex = toolCalls.findIndex(tc => {
      // Check if the assistant message ID matches the one stored in the tool result's metadata
      if (!tc.toolResult?.content || tc.toolResult.content === "STREAMING") return false; // Skip streaming or incomplete calls

      // Directly compare assistant message IDs if available in the structure
      // Find the original assistant message based on the ID
      const assistantMessage = messages.find(m => m.message_id === clickedAssistantMessageId && m.type === 'assistant');
      if (!assistantMessage) return false;

      // Find the corresponding tool message using metadata
      const toolMessage = messages.find(m => {
        if (m.type !== 'tool' || !m.metadata) return false;
        try {
          const metadata = safeJsonParse<ParsedMetadata>(m.metadata, {});
          return metadata.assistant_message_id === assistantMessage.message_id;
        } catch {
          return false;
        }
      });
      
      // Check if the current toolCall 'tc' corresponds to this assistant/tool message pair
      return tc.assistantCall?.content === assistantMessage.content &&
             tc.toolResult?.content === toolMessage?.content;
    });


    if (toolIndex !== -1) {
      console.log(`[PAGE] Found tool call at index ${toolIndex} for assistant message ${clickedAssistantMessageId}`);
      setCurrentToolIndex(toolIndex);
      setIsSidePanelOpen(true); // Explicitly open the panel
    } else {
      console.warn(`[PAGE] Could not find matching tool call in toolCalls array for assistant message ID: ${clickedAssistantMessageId}`);
      toast.info("Could not find details for this tool call.");
      // Optionally, still open the panel but maybe at the last index or show a message?
      // setIsSidePanelOpen(true);
    }
  }, [messages, toolCalls]); // Add toolCalls as a dependency

  // Handle streaming tool calls
  const handleStreamingToolCall = useCallback((toolCall: StreamingToolCall | null) => {
    if (!toolCall) return;
    
    const toolName = toolCall.name || toolCall.xml_tag_name || 'Unknown Tool';
    
    // Skip <ask> tags from showing in the side panel during streaming
    if (toolName === 'ask' || toolName === 'complete') {
      return;
    }
    
    console.log("[STREAM] Received tool call:", toolName);
    
    // If user explicitly closed the panel, don't reopen it for streaming calls
    if (userClosedPanelRef.current) return;
    
    // Create a properly formatted tool call input for the streaming tool
    // that matches the format of historical tool calls
    const toolArguments = toolCall.arguments || '';
    
    // Format the arguments in a way that matches the expected XML format for each tool
    // This ensures the specialized tool views render correctly
    let formattedContent = toolArguments;
    if (toolName.toLowerCase().includes('command') && !toolArguments.includes('<execute-command>')) {
      formattedContent = `<execute-command>${toolArguments}</execute-command>`;
    } else if (toolName.toLowerCase().includes('file') && !toolArguments.includes('<create-file>')) {
      // For file operations, wrap with appropriate tag if not already wrapped
      const fileOpTags = ['create-file', 'delete-file', 'full-file-rewrite'];
      const matchingTag = fileOpTags.find(tag => toolName.toLowerCase().includes(tag));
      if (matchingTag && !toolArguments.includes(`<${matchingTag}>`)) {
        formattedContent = `<${matchingTag}>${toolArguments}</${matchingTag}>`;
      }
    }
    
    const newToolCall: ToolCallInput = {
      assistantCall: {
        name: toolName,
        content: formattedContent,
        timestamp: new Date().toISOString()
      },
      // For streaming tool calls, provide empty content that indicates streaming
      toolResult: {
        content: "STREAMING",
        isSuccess: true, 
        timestamp: new Date().toISOString()
      }
    };
    
    // Update the tool calls state to reflect the streaming tool
    setToolCalls(prev => {
      // If the same tool is already being streamed, update it instead of adding a new one
      if (prev.length > 0 && prev[0].assistantCall.name === toolName) {
        return [{
          ...prev[0],
          assistantCall: {
            ...prev[0].assistantCall,
            content: formattedContent
          }
        }];
      }
      return [newToolCall];
    });
    
    setCurrentToolIndex(0);
    setIsSidePanelOpen(true);
  }, []);

  // SEO title update
  useEffect(() => {
    if (projectName) {
      // Update document title when project name changes
      document.title = `${projectName} | Kortix Suna`;
      
      // Update meta tags for SEO
      const metaDescription = document.querySelector('meta[name="description"]');
      if (metaDescription) {
        metaDescription.setAttribute('content', `${projectName} - Interactive agent conversation powered by Kortix Suna`);
      }
      
      // Update OpenGraph tags if they exist
      const ogTitle = document.querySelector('meta[property="og:title"]');
      if (ogTitle) {
        ogTitle.setAttribute('content', `${projectName} | Kortix Suna`);
      }
      
      const ogDescription = document.querySelector('meta[property="og:description"]');
      if (ogDescription) {
        ogDescription.setAttribute('content', `Interactive AI conversation for ${projectName}`);
      }
    }
  }, [projectName]);

  // POLLING FOR MESSAGES
  // Set up polling for messages
  useEffect(() => {
    // Function to fetch messages
    const fetchMessages = async () => {
      if (!threadId) return;
      
      try {
        console.log('[POLLING] Refetching messages...');
        const messagesData = await getMessages(threadId);
        
        if (messagesData) {
          console.log(`[POLLING] Refetch completed with ${messagesData.length} messages`);
          // Map API message type to UnifiedMessage type
          const unifiedMessages = (messagesData || [])
            .filter(msg => msg.type !== 'status') 
            .map((msg: ApiMessageType) => ({
              message_id: msg.message_id || null, 
              thread_id: msg.thread_id || threadId,
              type: (msg.type || 'system') as UnifiedMessage['type'], 
              is_llm_message: Boolean(msg.is_llm_message),
              content: msg.content || '',
              metadata: msg.metadata || '{}',
              created_at: msg.created_at || new Date().toISOString(),
              updated_at: msg.updated_at || new Date().toISOString()
            }));
          
          setMessages(unifiedMessages);
          
          // Only auto-scroll if not manually scrolled up
          if (!userHasScrolled) {
            scrollToBottom('smooth');
          }
        }
      } catch (error) {
        console.error('[POLLING] Error fetching messages:', error);
      }
    };

    // Start polling once initial load is complete
    if (initialLoadCompleted.current && !pollingIntervalRef.current) {
      // Initial fetch
      fetchMessages();
      
      // Set up interval (every 2 seconds)
      pollingIntervalRef.current = setInterval(fetchMessages, 2000);
    }

    // Clean up interval when component unmounts
    return () => {
      if (pollingIntervalRef.current) {
        clearInterval(pollingIntervalRef.current);
        pollingIntervalRef.current = null;
      }
    };
  }, [threadId, userHasScrolled, initialLoadCompleted]);
  // POLLING FOR MESSAGES

  // Add another useEffect to ensure messages are refreshed when agent status changes to idle
  useEffect(() => {
    if (agentStatus === 'idle' && streamHookStatus !== 'streaming' && streamHookStatus !== 'connecting') {
      console.log('[PAGE] Agent status changed to idle, ensuring messages are up to date');
      // Only do this if we're not in the initial loading state
      if (!isLoading && initialLoadCompleted.current) {
        // Double-check messages after a short delay to ensure we have latest content
        const timer = setTimeout(() => {
          getMessages(threadId).then(messagesData => {
            if (messagesData) {
              console.log(`[PAGE] Backup refetch completed with ${messagesData.length} messages`);
              // Map API message type to UnifiedMessage type
              const unifiedMessages = (messagesData || [])
                .filter(msg => msg.type !== 'status') 
                .map((msg: ApiMessageType) => ({
                  message_id: msg.message_id || null, 
                  thread_id: msg.thread_id || threadId,
                  type: (msg.type || 'system') as UnifiedMessage['type'], 
                  is_llm_message: Boolean(msg.is_llm_message),
                  content: msg.content || '',
                  metadata: msg.metadata || '{}',
                  created_at: msg.created_at || new Date().toISOString(),
                  updated_at: msg.updated_at || new Date().toISOString()
                }));
              
              setMessages(unifiedMessages);
              // Reset auto-opened panel to allow tool detection with fresh messages
              setAutoOpenedPanel(false);
              scrollToBottom('smooth');
            }
          }).catch(err => {
            console.error('Error in backup message refetch:', err);
          });
        }, 1000);
        
        return () => clearTimeout(timer);
      }
    }
  }, [agentStatus, threadId, isLoading, streamHookStatus]);

  // Check billing status when agent completes
  const checkBillingStatus = useCallback(async () => {
    if (!project?.account_id) return;
    
    const supabase = createClient();
    
    try {
      // Check subscription status
      const { data: subscriptionData } = await supabase
        .schema('basejump')
        .from('billing_subscriptions')
        .select('price_id')
        .eq('account_id', project.account_id)
        .eq('status', 'active')
        .single();
      
      const currentPlanId = subscriptionData?.price_id || SUBSCRIPTION_PLANS.FREE;
      
      // Only check usage limits for free tier users
      if (currentPlanId === SUBSCRIPTION_PLANS.FREE) {
        // Calculate usage
        const startOfMonth = new Date();
        startOfMonth.setDate(1);
        startOfMonth.setHours(0, 0, 0, 0);
        
        // Get threads for this account
        const { data: threadsData } = await supabase
          .from('threads')
          .select('thread_id')
          .eq('account_id', project.account_id);
        
        const threadIds = threadsData?.map(t => t.thread_id) || [];
        
        // Get agent runs for those threads
        const { data: agentRunData } = await supabase
          .from('agent_runs')
          .select('started_at, completed_at')
          .in('thread_id', threadIds)
          .gte('started_at', startOfMonth.toISOString());
        
        let totalSeconds = 0;
        if (agentRunData) {
          totalSeconds = agentRunData.reduce((acc, run) => {
            const start = new Date(run.started_at);
            const end = run.completed_at ? new Date(run.completed_at) : new Date();
            const seconds = (end.getTime() - start.getTime()) / 1000;
            return acc + seconds;
          }, 0);
        }
        
        // Convert to hours for display
        const hours = totalSeconds / 3600;
        const minutesUsed = totalSeconds / 60;
        
        // The free plan has a 10 minute limit as defined in backend/utils/billing.py
        const FREE_PLAN_LIMIT_MINUTES = 10;
        const FREE_PLAN_LIMIT_HOURS = FREE_PLAN_LIMIT_MINUTES / 60;
        
        // Show alert if over limit
        if (minutesUsed > FREE_PLAN_LIMIT_MINUTES) {
          console.log("Usage limit exceeded:", {
            minutesUsed,
            hoursUsed: hours,
            limit: FREE_PLAN_LIMIT_MINUTES
          });
          setBillingData({
            currentUsage: Number(hours.toFixed(2)),
            limit: FREE_PLAN_LIMIT_HOURS,
            message: `You've used ${Math.floor(minutesUsed)} minutes on the Free plan. The limit is ${FREE_PLAN_LIMIT_MINUTES} minutes per month.`,
            accountId: project.account_id
          });
          setShowBillingAlert(true);
          return true; // Return true if over limit
        }
      }
      return false; // Return false if not over limit
    } catch (err) {
      console.error('Error checking billing status:', err);
      return false;
    }
  }, [project?.account_id]);

  // Update useEffect to check billing when agent completes
  useEffect(() => {
    const previousStatus = previousAgentStatus.current;
    
    // Check if agent just completed (status changed from running to idle)
    if (previousStatus === 'running' && agentStatus === 'idle') {
      checkBillingStatus();
    }
    
    // Store current status for next comparison
    previousAgentStatus.current = agentStatus;
  }, [agentStatus, checkBillingStatus]);

  // Add new useEffect to check billing limits when page first loads or project changes
  useEffect(() => {
    if (project?.account_id && initialLoadCompleted.current) {
      console.log("Checking billing status on page load");
      checkBillingStatus();
    }
  }, [project?.account_id, checkBillingStatus, initialLoadCompleted]);
  
  // Also check after messages are loaded to ensure we have the complete state
  useEffect(() => {
    if (messagesLoadedRef.current && project?.account_id && !isLoading) {
      console.log("Checking billing status after messages loaded");
      checkBillingStatus();
    }
  }, [messagesLoadedRef.current, checkBillingStatus, project?.account_id, isLoading]);

  if (isLoading && !initialLoadCompleted.current) {
    return (
      <div className="flex h-screen">
        <div className={`flex flex-col flex-1 overflow-hidden transition-all duration-200 ease-in-out ${isSidePanelOpen ? 'mr-[90%] sm:mr-[450px] md:mr-[500px] lg:mr-[550px] xl:mr-[650px]' : ''}`}>
          {/* Skeleton Header */}
          <div className="border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
            <div className="flex h-14 items-center gap-4 px-4">
              <div className="flex-1">
                <div className="flex items-center gap-2">
                  <Skeleton className="h-6 w-6 rounded-full" />
                  <Skeleton className="h-5 w-40" />
                </div>
              </div>
              <div className="flex items-center gap-2">
                <Skeleton className="h-8 w-8 rounded-full" />
                <Skeleton className="h-8 w-8 rounded-full" />
              </div>
            </div>
          </div>
          
          {/* Skeleton Chat Messages */}
          <div className="flex-1 overflow-y-auto px-6 py-4 pb-[5.5rem]">
            <div className="mx-auto max-w-3xl space-y-6">
              {/* User message */}
              <div className="flex justify-end">
                <div className="max-w-[85%] rounded-lg bg-primary/10 px-4 py-3">
                  <div className="space-y-2">
                    <Skeleton className="h-4 w-48" />
                    <Skeleton className="h-4 w-32" />
                  </div>
                </div>
              </div>
              
              {/* Assistant response with tool usage */}
              <div>
                <div className="flex items-start gap-3">
                  <Skeleton className="flex-shrink-0 w-5 h-5 mt-2 rounded-full" />
                  <div className="flex-1 space-y-2">
                    <div className="max-w-[90%] w-full rounded-lg bg-muted px-4 py-3">
                      <div className="space-y-3">
                        <div>
                          <Skeleton className="h-4 w-full max-w-[360px] mb-2" />
                          <Skeleton className="h-4 w-full max-w-[320px] mb-2" />
                          <Skeleton className="h-4 w-full max-w-[290px]" />
                        </div>
                        
                        {/* Tool call button skeleton */}
                        <div className="py-1">
                          <Skeleton className="h-6 w-32 rounded-md" />
                        </div>
                        
                        <div>
                          <Skeleton className="h-4 w-full max-w-[340px] mb-2" />
                          <Skeleton className="h-4 w-full max-w-[280px]" />
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              
              {/* User message */}
              <div className="flex justify-end">
                <div className="max-w-[85%] rounded-lg bg-primary/10 px-4 py-3">
                  <Skeleton className="h-4 w-36" />
                </div>
              </div>
              
              {/* Assistant thinking state */}
              <div>
                <div className="flex items-start gap-3">
                  <Skeleton className="flex-shrink-0 w-5 h-5 mt-2 rounded-full" />
                  <div className="flex-1 space-y-2">
                    <div className="flex items-center gap-1.5 py-1">
                      <div className="h-1.5 w-1.5 rounded-full bg-gray-400/50 animate-pulse" />
                      <div className="h-1.5 w-1.5 rounded-full bg-gray-400/50 animate-pulse delay-150" />
                      <div className="h-1.5 w-1.5 rounded-full bg-gray-400/50 animate-pulse delay-300" />
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          
          {/* Skeleton Chat Input */}
          <div className="border-t p-4">
            <div className="mx-auto max-w-3xl">
              <div className="relative">
                <Skeleton className="h-10 w-full rounded-md" />
                <div className="absolute right-2 top-2">
                  <Skeleton className="h-6 w-6 rounded-full" />
                </div>
              </div>
            </div>
          </div>
        </div>
        
        {/* Skeleton Side Panel (closed state) */}
        <div className={`hidden ${isSidePanelOpen ? 'block' : ''}`}>
          <div className="h-screen w-[450px] border-l">
            <div className="p-4">
              <Skeleton className="h-8 w-32 mb-4" />
              <Skeleton className="h-20 w-full rounded-md mb-4" />
              <Skeleton className="h-40 w-full rounded-md" />
            </div>
          </div>
        </div>
      </div>
    );
  }

  if (error) {
    return (
      <div className="flex h-screen">
        <div className={`flex flex-col flex-1 overflow-hidden transition-all duration-200 ease-in-out ${isSidePanelOpen ? 'mr-[90%] sm:mr-[450px] md:mr-[500px] lg:mr-[550px] xl:mr-[650px]' : ''}`}>
          <SiteHeader 
            threadId={threadId} 
            projectName={projectName}
            projectId={project?.id || ""}
            onViewFiles={handleOpenFileViewer} 
            onToggleSidePanel={toggleSidePanel}
            isMobileView={isMobile}
          />
          <div className="flex flex-1 items-center justify-center p-4">
            <div className="flex w-full max-w-md flex-col items-center gap-4 rounded-lg border bg-card p-6 text-center">
              <h2 className="text-lg font-semibold text-destructive">Error</h2>
              <p className="text-sm text-muted-foreground">{error}</p>
              <Button variant="outline" onClick={() => router.push(`/projects/${project?.id || ''}`)}>
                Back to Project
              </Button>
            </div>
          </div>
        </div>
        <ToolCallSidePanel 
          isOpen={isSidePanelOpen} 
          onClose={() => setIsSidePanelOpen(false)}
          toolCalls={[]}
          currentIndex={0}
          onNavigate={handleSidePanelNavigate}
          project={project || undefined}
          agentStatus="error"
        />
      </div>
    );
  }

  return (
    <div className="flex h-screen">
      <div className={`flex flex-col flex-1 overflow-hidden transition-all duration-200 ease-in-out ${isSidePanelOpen ? 'mr-[90%] sm:mr-[450px] md:mr-[500px] lg:mr-[550px] xl:mr-[650px]' : ''}`}>
        <SiteHeader 
          threadId={threadId} 
          projectName={projectName}
          projectId={project?.id || ""}
          onViewFiles={handleOpenFileViewer} 
          onToggleSidePanel={toggleSidePanel}
          onProjectRenamed={handleProjectRenamed}
          isMobileView={isMobile}
        />
        <div 
          ref={messagesContainerRef}
          className="flex-1 overflow-y-auto px-6 py-4 pb-24 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"
          onScroll={handleScroll}
        >
          <div className="mx-auto max-w-3xl">
            {messages.length === 0 && !streamingTextContent && !streamingToolCall && agentStatus === 'idle' ? (
              <div className="flex h-full items-center justify-center">
                <div className="text-center text-muted-foreground">Send a message to start.</div>
              </div>
            ) : (
              <div className="space-y-8">
                {(() => {
                  // Group messages logic
                  type MessageGroup = {
                    type: 'user' | 'assistant_group';
                    messages: UnifiedMessage[];
                    key: string;
                  };
                  const groupedMessages: MessageGroup[] = [];
                  let currentGroup: MessageGroup | null = null;

                  messages.forEach((message, index) => {
                    const messageType = message.type;
                    const key = message.message_id || `msg-${index}`;

                    if (messageType === 'user') {
                      if (currentGroup) {
                        groupedMessages.push(currentGroup);
                      }
                      groupedMessages.push({ type: 'user', messages: [message], key });
                      currentGroup = null;
                    } else if (messageType === 'assistant' || messageType === 'tool' || messageType === 'browser_state') {
                      if (currentGroup && currentGroup.type === 'assistant_group') {
                        currentGroup.messages.push(message);
                      } else {
                        if (currentGroup) {
                          groupedMessages.push(currentGroup);
                        }
                        currentGroup = { type: 'assistant_group', messages: [message], key };
                      }
                    } else if (messageType !== 'status') {
                      if (currentGroup) {
                        groupedMessages.push(currentGroup);
                        currentGroup = null;
                      }
                    }
                  });

                  if (currentGroup) {
                    groupedMessages.push(currentGroup);
                  }
                  
                  return groupedMessages.map((group, groupIndex) => {
                    if (group.type === 'user') {
                      const message = group.messages[0];
                      const messageContent = (() => {
                        try {
                          const parsed = safeJsonParse<ParsedContent>(message.content, { content: message.content });
                          return parsed.content || message.content;
                        } catch {
                          return message.content;
                        }
                      })();
                      
                      // Extract attachments from the message content
                      const attachmentsMatch = messageContent.match(/\[Uploaded File: (.*?)\]/g);
                      const attachments = attachmentsMatch 
                        ? attachmentsMatch.map(match => {
                            const pathMatch = match.match(/\[Uploaded File: (.*?)\]/);
                            return pathMatch ? pathMatch[1] : null;
                          }).filter(Boolean)
                        : [];
                      
                      // Remove attachment info from the message content
                      const cleanContent = messageContent.replace(/\[Uploaded File: .*?\]/g, '').trim();
                      
                      return (
                        <div key={group.key} className="flex justify-end">
                          <div className="inline-flex max-w-[85%] rounded-lg bg-primary/10 px-4 py-3">
                            <div className="space-y-3">
                              {cleanContent && (
                                <Markdown className="text-sm prose prose-sm dark:prose-invert chat-markdown max-w-none [&>:first-child]:mt-0 prose-headings:mt-3">{cleanContent}</Markdown>
                              )}
                              
                              {/* Use the helper function to render user attachments */}
                              {renderAttachments(attachments as string[], handleOpenFileViewer)}
                            </div>
                          </div>
                        </div>
                      );
                    } else if (group.type === 'assistant_group') {
                      return (
                        <div key={group.key} ref={groupIndex === groupedMessages.length - 1 ? latestMessageRef : null}>
                          <div className="flex items-start gap-3">
                            <div className="flex-shrink-0 w-5 h-5 mt-2 rounded-md flex items-center justify-center overflow-hidden ml-auto mr-2">
                              <Image src="/kortix-symbol.svg" alt="Kortix" width={14} height={14} className="object-contain invert dark:invert-0 opacity-70" />
                            </div>
                            <div className="flex-1">
                              <div className="inline-flex max-w-[90%] rounded-lg bg-muted/5 px-4 py-3 text-sm">
                                <div className="space-y-2">
                                  {(() => {
                                    const toolResultsMap = new Map<string | null, UnifiedMessage[]>();
                                    group.messages.forEach(msg => {
                                      if (msg.type === 'tool') {
                                        const meta = safeJsonParse<ParsedMetadata>(msg.metadata, {});
                                        const assistantId = meta.assistant_message_id || null;
                                        if (!toolResultsMap.has(assistantId)) {
                                          toolResultsMap.set(assistantId, []);
                                        }
                                        toolResultsMap.get(assistantId)?.push(msg);
                                      }
                                    });
                                    
                                    const renderedToolResultIds = new Set<string>();
                                    const elements: React.ReactNode[] = [];

                                    group.messages.forEach((message, msgIndex) => {
                                      if (message.type === 'assistant') {
                                        const parsedContent = safeJsonParse<ParsedContent>(message.content, {});
                                        const msgKey = message.message_id || `submsg-assistant-${msgIndex}`;

                                        if (!parsedContent.content) return;

                                        const renderedContent = renderMarkdownContent(
                                          parsedContent.content, 
                                          handleToolClick, 
                                          message.message_id,
                                          handleOpenFileViewer
                                        );

                                        elements.push(
                                          <div key={msgKey} className={msgIndex > 0 ? "mt-2" : ""}>
                                            <div className="prose prose-sm dark:prose-invert chat-markdown max-w-none [&>:first-child]:mt-0 prose-headings:mt-3">
                                              {renderedContent}
                                            </div>
                                          </div>
                                        );
                                      }
                                    });

                                    return elements;
                                  })()}

                                  {groupIndex === groupedMessages.length - 1 && (streamHookStatus === 'streaming' || streamHookStatus === 'connecting') && (
                                    <div className="mt-2"> 
                                      {(() => {
                                          let detectedTag: string | null = null;
                                          let tagStartIndex = -1;
                                          if (streamingTextContent) {
                                              for (const tag of HIDE_STREAMING_XML_TAGS) {
                                                  const openingTagPattern = `<${tag}`;
                                                  const index = streamingTextContent.indexOf(openingTagPattern);
                                                  if (index !== -1) {
                                                      detectedTag = tag;
                                                      tagStartIndex = index;
                                                      break;
                                                  }
                                              }
                                          }

                                          const textToRender = streamingTextContent || '';
                                          const textBeforeTag = detectedTag ? textToRender.substring(0, tagStartIndex) : textToRender;
                                          const showCursor = (streamHookStatus === 'streaming' || streamHookStatus === 'connecting') && !detectedTag;

                                          return (
                                            <>
                                              {textBeforeTag && (
                                                <Markdown className="text-sm prose prose-sm dark:prose-invert chat-markdown max-w-none [&>:first-child]:mt-0 prose-headings:mt-3">{textBeforeTag}</Markdown>
                                              )}
                                              {showCursor && (
                                                <span className="inline-block h-4 w-0.5 bg-primary ml-0.5 -mb-1 animate-pulse" />
                                              )}

                                              {detectedTag && (
                                                <div className="mt-2 mb-1">
                                                  <button
                                                    className="inline-flex items-center gap-1.5 py-1 px-2.5 text-xs font-medium text-primary bg-primary/10 hover:bg-primary/20 rounded-md transition-colors cursor-pointer border border-primary/20"
                                                  >
                                                    <CircleDashed className="h-3.5 w-3.5 text-primary flex-shrink-0 animate-spin animation-duration-2000" />
                                                    <span className="font-mono text-xs text-primary">{detectedTag}</span>
                                                  </button>
                                                </div>
                                              )}

                                              {streamingToolCall && !detectedTag && (
                                                <div className="mt-2 mb-1">
                                                  {(() => {
                                                    const toolName = streamingToolCall.name || streamingToolCall.xml_tag_name || 'Tool';
                                                    const IconComponent = getToolIcon(toolName);
                                                    const paramDisplay = extractPrimaryParam(toolName, streamingToolCall.arguments || '');
                                                    return (
                                                      <button
                                                        className="inline-flex items-center gap-1.5 py-1 px-2.5 text-xs font-medium text-primary bg-primary/10 hover:bg-primary/20 rounded-md transition-colors cursor-pointer border border-primary/20"
                                                      >
                                                        <CircleDashed className="h-3.5 w-3.5 text-primary flex-shrink-0 animate-spin animation-duration-2000" />
                                                        <span className="font-mono text-xs text-primary">{toolName}</span>
                                                        {paramDisplay && <span className="ml-1 text-primary/70 truncate max-w-[200px]" title={paramDisplay}>{paramDisplay}</span>}
                                                      </button>
                                                    );
                                                  })()}
                                                </div>
                                              )}
                                            </>
                                          );
                                      })()}
                                    </div>
                                  )}
                                </div>
                              </div>
                            </div>
                          </div>
                        </div>
                      );
                    }
                    return null;
                  });
                })()}
                {(agentStatus === 'running' || agentStatus === 'connecting') && 
                  (messages.length === 0 || messages[messages.length - 1].type === 'user') && (
                    <div ref={latestMessageRef}>
                      <div className="flex items-start gap-3">
                        <div className="flex-shrink-0 w-5 h-5 rounded-md flex items-center justify-center overflow-hidden bg-primary/10">
                          <Image src="/kortix-symbol.svg" alt="Suna" width={14} height={14} className="object-contain"/>
                        </div>
                        <div className="flex-1 space-y-2">
                          <div className="max-w-[90%] px-4 py-3 text-sm">
                            <div className="flex items-center gap-1.5 py-1">
                              <div className="h-1.5 w-1.5 rounded-full bg-primary/50 animate-pulse" />
                              <div className="h-1.5 w-1.5 rounded-full bg-primary/50 animate-pulse delay-150" />
                              <div className="h-1.5 w-1.5 rounded-full bg-primary/50 animate-pulse delay-300" />
                            </div>
                          </div>
                        </div>
                      </div>
                    </div>
                )}
              </div>
            )}
            <div ref={messagesEndRef} className="h-1" />
          </div>
        </div>
      </div>

      <div className={cn(
        "fixed bottom-0 z-10 bg-gradient-to-t from-background via-background/90 to-transparent px-4 pt-8 transition-all duration-200 ease-in-out",
        leftSidebarState === 'expanded' ? 'left-[72px] lg:left-[256px]' : 'left-[72px]',
        isSidePanelOpen ? 'right-[90%] sm:right-[450px] md:right-[500px] lg:right-[550px] xl:right-[650px]' : 'right-0',
        isMobile ? 'left-0 right-0' : ''
      )}>
        <div className={cn(
          "mx-auto",
          isMobile ? "w-full px-4" : "max-w-3xl"
        )}>
          <ChatInput
            value={newMessage}
            onChange={setNewMessage}
            onSubmit={handleSubmitMessage}
            placeholder="Ask Suna anything..."
            loading={isSending}
            disabled={isSending || agentStatus === 'running' || agentStatus === 'connecting'}
            isAgentRunning={agentStatus === 'running' || agentStatus === 'connecting'}
            onStopAgent={handleStopAgent}
            autoFocus={!isLoading}
            onFileBrowse={handleOpenFileViewer}
            sandboxId={sandboxId || undefined}
          />
        </div>
      </div>

      <ToolCallSidePanel 
        isOpen={isSidePanelOpen} 
        onClose={() => {
          setIsSidePanelOpen(false);
          userClosedPanelRef.current = true;
          setAutoOpenedPanel(true);
        }}
        toolCalls={toolCalls}
        messages={messages as ApiMessageType[]}
        agentStatus={agentStatus}
        currentIndex={currentToolIndex}
        onNavigate={handleSidePanelNavigate}
        project={project || undefined}
        renderAssistantMessage={toolViewAssistant}
        renderToolResult={toolViewResult}
      />

      {sandboxId && (
        <FileViewerModal
          open={fileViewerOpen}
          onOpenChange={setFileViewerOpen}
          sandboxId={sandboxId}
          initialFilePath={fileToView}
          project={project || undefined}
        />
      )}

      {/* Billing Alert for usage limit */}
      <BillingErrorAlert 
        message={billingData.message}
        currentUsage={billingData.currentUsage}
        limit={billingData.limit}
        accountId={billingData.accountId || null}
        onDismiss={() => setShowBillingAlert(false)}
        isOpen={showBillingAlert}
      />
    </div>
  );
}

