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

  // Data maps - all data loaded upfront
  let threadsMap = $state<Map<string, NostrEvent>>(new Map()); // threadId -> thread
  let reactionsMap = $state<Map<string, NostrEvent[]>>(new Map()); // threadId -> reactions[]
  let zapReceiptsMap = $state<Map<string, NostrEvent[]>>(new Map()); // threadId -> zapReceipts[]
  let commentsMap = $state<Map<string, NostrEvent[]>>(new Map()); // threadId -> comments[]
  
  let loading = $state(true);
  let sortBy = $state<'newest' | 'active' | 'upvoted'>('newest');
  let showOlder = $state(false);
  let selectedTopic = $state<string | null | undefined>(null); // null = All, undefined = General, string = specific topic
  
  // Thread drawer state
  let drawerOpen = $state(false);
  let selectedEvent = $state<NostrEvent | null>(null);

  // Computed: get sorted and filtered threads from maps
  let threads = $derived.by(() => {
    const allThreads = Array.from(threadsMap.values());
    const sorted = sortThreadsFromMaps(allThreads, sortBy);
    return sorted;
  });

  $effect(() => {
    loadAllData();
  });

  async function loadAllData() {
    loading = true;
    try {
      const config = nostrClient.getConfig();
      const since = showOlder
        ? undefined
        : Math.floor(Date.now() / 1000) - config.threadTimeoutDays * 86400;

      const threadRelays = relayManager.getThreadReadRelays();
      const commentRelays = relayManager.getCommentReadRelays();
      // Use getProfileReadRelays() for reactions to include defaultRelays + profileRelays + user inbox + localRelays
      // This ensures we get all reactions from the complete relay set
      const reactionRelays = relayManager.getProfileReadRelays();
      const zapRelays = relayManager.getZapReceiptReadRelays();

      // Step 1: Load from cache first (immediate display)
      const cachedThreads = await nostrClient.fetchEvents(
        [{ kinds: [KIND.DISCUSSION_THREAD], since, limit: 50 }],
        threadRelays,
        {
          useCache: true,
          cacheResults: false, // Don't cache again, we already have it
          timeout: 100 // Quick timeout for cache-only fetch
        }
      );

      // Build threads map from cache immediately
      const newThreadsMap = new Map<string, NostrEvent>();
      for (const event of cachedThreads) {
        newThreadsMap.set(event.id, event);
      }
      threadsMap = newThreadsMap;
      loading = false; // Show cached data immediately

      // Step 2: Fetch from relays in background and update incrementally
      // Use a Set to track which events we've already processed to avoid loops
      const processedEventIds = new Set<string>(Array.from(newThreadsMap.keys()));
      
      nostrClient.fetchEvents(
        [{ kinds: [KIND.DISCUSSION_THREAD], since, limit: 50 }],
        threadRelays,
        {
          useCache: false, // Force query relays
          cacheResults: true,
          onUpdate: async (updatedEvents) => {
            // Only add new events that aren't already in the map
            let hasNewEvents = false;
            for (const event of updatedEvents) {
              if (!processedEventIds.has(event.id)) {
                newThreadsMap.set(event.id, event);
                processedEventIds.add(event.id);
                hasNewEvents = true;
              } else {
                // Update existing event if this one is newer
                const existing = newThreadsMap.get(event.id);
                if (existing && event.created_at > existing.created_at) {
                  newThreadsMap.set(event.id, event);
                }
              }
            }
            if (hasNewEvents) {
              threadsMap = new Map(newThreadsMap); // Trigger reactivity
            }
          }
        }
      ).then((relayThreads) => {
        // Final update after relay fetch completes
        let hasNewEvents = false;
        for (const event of relayThreads) {
          if (!processedEventIds.has(event.id)) {
            newThreadsMap.set(event.id, event);
            processedEventIds.add(event.id);
            hasNewEvents = true;
          } else {
            // Update existing event if this one is newer
            const existing = newThreadsMap.get(event.id);
            if (existing && event.created_at > existing.created_at) {
              newThreadsMap.set(event.id, event);
            }
          }
        }
        if (hasNewEvents) {
          threadsMap = new Map(newThreadsMap); // Trigger reactivity
        }
      }).catch((error) => {
        console.debug('Background relay fetch error (non-critical):', error);
      });

      // Get all thread IDs (use current threadsMap, not newThreadsMap, since it may have been updated)
      const threadIds = Array.from(threadsMap.keys());

      if (threadIds.length > 0) {
        // Fetch all comments in parallel
        const allComments = await nostrClient.fetchEvents(
          [{ kinds: [KIND.COMMENT], '#E': threadIds, '#K': ['11'] }],
          commentRelays,
          { useCache: true }
        );

        // Fetch all reactions in parallel
        // Note: Some relays reject '#E' filter, so we only use '#e' and handle both cases in grouping
        // Use onUpdate to handle real-time reaction updates
        const allReactionsMap = new Map<string, NostrEvent>();
        
        // Function to process and group reactions (called initially and on updates)
        const processReactionUpdates = async () => {
          const allReactions = Array.from(allReactionsMap.values());
          console.log('[ThreadList] Processing reaction updates, total reactions:', allReactions.length);
          
          if (allReactions.length === 0) return;
          
          // Fetch deletion events for current reactions
          const deletionEvents = await nostrClient.fetchEvents(
            [{ kinds: [KIND.EVENT_DELETION], authors: Array.from(new Set(allReactions.map(r => r.pubkey))) }],
            reactionRelays,
            { useCache: true }
          );
          
          // Build deleted reaction IDs map
          const deletedReactionIdsByPubkey = new Map<string, Set<string>>();
          for (const deletionEvent of deletionEvents) {
            const pubkey = deletionEvent.pubkey;
            if (!deletedReactionIdsByPubkey.has(pubkey)) {
              deletedReactionIdsByPubkey.set(pubkey, new Set());
            }
            for (const tag of deletionEvent.tags) {
              if (tag[0] === 'e' && tag[1]) {
                deletedReactionIdsByPubkey.get(pubkey)!.add(tag[1]);
              }
            }
          }
          
          // Rebuild reactions map
          const updatedReactionsMap = new Map<string, NostrEvent[]>();
          for (const reaction of allReactions) {
            const deletedIds = deletedReactionIdsByPubkey.get(reaction.pubkey);
            if (deletedIds && deletedIds.has(reaction.id)) {
              continue;
            }
            
            const threadId = reaction.tags.find(t => {
              const tagName = t[0];
              return (tagName === 'e' || tagName === 'E') && t[1];
            })?.[1];
            
            if (threadId && newThreadsMap.has(threadId)) {
              if (!updatedReactionsMap.has(threadId)) {
                updatedReactionsMap.set(threadId, []);
              }
              updatedReactionsMap.get(threadId)!.push(reaction);
            }
          }
          
          reactionsMap = updatedReactionsMap;
          console.log('[ThreadList] Updated reactions map:', {
            threadCounts: Array.from(updatedReactionsMap.entries()).map(([threadId, reactions]) => ({
              threadId: threadId.substring(0, 16) + '...',
              count: reactions.length,
              upvotes: reactions.filter(r => {
                const content = r.content.trim();
                return content === '+' || content === '⬆️' || content === '↑';
              }).length,
              reactionEvents: reactions.map(r => ({
                id: r.id.substring(0, 16) + '...',
                pubkey: r.pubkey.substring(0, 16) + '...',
                content: r.content,
                fullEvent: r
              }))
            }))
          });
        };
        
        const handleReactionUpdate = async (updated: NostrEvent[]) => {
          console.log('[ThreadList] Received reaction update:', {
            count: updated.length,
            events: updated.map(r => ({
              id: r.id.substring(0, 16) + '...',
              pubkey: r.pubkey.substring(0, 16) + '...',
              content: r.content,
              tags: r.tags.filter(t => t[0] === 'e' || t[0] === 'E'),
              fullEvent: r
            }))
          });
          for (const r of updated) {
            allReactionsMap.set(r.id, r);
          }
          // Reprocess reactions when updates arrive
          await processReactionUpdates();
        };
        
        const reactionsWithLowerE = await nostrClient.fetchEvents(
          [{ kinds: [KIND.REACTION], '#e': threadIds }],
          reactionRelays,
          { 
            useCache: true,
            onUpdate: handleReactionUpdate
          }
        );
        
        // Try uppercase filter, but some relays reject it - that's okay
        let reactionsWithUpperE: NostrEvent[] = [];
        try {
          reactionsWithUpperE = await nostrClient.fetchEvents(
            [{ kinds: [KIND.REACTION], '#E': threadIds }],
            reactionRelays,
            { 
              useCache: true,
              onUpdate: handleReactionUpdate
            }
          );
        } catch (error) {
          console.log('[ThreadList] Upper case #E filter rejected by relay (this is normal):', error);
        }
        
        console.log('[ThreadList] Reactions fetched:', {
          withLowerE: reactionsWithLowerE.length,
          withUpperE: reactionsWithUpperE.length,
          lowerE_events: reactionsWithLowerE,
          upperE_events: reactionsWithUpperE
        });
        
        // Combine and deduplicate by reaction ID
        for (const r of reactionsWithLowerE) {
          allReactionsMap.set(r.id, r);
        }
        for (const r of reactionsWithUpperE) {
          allReactionsMap.set(r.id, r);
        }
        
        const allReactions = Array.from(allReactionsMap.values());
        console.log('[ThreadList] All reactions (deduplicated):', {
          total: allReactions.length,
          events: allReactions.map(r => ({
            id: r.id.substring(0, 16) + '...',
            pubkey: r.pubkey.substring(0, 16) + '...',
            content: r.content,
            tags: r.tags.filter(t => t[0] === 'e' || t[0] === 'E'),
            created_at: new Date(r.created_at * 1000).toISOString()
          }))
        });

        // Fetch all zap receipts in parallel
        const allZapReceipts = await nostrClient.fetchEvents(
          [{ kinds: [KIND.ZAP_RECEIPT], '#e': threadIds }],
          zapRelays,
          { useCache: true }
        );

        // Build maps
        const newCommentsMap = new Map<string, NostrEvent[]>();
        let newReactionsMap = new Map<string, NostrEvent[]>();
        const newZapReceiptsMap = new Map<string, NostrEvent[]>();
        
        // Process initial reactions (this will set newReactionsMap)
        await processReactionUpdates();
        newReactionsMap = reactionsMap; // Use the processed reactions map

        // Group comments by thread ID
        for (const comment of allComments) {
          const threadId = comment.tags.find(t => t[0] === 'E' || t[0] === 'e')?.[1];
          if (threadId && newThreadsMap.has(threadId)) {
            if (!newCommentsMap.has(threadId)) {
              newCommentsMap.set(threadId, []);
            }
            newCommentsMap.get(threadId)!.push(comment);
          }
        }

        // Reactions are already processed by processReactionUpdates() above
        // newReactionsMap is now set from reactionsMap

        // Group zap receipts by thread ID
        for (const zapReceipt of allZapReceipts) {
          const threadId = zapReceipt.tags.find(t => t[0] === 'e')?.[1];
          if (threadId && newThreadsMap.has(threadId)) {
            if (!newZapReceiptsMap.has(threadId)) {
              newZapReceiptsMap.set(threadId, []);
            }
            newZapReceiptsMap.get(threadId)!.push(zapReceipt);
          }
        }

        commentsMap = newCommentsMap;
        reactionsMap = newReactionsMap;
        zapReceiptsMap = newZapReceiptsMap;
      } else {
        // Clear maps if no threads
        commentsMap = new Map();
        reactionsMap = new Map();
        zapReceiptsMap = new Map();
      }
    } catch (error) {
      console.error('Error loading thread data:', error);
      threadsMap = new Map();
      commentsMap = new Map();
      reactionsMap = new Map();
      zapReceiptsMap = new Map();
    } finally {
      loading = false;
    }
  }

  // Sort threads from the maps (synchronous, no fetching)
  function sortThreadsFromMaps(events: NostrEvent[], sortType: 'newest' | 'active' | 'upvoted'): NostrEvent[] {
    switch (sortType) {
      case 'newest':
        return [...events].sort((a, b) => b.created_at - a.created_at);
      case 'active':
        // Sort by most recent activity (comments, reactions, or zaps)
        const activeSorted = events.map((event) => {
          const comments = commentsMap.get(event.id) || [];
          const reactions = reactionsMap.get(event.id) || [];
          const zapReceipts = zapReceiptsMap.get(event.id) || [];
          
          const lastCommentTime = comments.length > 0
            ? Math.max(...comments.map(c => c.created_at))
            : 0;
          
          const lastReactionTime = reactions.length > 0
            ? Math.max(...reactions.map(r => r.created_at))
            : 0;
          
          const lastZapTime = zapReceipts.length > 0
            ? Math.max(...zapReceipts.map(z => z.created_at))
            : 0;
          
          const lastActivity = Math.max(
            event.created_at,
            lastCommentTime,
            lastReactionTime,
            lastZapTime
          );
          
          return { event, lastActivity };
        });
        
        return activeSorted
          .sort((a, b) => b.lastActivity - a.lastActivity)
          .map(({ event }) => event);
      case 'upvoted':
        // Sort by upvote count
        const upvotedSorted = events.map((event) => {
          const reactions = reactionsMap.get(event.id) || [];
          const upvoteCount = reactions.filter(
            (r) => r.content.trim() === '+' || r.content.trim() === '⬆️' || r.content.trim() === '↑'
          ).length;
          return { event, upvotes: upvoteCount };
        });
        
        return upvotedSorted
          .sort((a, b) => b.upvotes - a.upvotes)
          .map(({ event }) => event);
      default:
        return events;
    }
  }

  /**
   * Filter threads by age (30 days)
   */
  function filterByAge(events: NostrEvent[]): NostrEvent[] {
    if (showOlder) {
      return events; // Show all threads if "show older" is checked
    }
    
    const config = nostrClient.getConfig();
    const cutoffTime = Math.floor(Date.now() / 1000) - config.threadTimeoutDays * 86400;
    return events.filter((t) => t.created_at >= cutoffTime);
  }

  // Get filtered threads (by age and topic) - reactive derived value
  let filteredThreads = $derived.by(() => {
    let filtered = threads;
    
    // Filter by age first
    filtered = filterByAge(filtered);
    
    // Then filter by topic
    // selectedTopic === null means "All" - show all threads
    if (selectedTopic === null) {
      return filtered;
    }
    // selectedTopic === undefined means "General" - show threads without topics
    if (selectedTopic === undefined) {
      return filtered.filter((t) => !t.tags.some((tag) => tag[0] === 't'));
    }
    // selectedTopic is a string - show threads with that topic
    return filtered.filter((t) => t.tags.some((tag) => tag[0] === 't' && tag[1] === selectedTopic));
  });

  function getTopics(): string[] {
    const topicSet = new Set<string>();
    // Use age-filtered threads for topic extraction
    const ageFiltered = filterByAge(threads);
    for (const thread of ageFiltered) {
      const topics = thread.tags.filter((t) => t[0] === 't').map((t) => t[1]);
      topics.forEach((t) => topicSet.add(t));
    }
    return Array.from(topicSet).sort();
  }

  function getTopicsWithCounts(): Array<{ topic: string | null; count: number }> {
    // Use age-filtered threads for counts
    const ageFiltered = filterByAge(threads);
    const topics = getTopics();
    const result: Array<{ topic: string | null; count: number }> = [];
    
    // Add "General" (threads without topics)
    const generalCount = ageFiltered.filter((t) => !t.tags.some((tag) => tag[0] === 't')).length;
    if (generalCount > 0) {
      result.push({ topic: null, count: generalCount });
    }
    
    // Add topics with counts
    for (const topic of topics) {
      const count = ageFiltered.filter((t) => t.tags.some((tag) => tag[0] === 't' && tag[1] === topic)).length;
      if (count > 0) {
        result.push({ topic, count });
      }
    }
    
    return result;
  }

  function openThreadDrawer(event: NostrEvent, e?: MouseEvent) {
    // Don't open drawer if clicking on interactive elements
    if (e) {
      const target = e.target as HTMLElement;
      if (target.closest('button') || target.closest('a') || target.closest('[role="button"]')) {
        return;
      }
    }
    selectedEvent = event;
    drawerOpen = true;
  }

  function closeThreadDrawer() {
    drawerOpen = false;
    selectedEvent = null;
  }

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

