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

  interface Props {
    threadId: string; // The event ID of the root event
    event?: NostrEvent; // The root event itself (optional, used to determine reply types)
    onCommentsLoaded?: (eventIds: string[]) => void; // Callback when comments are loaded
    preloadedReactions?: Map<string, NostrEvent[]>; // Pre-loaded reactions by event ID
    hideCommentForm?: boolean; // If true, don't show the comment form at the bottom
  }

  let { threadId, event, onCommentsLoaded, preloadedReactions, hideCommentForm = false }: Props = $props();

  let comments = $state<NostrEvent[]>([]); // kind 1111
  let kind1Replies = $state<NostrEvent[]>([]); // kind 1 replies (should only be for kind 1 events, but some apps use them for everything)
  let yakBacks = $state<NostrEvent[]>([]); // kind 1244 (voice replies)
  let loading = $state(true);
  let replyingTo = $state<NostrEvent | null>(null);
  let loadingPromise: Promise<void> | null = null; // Track ongoing load to prevent concurrent calls
  let nestedSubscriptionActive = $state(false); // Track if nested subscription is active
  let isProcessingUpdate = $state(false); // Prevent recursive update processing
  
  const isKind1 = $derived(event?.kind === KIND.SHORT_TEXT_NOTE);
  const rootKind = $derived(event?.kind || null);

  // Cleanup tracking
  let isMounted = $state(true);
  let activeFetchPromises = $state<Set<Promise<any>>>(new Set());
  
  // Cleanup on unmount
  $effect(() => {
    return () => {
      isMounted = false;
      activeFetchPromises.clear();
    };
  });
  
  onMount(async () => {
    isMounted = true;
    await nostrClient.initialize();
  });

  // Reload comments when threadId changes
  $effect(() => {
    if (!threadId) {
      // Reset state when threadId is cleared
      comments = [];
      kind1Replies = [];
      yakBacks = [];
      nestedSubscriptionActive = false;
      isProcessingUpdate = false;
      return;
    }
    
    // Prevent concurrent loads
    if (loadingPromise) {
      return;
    }
    
    // Reset subscription flag when loading new thread
    nestedSubscriptionActive = false;
    isProcessingUpdate = false;
    
    // Load comments - filters will adapt based on whether event is available
    // Ensure nostrClient is initialized first
    loadingPromise = nostrClient.initialize().then(() => {
      if (!isMounted) return; // Don't load if unmounted
      return loadComments();
    }).catch((error) => {
      if (isMounted) { // Only log if still mounted
        // Failed to initialize client
      }
      // Still try to load comments even if initialization fails
      if (isMounted) {
        return loadComments();
      }
    }).finally(() => {
      loadingPromise = null;
    });
  });

  /**
   * Get the parent event ID from a reply event
   * For kind 1111: checks both E/e and A/a tags (NIP-22)
   * For kind 1: checks e tag (NIP-10)
   * For kind 1244: checks E/e and A/a tags (follows NIP-22)
   */
  function getParentEventId(replyEvent: NostrEvent): string | null {
    // For kind 1111, check both uppercase and lowercase E and A tags
    if (replyEvent.kind === KIND.COMMENT) {
      // Check uppercase E tag first (NIP-22 standard for root)
      const eTag = replyEvent.tags.find((t) => t[0] === 'E');
      if (eTag && eTag[1]) {
        // If it points to root, check lowercase e for parent
        if (eTag[1] === threadId) {
          const parentETag = replyEvent.tags.find((t) => t[0] === 'e' && t[1] !== threadId);
          if (parentETag && parentETag[1]) return parentETag[1];
        } else {
          // E tag points to parent (non-standard but some clients do this)
          return eTag[1];
        }
      }
      // Check lowercase e tag
      const lowerETag = replyEvent.tags.find((t) => t[0] === 'e' && t[1] !== threadId && t[1] !== replyEvent.id);
      if (lowerETag && lowerETag[1]) return lowerETag[1];
      
      // Check uppercase A tag (NIP-22 for addressable events)
      const aTag = replyEvent.tags.find((t) => t[0] === 'A');
      if (aTag && aTag[1]) {
        // If it points to root, check lowercase a for parent
        const parentATag = replyEvent.tags.find((t) => t[0] === 'a' && t[1] !== aTag[1]);
        if (parentATag && parentATag[1]) {
          // Need to find event by address - for now, check if we have it
          // This is complex, so we'll handle it in the parent lookup
        }
      }
      // Check lowercase a tag
      const lowerATag = replyEvent.tags.find((t) => t[0] === 'a');
      if (lowerATag && lowerATag[1]) {
        // Try to find event with matching address
        // For now, we'll handle this by checking all events
      }
    }
    
    // For kind 1, 1244: check e tag
    if (replyEvent.kind === KIND.SHORT_TEXT_NOTE || replyEvent.kind === KIND.VOICE_REPLY) {
      // For kind 1, check all e tags (NIP-10)
      const eTags = replyEvent.tags.filter((t) => t[0] === 'e' && t[1] && t[1] !== replyEvent.id);
      // Prefer e tag with 'reply' marker, otherwise use first e tag
      const replyTag = eTags.find((t) => t[3] === 'reply');
      if (replyTag && replyTag[1]) return replyTag[1];
      if (eTags.length > 0 && eTags[0][1]) return eTags[0][1];
    }
    
    return null;
  }

  /**
   * Check if a reply event references the root thread
   * For kind 1111: checks both E/e and A/a tags (NIP-22)
   * For other kinds: checks e tag
   */
  function referencesRoot(replyEvent: NostrEvent): boolean {
    if (replyEvent.kind === KIND.COMMENT) {
      // Check uppercase E tag (NIP-22 standard for root)
      const eTag = replyEvent.tags.find((t) => t[0] === 'E');
      if (eTag && eTag[1] === threadId) return true;
      
      // Check lowercase e tag (fallback)
      const lowerETag = replyEvent.tags.find((t) => t[0] === 'e' && t[1] === threadId);
      if (lowerETag) return true;
      
      // Check A and a tags for addressable events
      // If root event has an address (a-tag), check if reply references it
      if (event) {
        const rootATag = event.tags.find((t) => t[0] === 'a');
        if (rootATag && rootATag[1]) {
          const replyATag = replyEvent.tags.find((t) => t[0] === 'A');
          if (replyATag && replyATag[1] === rootATag[1]) return true;
          const replyLowerATag = replyEvent.tags.find((t) => t[0] === 'a' && t[1] === rootATag[1]);
          if (replyLowerATag) return true;
        }
      }
      
      // If no direct reference found, check if parent is root
      // Only include if parent is explicitly the root thread, not if parent is null
      const parentId = getParentEventId(replyEvent);
      return parentId === threadId;
    }
    
    // For other kinds, check e tag
    const eTag = replyEvent.tags.find((t) => t[0] === 'e' && t[1] === threadId);
    return !!eTag;
  }

  function handleReplyUpdate(updated: NostrEvent[]) {
    if (!isMounted) return; // Don't update if unmounted
    // Prevent recursive calls
    if (isProcessingUpdate) {
      return;
    }
    
    // Process immediately - don't batch with requestAnimationFrame for faster UI updates
    isProcessingUpdate = true;
    
    try {
      let hasNewReplies = false;
      const commentsMap = new Map(comments.map(c => [c.id, c]));
      const kind1RepliesMap = new Map(kind1Replies.map(r => [r.id, r]));
      const yakBacksMap = new Map(yakBacks.map(y => [y.id, y]));
      
      for (const reply of updated) {
        // Skip if we already have this reply
        if (commentsMap.has(reply.id) || kind1RepliesMap.has(reply.id) || 
            yakBacksMap.has(reply.id)) {
          continue;
        }
        
        // Check if this reply references the root OR is a reply to any existing comment/reply
        const parentId = getParentEventId(reply);
        const isReplyToRoot = referencesRoot(reply);
        const isReplyToExisting = parentId && (
          parentId === threadId ||
          commentsMap.has(parentId) ||
          kind1RepliesMap.has(parentId) ||
          yakBacksMap.has(parentId)
        );
        
        if (!isReplyToRoot && !isReplyToExisting) {
          continue;
        }
        
        // Add the reply to the appropriate map
        if (reply.kind === KIND.COMMENT) {
          commentsMap.set(reply.id, reply);
          hasNewReplies = true;
        } else if (reply.kind === KIND.SHORT_TEXT_NOTE) {
          kind1RepliesMap.set(reply.id, reply);
          hasNewReplies = true;
        } else if (reply.kind === KIND.VOICE_REPLY) {
          yakBacksMap.set(reply.id, reply);
          hasNewReplies = true;
        }
      }
      
      // Update state immediately if we have new replies
      if (!isMounted) return; // Don't update if unmounted
      
      if (hasNewReplies) {
        const allComments = Array.from(commentsMap.values());
        const allKind1Replies = Array.from(kind1RepliesMap.values());
        const allYakBacks = Array.from(yakBacksMap.values());
        
        // Limit array sizes to prevent memory bloat (keep most recent 500 of each type)
        const MAX_COMMENTS = 500;
        const MAX_REPLIES = 500;
        
        // Sort by created_at descending and take most recent
        comments = allComments
          .sort((a, b) => b.created_at - a.created_at)
          .slice(0, MAX_COMMENTS);
        kind1Replies = allKind1Replies
          .sort((a, b) => b.created_at - a.created_at)
          .slice(0, MAX_REPLIES);
        yakBacks = allYakBacks
          .sort((a, b) => b.created_at - a.created_at)
          .slice(0, MAX_REPLIES);
        
        // Notify parent when comments are loaded
        if (onCommentsLoaded) {
          const allEventIds = [
            ...allComments.map(c => c.id),
            ...allKind1Replies.map(r => r.id),
            ...allYakBacks.map(y => y.id)
          ];
          onCommentsLoaded(allEventIds);
        }
        
        // Clear loading flag as soon as we get the first results
        // This allows comments to render immediately instead of waiting for all fetches
        if (loading) {
          loading = false;
        }
      } else if (updated.length > 0 && loading) {
        // If we got events but they were all filtered out, still clear loading
        // This prevents the UI from being stuck in loading state
        // The events might be nested replies that will be processed later
        loading = false;
      }
    } finally {
      isProcessingUpdate = false;
    }
  }

  async function loadComments() {
    if (!isMounted) return;
    if (!threadId) {
      loading = false;
      return;
    }
    
    // Load from cache first (fast - instant display)
    // Optimized: Batch all kinds into single cache query
    try {
      const { getRecentCachedEvents } = await import('../../services/cache/event-cache.js');
      // Batch all kinds into one call (optimized in getRecentCachedEvents to use single transaction)
      const allCached = await getRecentCachedEvents(
        [KIND.COMMENT, KIND.SHORT_TEXT_NOTE, KIND.VOICE_REPLY], 
        60 * 60 * 1000, 
        config.feedLimit * 4 // Get more since we're filtering by thread
      );
      
      // Filter cached events to only those that reference this thread
      const cachedReplies = allCached.filter(r => referencesRoot(r));
      
      if (cachedReplies.length > 0 && isMounted) {
        // Process cached replies immediately
        handleReplyUpdate(cachedReplies);
        loading = false; // Show cached content immediately
      } else {
        loading = true; // Only show loading if no cache
      }
    } catch (error) {
      // Cache error (non-critical)
      loading = true; // Show loading if cache check fails
    }
    
    const allRelays = relayManager.getProfileReadRelays();
      const replyFilters: any[] = [
      { kinds: [KIND.COMMENT], '#e': [threadId], limit: config.feedLimit },
      { kinds: [KIND.COMMENT], '#E': [threadId], limit: config.feedLimit },
      { kinds: [KIND.COMMENT], '#a': [threadId], limit: config.feedLimit },
      { kinds: [KIND.COMMENT], '#A': [threadId], limit: config.feedLimit },
      { kinds: [KIND.SHORT_TEXT_NOTE], '#e': [threadId], limit: config.feedLimit },
      { kinds: [KIND.VOICE_REPLY], '#e': [threadId], limit: config.feedLimit }
    ];
    
    // Stream fresh data from relays (progressive enhancement)
    try {
      // Use cache-first strategy - already shown cache above, now stream updates
      const fetchPromise = nostrClient.fetchEvents(
        replyFilters,
        allRelays,
        { 
          useCache: 'cache-first', // Already shown cache above, now stream updates
          cacheResults: true, 
          timeout: config.longTimeout,
          onUpdate: handleReplyUpdate, // Stream events as they arrive
          priority: 'high'
        }
      );
      activeFetchPromises.add(fetchPromise);
      const allReplies = await fetchPromise;
      activeFetchPromises.delete(fetchPromise);
      
      if (!isMounted) return; // Don't process if unmounted

      // Process initial results (from relays or cache)
      // Note: onUpdate may have already updated the state and cleared loading
      // But if onUpdate didn't process them (e.g., filtered out), we need to process them here
      const rootReplies = allReplies.filter(reply => referencesRoot(reply));

      // Only update if we have new replies not already processed by onUpdate
      const existingIds = new Set([
        ...comments.map(c => c.id),
        ...kind1Replies.map(r => r.id),
        ...yakBacks.map(y => y.id)
      ]);
      
      const newRootReplies = rootReplies.filter(r => !existingIds.has(r.id));
      
      if (newRootReplies.length > 0) {
        // Merge with existing (onUpdate may have already added some)
        const allComments = [...comments, ...newRootReplies.filter(e => e.kind === KIND.COMMENT)];
        const allKind1Replies = [...kind1Replies, ...newRootReplies.filter(e => e.kind === KIND.SHORT_TEXT_NOTE)];
        const allYakBacks = [...yakBacks, ...newRootReplies.filter(e => e.kind === KIND.VOICE_REPLY)];
        
        // Deduplicate
        comments = Array.from(new Map(allComments.map(c => [c.id, c])).values());
        kind1Replies = Array.from(new Map(allKind1Replies.map(r => [r.id, r])).values());
        yakBacks = Array.from(new Map(allYakBacks.map(y => [y.id, y])).values());
        
        // Notify parent when comments are loaded
        if (onCommentsLoaded) {
          const allEventIds = [
            ...allComments.map(c => c.id),
            ...allKind1Replies.map(r => r.id),
            ...allYakBacks.map(y => y.id)
          ];
          onCommentsLoaded(allEventIds);
        }
      }
      
      // Notify parent after initial load completes (even if no new replies)
      if (onCommentsLoaded && comments.length === 0 && kind1Replies.length === 0 && yakBacks.length === 0) {
        // Still notify with empty array so parent knows loading is complete
        onCommentsLoaded([]);
      } else if (onCommentsLoaded && (comments.length > 0 || kind1Replies.length > 0 || yakBacks.length > 0)) {
        // Notify with all event IDs
        const allEventIds = [
          ...comments.map(c => c.id),
          ...kind1Replies.map(r => r.id),
          ...yakBacks.map(y => y.id)
        ];
        onCommentsLoaded(allEventIds);
      }
      
      // ALWAYS clear loading flag after fetch completes, even if no events matched
      // This prevents the UI from being stuck in loading state
      loading = false;

      // Recursively fetch all nested replies (non-blocking - let it run in background)
      fetchNestedReplies().then(() => {
        subscribeToNestedReplies();
      }).catch((error) => {
        // Failed to fetch nested replies
        subscribeToNestedReplies();
      });

    } catch (error) {
      // Failed to load comments
      loading = false;
    }
  }

  function subscribeToNestedReplies() {
    // Prevent duplicate subscriptions
    if (nestedSubscriptionActive) {
      return;
    }
    
    // Subscribe to replies to any existing comments/replies
    const allRelays = relayManager.getProfileReadRelays();
    const allReplyIds = new Set([
      ...comments.map(c => c.id),
      ...kind1Replies.map(r => r.id),
      ...yakBacks.map(y => y.id)
    ]);
    
    if (allReplyIds.size === 0) {
      return;
    }
    
    nestedSubscriptionActive = true;
    
    // Limit reply IDs to prevent massive subscriptions
    const limitedReplyIds = Array.from(allReplyIds).slice(0, 100);
    
    // Use a single subscription that covers all reply IDs
    const nestedFilters: any[] = [
      { kinds: [KIND.COMMENT], '#e': limitedReplyIds, limit: config.feedLimit },
      { kinds: [KIND.COMMENT], '#E': limitedReplyIds, limit: config.feedLimit },
      { kinds: [KIND.SHORT_TEXT_NOTE], '#e': limitedReplyIds, limit: config.feedLimit },
      { kinds: [KIND.VOICE_REPLY], '#e': limitedReplyIds, limit: config.feedLimit }
    ];
    
    if (!isMounted) return; // Don't subscribe if unmounted
    
    const subscriptionPromise = nostrClient.fetchEvents(
      nestedFilters,
      allRelays,
      {
        useCache: true,
        cacheResults: true,
        onUpdate: handleReplyUpdate,
        priority: 'high'
      }
    );
    activeFetchPromises.add(subscriptionPromise);
    subscriptionPromise.catch(error => {
      if (isMounted) { // Only log if still mounted
        // Failed to subscribe to replies
      }
      nestedSubscriptionActive = false;
    }).finally(() => {
      activeFetchPromises.delete(subscriptionPromise);
    });
  }

  async function fetchNestedReplies() {
    if (!isMounted) return; // Don't fetch if unmounted
    
    // Use all relay sources: profileRelays + defaultRelays + user's inboxes + user's localrelays + cache
    const allRelays = relayManager.getProfileReadRelays();
    let hasNewReplies = true;
    let iterations = 0;
    const maxIterations = 3; // Reduced from 10 to prevent excessive fetching
    const maxReplyIdsPerIteration = 100; // Limit number of reply IDs to check per iteration
    
    while (isMounted && hasNewReplies && iterations < maxIterations) {
      iterations++;
      hasNewReplies = false;
      const allReplyIds = Array.from(new Set([
        ...comments.map(c => c.id),
        ...kind1Replies.map(r => r.id),
        ...yakBacks.map(y => y.id)
      ]));
      
      // Limit the number of reply IDs to prevent massive queries
      const limitedReplyIds = allReplyIds.slice(0, maxReplyIdsPerIteration);
      
      if (limitedReplyIds.length > 0) {
        const nestedFilters: any[] = [
          // Fetch nested kind 1111 comments - check both e/E and a/A tags
          { kinds: [KIND.COMMENT], '#e': limitedReplyIds, limit: config.feedLimit },
          { kinds: [KIND.COMMENT], '#E': limitedReplyIds, limit: config.feedLimit },
          { kinds: [KIND.COMMENT], '#a': limitedReplyIds, limit: config.feedLimit },
          { kinds: [KIND.COMMENT], '#A': limitedReplyIds, limit: config.feedLimit },
          // Fetch nested kind 1 replies
          { kinds: [KIND.SHORT_TEXT_NOTE], '#e': limitedReplyIds, limit: config.feedLimit },
          // Fetch nested yak backs
          { kinds: [KIND.VOICE_REPLY], '#e': limitedReplyIds, limit: config.feedLimit }
        ];
        
        const fetchPromise = nostrClient.fetchEvents(
          nestedFilters,
          allRelays,
          { useCache: true, cacheResults: true, timeout: config.mediumTimeout }
        );
        activeFetchPromises.add(fetchPromise);
        const nestedReplies = await fetchPromise;
        activeFetchPromises.delete(fetchPromise);
        
        if (!isMounted) break; // Exit loop if unmounted
        
        // Add new replies by type
        for (const reply of nestedReplies) {
          if (reply.kind === KIND.COMMENT && !comments.some(c => c.id === reply.id)) {
            comments.push(reply);
            hasNewReplies = true;
          } else if (reply.kind === KIND.SHORT_TEXT_NOTE && !kind1Replies.some(r => r.id === reply.id)) {
            kind1Replies.push(reply);
            hasNewReplies = true;
          } else if (reply.kind === KIND.VOICE_REPLY && !yakBacks.some(y => y.id === reply.id)) {
            yakBacks.push(reply);
            hasNewReplies = true;
          }
        }
      }
    }
  }

  /**
   * Get parent event from any of our loaded events
   */
  function getParentEvent(replyEvent: NostrEvent): NostrEvent | undefined {
    const parentId = getParentEventId(replyEvent);
    if (!parentId) return undefined;
    
    // Check if parent is the root event
    if (parentId === threadId) return event || undefined;
    
    // Find parent in loaded events
    return comments.find(c => c.id === parentId)
      || kind1Replies.find(r => r.id === parentId)
      || yakBacks.find(y => y.id === parentId);
  }

  /**
   * Sort thread items with proper nesting
   */
  function sortThreadItems(items: Array<{ event: NostrEvent; type: 'comment' | 'reply' | 'yak' }>): Array<{ event: NostrEvent; type: 'comment' | 'reply' | 'yak' }> {
    const eventMap = new Map<string, { event: NostrEvent; type: 'comment' | 'reply' | 'yak' }>();
    const replyMap = new Map<string, string[]>(); // parentId -> childIds[]
    const rootItems: Array<{ event: NostrEvent; type: 'comment' | 'reply' | 'yak' }> = [];
    const allEventIds = new Set<string>();

    // First pass: build event map
    for (const item of items) {
      eventMap.set(item.event.id, item);
      allEventIds.add(item.event.id);
    }
      
    // Second pass: determine parent-child relationships
    for (const item of items) {
      const parentId = getParentEventId(item.event);
      if (parentId) {
        if (parentId === threadId) {
          // This is a direct reply to the root OP
          if (!replyMap.has(threadId)) {
            replyMap.set(threadId, []);
          }
          replyMap.get(threadId)!.push(item.event.id);
        } else if (allEventIds.has(parentId)) {
          // This is a reply to another reply
          if (!replyMap.has(parentId)) {
            replyMap.set(parentId, []);
          }
          replyMap.get(parentId)!.push(item.event.id);
        } else {
          // Parent not found - treat as root item (orphaned reply)
          rootItems.push(item);
        }
      } else {
        // No parent - treat as root item (direct reply without parent tag)
        rootItems.push(item);
      }
    }

    // Third pass: recursively collect all items in thread order
    const result: Array<{ event: NostrEvent; type: 'comment' | 'reply' | 'yak' }> = [];
    const processed = new Set<string>();

    function addThread(item: { event: NostrEvent; type: 'comment' | 'reply' | 'yak' }) {
      if (processed.has(item.event.id)) return;
      processed.add(item.event.id);
      
      result.push(item);
      
      // Add all replies to this item
      const replies = replyMap.get(item.event.id) || [];
      const replyItems = replies
        .map(id => eventMap.get(id))
        .filter((item): item is { event: NostrEvent; type: 'comment' | 'reply' | 'yak' } => item !== undefined)
        .sort((a, b) => a.event.created_at - b.event.created_at);
      
      for (const reply of replyItems) {
        addThread(reply);
      }
    }

    // First, add all direct replies to the root OP (threadId)
    const rootReplies = replyMap.get(threadId) || [];
    const rootReplyItems = rootReplies
      .map(id => eventMap.get(id))
      .filter((item): item is { event: NostrEvent; type: 'comment' | 'reply' | 'yak' } => item !== undefined)
      .sort((a, b) => a.event.created_at - b.event.created_at);
    
    for (const reply of rootReplyItems) {
      addThread(reply);
    }

    // Then, add all root items (orphaned replies) sorted by time
    rootItems.sort((a, b) => a.event.created_at - b.event.created_at);
    for (const root of rootItems) {
      addThread(root);
    }

    return result;
  }

  function getThreadItems(): Array<{ event: NostrEvent; type: 'comment' | 'reply' | 'yak' }> {
    const items: Array<{ event: NostrEvent; type: 'comment' | 'reply' | 'yak' }> = [
      ...comments.map(c => ({ event: c, type: 'comment' as const })),
      ...kind1Replies.map(r => ({ event: r, type: 'reply' as const })),
      ...yakBacks.map(y => ({ event: y, type: 'yak' as const }))
    ];
    return sortThreadItems(items);
  }

  function handleReply(replyEvent: NostrEvent) {
    replyingTo = replyEvent;
  }

  async function handleCommentPublished() {
    replyingTo = null;
    // Wait a short delay to allow the comment to propagate to relays
    await new Promise(resolve => setTimeout(resolve, 1500));
    // Reload comments without using cache to ensure we get the new comment
    await loadCommentsFresh();
  }

  async function loadCommentsFresh() {
    if (!isMounted) return;
    if (!threadId) {
      loading = false;
      return;
    }
    
    loading = true;
    try {
      // Use all relay sources: profileRelays + defaultRelays + user's inboxes + user's localrelays
      const allRelays = relayManager.getProfileReadRelays();
      
      const replyFilters: any[] = [];
      
      // Always fetch kind 1111 comments - check both e and E tags, and a and A tags
      replyFilters.push(
        { kinds: [KIND.COMMENT], '#e': [threadId], limit: config.feedLimit }, // Lowercase e tag
        { kinds: [KIND.COMMENT], '#E': [threadId], limit: config.feedLimit }, // Uppercase E tag (NIP-22)
        { kinds: [KIND.COMMENT], '#a': [threadId], limit: config.feedLimit }, // Lowercase a tag (some clients use wrong tags)
        { kinds: [KIND.COMMENT], '#A': [threadId], limit: config.feedLimit }  // Uppercase A tag (NIP-22 for addressable events)
      );
      
      // For kind 1 events, fetch kind 1 replies
      // Also fetch kind 1 replies for any event (some apps use kind 1 for everything)
      replyFilters.push({ kinds: [KIND.SHORT_TEXT_NOTE], '#e': [threadId], limit: config.feedLimit });
      
      // Fetch yak backs (kind 1244) - voice replies
      replyFilters.push({ kinds: [KIND.VOICE_REPLY], '#e': [threadId], limit: config.feedLimit });
      
      // Don't use cache when reloading after publishing - we want fresh data
      // Use high priority to ensure comments load before background fetches
      const allReplies = await nostrClient.fetchEvents(
        replyFilters,
        allRelays,
        { useCache: false, cacheResults: true, timeout: config.longTimeout, priority: 'high' }
      );

      // Filter to only replies that reference the root
      const rootReplies = allReplies.filter(reply => referencesRoot(reply));

      // Separate by type
      comments = rootReplies.filter(e => e.kind === KIND.COMMENT);
      kind1Replies = rootReplies.filter(e => e.kind === KIND.SHORT_TEXT_NOTE);
      yakBacks = rootReplies.filter(e => e.kind === KIND.VOICE_REPLY);

      // Recursively fetch all nested replies (non-blocking - let it run in background)
      fetchNestedReplies().then(() => {
        // After fetching nested replies, set up a single persistent subscription
        // This subscription will handle all future updates for nested replies
        subscribeToNestedReplies();
      }).catch((error) => {
        // Failed to fetch nested replies
        // Still set up subscription even if fetch fails
        subscribeToNestedReplies();
      });

    } catch (error) {
      // Failed to reload comments
    } finally {
      loading = false;
    }
  }

  /**
   * Determine what kind of reply is allowed for a given event
   */
  function getAllowedReplyKind(targetEvent: NostrEvent | null): number {
    if (!targetEvent) {
      // If replying to root, check root kind
      if (isKind1) return KIND.SHORT_TEXT_NOTE;
      return KIND.COMMENT;
    }
    
    // If target is kind 1, allow kind 1 reply
    if (targetEvent.kind === KIND.SHORT_TEXT_NOTE) return KIND.SHORT_TEXT_NOTE;
    
    // Everything else gets kind 1111
    return KIND.COMMENT;
  }

  // Calculate total comment count (includes all reply types)
  const totalCommentCount = $derived.by(() => {
    return getThreadItems().length;
  });
