<script lang="ts">
  import { nostrClient } from '../../services/nostr/nostr-client.js';
  import { relayManager } from '../../services/nostr/relay-manager.js';
  import { sessionManager } from '../../services/auth/session-manager.js';
  import FeedPost from './FeedPost.svelte';
  import HighlightCard from './HighlightCard.svelte';
  import ThreadDrawer from './ThreadDrawer.svelte';
  import type { NostrEvent } from '../../types/nostr.js';
  import { onMount, tick } from 'svelte';
  import { KIND, getFeedKinds, getKindInfo } from '../../types/kind-lookup.js';

  interface Props {
    singleRelay?: string; // If provided, use only this relay and disable cache
  }

  let { singleRelay }: Props = $props();

  let posts = $state<NostrEvent[]>([]);
  let allPosts = $state<NostrEvent[]>([]); // Store all posts before filtering
  let highlights = $state<NostrEvent[]>([]); // Store highlight events (kind 9802)
  let allHighlights = $state<NostrEvent[]>([]); // Store all highlights before filtering
  let otherFeedEvents = $state<NostrEvent[]>([]); // Store other feed kinds (not kind 1 or 9802)
  let allOtherFeedEvents = $state<NostrEvent[]>([]); // Store all other feed events before filtering
  let loading = $state(true);
  let loadingMore = $state(false);
  let hasMore = $state(true);
  let oldestTimestamp = $state<number | null>(null);
  
  // List filter state
  let availableLists = $state<Array<{ kind: number; name: string; event: NostrEvent }>>([]);
  let selectedListId = $state<string | null>(null); // Format: "kind:eventId"
  let listFilterIds = $state<Set<string>>(new Set()); // Event IDs or pubkeys to filter by
  
  // Batch-loaded reactions: eventId -> reactions[]
  let reactionsMap = $state<Map<string, NostrEvent[]>>(new Map());
  
  // Drawer state for viewing parent/quoted events
  let drawerOpen = $state(false);
  let drawerEvent = $state<NostrEvent | null>(null);
  
  // Debounce updates to prevent rapid re-renders
  let updateTimeout: ReturnType<typeof setTimeout> | null = null;
  let pendingUpdates: NostrEvent[] = [];
  
  function openDrawer(event: NostrEvent) {
    drawerEvent = event;
    drawerOpen = true;
  }
  
  function closeDrawer() {
    drawerOpen = false;
    drawerEvent = null;
  }

  let sentinelElement = $state<HTMLElement | null>(null);
  let observer: IntersectionObserver | null = null;
  let subscriptionId: string | null = $state(null);
  let refreshInterval: ReturnType<typeof setInterval> | null = null;
  let subscriptionSetup = $state(false); // Track if subscription is already set up

  onMount(async () => {
    await nostrClient.initialize();
    await loadUserLists();
    await loadFeed();
    // Set up persistent subscription for new events (only once)
    if (!subscriptionSetup) {
      setupSubscription();
      setupPeriodicRefresh();
      subscriptionSetup = true;
    }
  });
  
  // Load user lists for filtering
  async function loadUserLists() {
    // Don't load user lists for single relay mode
    if (singleRelay) {
      return;
    }
    
    const session = sessionManager.getSession();
    if (!session) return;
    
    const listKinds = [
      KIND.CONTACTS,
      KIND.FAVORITE_RELAYS,
      KIND.RELAY_LIST,
      KIND.LOCAL_RELAYS,
      KIND.PIN_LIST,
      KIND.BOOKMARKS,
      KIND.INTEREST_LIST,
      KIND.FOLOW_SET
    ];
    
    try {
      const relays = relayManager.getProfileReadRelays();
      const lists: Array<{ kind: number; name: string; event: NostrEvent }> = [];
      
      // Fetch all list types
      for (const kind of listKinds) {
        const limit = kind === KIND.FOLOW_SET ? 50 : 1; // Multiple follow sets allowed
        const events = await nostrClient.fetchEvents(
          [{ kinds: [kind], authors: [session.pubkey], limit }],
          relays,
          { useCache: true, cacheResults: true }
        );
        
        const kindName = getKindName(kind);
        for (const event of events) {
          lists.push({
            kind,
            name: `${kindName}${kind === KIND.FOLOW_SET ? ` (${new Date(event.created_at * 1000).toLocaleDateString()})` : ''}`,
            event
          });
        }
      }
      
      availableLists = lists;
    } catch (error) {
      console.error('Error loading user lists:', error);
    }
  }
  
  function getKindName(kind: number): string {
    const names: Record<number, string> = {
      [KIND.CONTACTS]: 'Contacts',
      [KIND.FAVORITE_RELAYS]: 'Favorite Relays',
      [KIND.RELAY_LIST]: 'Relay List',
      [KIND.LOCAL_RELAYS]: 'Local Relays',
      [KIND.PIN_LIST]: 'Pin List',
      [KIND.BOOKMARKS]: 'Bookmarks',
      [KIND.INTEREST_LIST]: 'Interest List',
      [KIND.FOLOW_SET]: 'Follow Set'
    };
    return names[kind] || `Kind ${kind}`;
  }
  
  function handleListFilterChange(listId: string | null) {
    selectedListId = listId;
    
    if (!listId) {
      // No filter selected - show all posts, highlights, and other feed events
      listFilterIds = new Set();
      posts = [...allPosts];
      highlights = [...allHighlights];
      otherFeedEvents = [...allOtherFeedEvents];
      return;
    }
    
    // Find the selected list
    const [kindStr, eventId] = listId.split(':');
    const kind = parseInt(kindStr, 10);
    const list = availableLists.find(l => l.kind === kind && l.event.id === eventId);
    
    if (!list) {
      listFilterIds = new Set();
      posts = [...allPosts];
      highlights = [...allHighlights];
      otherFeedEvents = [...allOtherFeedEvents];
      return;
    }
    
    // Extract IDs from the list
    const ids = new Set<string>();
    
    // For contacts and follow sets, extract pubkeys from 'p' tags
    if (kind === KIND.CONTACTS || kind === KIND.FOLOW_SET) {
      for (const tag of list.event.tags) {
        if (tag[0] === 'p' && tag[1]) {
          ids.add(tag[1]);
        }
      }
    } else {
      // For other lists, extract event IDs from 'e' and 'a' tags
      for (const tag of list.event.tags) {
        if (tag[0] === 'e' && tag[1]) {
          ids.add(tag[1]);
        } else if (tag[0] === 'a' && tag[1]) {
          // For 'a' tags, we'd need to resolve them to event IDs
          // For now, we'll just use the 'a' tag value as-is
          // This is a simplified approach - full implementation would resolve 'a' tags
          ids.add(tag[1]);
        }
      }
    }
    
    listFilterIds = ids;
    
    // Filter posts, highlights, and other feed events
    if (kind === KIND.CONTACTS || kind === KIND.FOLOW_SET) {
      // Filter by author pubkey
      posts = allPosts.filter(post => ids.has(post.pubkey));
      highlights = allHighlights.filter(highlight => ids.has(highlight.pubkey));
      otherFeedEvents = allOtherFeedEvents.filter(event => ids.has(event.pubkey));
    } else {
      // Filter by event ID
      posts = allPosts.filter(post => ids.has(post.id));
      highlights = allHighlights.filter(highlight => ids.has(highlight.id));
      otherFeedEvents = allOtherFeedEvents.filter((event: NostrEvent) => ids.has(event.id));
    }
  }
  
  // Apply filter when allPosts, allHighlights, or allOtherFeedEvents changes
  $effect(() => {
    if (selectedListId) {
      handleListFilterChange(selectedListId);
    } else {
      posts = [...allPosts];
      highlights = [...allHighlights];
      otherFeedEvents = [...allOtherFeedEvents];
    }
  });
  
  // Cleanup subscription on unmount
  $effect(() => {
    return () => {
      if (subscriptionId) {
        nostrClient.unsubscribe(subscriptionId);
        subscriptionId = null;
      }
      if (refreshInterval) {
        clearInterval(refreshInterval);
        refreshInterval = null;
      }
      subscriptionSetup = false;
    };
  });

  // Listen for custom event from EmbeddedEvent components
  $effect(() => {
    const handleOpenEvent = (e: CustomEvent) => {
      if (e.detail?.event) {
        openDrawer(e.detail.event);
      }
    };
    
    window.addEventListener('openEventInDrawer', handleOpenEvent as EventListener);
    
    return () => {
      window.removeEventListener('openEventInDrawer', handleOpenEvent as EventListener);
    };
  });

  // Cleanup on unmount
  $effect(() => {
    return () => {
      if (observer) {
        observer.disconnect();
      }
      if (updateTimeout) {
        clearTimeout(updateTimeout);
      }
      if (subscriptionId) {
        nostrClient.unsubscribe(subscriptionId);
        subscriptionId = null;
      }
      if (refreshInterval) {
        clearInterval(refreshInterval);
        refreshInterval = null;
      }
    };
  });
  
  // Set up persistent subscription for real-time updates
  function setupSubscription() {
    if (subscriptionId) {
      // Already subscribed
      return;
    }
    
    // Don't set up subscription for single relay mode
    if (singleRelay) {
      return;
    }
    
    const relays = relayManager.getFeedReadRelays();
    const feedKinds = getFeedKinds();
    const filters = feedKinds.map(kind => ({ kinds: [kind], limit: 20 }));
    
    // Subscribe to new kind 1 events and kind 9802 highlights
    subscriptionId = nostrClient.subscribe(
      filters,
      relays,
      (event: NostrEvent) => {
        // Only add events that are newer than what we already have
        // Check against all feed event types
        const existingIds = new Set([
          ...posts.map(p => p.id),
          ...highlights.map(h => h.id),
          ...otherFeedEvents.map(e => e.id)
        ]);
        if (!existingIds.has(event.id)) {
          handleUpdate([event]);
        }
      },
      (relay: string) => {
        console.debug(`[FeedPage] Subscription EOSE from ${relay}`);
      }
    );
    
    console.log(`[FeedPage] Set up persistent subscription for new events (ID: ${subscriptionId})`);
  }
  
  // Set up periodic refresh to ensure we get new events even if subscription fails
  function setupPeriodicRefresh() {
    if (refreshInterval) {
      return; // Already set up
    }
    
    // Don't set up periodic refresh for single relay mode
    if (singleRelay) {
      return;
    }
    
    // Refresh every 30 seconds
    refreshInterval = setInterval(async () => {
      try {
        const relays = relayManager.getFeedReadRelays();
        
        // Get the newest event's timestamp from all feed event types to only fetch newer events
        const allFeedEvents = [...posts, ...highlights, ...otherFeedEvents];
        const newestTimestamp = allFeedEvents.length > 0 
          ? Math.max(...allFeedEvents.map(e => e.created_at))
          : Math.floor(Date.now() / 1000) - 60; // Last minute if no events
        
        const feedKinds = getFeedKinds();
        const filters = feedKinds.map(kind => ({ 
          kinds: [kind], 
          limit: 50,
          since: newestTimestamp + 1 // Only get events newer than what we have
        }));
        
        // Fetch new events (without cache to ensure we query relays)
        const events = await nostrClient.fetchEvents(
          filters,
          relays,
          {
            useCache: false, // Don't use cache for refresh - always query relays
            cacheResults: true,
            timeout: 10000
          }
        );
        
        // Check for new events
        const existingIds = new Set(posts.map(p => p.id));
        const newEvents = events.filter(e => !existingIds.has(e.id));
        
        if (newEvents.length > 0) {
          console.log(`[FeedPage] Periodic refresh found ${newEvents.length} new events`);
          handleUpdate(newEvents);
        }
      } catch (error) {
        console.debug('[FeedPage] Periodic refresh error:', error);
      }
    }, 30000); // 30 seconds
    
    console.log('[FeedPage] Set up periodic refresh (every 30 seconds)');
  }

  // Set up observer when sentinel element is available
  $effect(() => {
    if (sentinelElement && !loading && !observer) {
      observer = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting && hasMore && !loadingMore) {
          loadMore();
        }
      }, { threshold: 0.1 });

      observer.observe(sentinelElement);
      
      return () => {
        if (observer) {
          observer.disconnect();
          observer = null;
        }
      };
    }
  });

  async function loadFeed() {
    loading = true;
    try {
      const config = nostrClient.getConfig();
      
      // Use single relay if provided, otherwise use normal relay list
      const relays = singleRelay ? [singleRelay] : relayManager.getFeedReadRelays();
      
      // Load all feed kinds
      const feedKinds = getFeedKinds();
      const filters = feedKinds.map(kind => ({ kinds: [kind], limit: 20 }));
      
      // For single relay mode, load from cache first for immediate display
      // Then query the relay in background to get fresh data
      let events: NostrEvent[] = [];
      
      if (singleRelay) {
        // Step 1: Load from cache immediately (fast, shows something right away)
        const cachedEvents = await nostrClient.fetchEvents(
          filters,
          relays,
          {
            useCache: true, // Use cache for fast initial load
            cacheResults: false, // Don't cache again
            timeout: 2000 // Short timeout for cache
          }
        );
        
        // Show cached data immediately if available
        if (cachedEvents.length > 0) {
          events = cachedEvents;
          console.log(`[FeedPage] Loaded ${cachedEvents.length} cached events from ${singleRelay}`);
          // Process cached events immediately so they show up
          // (will be processed below)
        }
        
        // Step 2: Ensure relay is connected and query for fresh data
        // If we have cached data, do this in background. Otherwise, wait for it.
        const queryPromise = (async () => {
          try {
            console.log(`[FeedPage] Single relay mode: ensuring ${singleRelay} is connected...`);
          // Force connection to the relay
          await nostrClient.addRelay(singleRelay);
          // Give it a moment to establish connection
            await new Promise(resolve => setTimeout(resolve, 1000));
            
            // Query relay for fresh data
            const freshEvents = await nostrClient.fetchEvents(
              filters,
              relays,
              {
                useCache: false, // Force query relay
                cacheResults: true, // Cache the results
                timeout: 15000
              }
            );
            
            console.log(`[FeedPage] Fresh query returned ${freshEvents.length} events from ${singleRelay}`);
            
            // Update with fresh data
            if (freshEvents.length > 0) {
              const existingIds = new Set([...posts.map(p => p.id), ...highlights.map(h => h.id)]);
              const trulyNew = freshEvents.filter(e => !existingIds.has(e.id));
              if (trulyNew.length > 0 || freshEvents.length !== events.length) {
                handleUpdate(freshEvents);
              }
            }
            
            return freshEvents;
          } catch (error) {
            console.warn(`[FeedPage] Failed to query relay ${singleRelay}:`, error);
            // If query fails but we have cached data, that's okay - keep showing cached data
            return [];
          }
        })();
        
        // If we don't have cached data, wait for the relay query
        if (events.length === 0) {
          const freshEvents = await queryPromise;
          if (freshEvents.length > 0) {
            events = freshEvents;
          }
        } else {
          // If we have cached data, query in background (don't await)
          queryPromise.catch(() => {
            // Already logged error above
          });
        }
      } else {
        // Normal mode: use cache first, then query relays
        events = await nostrClient.fetchEvents(
        filters,
        relays,
        {
            useCache: true, // Use cache for fast initial load
            cacheResults: true, // Cache results
            timeout: 15000
        }
      );

        console.log(`[FeedPage] Loaded ${events.length} events from relays`);

      // Also immediately query relays to ensure we get fresh data in background
        nostrClient.fetchEvents(
          filters,
          relays,
          {
            useCache: false, // Force query relays
            cacheResults: true, // Cache results
            timeout: 15000
          }
        ).then((newEvents) => {
          console.log(`[FeedPage] Background query returned ${newEvents.length} events`);
          // Only update if we got new events that aren't already in posts
          if (newEvents.length > 0) {
            const existingIds = new Set([...posts.map(p => p.id), ...highlights.map(h => h.id)]);
            const trulyNew = newEvents.filter(e => !existingIds.has(e.id));
            if (trulyNew.length > 0) {
              handleUpdate(trulyNew);
            }
          }
        }).catch(error => {
          console.warn('[FeedPage] Background relay query error:', error);
        });
      }

      // Separate events by kind - we'll handle all showInFeed kinds
      const postsList = events.filter(e => e.kind === KIND.SHORT_TEXT_NOTE);
      const highlightsList = events.filter(e => e.kind === KIND.HIGHLIGHTED_ARTICLE);
      // Store other feed kinds separately for now (we'll create cards for them)
      const newOtherFeedEvents = events.filter((e: NostrEvent) => 
        e.kind !== KIND.SHORT_TEXT_NOTE && 
        e.kind !== KIND.HIGHLIGHTED_ARTICLE &&
        getKindInfo(e.kind).showInFeed === true
      );

      // Sort by created_at descending and deduplicate
      const uniquePostsMap = new Map<string, NostrEvent>();
      for (const event of postsList) {
        if (!uniquePostsMap.has(event.id)) {
          uniquePostsMap.set(event.id, event);
        }
      }
      const uniquePosts = Array.from(uniquePostsMap.values());
      const sortedPosts = uniquePosts.sort((a, b) => b.created_at - a.created_at);
      allPosts = sortedPosts;

      const uniqueHighlightsMap = new Map<string, NostrEvent>();
      for (const event of highlightsList) {
        if (!uniqueHighlightsMap.has(event.id)) {
          uniqueHighlightsMap.set(event.id, event);
        }
      }
      const uniqueHighlights = Array.from(uniqueHighlightsMap.values());
      const sortedHighlights = uniqueHighlights.sort((a, b) => b.created_at - a.created_at);
      allHighlights = sortedHighlights;
      
      // Store other feed events
      const uniqueOtherMap = new Map<string, NostrEvent>();
      for (const event of newOtherFeedEvents) {
        if (!uniqueOtherMap.has(event.id)) {
          uniqueOtherMap.set(event.id, event);
        }
      }
      const uniqueOther = Array.from(uniqueOtherMap.values());
      const sortedOther = uniqueOther.sort((a, b) => b.created_at - a.created_at);
      allOtherFeedEvents = sortedOther;
      
      // Always set posts, highlights, and other feed events immediately, even if empty
      // This ensures cached data shows up right away
      // Apply filter if one is selected
      if (selectedListId) {
        handleListFilterChange(selectedListId);
      } else {
        posts = [...allPosts];
        highlights = [...allHighlights];
        otherFeedEvents = [...allOtherFeedEvents];
      }
      
      // Set loading to false immediately after showing cached data
      // This allows the UI to render while fresh data loads in background
      loading = false;
      
      console.log(`[FeedPage] Loaded ${sortedPosts.length} posts and ${sortedHighlights.length} highlights`);
      
      if (sortedPosts.length > 0 || sortedHighlights.length > 0) {
        const allTimestamps = [...sortedPosts.map(e => e.created_at), ...sortedHighlights.map(e => e.created_at)];
        oldestTimestamp = Math.min(...allTimestamps);
        // Batch load reactions for all posts
        await loadReactionsForPosts(sortedPosts);
      } else {
        console.log('[FeedPage] No events found. Relays:', relays);
      }
      
      hasMore = events.length >= 20;
    } catch (error) {
      console.error('Error loading feed:', error);
    } finally {
      loading = false;
    }
  }

  async function loadMore() {
    if (loadingMore || !hasMore) return;
    
    loadingMore = true;
    try {
      const config = nostrClient.getConfig();
      
      // Use single relay if provided, otherwise use normal relay list
      const relays = singleRelay ? [singleRelay] : relayManager.getFeedReadRelays();
      
      const feedKinds = getFeedKinds();
      const filters = feedKinds.map(kind => ({ 
        kinds: [kind], 
        limit: 20,
        until: oldestTimestamp || undefined
      }));
      
      // For single relay mode, try cache first, then query relay
      let events: NostrEvent[] = [];
      
      if (singleRelay) {
        // Try cache first
        const cachedEvents = await nostrClient.fetchEvents(
        filters,
        relays,
        {
            useCache: true,
            cacheResults: false,
            timeout: 2000
          }
        );
        
        if (cachedEvents.length > 0) {
          events = cachedEvents;
        }
        
        // Query relay in background for fresh data
        nostrClient.fetchEvents(
          filters,
          relays,
          {
            useCache: false,
            cacheResults: true,
            timeout: 10000
          }
        ).then((freshEvents) => {
          if (freshEvents.length > 0) {
            const existingIds = new Set([...allPosts.map(p => p.id), ...allHighlights.map(h => h.id)]);
            const uniqueNewPosts = freshEvents.filter(e => e.kind === KIND.SHORT_TEXT_NOTE && !existingIds.has(e.id));
            const uniqueNewHighlights = freshEvents.filter(e => e.kind === KIND.HIGHLIGHTED_ARTICLE && !existingIds.has(e.id));
            
            if (uniqueNewPosts.length > 0 || uniqueNewHighlights.length > 0) {
              handleUpdate(freshEvents);
            }
          }
        }).catch(error => {
          console.warn('[FeedPage] Background query error:', error);
        });
      } else {
        events = await nostrClient.fetchEvents(
          filters,
          relays,
          {
            useCache: true,
            cacheResults: true,
          timeout: 10000
        }
      );
      }

      if (events.length === 0) {
        hasMore = false;
        return;
      }

      // Separate events by kind
      const newPosts = events.filter(e => e.kind === KIND.SHORT_TEXT_NOTE);
      const newHighlights = events.filter(e => e.kind === KIND.HIGHLIGHTED_ARTICLE);
      const newOtherFeedEvents = events.filter((e: NostrEvent) => 
        e.kind !== KIND.SHORT_TEXT_NOTE && 
        e.kind !== KIND.HIGHLIGHTED_ARTICLE &&
        getKindInfo(e.kind).showInFeed === true
      );

      // Filter out duplicates
      const existingPostIds = new Set(allPosts.map(p => p.id));
      const existingHighlightIds = new Set(allHighlights.map(h => h.id));
      const existingOtherIds = new Set(allOtherFeedEvents.map((e: NostrEvent) => e.id));
      const uniqueNewPosts = newPosts.filter(e => !existingPostIds.has(e.id));
      const uniqueNewHighlights = newHighlights.filter(e => !existingHighlightIds.has(e.id));
      const uniqueNewOther = newOtherFeedEvents.filter((e: NostrEvent) => !existingOtherIds.has(e.id));
      
      if (uniqueNewPosts.length > 0 || uniqueNewHighlights.length > 0) {
        if (uniqueNewPosts.length > 0) {
          const sorted = uniqueNewPosts.sort((a, b) => b.created_at - a.created_at);
        allPosts = [...allPosts, ...sorted];
          // Batch load reactions for new posts
          await loadReactionsForPosts(sorted);
        }
        
        if (uniqueNewHighlights.length > 0) {
          const sorted = uniqueNewHighlights.sort((a, b) => b.created_at - a.created_at);
          allHighlights = [...allHighlights, ...sorted];
        }
        
        if (uniqueNewOther.length > 0) {
          const sorted = uniqueNewOther.sort((a, b) => b.created_at - a.created_at);
          allOtherFeedEvents = [...allOtherFeedEvents, ...sorted];
          otherFeedEvents = [...allOtherFeedEvents];
        }
        
        // Apply filter if one is selected
        if (selectedListId) {
          handleListFilterChange(selectedListId);
        } else {
          posts = [...allPosts];
          highlights = [...allHighlights];
          otherFeedEvents = [...allOtherFeedEvents];
        }
        
        const allNewTimestamps = [...uniqueNewPosts.map(e => e.created_at), ...uniqueNewHighlights.map(e => e.created_at)];
        const oldest = Math.min(...allNewTimestamps);
        if (oldest < (oldestTimestamp || Infinity)) {
          oldestTimestamp = oldest;
        }
        hasMore = events.length >= 20;
      } else if (events.length > 0) {
        // All events were duplicates, but we got some results
        // This might mean we've reached the end, or we need to adjust the timestamp
        if (oldestTimestamp) {
          // Try moving the timestamp forward slightly to avoid getting the same results
          oldestTimestamp = oldestTimestamp - 1;
          hasMore = events.length >= 20;
        } else {
          hasMore = false;
        }
      } else {
        // No events returned at all
        hasMore = false;
      }
    } catch (error) {
      console.error('Error loading more:', error);
    } finally {
      loadingMore = false;
    }
  }

  // Debounced update handler to prevent rapid re-renders and loops
  function handleUpdate(updated: NostrEvent[]) {
    if (!updated || updated.length === 0) return;
    
    // Deduplicate incoming updates before adding to pending
    // Check against all feed event types
    const existingIds = new Set([
      ...allPosts.map(p => p.id),
      ...allHighlights.map(h => h.id),
      ...allOtherFeedEvents.map(e => e.id)
    ]);
    const newUpdates = updated.filter(e => e && e.id && !existingIds.has(e.id));
    
    if (newUpdates.length === 0) {
      return; // All duplicates, skip silently
    }
    
    // Also deduplicate within pendingUpdates
    const pendingIds = new Set(pendingUpdates.map(e => e.id));
    const trulyNew = newUpdates.filter(e => !pendingIds.has(e.id));
    
    if (trulyNew.length === 0) {
      return; // Already in pending, skip silently
    }
    
    pendingUpdates.push(...trulyNew);
    
    if (updateTimeout) {
      clearTimeout(updateTimeout);
    }
    
    // Batch updates every 500ms to prevent rapid re-renders
    updateTimeout = setTimeout(() => {
      if (pendingUpdates.length === 0) {
        return;
      }
      
      // Final deduplication check against all feed event types (may have changed)
      const currentIds = new Set([
        ...allPosts.map(p => p.id),
        ...allHighlights.map(h => h.id),
        ...allOtherFeedEvents.map(e => e.id)
      ]);
      const newEvents = pendingUpdates.filter(e => e && e.id && !currentIds.has(e.id));
      
      if (newEvents.length === 0) {
        pendingUpdates = [];
        return;
      }
      
      console.log(`[FeedPage] Processing ${newEvents.length} new events, existing: ${allPosts.length}`);
      
      // Separate events by kind
      const newPosts = newEvents.filter(e => e.kind === KIND.SHORT_TEXT_NOTE);
      const newHighlights = newEvents.filter(e => e.kind === KIND.HIGHLIGHTED_ARTICLE);
      const newOtherFeedEvents = newEvents.filter((e: NostrEvent) => 
        e.kind !== KIND.SHORT_TEXT_NOTE && 
        e.kind !== KIND.HIGHLIGHTED_ARTICLE &&
        getKindInfo(e.kind).showInFeed === true
      );

      // Merge and sort posts, then deduplicate by ID
      if (newPosts.length > 0) {
        const mergedPosts = [...allPosts, ...newPosts];
        const uniquePostsMap = new Map<string, NostrEvent>();
        for (const event of mergedPosts) {
          if (event && event.id && !uniquePostsMap.has(event.id)) {
            uniquePostsMap.set(event.id, event);
        }
      }
        const uniquePosts = Array.from(uniquePostsMap.values());
        const sortedPosts = uniquePosts.sort((a, b) => b.created_at - a.created_at);
      
      // Only update if we actually have new events to prevent loops
        if (sortedPosts.length > allPosts.length || sortedPosts.some((e, i) => e.id !== allPosts[i]?.id)) {
          allPosts = sortedPosts;
        }
      }

      // Merge and sort highlights, then deduplicate by ID
      if (newHighlights.length > 0) {
        const mergedHighlights = [...allHighlights, ...newHighlights];
        const uniqueHighlightsMap = new Map<string, NostrEvent>();
        for (const event of mergedHighlights) {
          if (event && event.id && !uniqueHighlightsMap.has(event.id)) {
            uniqueHighlightsMap.set(event.id, event);
          }
        }
        const uniqueHighlights = Array.from(uniqueHighlightsMap.values());
        const sortedHighlights = uniqueHighlights.sort((a, b) => b.created_at - a.created_at);
        
        // Only update if we actually have new events to prevent loops
        if (sortedHighlights.length > allHighlights.length || sortedHighlights.some((e, i) => e.id !== allHighlights[i]?.id)) {
          allHighlights = sortedHighlights;
        }
      }

      // Merge and sort other feed events, then deduplicate by ID
      if (newOtherFeedEvents.length > 0) {
        const mergedOther = [...allOtherFeedEvents, ...newOtherFeedEvents];
        const uniqueOtherMap = new Map<string, NostrEvent>();
        for (const event of mergedOther) {
          if (event && event.id && !uniqueOtherMap.has(event.id)) {
            uniqueOtherMap.set(event.id, event);
          }
        }
        const uniqueOther = Array.from(uniqueOtherMap.values());
        const sortedOther = uniqueOther.sort((a, b) => b.created_at - a.created_at);
        
        // Only update if we actually have new events to prevent loops
        if (sortedOther.length > allOtherFeedEvents.length || sortedOther.some((e, i) => e.id !== allOtherFeedEvents[i]?.id)) {
          allOtherFeedEvents = sortedOther;
        }
      }
        
        // Apply filter if one is selected
        if (selectedListId) {
          handleListFilterChange(selectedListId);
        } else {
          posts = [...allPosts];
        highlights = [...allHighlights];
        otherFeedEvents = [...allOtherFeedEvents];
        }
        
      console.debug(`[FeedPage] Updated: ${allPosts.length} posts, ${allHighlights.length} highlights`);
      
      pendingUpdates = [];
    }, 500);
  }

  // Batch load reactions for multiple posts at once
  async function loadReactionsForPosts(postsToLoad: NostrEvent[]) {
    if (postsToLoad.length === 0) return;
    
    try {
      const reactionRelays = relayManager.getProfileReadRelays();
      const eventIds = postsToLoad.map(p => p.id);
      
      // Use single relay if provided, otherwise use normal reaction relays
      const relaysForReactions = singleRelay ? [singleRelay] : reactionRelays;
      
      // For single relay mode, disable cache completely
      const useCache = !singleRelay;
      const cacheResults = !singleRelay;
      
      // Batch fetch all reactions for all posts in one query
      const allReactions = await nostrClient.fetchEvents(
        [
          { kinds: [KIND.REACTION], '#e': eventIds, limit: 1000 },
          { kinds: [KIND.REACTION], '#E': eventIds, limit: 1000 }
        ],
        relaysForReactions,
        { useCache, cacheResults, timeout: 10000 }
      );
      
      // Group reactions by event ID
      const newReactionsMap = new Map<string, NostrEvent[]>();
      for (const reaction of allReactions) {
        // Find which event(s) this reaction is for
        const eTags = reaction.tags.filter(t => (t[0] === 'e' || t[0] === 'E') && t[1]);
        for (const tag of eTags) {
          const eventId = tag[1];
          if (eventIds.includes(eventId)) {
            if (!newReactionsMap.has(eventId)) {
              newReactionsMap.set(eventId, []);
            }
            newReactionsMap.get(eventId)!.push(reaction);
          }
        }
      }
      
      // Merge with existing reactions
      for (const [eventId, reactions] of newReactionsMap.entries()) {
        const existing = reactionsMap.get(eventId) || [];
        const combined = [...existing, ...reactions];
        // Deduplicate by reaction ID
        const unique = Array.from(new Map(combined.map(r => [r.id, r])).values());
        reactionsMap.set(eventId, unique);
      }
    } catch (error) {
      console.error('[FeedPage] Error batch loading reactions:', error);
    }
  }
