<script lang="ts">
  import { nostrClient } from '../../services/nostr/nostr-client.js';
  import { relayManager } from '../../services/nostr/relay-manager.js';
  import { config } from '../../services/nostr/config.js';
  import DiscussionCard from './DiscussionCard.svelte';
  import type { NostrEvent } from '../../types/nostr.js';
  import { onMount } from 'svelte';
  import { KIND } from '../../types/kind-lookup.js';
  import { getRecentCachedEvents } from '../../services/cache/event-cache.js';
  import { nip19 } from 'nostr-tools';
  import { goto } from '$app/navigation';

  interface Props {
    filterResult?: { type: 'event' | 'pubkey' | 'text' | null; value: string | null };
  }

  let { filterResult = { type: null, value: null } }: Props = $props();
  
  // Expose state for parent component
  export { sortBy, showOlder };
  
  // Resolved pubkey from filter (handled by parent component's PubkeyFilter)
  // For now, we'll do basic normalization here since we don't have access to the filter component
  // The parent component should resolve NIP-05 before passing it here
  function normalizeToHex(input: string): string | null {
    const trimmed = input.trim();
    if (!trimmed) return null;

    if (/^[a-fA-F0-9]{64}$/.test(trimmed)) {
      return trimmed.toLowerCase();
    }

    try {
      const decoded = nip19.decode(trimmed);
      if (decoded.type === 'npub') {
        return decoded.data as string;
      } else if (decoded.type === 'nprofile') {
        return (decoded.data as any).pubkey;
      }
    } catch {
      // Not a valid bech32
    }

    return null;
  }

  // Data maps - threads and stats for sorting only (DiscussionCard loads its own stats for display)
  let threadsMap = $state<Map<string, NostrEvent>>(new Map()); // threadId -> thread
  let reactionsMap = $state<Map<string, NostrEvent[]>>(new Map()); // threadId -> reactions[] (for sorting only)
  let zapReceiptsMap = $state<Map<string, NostrEvent[]>>(new Map()); // threadId -> zapReceipts[] (for sorting only)
  let commentsMap = $state<Map<string, number>>(new Map()); // threadId -> commentCount (batch-loaded for display)
  let voteCountsMap = $state<Map<string, { upvotes: number; downvotes: number }>>(new Map()); // threadId -> {upvotes, downvotes} (calculated from reactionsMap)
  let voteCountsReady = $state(false); // Track when vote counts are fully calculated
  
  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
  

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

  // Track if we're currently loading to prevent loops
  let isLoading = $state(false);
  let prevSortBy = $state<'newest' | 'active' | 'upvoted' | null>(null);
  let prevShowOlder = $state<boolean | null>(null);
  let prevSelectedTopic = $state<string | null | undefined | null>(null);
  
  // Cleanup tracking
  let isMounted = $state(true);
  let activeFetchPromises = $state<Set<Promise<any>>>(new Set());

  // Initial load on mount
  onMount(() => {
    isMounted = true;
    prevSortBy = sortBy;
    prevShowOlder = showOlder;
    prevSelectedTopic = selectedTopic;
    // Load cached threads first, then refresh
    loadCachedThreads();
    loadAllData();
  });
  
  // Cleanup on unmount
  $effect(() => {
    return () => {
      isMounted = false;
      
      // Cancel all active fetch promises
      activeFetchPromises.clear();
      
      // Clear any pending operations
      isLoading = false;
      loading = false;
    };
  });

  // Only reload when sortBy, showOlder, or selectedTopic changes (after initial values are set)
  $effect(() => {
    // Skip if we haven't set initial values yet (onMount hasn't run)
    if (prevSortBy === null) return;
    
    // Read showOlder to ensure it's tracked by the effect
    const currentShowOlder = showOlder;
    const currentSortBy = sortBy;
    const currentSelectedTopic = selectedTopic;
    
    // Check if any filter parameter actually changed
    if (currentSortBy !== prevSortBy || currentShowOlder !== prevShowOlder || currentSelectedTopic !== prevSelectedTopic) {
      prevSortBy = currentSortBy;
      prevShowOlder = currentShowOlder;
      prevSelectedTopic = currentSelectedTopic;
      
      // Only reload if not already loading
      if (!isLoading) {
        // If showOlder changed, also reload from cache to get older threads
        if (currentShowOlder !== prevShowOlder) {
          loadCachedThreads();
        }
        loadAllData();
      }
    }
  });

  async function loadCachedThreads() {
    if (!isMounted || isLoading) return;
    
    try {
      const config = nostrClient.getConfig();
      const since = showOlder
        ? undefined
        : Math.floor(Date.now() / 1000) - config.threadTimeoutDays * 86400;
      
      // Load cached threads (within 15 minutes)
      const cachedThreads = await getRecentCachedEvents([KIND.DISCUSSION_THREAD], 15 * 60 * 1000, 50);
      
      if (cachedThreads.length > 0 && isMounted) {
        // Filter by time if needed
        let filtered = cachedThreads;
        if (since) {
          filtered = cachedThreads.filter(e => e.created_at >= since);
        }
        
        // Build threads map from cached results
        const newThreadsMap = new Map<string, NostrEvent>();
        for (const event of filtered) {
          newThreadsMap.set(event.id, event);
        }
        
        if (newThreadsMap.size > 0) {
          threadsMap = newThreadsMap;
          loading = false; // Show cached data immediately
        }
      }
    } catch (error) {
      console.error('Error loading cached threads:', error);
    }
  }

  async function loadAllData() {
    if (!isMounted || isLoading) return; // Don't load if unmounted or already loading
    
    // Only show loading spinner if we don't have cached threads
    const hasCachedThreads = threadsMap.size > 0;
    if (!hasCachedThreads) {
      loading = true;
    }
    isLoading = true;
    voteCountsReady = false; // Reset vote counts ready state
    try {
      const config = nostrClient.getConfig();
      const since = showOlder
        ? undefined
        : Math.floor(Date.now() / 1000) - config.threadTimeoutDays * 86400;

      const threadRelays = relayManager.getThreadReadRelays();
      // Use getProfileReadRelays() for reactions to include defaultRelays + profileRelays + user inbox + localRelays
      const reactionRelays = relayManager.getProfileReadRelays();
      const zapRelays = relayManager.getZapReceiptReadRelays();
      const commentRelays = relayManager.getCommentReadRelays();

      // Query relays first with 3-second timeout, then fill from cache if needed
      const fetchPromise = nostrClient.fetchEvents(
        [{ kinds: [KIND.DISCUSSION_THREAD], since, limit: config.feedLimit }],
        threadRelays,
        {
          useCache: 'relay-first', // Query relays first with timeout, fill from cache if relay query returns nothing
          cacheResults: true, // Cache the results
          timeout: config.standardTimeout,
          onUpdate: async (updatedEvents) => {
            if (!isMounted) return; // Don't update if unmounted
            // Update incrementally as events arrive
            const newThreadsMap = new Map(threadsMap);
            let hasNewEvents = false;
            for (const event of updatedEvents) {
              const existing = newThreadsMap.get(event.id);
              if (!existing) {
                newThreadsMap.set(event.id, event);
                hasNewEvents = true;
              } else if (event.created_at > existing.created_at) {
                // Update existing event if this one is newer
                newThreadsMap.set(event.id, event);
                hasNewEvents = true;
              }
            }
            if (hasNewEvents && isMounted) {
              threadsMap = newThreadsMap; // Trigger reactivity
            }
          }
        }
      );
      activeFetchPromises.add(fetchPromise);
      const relayThreads = await fetchPromise;
      activeFetchPromises.delete(fetchPromise);
      
      if (!isMounted) return; // Don't process if unmounted

      // Merge with existing cached threads
      const newThreadsMap = new Map(threadsMap);
      for (const event of relayThreads) {
        // Update or add new threads
        const existing = newThreadsMap.get(event.id);
        if (!existing || event.created_at > existing.created_at) {
          newThreadsMap.set(event.id, event);
        }
      }
      threadsMap = newThreadsMap;
      loading = false; // Show data immediately

      // Get all thread IDs for loading stats (for sorting only - DiscussionCard loads its own for display)
      const threadIds = Array.from(threadsMap.keys());

      if (threadIds.length > 0) {
        // Load reactions and zaps for sorting purposes only
        // DiscussionCard components will load their own stats for display
        
        // Fetch all reactions in parallel (for sorting)
        const allReactionsMap = new Map<string, NostrEvent>();
        
        const processReactionUpdates = async () => {
          const allReactions = Array.from(allReactionsMap.values());
          if (allReactions.length === 0) return;
          if (!isMounted) return;
          
          // Fetch deletion events for specific reaction IDs only
          const reactionIds = allReactions.map(r => r.id);
          const deletionFetchPromise = nostrClient.fetchEvents(
            [{ kinds: [KIND.EVENT_DELETION], '#e': reactionIds, limit: config.feedLimit }],
            reactionRelays,
            { useCache: 'relay-first', cacheResults: true, timeout: config.standardTimeout }
          );
          activeFetchPromises.add(deletionFetchPromise);
          const deletionEvents = await deletionFetchPromise;
          activeFetchPromises.delete(deletionFetchPromise);
          
          if (!isMounted) return;
          
          // Build deleted reaction IDs set
          const deletedReactionIds = new Set<string>();
          for (const deletionEvent of deletionEvents) {
            for (const tag of deletionEvent.tags) {
              if (tag[0] === 'e' && tag[1]) {
                deletedReactionIds.add(tag[1]);
              }
            }
          }
          
          // Rebuild reactions map (for sorting only)
          const updatedReactionsMap = new Map<string, NostrEvent[]>();
          for (const reaction of allReactions) {
            if (deletedReactionIds.has(reaction.id)) continue;
            
            const threadId = reaction.tags.find(t => {
              const tagName = t[0];
              return (tagName === 'e' || tagName === 'E') && t[1];
            })?.[1];
            
            if (threadId && threadsMap.has(threadId)) {
              if (!updatedReactionsMap.has(threadId)) {
                updatedReactionsMap.set(threadId, []);
              }
              updatedReactionsMap.get(threadId)!.push(reaction);
            }
          }
          
          if (isMounted) {
            reactionsMap = updatedReactionsMap;
            // Don't update vote counts during real-time updates - only show after initial load
          }
        };
        
        const handleReactionUpdate = async (updated: NostrEvent[]) => {
          if (!isMounted) return;
          for (const r of updated) {
            allReactionsMap.set(r.id, r);
          }
          await processReactionUpdates();
        };
        
        if (!isMounted) return;
        
        // Fetch reactions with lowercase e
        const reactionsFetchPromise1 = nostrClient.fetchEvents(
          [{ kinds: [KIND.REACTION], '#e': threadIds, limit: config.feedLimit }],
          reactionRelays,
          { 
            useCache: 'relay-first',
            cacheResults: true,
            timeout: config.standardTimeout,
            onUpdate: handleReactionUpdate
          }
        );
        activeFetchPromises.add(reactionsFetchPromise1);
        const reactionsWithLowerE = await reactionsFetchPromise1;
        activeFetchPromises.delete(reactionsFetchPromise1);
        
        if (!isMounted) return;
        
        // Try uppercase filter
        let reactionsWithUpperE: NostrEvent[] = [];
        try {
          const reactionsFetchPromise2 = nostrClient.fetchEvents(
            [{ kinds: [KIND.REACTION], '#E': threadIds, limit: config.feedLimit }],
            reactionRelays,
            { 
              useCache: 'relay-first',
              cacheResults: true,
              timeout: config.standardTimeout,
              onUpdate: handleReactionUpdate
            }
          );
          activeFetchPromises.add(reactionsFetchPromise2);
          reactionsWithUpperE = await reactionsFetchPromise2;
          activeFetchPromises.delete(reactionsFetchPromise2);
          if (!isMounted) return;
        } catch (error) {
          if (isMounted) {
            console.log('[DiscussionList] Upper case #E filter rejected by relay (this is normal):', error);
          }
        }
        
        // Combine reactions
        for (const r of reactionsWithLowerE) {
          allReactionsMap.set(r.id, r);
        }
        for (const r of reactionsWithUpperE) {
          allReactionsMap.set(r.id, r);
        }
        
        // Process reactions
        await processReactionUpdates();
        
        // Calculate vote counts from reactions for preview cards (only after initial load is complete)
        updateVoteCountsMap();
        voteCountsReady = true;
        
        // Fetch zap receipts (for sorting)
        if (!isMounted) return;
        const zapFetchPromise = nostrClient.fetchEvents(
          [{ kinds: [KIND.ZAP_RECEIPT], '#e': threadIds, limit: config.feedLimit }],
          zapRelays,
          { useCache: 'relay-first', cacheResults: true, timeout: config.standardTimeout }
        );
        activeFetchPromises.add(zapFetchPromise);
        const allZapReceipts = await zapFetchPromise;
        activeFetchPromises.delete(zapFetchPromise);
        
        if (!isMounted) return;
        
        // Group zap receipts by thread ID (for sorting)
        const newZapReceiptsMap = new Map<string, NostrEvent[]>();
        for (const zapReceipt of allZapReceipts) {
          const threadId = zapReceipt.tags.find((t: string[]) => t[0] === 'e')?.[1];
          if (threadId && threadsMap.has(threadId)) {
            if (!newZapReceiptsMap.has(threadId)) {
              newZapReceiptsMap.set(threadId, []);
            }
            newZapReceiptsMap.get(threadId)!.push(zapReceipt);
          }
        }
        zapReceiptsMap = newZapReceiptsMap;
        
        // Batch-load comment counts for all threads
        if (!isMounted) return;
        const commentsFetchPromise = nostrClient.fetchEvents(
          [{ kinds: [KIND.COMMENT], '#E': threadIds, '#K': ['11'], limit: config.feedLimit }],
          commentRelays,
          { useCache: 'relay-first', cacheResults: true, timeout: config.standardTimeout, priority: 'low' }
        );
        activeFetchPromises.add(commentsFetchPromise);
        const allComments = await commentsFetchPromise;
        activeFetchPromises.delete(commentsFetchPromise);
        
        if (!isMounted) return;
        
        // Count comments per thread
        const newCommentsMap = new Map<string, number>();
        for (const comment of allComments) {
          const threadId = comment.tags.find((t: string[]) => {
            const tagName = t[0];
            return (tagName === 'e' || tagName === 'E') && t[1];
          })?.[1];
          
          if (threadId && threadsMap.has(threadId)) {
            newCommentsMap.set(threadId, (newCommentsMap.get(threadId) || 0) + 1);
          }
        }
        
        // Set count to 0 for threads with no comments
        for (const threadId of threadIds) {
          if (!newCommentsMap.has(threadId)) {
            newCommentsMap.set(threadId, 0);
          }
        }
        commentsMap = newCommentsMap;
      }
    } catch (error) {
      console.error('Error loading thread data:', error);
      threadsMap = new Map();
      voteCountsReady = false;
    } finally {
      loading = false;
      isLoading = false;
    }
  }

  // Calculate vote counts from reactionsMap for preview cards
  function updateVoteCountsMap() {
    const newVoteCountsMap = new Map<string, { upvotes: number; downvotes: number }>();
    for (const threadId of threadsMap.keys()) {
      const threadReactions = reactionsMap.get(threadId) || [];
      let upvotes = 0;
      let downvotes = 0;
      for (const reaction of threadReactions) {
        const content = reaction.content.trim();
        if (content === '+' || content === '⬆️' || content === '↑') {
          upvotes++;
        } else if (content === '-' || content === '⬇️' || content === '↓') {
          downvotes++;
        }
      }
      newVoteCountsMap.set(threadId, { upvotes, downvotes });
    }
    voteCountsMap = newVoteCountsMap;
  }

  // Sort threads using stats loaded for sorting (DiscussionCard loads its own stats for display)
  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 (reactions or zaps)
        const activeSorted = events.map((event) => {
          const reactions = reactionsMap.get(event.id) || [];
          const zapReceipts = zapReceiptsMap.get(event.id) || [];
          
          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,
            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, topic, and filter result) - reactive derived value
  let filteredThreads = $derived.by(() => {
    let filtered = threads;
    
    // Filter by age first
    filtered = filterByAge(filtered);
    
    // Apply filter based on filterResult type
    if (filterResult.type === 'event' && filterResult.value) {
      // Filter by specific event ID
      filtered = filtered.filter(t => t.id === filterResult.value);
    } else if (filterResult.type === 'pubkey' && filterResult.value) {
      // Filter by pubkey (should already be normalized hex)
      const normalizedPubkey = filterResult.value.toLowerCase();
      if (/^[a-f0-9]{64}$/i.test(normalizedPubkey)) {
        filtered = filtered.filter(t => t.pubkey.toLowerCase() === normalizedPubkey);
      }
    } else if (filterResult.type === 'text' && filterResult.value) {
      // Filter by text search (pubkey, p, q, and content fields)
      const queryLower = filterResult.value.toLowerCase();
      filtered = filtered.filter(event => {
        // Search pubkey
        const pubkeyMatch = event.pubkey.toLowerCase().includes(queryLower);
        
        // Search p tags
        const pTagMatch = event.tags.some(tag => 
          tag[0] === 'p' && tag[1]?.toLowerCase().includes(queryLower)
        );
        
        // Search q tags
        const qTagMatch = event.tags.some(tag => 
          tag[0] === 'q' && tag.some((val, idx) => idx > 0 && val?.toLowerCase().includes(queryLower))
        );
        
        // Search content
        const contentMatch = event.content.toLowerCase().includes(queryLower);
        
        return pubkeyMatch || pTagMatch || qTagMatch || contentMatch;
      });
    }
    
    // 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 navigateToEvent(event: NostrEvent, e?: MouseEvent) {
    // Don't navigate 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;
      }
    }
    goto(`/event/${event.id}`);
  }

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

<div class="thread-list">

  <!-- Filter by topic buttons -->
  <div class="mb-6">
    <div class="flex flex-wrap gap-2 items-center">
      <span class="font-semibold text-fog-text dark:text-fog-dark-text mr-2" style="font-size: 0.875em;">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}
        {@const voteCounts = voteCountsMap.get(thread.id) ?? { upvotes: 0, downvotes: 0 }}
        <div 
          data-thread-id={thread.id} 
          class="thread-wrapper" 
          onclick={(e) => navigateToEvent(thread, e)}
          role="button"
          tabindex="0"
          onkeydown={(e) => {
            if (e.key === 'Enter' || e.key === ' ') {
              e.preventDefault();
              navigateToEvent(thread);
            }
          }}
        >
          <DiscussionCard 
            thread={thread} 
            commentCount={commentsMap.get(thread.id) ?? 0}
            upvotes={voteCounts.upvotes}
            downvotes={voteCounts.downvotes}
            votesCalculated={voteCountsReady}
          />
        </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>

<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>