</script>

<div class="comment-thread">
  <h2 class="font-bold mb-4" style="font-size: 1.25em;">
    Comments
    {#if !loading && totalCommentCount > 0}
      <span class="font-normal text-fog-text-light dark:text-fog-dark-text-light ml-2">
        ({totalCommentCount})
      </span>
    {/if}
  </h2>

  {#if loading}
    <p class="text-fog-text-light dark:text-fog-dark-text-light">Loading comments...</p>
  {:else if comments.length === 0 && kind1Replies.length === 0 && yakBacks.length === 0}
    <p class="text-fog-text-light dark:text-fog-dark-text-light">No replies yet. Be the first to reply!</p>
  {:else}
    <div class="comments-list">
      {#each getThreadItems() as item (item.event.id)}
        {@const parent = getParentEvent(item.event)}
        {#if item.type === 'comment'}
          <Comment
            comment={item.event}
            parentEvent={parent}
            onReply={handleReply}
            rootEventKind={rootKind ?? undefined}
            threadId={threadId}
            rootEvent={event}
            onCommentPublished={handleCommentPublished}
          />
        {:else if item.type === 'reply'}
          <!-- Kind 1 reply - render as FeedPost -->
          <div class="kind1-reply mb-4">
            <FeedPost 
              post={item.event} 
              fullView={true}
              preloadedReactions={preloadedReactions?.get(item.event.id)}
            />
          </div>
        {:else if item.type === 'yak'}
          <!-- Yak back (kind 1244) - render as FeedPost -->
          <div class="yak-back mb-4">
            <FeedPost 
              post={item.event} 
              fullView={true}
              preloadedReactions={preloadedReactions?.get(item.event.id)}
            />
          </div>
        {/if}
      {/each}
    </div>
  {/if}

  {#if !hideCommentForm}
    {#if replyingTo}
      <div class="reply-form-container mt-4">
        <CommentForm
          threadId={threadId}
          rootEvent={event}
          parentEvent={replyingTo}
          onPublished={handleCommentPublished}
          onCancel={() => (replyingTo = null)}
        />
      </div>
    {:else}
      <div class="new-comment-container mt-4">
        <CommentForm
          threadId={threadId}
          rootEvent={event}
          onPublished={handleCommentPublished}
        />
      </div>
    {/if}
  {/if}
</div>

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

  .comments-list {
    margin-bottom: 2rem;
  }
</style>
