<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 } from 'svelte';
  import { KIND, getFeedKinds, getKindInfo } from '../../types/kind-lookup.js';
  import { getRecentFeedEvents } from '../../services/cache/event-cache.js';

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

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

  let events = $state<NostrEvent[]>([]);
  let loading = $state(true);
  let loadingMore = $state(false);
  let hasMore = $state(true);
  let oldestTimestamp = $state<number | null>(null);
  let relayError = $state<string | null>(null);
  
  // Batch-loaded parent and quoted events
  let parentEventsMap = $state<Map<string, NostrEvent>>(new Map());
  let quotedEventsMap = $state<Map<string, NostrEvent>>(new Map());
  
  // Drawer state
  let drawerOpen = $state(false);
  let drawerEvent = $state<NostrEvent | null>(null);
  
  let sentinelElement = $state<HTMLElement | null>(null);
  let observer: IntersectionObserver | null = null;
  let subscriptionId: string | null = $state(null);
  let isMounted = $state(true);
  let loadingParents = $state(false); // Guard to prevent concurrent parent/quoted event loads
  let loadingFeed = $state(false); // Guard to prevent concurrent feed loads
  let pendingSubscriptionEvents = $state<NostrEvent[]>([]); // Batch subscription events
  let subscriptionBatchTimeout: ReturnType<typeof setTimeout> | null = null;
  let loadMoreTimeout: ReturnType<typeof setTimeout> | null = null; // Debounce loadMore calls
  let lastLoadMoreTime = $state<number>(0); // Track last loadMore call time

  function openDrawer(event: NostrEvent) {
    drawerEvent = event;
    drawerOpen = true;
  }

  function closeDrawer() {
    drawerOpen = false;
    drawerEvent = null;
  }

  onMount(() => {
    isMounted = true;
    (async () => {
      await nostrClient.initialize();
      if (!isMounted) return;
      
      // Load cached feed events immediately (15 minute cache)
      await loadCachedFeed();
      
      if (!isMounted) return;
      // Then fetch fresh data in the background
      await loadFeed();
      if (!isMounted) return;
      setupSubscription();
      setupObserver();
    })();
    
    return () => {
      isMounted = false;
      if (subscriptionId) {
        nostrClient.unsubscribe(subscriptionId);
        subscriptionId = null;
      }
      if (observer) {
        observer.disconnect();
        observer = null;
      }
      if (subscriptionBatchTimeout) {
        clearTimeout(subscriptionBatchTimeout);
        subscriptionBatchTimeout = null;
      }
      if (loadMoreTimeout) {
        clearTimeout(loadMoreTimeout);
        loadMoreTimeout = null;
      }
      pendingSubscriptionEvents = [];
    };
  });

  // 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);
    };
  });

  function processSubscriptionBatch() {
    if (!isMounted || pendingSubscriptionEvents.length === 0 || loadingFeed) {
      pendingSubscriptionEvents = [];
      subscriptionBatchTimeout = null;
      return;
    }
    
    // Get current events snapshot to avoid race conditions
    const currentEvents = events;
    const currentEventIds = new Set(currentEvents.map(e => e.id));
    
    // Filter out discussion threads and deduplicate within pending events
    const seenInPending = new Set<string>();
    const newEvents: NostrEvent[] = [];
    
    for (const event of pendingSubscriptionEvents) {
      // Skip discussion threads
      if (event.kind === KIND.DISCUSSION_THREAD) continue;
      
      // Skip if already in current events or already seen in this batch
      if (!currentEventIds.has(event.id) && !seenInPending.has(event.id)) {
        newEvents.push(event);
        seenInPending.add(event.id);
      }
    }
    
    if (newEvents.length === 0) {
      pendingSubscriptionEvents = [];
      subscriptionBatchTimeout = null;
      return;
    }
    
    // Create a completely new events array with all unique events
    // Use a Map to ensure uniqueness by event ID (last one wins if duplicates somehow exist)
    const eventsMap = new Map<string, NostrEvent>();
    
    // Add all current events first
    for (const event of currentEvents) {
      eventsMap.set(event.id, event);
    }
    
    // Add new events (will overwrite if somehow duplicate, but shouldn't happen)
    for (const event of newEvents) {
      eventsMap.set(event.id, event);
    }
    
    // Convert to array and sort
    events = Array.from(eventsMap.values()).sort((a, b) => b.created_at - a.created_at);
    
    pendingSubscriptionEvents = [];
    subscriptionBatchTimeout = null;
  }

  function setupSubscription() {
    if (subscriptionId || singleRelay) return;
    
    const relays = relayManager.getFeedReadRelays();
    const feedKinds = getFeedKinds().filter(kind => kind !== KIND.DISCUSSION_THREAD);
    const filters = feedKinds.map(kind => ({ kinds: [kind], limit: 20 }));
    
    subscriptionId = nostrClient.subscribe(
      filters,
      relays,
      (event: NostrEvent) => {
        if (!isMounted || event.kind === KIND.DISCUSSION_THREAD || loadingFeed) return;
        
        // Add to pending batch
        pendingSubscriptionEvents.push(event);
        
        // Clear existing timeout
        if (subscriptionBatchTimeout) {
          clearTimeout(subscriptionBatchTimeout);
        }
        
        // Process batch after a short delay (debounce rapid updates)
        subscriptionBatchTimeout = setTimeout(() => {
          processSubscriptionBatch();
        }, 100); // 100ms debounce
      },
      () => {}
    );
  }

  function setupObserver() {
    if (!sentinelElement || loading || observer) return;
    
    observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting && hasMore && !loadingMore) {
        // Debounce rapid scroll events - clear any pending loadMore call
        if (loadMoreTimeout) {
          clearTimeout(loadMoreTimeout);
        }
        
        // Wait 300ms before actually loading more to batch rapid scroll events
        loadMoreTimeout = setTimeout(() => {
          if (hasMore && !loadingMore && isMounted) {
            loadMore();
          }
          loadMoreTimeout = null;
        }, 300);
      }
    }, { threshold: 0.1, rootMargin: '100px' }); // Add rootMargin to trigger slightly earlier

    observer.observe(sentinelElement);
  }

  async function loadCachedFeed() {
    if (!isMounted || singleRelay) return;
    
    try {
      const feedKinds = getFeedKinds().filter(kind => kind !== KIND.DISCUSSION_THREAD);
      // Load events cached within the last 15 minutes
      const cachedEvents = await getRecentFeedEvents(feedKinds, 15 * 60 * 1000, 50);
      
      if (cachedEvents.length > 0 && isMounted) {
        // Filter to only showInFeed kinds and exclude kind 11
        const filteredEvents = cachedEvents.filter((e: NostrEvent) => 
          e.kind !== KIND.DISCUSSION_THREAD && 
          getKindInfo(e.kind).showInFeed === true
        );
        
        // Deduplicate
        const uniqueMap = new Map<string, NostrEvent>();
        for (const event of filteredEvents) {
          if (!uniqueMap.has(event.id)) {
            uniqueMap.set(event.id, event);
          }
        }
        
        const sortedEvents = Array.from(uniqueMap.values()).sort((a, b) => b.created_at - a.created_at);
        
        if (sortedEvents.length > 0) {
          events = sortedEvents;
          oldestTimestamp = Math.min(...events.map(e => e.created_at));
          // Load parent/quoted events in background, don't await
          // Only load if not already loading to prevent cascading fetches
          if (!loadingParents) {
            loadParentAndQuotedEvents(events).catch(err => {
              console.error('Error loading parent/quoted events from cache:', err);
            });
          }
          // Don't set loading to false here - let loadFeed() handle that
        }
      }
    } catch (error) {
      console.error('Error loading cached feed:', error);
      // Don't set loading to false - let loadFeed() handle that
    }
  }

  async function loadFeed() {
    if (!isMounted || loadingFeed) return; // Prevent concurrent loads
    
    loadingFeed = true;
    // Only show loading spinner if we don't have cached events
    const hasCachedEvents = events.length > 0;
    if (!hasCachedEvents) {
      loading = true;
    }
    relayError = null;
    
    try {
      const relays = singleRelay ? [singleRelay] : relayManager.getFeedReadRelays();
      
      if (singleRelay) {
        try {
          const relay = await nostrClient.getRelay(singleRelay);
          if (!relay) {
            relayError = `Relay ${singleRelay} is unavailable or returned an error.`;
            loading = false;
            return;
          }
        } catch (error) {
          relayError = `Failed to connect to relay ${singleRelay}: ${error instanceof Error ? error.message : 'Unknown error'}`;
          loading = false;
          return;
        }
      }
      
      const feedKinds = getFeedKinds().filter(kind => kind !== KIND.DISCUSSION_THREAD);
      const filters = feedKinds.map(kind => ({ kinds: [kind], limit: 20 }));
      
      const fetchOptions = singleRelay ? {
        relayFirst: true,
        useCache: false,
        cacheResults: false,
        timeout: 15000
      } : {
        relayFirst: true,
        useCache: true,
        cacheResults: true,
        timeout: 3000
      };
      
      const fetchedEvents = await nostrClient.fetchEvents(filters, relays, fetchOptions);
      
      if (!isMounted) return;
      
      // Filter to only showInFeed kinds and exclude kind 11
      const filteredEvents = fetchedEvents.filter((e: NostrEvent) => 
        e.kind !== KIND.DISCUSSION_THREAD && 
        getKindInfo(e.kind).showInFeed === true
      );
      
      // Deduplicate and merge with existing events
      const existingIds = new Set(events.map(e => e.id));
      const uniqueMap = new Map<string, NostrEvent>();
      
      // Add existing events first
      for (const event of events) {
        uniqueMap.set(event.id, event);
      }
      
      // Add new events
      for (const event of filteredEvents) {
        if (!uniqueMap.has(event.id)) {
          uniqueMap.set(event.id, event);
        }
      }
      
      events = Array.from(uniqueMap.values()).sort((a, b) => b.created_at - a.created_at);
      
      if (events.length > 0) {
        oldestTimestamp = Math.min(...events.map(e => e.created_at));
        // Load parent/quoted events in background, don't await
        // Only load if not already loading to prevent cascading fetches
        if (!loadingParents) {
          loadParentAndQuotedEvents(events).catch(err => {
            console.error('Error loading parent/quoted events:', err);
          });
        }
      }
      
      hasMore = fetchedEvents.length >= 20;
    } catch (error) {
      console.error('Error loading feed:', error);
    } finally {
      loading = false;
      loadingFeed = false;
    }
  }

  async function loadMore() {
    // Double-check guards to prevent concurrent calls
    if (!isMounted || loadingMore || !hasMore || loadingFeed) {
      return;
    }
    
    // Prevent rapid successive calls - minimum 1 second between loads
    const now = Date.now();
    if (now - lastLoadMoreTime < 1000) {
      return;
    }
    
    lastLoadMoreTime = now;
    loadingMore = true;
    try {
      const relays = singleRelay ? [singleRelay] : relayManager.getFeedReadRelays();
      const feedKinds = getFeedKinds().filter(kind => kind !== KIND.DISCUSSION_THREAD);
      const filters = feedKinds.map(kind => ({ 
        kinds: [kind], 
        limit: 20,
        until: oldestTimestamp || undefined
      }));
      
      const fetchOptions = singleRelay ? {
        relayFirst: true,
        useCache: false,
        cacheResults: false,
        timeout: 3000
      } : {
        relayFirst: true,
        useCache: true,
        cacheResults: true,
        timeout: 3000
      };
      
      const fetchedEvents = await nostrClient.fetchEvents(filters, relays, fetchOptions);
      
      if (!isMounted) return;
      
      if (fetchedEvents.length === 0) {
        hasMore = false;
        return;
      }
      
      const filteredEvents = fetchedEvents.filter((e: NostrEvent) => 
        e.kind !== KIND.DISCUSSION_THREAD && 
        getKindInfo(e.kind).showInFeed === true
      );
      
      // Use Map-based deduplication to ensure no duplicates
      const existingIds = new Set(events.map(e => e.id));
      const eventsMap = new Map<string, NostrEvent>();
      
      // Add all existing events first
      for (const event of events) {
        eventsMap.set(event.id, event);
      }
      
      // Track which events are actually new
      const newEvents: NostrEvent[] = [];
      
      // Add new events (will only add if not already present)
      for (const event of filteredEvents) {
        if (!eventsMap.has(event.id)) {
          eventsMap.set(event.id, event);
          newEvents.push(event);
        }
      }
      
      // Only update if we have new events
      if (newEvents.length > 0) {
        // Convert to array and sort
        events = Array.from(eventsMap.values()).sort((a, b) => b.created_at - a.created_at);
        const allTimestamps = events.map(e => e.created_at);
        oldestTimestamp = Math.min(...allTimestamps);
        // Load parent/quoted events in background, don't await
        // Only load if not already loading to prevent cascading fetches
        if (!loadingParents) {
          loadParentAndQuotedEvents(newEvents).catch(err => {
            console.error('Error loading parent/quoted events for new events:', err);
          });
        }
      }
      
      hasMore = fetchedEvents.length >= 20;
    } catch (error) {
      console.error('Error loading more:', error);
    } finally {
      loadingMore = false;
    }
  }

  async function loadParentAndQuotedEvents(postsToLoad: NostrEvent[]) {
    if (!isMounted || postsToLoad.length === 0 || loadingParents) return;
    
    loadingParents = true;
    try {
      const relays = singleRelay ? [singleRelay] : relayManager.getFeedReadRelays();
      const parentEventIds = new Set<string>();
      const quotedEventIds = new Set<string>();
      
      for (const post of postsToLoad) {
        const replyTag = post.tags.find((t) => t[0] === 'e' && t[3] === 'reply');
        if (replyTag && replyTag[1]) {
          parentEventIds.add(replyTag[1]);
        } else {
          const rootId = post.tags.find((t) => t[0] === 'root')?.[1];
          const eTag = post.tags.find((t) => t[0] === 'e' && t[1] !== rootId && t[1] !== post.id);
          if (eTag && eTag[1]) {
            parentEventIds.add(eTag[1]);
          }
        }
        
        const quotedTag = post.tags.find((t) => t[0] === 'q');
        if (quotedTag && quotedTag[1]) {
          quotedEventIds.add(quotedTag[1]);
        }
      }
      
      const allEventIds = [...parentEventIds, ...quotedEventIds];
      if (allEventIds.length === 0) return;
      
      const fetchedEvents = await nostrClient.fetchEvents(
        [{ kinds: [KIND.SHORT_TEXT_NOTE], ids: allEventIds }],
        relays,
        singleRelay ? {
          relayFirst: true,
          useCache: false,
          cacheResults: false,
          timeout: 3000
        } : {
          relayFirst: true,
          useCache: true,
          cacheResults: true,
          timeout: 3000
        }
      );
      
      if (!isMounted) return;
      
      const eventsById = new Map<string, NostrEvent>();
      for (const event of fetchedEvents) {
        eventsById.set(event.id, event);
      }
      
      for (const post of postsToLoad) {
        const replyTag = post.tags.find((t) => t[0] === 'e' && t[3] === 'reply');
        let parentId: string | undefined;
        if (replyTag && replyTag[1]) {
          parentId = replyTag[1];
        } else {
          const rootId = post.tags.find((t) => t[0] === 'root')?.[1];
          const eTag = post.tags.find((t) => t[0] === 'e' && t[1] !== rootId && t[1] !== post.id);
          parentId = eTag?.[1];
        }
        if (parentId && eventsById.has(parentId)) {
          parentEventsMap.set(post.id, eventsById.get(parentId)!);
        }
        
        const quotedTag = post.tags.find((t) => t[0] === 'q');
        if (quotedTag && quotedTag[1] && eventsById.has(quotedTag[1])) {
          quotedEventsMap.set(post.id, eventsById.get(quotedTag[1])!);
        }
      }
    } catch (error) {
      console.error('[FeedPage] Error loading parent/quoted events:', error);
    } finally {
      loadingParents = false;
    }
  }
</script>

<div class="feed-page">
  {#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 relayError}
    <div class="error-state">
      <p class="text-fog-text dark:text-fog-dark-text error-message">{relayError}</p>
    </div>
  {:else if events.length === 0}
    <div class="empty-state">
      <p class="text-fog-text dark:text-fog-dark-text">No posts found.</p>
    </div>
  {:else}
    <div class="feed-posts">
      {#each events as event (event.id)}
        <FeedPost 
          post={event} 
          onOpenEvent={openDrawer} 
          parentEvent={parentEventsMap.get(event.id)}
          quotedEvent={quotedEventsMap.get(event.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,
  .error-state {
    padding: 2rem;
    text-align: center;
  }

  .error-message {
    font-weight: 600;
    color: var(--fog-accent, #64748b);
  }

  :global(.dark) .error-message {
    color: var(--fog-dark-accent, #94a3b8);
  }

  .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);
  }

  /* Limit all images to 600px wide */
  :global(.feed-page img) {
    max-width: 600px;
    height: auto;
  }
</style>