</script>

<div class="feed-page">
  {#if !loading && availableLists.length > 0 && !singleRelay}
    <div class="feed-filter">
      <label for="list-filter" class="filter-label">Filter by list:</label>
      <select
        id="list-filter"
        bind:value={selectedListId}
        onchange={(e) => handleListFilterChange((e.target as HTMLSelectElement).value || null)}
        class="filter-select"
      >
        <option value="">All Posts</option>
        {#each availableLists as list}
          <option value="{list.kind}:{list.event.id}">{list.name}</option>
        {/each}
      </select>
    </div>
  {/if}
  
  {#if singleRelay}
    <div class="relay-info">
      <p class="relay-info-text">
        Showing feed from: <code class="relay-url">{singleRelay}</code>
      </p>
    </div>
  {/if}
  
  {#if loading}
    <div class="loading-state">
      <p class="text-fog-text dark:text-fog-dark-text">Loading feed...</p>
    </div>
  {:else if posts.length === 0 && highlights.length === 0 && otherFeedEvents.length === 0}
    <div class="empty-state">
      <p class="text-fog-text dark:text-fog-dark-text">
        {#if selectedListId}
          No posts found in selected list.
        {:else}
          No posts found. Check back later!
        {/if}
      </p>
    </div>
  {:else}
    <div class="feed-posts">
      {#each [...posts, ...highlights, ...otherFeedEvents].sort((a, b) => b.created_at - a.created_at) as event (event.id)}
        {#if event.kind === KIND.HIGHLIGHTED_ARTICLE}
          <HighlightCard highlight={event} onOpenEvent={openDrawer} />
        {:else}
          <FeedPost post={event} onOpenEvent={openDrawer} reactions={reactionsMap.get(event.id)} />
        {/if}
      {/each}
    </div>
    
    {#if drawerOpen && drawerEvent}
      <ThreadDrawer opEvent={drawerEvent} isOpen={drawerOpen} onClose={closeDrawer} />
    {/if}
    
    <div id="feed-sentinel" class="feed-sentinel" bind:this={sentinelElement}>
      {#if loadingMore}
        <p class="text-fog-text-light dark:text-fog-dark-text-light">Loading more...</p>
      {:else if hasMore}
        <p class="text-fog-text-light dark:text-fog-dark-text-light">Scroll for more</p>
      {:else}
        <p class="text-fog-text-light dark:text-fog-dark-text-light">No more posts</p>
      {/if}
    </div>
  {/if}
</div>

<style>
  .feed-page {
    max-width: 100%;
  }

  .feed-filter {
    margin-bottom: 1.5rem;
    display: flex;
    align-items: center;
    gap: 0.75rem;
  }

  .filter-label {
    font-size: 0.875rem;
    font-weight: 500;
    color: var(--fog-text, #1f2937);
  }

  :global(.dark) .filter-label {
    color: var(--fog-dark-text, #f9fafb);
  }

  .filter-select {
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--fog-border, #e5e7eb);
    border-radius: 0.375rem;
    background: var(--fog-post, #ffffff);
    color: var(--fog-text, #1f2937);
    font-size: 0.875rem;
    cursor: pointer;
    min-width: 200px;
  }

  .filter-select:focus {
    outline: none;
    border-color: var(--fog-accent, #64748b);
    box-shadow: 0 0 0 3px rgba(100, 116, 139, 0.1);
  }

  :global(.dark) .filter-select {
    background: var(--fog-dark-post, #1f2937);
    border-color: var(--fog-dark-border, #374151);
    color: var(--fog-dark-text, #f9fafb);
  }

  :global(.dark) .filter-select:focus {
    border-color: var(--fog-dark-accent, #94a3b8);
    box-shadow: 0 0 0 3px rgba(148, 163, 184, 0.1);
  }

  .loading-state,
  .empty-state {
    padding: 2rem;
    text-align: center;
  }

  .feed-posts {
    display: flex;
    flex-direction: column;
    gap: 1rem;
  }

  .feed-sentinel {
    padding: 2rem;
    text-align: center;
    min-height: 100px;
  }

  .relay-info {
    margin-bottom: 1.5rem;
    padding: 1rem;
    background: var(--fog-highlight, #f3f4f6);
    border: 1px solid var(--fog-border, #e5e7eb);
    border-radius: 0.375rem;
  }

  :global(.dark) .relay-info {
    background: var(--fog-dark-highlight, #374151);
    border-color: var(--fog-dark-border, #475569);
  }

  .relay-info-text {
    margin: 0;
    font-size: 0.875rem;
    color: var(--fog-text, #1f2937);
  }

  :global(.dark) .relay-info-text {
    color: var(--fog-dark-text, #f9fafb);
  }

  .relay-url {
    font-family: monospace;
    font-size: 0.875rem;
    background: var(--fog-post, #ffffff);
    padding: 0.25rem 0.5rem;
    border-radius: 0.25rem;
    border: 1px solid var(--fog-border, #e5e7eb);
  }

  :global(.dark) .relay-url {
    background: var(--fog-dark-post, #1f2937);
    border-color: var(--fog-dark-border, #374151);
  }
</style>
