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

  interface Props {
    threadId: string; // The event ID
    event?: NostrEvent; // The event itself (optional, used to determine reply types)
  }

  let { threadId, event }: Props = $props();

  let comments = $state<NostrEvent[]>([]);
  let kind1Replies = $state<NostrEvent[]>([]);
  let yakBacks = $state<NostrEvent[]>([]);
  let zapReceipts = $state<NostrEvent[]>([]);
  let loading = $state(true);
  let replyingTo = $state<NostrEvent | null>(null);
  
  const isKind1 = $derived(event?.kind === 1);

  onMount(async () => {
    await nostrClient.initialize();
    loadComments();
  });

  async function loadComments() {
    loading = true;
    try {
      const config = nostrClient.getConfig();
      const relays = relayManager.getCommentReadRelays();
      const feedRelays = relayManager.getFeedReadRelays();
      const allRelays = [...new Set([...relays, ...feedRelays])];
      
      const replyFilters: any[] = [
        { kinds: [9735], '#e': [threadId] }, // Zap receipts
        { kinds: [1244], '#e': [threadId] }, // Yak backs (voice replies)
      ];
      
      // For kind 1 events, also fetch kind 1 replies
      if (isKind1) {
        replyFilters.push({ kinds: [1], '#e': [threadId] });
      }
      
      // For all events, fetch kind 1111 comments
      // For kind 11 threads, use #E and #K tags (NIP-22)
      // For other events, use #e tag
      if (event?.kind === 11) {
        replyFilters.push(
          { kinds: [1111], '#E': [threadId], '#K': ['11'] }, // NIP-22 standard (uppercase)
          { kinds: [1111], '#e': [threadId] } // Fallback (lowercase)
        );
      } else {
        replyFilters.push({ kinds: [1111], '#e': [threadId] });
      }
      
      const allReplies = await nostrClient.fetchEvents(
        replyFilters,
        allRelays,
        { useCache: true, cacheResults: true }
      );

      // Separate by type
      comments = allReplies.filter(e => e.kind === 1111);
      kind1Replies = allReplies.filter(e => e.kind === 1);
      yakBacks = allReplies.filter(e => e.kind === 1244);
      zapReceipts = allReplies.filter(e => e.kind === 9735);

      // Recursively fetch all nested replies
      await fetchNestedReplies();

      // Fetch zap receipts that reference this thread or any comment/reply
      await fetchZapReceipts();
    } catch (error) {
      console.error('Error loading comments:', error);
    } finally {
      loading = false;
    }
  }

  async function fetchNestedReplies() {
    const relays = relayManager.getCommentReadRelays();
    const feedRelays = relayManager.getFeedReadRelays();
    const allRelays = [...new Set([...relays, ...feedRelays])];
    let hasNewReplies = true;
    let iterations = 0;
    const maxIterations = 10; // Prevent infinite loops
    
    // Keep fetching until we have all nested replies
    while (hasNewReplies && iterations < maxIterations) {
      iterations++;
      hasNewReplies = false;
      const allReplyIds = new Set([
        ...comments.map(c => c.id),
        ...kind1Replies.map(r => r.id),
        ...yakBacks.map(y => y.id)
      ]);
      
      if (allReplyIds.size > 0) {
        const nestedFilters: any[] = [
          { kinds: [9735], '#e': Array.from(allReplyIds) }, // Zap receipts
          { kinds: [1244], '#e': Array.from(allReplyIds) }, // Yak backs
        ];
      
        // For kind 1 events, also fetch nested kind 1 replies
        if (isKind1) {
          nestedFilters.push({ kinds: [1], '#e': Array.from(allReplyIds) });
        }
        
        // Fetch nested comments
        if (event?.kind === 11) {
          nestedFilters.push(
            { kinds: [1111], '#E': Array.from(allReplyIds), '#K': ['11'] },
            { kinds: [1111], '#e': Array.from(allReplyIds) }
          );
        } else {
          nestedFilters.push({ kinds: [1111], '#e': Array.from(allReplyIds) });
        }
        
        const nestedReplies = await nostrClient.fetchEvents(
          nestedFilters,
          allRelays,
        { useCache: true, cacheResults: true }
      );
      
        // Add new replies by type
        for (const reply of nestedReplies) {
          if (reply.kind === 1111 && !comments.some(c => c.id === reply.id)) {
          comments.push(reply);
            hasNewReplies = true;
          } else if (reply.kind === 1 && !kind1Replies.some(r => r.id === reply.id)) {
            kind1Replies.push(reply);
            hasNewReplies = true;
          } else if (reply.kind === 1244 && !yakBacks.some(y => y.id === reply.id)) {
            yakBacks.push(reply);
            hasNewReplies = true;
        }
      }
      }
    }
  }


  async function fetchZapReceipts() {
    const config = nostrClient.getConfig();
    const relays = relayManager.getCommentReadRelays();
    const feedRelays = relayManager.getFeedReadRelays();
    const allRelays = [...new Set([...relays, ...feedRelays])];
    
    // Keep fetching until we have all zaps
    let previousCount = -1;
    while (zapReceipts.length !== previousCount) {
      previousCount = zapReceipts.length;
      const allEventIds = new Set([
        threadId, 
        ...comments.map(c => c.id), 
        ...kind1Replies.map(r => r.id),
        ...yakBacks.map(y => y.id),
        ...zapReceipts.map(z => z.id)
      ]);
      
      // Fetch zap receipts that reference thread or any comment/reply/yak/zap
      const zapFilters = [
        {
          kinds: [9735],
          '#e': Array.from(allEventIds) // Zap receipts for thread and all replies
        }
      ];

      const zapEvents = await nostrClient.fetchEvents(
        zapFilters,
        allRelays,
        { useCache: true, cacheResults: true }
      );

      const validZaps = zapEvents.filter(receipt => {
        // Filter by threshold
        const amountTag = receipt.tags.find((t) => t[0] === 'amount');
        if (amountTag && amountTag[1]) {
          const amount = parseInt(amountTag[1], 10);
          return !isNaN(amount) && amount >= config.zapThreshold;
        }
        return false;
      });

      // Add new zap receipts
      const existingZapIds = new Set(zapReceipts.map(z => z.id));
      for (const zap of validZaps) {
        if (!existingZapIds.has(zap.id)) {
          zapReceipts.push(zap);
        }
      }

      // Check if any zaps reference events we don't have
      const missingEventIds = new Set<string>();
      for (const zap of validZaps) {
        const eTag = zap.tags.find((t) => t[0] === 'e');
        if (eTag && eTag[1] && eTag[1] !== threadId) {
          const exists = comments.some(c => c.id === eTag[1])
            || kind1Replies.some(r => r.id === eTag[1])
            || yakBacks.some(y => y.id === eTag[1]);
          if (!exists) {
            missingEventIds.add(eTag[1]);
          }
        }
      }
      
      // Fetch missing events (could be comments, replies, or yak backs)
      if (missingEventIds.size > 0) {
        const missingEvents = await nostrClient.fetchEvents(
          [
            { kinds: [1111], ids: Array.from(missingEventIds) },
            { kinds: [1], ids: Array.from(missingEventIds) },
            { kinds: [1244], ids: Array.from(missingEventIds) }
          ],
          allRelays,
          { useCache: true, cacheResults: true }
        );
        
        for (const event of missingEvents) {
          if (event.kind === 1111 && !comments.some(c => c.id === event.id)) {
            comments.push(event);
          } else if (event.kind === 1 && !kind1Replies.some(r => r.id === event.id)) {
            kind1Replies.push(event);
          } else if (event.kind === 1244 && !yakBacks.some(y => y.id === event.id)) {
            yakBacks.push(event);
          }
        }
        
        // Fetch nested replies to newly found events
        await fetchNestedReplies();
      }
    }
  }


  function sortThreadItems(items: Array<{ event: NostrEvent; type: 'comment' | 'reply' | 'yak' | 'zap' }>): Array<{ event: NostrEvent; type: 'comment' | 'reply' | 'yak' | 'zap' }> {
    // Build thread structure similar to feed
    const eventMap = new Map<string, { event: NostrEvent; type: 'comment' | 'reply' | 'yak' | 'zap' }>();
    const replyMap = new Map<string, string[]>(); // parentId -> childIds[]
    const rootItems: Array<{ event: NostrEvent; type: 'comment' | 'reply' | 'yak' | 'zap' }> = [];
    const allEventIds = new Set<string>();

    // First pass: build event map and collect all event IDs
    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) {
      // Check if this is a reply - check both uppercase E (NIP-22) and lowercase e tags
      const eTag = item.event.tags.find((t) => t[0] === 'E') || item.event.tags.find((t) => t[0] === 'e' && t[1] !== item.event.id);
      const parentId = eTag?.[1];
      
      if (parentId) {
        // Check if parent is the thread or another reply we have
        if (parentId === threadId || allEventIds.has(parentId)) {
        // This is a reply
        if (!replyMap.has(parentId)) {
          replyMap.set(parentId, []);
        }
        replyMap.get(parentId)!.push(item.event.id);
      } else {
          // Parent not found - treat as root item (might be a missing parent)
          rootItems.push(item);
        }
      } else {
        // No parent tag - this is a root item (direct reply to thread)
        rootItems.push(item);
      }
    }

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

    function addThread(item: { event: NostrEvent; type: 'comment' | 'reply' | 'yak' | 'zap' }) {
      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' | 'zap' } => item !== undefined)
        .sort((a, b) => a.event.created_at - b.event.created_at); // Sort replies chronologically
      
      for (const reply of replyItems) {
        addThread(reply);
      }
    }

    // Add all root items 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' | 'zap' }> {
    const items: Array<{ event: NostrEvent; type: 'comment' | 'reply' | 'yak' | 'zap' }> = [
      ...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 })),
      ...zapReceipts.map(z => ({ event: z, type: 'zap' as const }))
    ];
    return sortThreadItems(items);
  }

  function getParentEvent(event: NostrEvent): NostrEvent | undefined {
    // NIP-22: E tag (uppercase) points to parent event, or lowercase e tag
    const eTag = event.tags.find((t) => t[0] === 'E') || event.tags.find((t) => t[0] === 'e' && t[1] !== event.id);
    if (eTag && eTag[1]) {
      // Find parent in comments, replies, yak backs, or zap receipts
      const parent = comments.find((c) => c.id === eTag[1]) 
        || kind1Replies.find((r) => r.id === eTag[1])
        || yakBacks.find((y) => y.id === eTag[1])
        || zapReceipts.find((z) => z.id === eTag[1]);
      if (parent) return parent;
      
      // If parent not found, it might be the thread itself
      return undefined;
    }
    return undefined;
  }

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

  function handleCommentPublished() {
    replyingTo = null;
    loadComments();
  }
</script>

<div class="comment-thread">
  <h2 class="text-xl font-bold mb-4">Comments</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 && zapReceipts.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}
          />
        {:else if item.type === 'reply'}
          <!-- Kind 1 reply - render as FeedPost -->
          <div class="kind1-reply mb-4">
            <FeedPost post={item.event} />
          </div>
        {:else if item.type === 'yak'}
          <!-- Yak back (kind 1244) - render as FeedPost -->
          <div class="yak-back mb-4">
            <FeedPost post={item.event} />
          </div>
        {:else if item.type === 'zap'}
          <ZapReceiptReply
            zapReceipt={item.event}
            parentEvent={parent}
            onReply={handleReply}
          />
        {/if}
      {/each}
    </div>
  {/if}

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

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

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