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

  let posts = $state<NostrEvent[]>([]);
  let loading = $state(true);
  let loadingMore = $state(false);
  let hasMore = $state(true);
  let oldestTimestamp = $state<number | null>(null);
  
  // 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 loadFeed();
    // Set up persistent subscription for new events (only once)
    if (!subscriptionSetup) {
      setupSubscription();
      setupPeriodicRefresh();
      subscriptionSetup = true;
    }
  });
  
  // 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;
    }
    
    const relays = relayManager.getFeedReadRelays();
    const filters = [{ kinds: [KIND.SHORT_TEXT_NOTE], limit: 20 }];
    
    // Subscribe to new kind 1 events
    subscriptionId = nostrClient.subscribe(
      filters,
      relays,
      (event: NostrEvent) => {
        // Only add events that are newer than what we already have
        const existingIds = new Set(posts.map(p => p.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
    }
    
    // Refresh every 30 seconds
    refreshInterval = setInterval(async () => {
      try {
        const relays = relayManager.getFeedReadRelays();
        
        // Get the newest post's timestamp to only fetch newer events
        const newestTimestamp = posts.length > 0 
          ? Math.max(...posts.map(p => p.created_at))
          : Math.floor(Date.now() / 1000) - 60; // Last minute if no posts
        
        const filters = [{ 
          kinds: [KIND.SHORT_TEXT_NOTE], 
          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();
      const relays = relayManager.getFeedReadRelays();
      
      // Load initial feed - use cache for fast initial load
      const filters = [{ kinds: [KIND.SHORT_TEXT_NOTE], limit: 20 }];
      const events = await nostrClient.fetchEvents(
        filters,
        relays,
        {
          useCache: true, // Use cache for fast initial display
          cacheResults: true,
          // Don't use onUpdate here - subscriptions handle updates
          timeout: 10000
        }
      );

      // Also immediately query relays to ensure we get fresh data in background
      // This runs in parallel but doesn't use onUpdate to avoid loops
      nostrClient.fetchEvents(
        filters,
        relays,
        {
          useCache: false, // Force query relays
          cacheResults: true,
          // Don't use onUpdate - let subscriptions handle it
          timeout: 10000
        }
      ).then((newEvents) => {
        // 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));
          const trulyNew = newEvents.filter(e => !existingIds.has(e.id));
          if (trulyNew.length > 0) {
            handleUpdate(trulyNew);
          }
        }
      }).catch(error => {
        console.debug('[FeedPage] Background relay query error:', error);
      });

      // Sort by created_at descending and deduplicate
      const uniqueMap = new Map<string, NostrEvent>();
      for (const event of events) {
        if (!uniqueMap.has(event.id)) {
          uniqueMap.set(event.id, event);
        }
      }
      const unique = Array.from(uniqueMap.values());
      const sorted = unique.sort((a, b) => b.created_at - a.created_at);
      posts = sorted;
      
      if (sorted.length > 0) {
        oldestTimestamp = Math.min(...sorted.map(e => e.created_at));
        // Batch load reactions for all posts
        await loadReactionsForPosts(sorted);
      }
      
      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();
      const relays = relayManager.getFeedReadRelays();
      
      const filters = [{ 
        kinds: [KIND.SHORT_TEXT_NOTE], 
        limit: 20,
        until: oldestTimestamp || undefined
      }];
      
      const events = await nostrClient.fetchEvents(
        filters,
        relays,
        {
          useCache: true,
          cacheResults: true,
          timeout: 10000
        }
      );

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

      // Filter out duplicates
      const existingIds = new Set(posts.map(p => p.id));
      const newEvents = events.filter(e => !existingIds.has(e.id));
      
      if (newEvents.length > 0) {
        const sorted = newEvents.sort((a, b) => b.created_at - a.created_at);
        posts = [...posts, ...sorted];
        
        const oldest = Math.min(...newEvents.map(e => e.created_at));
        if (oldest < (oldestTimestamp || Infinity)) {
          oldestTimestamp = oldest;
        }
        // Batch load reactions for new posts
        await loadReactionsForPosts(sorted);
        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
    const existingIds = new Set(posts.map(p => p.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 current posts (posts may have changed)
      const currentIds = new Set(posts.map(p => p.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: ${posts.length}`);
      
      // Merge and sort, then deduplicate by ID
      const merged = [...posts, ...newEvents];
      // Deduplicate by ID (keep first occurrence)
      const uniqueMap = new Map<string, NostrEvent>();
      for (const event of merged) {
        if (event && event.id && !uniqueMap.has(event.id)) {
          uniqueMap.set(event.id, event);
        }
      }
      const unique = Array.from(uniqueMap.values());
      const sorted = unique.sort((a, b) => b.created_at - a.created_at);
      
      // Only update if we actually have new events to prevent loops
      if (sorted.length > posts.length || sorted.some((e, i) => e.id !== posts[i]?.id)) {
        posts = sorted;
        console.debug(`[FeedPage] Updated posts to ${sorted.length} events`);
      }
      
      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);
      
      // 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 }
        ],
        reactionRelays,
        { useCache: true, cacheResults: true, 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}
    <div class="loading-state">
      <p class="text-fog-text dark:text-fog-dark-text">Loading feed...</p>
    </div>
  {:else if posts.length === 0}
    <div class="empty-state">
      <p class="text-fog-text dark:text-fog-dark-text">No posts found. Check back later!</p>
    </div>
  {:else}
    <div class="feed-posts">
      {#each posts as post (post.id)}
        <FeedPost post={post} onOpenEvent={openDrawer} reactions={reactionsMap.get(post.id)} />
      {/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%;
  }

  .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;
  }
</style>