<div class="thread-list">
  <!-- Top row: Sorting and Show Older checkbox -->
  <div class="controls mb-4 flex gap-4 items-center flex-wrap">
    <select 
      bind:value={sortBy} 
      class="border border-fog-border dark:border-fog-dark-border bg-fog-post dark:bg-fog-dark-post text-fog-text dark:text-fog-dark-text px-2 py-1 rounded"
    >
      <option value="newest">Newest</option>
      <option value="active">Most Active</option>
      <option value="upvoted">Most Upvoted</option>
    </select>
    
    <label class="text-fog-text dark:text-fog-dark-text flex items-center gap-2">
      <input
        type="checkbox"
        bind:checked={showOlder}
        onchange={() => {
          // If showing older threads, reload to fetch them
          if (showOlder) {
            loadAllData();
          }
        }}
      />
      Show older posts (than 30 days)
    </label>
  </div>

  <!-- Filter by topic buttons -->
  <div class="mb-6">
    <div class="flex flex-wrap gap-2 items-center">
      <span class="text-sm font-semibold text-fog-text dark:text-fog-dark-text mr-2">Filter by topic:</span>
      <button
        onclick={() => (selectedTopic = null)}
        class="px-3 py-1 rounded border transition-colors {selectedTopic === null
          ? 'bg-fog-accent dark:bg-fog-dark-accent text-white border-fog-accent dark:border-fog-dark-accent'
          : 'bg-fog-post dark:bg-fog-dark-post text-fog-text dark:text-fog-dark-text border-fog-border dark:border-fog-dark-border hover:bg-fog-highlight dark:hover:bg-fog-dark-highlight'}"
      >
        All ({filterByAge(threads).length})
      </button>
      {#each getTopicsWithCounts() as { topic, count }}
        <button
          onclick={() => (selectedTopic = topic === null ? undefined : topic)}
          class="px-3 py-1 rounded border transition-colors {selectedTopic === (topic === null ? undefined : topic)
            ? 'bg-fog-accent dark:bg-fog-dark-accent text-white border-fog-accent dark:border-fog-dark-accent'
            : 'bg-fog-post dark:bg-fog-dark-post text-fog-text dark:text-fog-dark-text border-fog-border dark:border-fog-dark-border hover:bg-fog-highlight dark:hover:bg-fog-dark-highlight'}"
        >
          {topic === null ? 'General' : topic} ({count})
        </button>
      {/each}
    </div>
  </div>

  <!-- Thread list -->
  {#if loading}
    <p class="text-fog-text dark:text-fog-dark-text">Loading threads...</p>
  {:else}
    <div>
      {#each filteredThreads as thread}
        <div 
          data-thread-id={thread.id} 
          class="thread-wrapper" 
          onclick={(e) => openThreadDrawer(thread, e)}
          role="button"
          tabindex="0"
          onkeydown={(e) => {
            if (e.key === 'Enter' || e.key === ' ') {
              e.preventDefault();
              openThreadDrawer(thread);
            }
          }}
        >
          <FeedPost 
            post={thread} 
            previewMode={true}
            reactions={reactionsMap.get(thread.id) || []}
          />
        </div>
      {/each}
      {#if filteredThreads.length === 0}
        <p class="text-fog-text-light dark:text-fog-dark-text-light">No threads found.</p>
      {/if}
    </div>
  {/if}
</div>

<ThreadDrawer 
  opEvent={selectedEvent}
  isOpen={drawerOpen}
  onClose={closeThreadDrawer}
/>

<style>
  .thread-list {
    max-width: var(--content-width);
    margin: 0 auto;
    padding: 1rem;
  }

  .thread-wrapper {
    cursor: pointer;
    transition: background 0.2s;
    margin-bottom: 1rem;
  }

  .thread-wrapper:hover {
    background: var(--fog-highlight, #f3f4f6);
    border-radius: 0.25rem;
  }

  :global(.dark) .thread-wrapper:hover {
    background: var(--fog-dark-highlight, #374151);
  }
</style>