<script lang="ts">
  import { onMount, tick, untrack } from "svelte";
  import { createVirtualizer, elementScroll } from "@tanstack/svelte-virtual";
  import type { SvelteVirtualizer } from "@tanstack/svelte-virtual";
  import { MessageContainer } from "$lib/components/message";
  import type { Message as MessageType } from "$lib/matrix/types";
  import { auth } from "$lib/stores/matrix";

  interface Props {
    messages: MessageType[];
    onReact?: (messageId: string, emoji: string) => void;
    onToggleReaction?: (
      messageId: string,
      emoji: string,
      reactionEventId: string | null,
    ) => void;
    onEdit?: (message: MessageType) => void;
    onDelete?: (messageId: string) => void;
    onReply?: (message: MessageType) => void;
    onLoadMore?: () => void;
    isLoading?: boolean;
    enableVirtualization?: boolean;
  }

  let {
    messages,
    onReact,
    onToggleReaction,
    onEdit,
    onDelete,
    onReply,
    onLoadMore,
    isLoading = false,
    enableVirtualization = false, // Disabled until we find a Svelte 5-compatible solution
  }: Props = $props();

  let containerRef: HTMLDivElement | undefined = $state();
  let shouldAutoScroll = $state(true);
  let previousMessageCount = $state(0);

  // Filter out deleted/redacted messages (hide them like Discord)
  const allVisibleMessages = $derived(messages.filter((m) => !m.isRedacted));

  // Virtualizer state - managed via subscription
  let virtualizer = $state<SvelteVirtualizer<HTMLDivElement, Element> | null>(
    null,
  );
  let virtualizerCleanup: (() => void) | null = null;

  // Estimate size based on message type
  function estimateSize(index: number): number {
    const msg = allVisibleMessages[index];
    if (!msg) return 80;
    if (msg.type === "image") return 300;
    if (msg.type === "video") return 350;
    if (msg.type === "file" || msg.type === "audio") return 100;
    const lines = Math.ceil((msg.content?.length || 0) / 60);
    return Math.max(60, Math.min(lines * 24 + 40, 400));
  }

  // Create/update virtualizer when container or messages change
  $effect(() => {
    if (
      !containerRef ||
      !enableVirtualization ||
      allVisibleMessages.length === 0
    ) {
      virtualizer = null;
      return;
    }

    // Clean up previous subscription
    if (virtualizerCleanup) {
      virtualizerCleanup();
      virtualizerCleanup = null;
    }

    // Create new virtualizer store
    const store = createVirtualizer({
      count: allVisibleMessages.length,
      getScrollElement: () => containerRef!,
      estimateSize,
      overscan: 5,
      getItemKey: (index) => allVisibleMessages[index]?.eventId ?? index,
      scrollToFn: elementScroll,
    });

    // Subscribe to store updates
    virtualizerCleanup = store.subscribe((v) => {
      virtualizer = v;
    });

    // Cleanup on effect re-run or component destroy
    return () => {
      if (virtualizerCleanup) {
        virtualizerCleanup();
        virtualizerCleanup = null;
      }
    };
  });

  // Get virtual items for rendering (reactive to virtualizer changes)
  const virtualItems = $derived(virtualizer?.getVirtualItems() ?? []);
  const totalSize = $derived(virtualizer?.getTotalSize() ?? 0);

  /**
   * Svelte action for dynamic height measurement
   * Re-measures when images/media finish loading
   */
  function measureRow(node: HTMLElement, index: number) {
    function measure() {
      if (virtualizer) {
        virtualizer.measureElement(node);
      }
    }

    // Initial measurement
    measure();

    // Re-measure when images load
    const images = node.querySelectorAll("img");
    const imageHandlers: Array<() => void> = [];
    images.forEach((img) => {
      if (!img.complete) {
        const handler = () => measure();
        img.addEventListener("load", handler, { once: true });
        img.addEventListener("error", handler, { once: true });
        imageHandlers.push(() => {
          img.removeEventListener("load", handler);
          img.removeEventListener("error", handler);
        });
      }
    });

    // Re-measure when videos load metadata
    const videos = node.querySelectorAll("video");
    const videoHandlers: Array<() => void> = [];
    videos.forEach((video) => {
      if (video.readyState < 1) {
        const handler = () => measure();
        video.addEventListener("loadedmetadata", handler, { once: true });
        videoHandlers.push(() =>
          video.removeEventListener("loadedmetadata", handler),
        );
      }
    });

    return {
      update(newIndex: number) {
        // Re-measure on update
        measure();
      },
      destroy() {
        // Cleanup listeners
        imageHandlers.forEach((cleanup) => cleanup());
        videoHandlers.forEach((cleanup) => cleanup());
      },
    };
  }

  // Track if we're currently loading to prevent scroll jumps
  let isLoadingMore = $state(false);
  let scrollTopBeforeLoad = $state(0);
  let scrollHeightBeforeLoad = $state(0);

  // Check if we should auto-scroll and load more
  function handleScroll() {
    if (!containerRef) return;
    const { scrollTop, scrollHeight, clientHeight } = containerRef;

    // Check if at bottom for auto-scroll
    const distanceToBottom = scrollHeight - scrollTop - clientHeight;
    shouldAutoScroll = distanceToBottom < 100;

    // Check if at top to load more messages (with debounce via isLoadingMore)
    if (scrollTop < 100 && onLoadMore && !isLoading && !isLoadingMore) {
      // Save scroll position before loading
      isLoadingMore = true;
      scrollTopBeforeLoad = scrollTop;
      scrollHeightBeforeLoad = scrollHeight;
      onLoadMore();
    }
  }

  // Restore scroll position after loading older messages
  $effect(() => {
    if (!isLoading && isLoadingMore && containerRef) {
      // Loading finished - restore scroll position
      tick().then(() => {
        if (containerRef) {
          const newScrollHeight = containerRef.scrollHeight;
          const addedHeight = newScrollHeight - scrollHeightBeforeLoad;
          // Adjust scroll to maintain visual position
          containerRef.scrollTop = scrollTopBeforeLoad + addedHeight;
        }
        isLoadingMore = false;
      });
    }
  });

  // Scroll to bottom
  async function scrollToBottom(force = false) {
    if (!containerRef) return;
    if (force || shouldAutoScroll) {
      await tick();
      containerRef.scrollTop = containerRef.scrollHeight;
    }
  }

  // Auto-scroll when new messages arrive (only if at bottom)
  $effect(() => {
    const count = allVisibleMessages.length;

    if (count > previousMessageCount) {
      if (shouldAutoScroll || previousMessageCount === 0) {
        // User is at bottom or first load - scroll to new messages
        scrollToBottom(true);
      }
      // If user is scrolled up, scroll anchoring handles it
    }
    previousMessageCount = count;
  });

  // Initial scroll to bottom
  onMount(() => {
    tick().then(() => {
      scrollToBottom(true);
    });
  });

  // Check if message should be grouped with previous
  function shouldGroup(
    current: MessageType,
    previous: MessageType | null,
  ): boolean {
    if (!previous) return false;
    if (current.sender !== previous.sender) return false;

    // Group if within 5 minutes
    const timeDiff = current.timestamp - previous.timestamp;
    return timeDiff < 5 * 60 * 1000;
  }

  // Check if we need a date separator
  function needsDateSeparator(
    current: MessageType,
    previous: MessageType | null,
  ): boolean {
    if (!previous) return true;

    const currentDate = new Date(current.timestamp).toDateString();
    const previousDate = new Date(previous.timestamp).toDateString();

    return currentDate !== previousDate;
  }

  function formatDateSeparator(timestamp: number): string {
    const date = new Date(timestamp);
    const today = new Date();
    const yesterday = new Date(today);
    yesterday.setDate(yesterday.getDate() - 1);

    if (date.toDateString() === today.toDateString()) {
      return "Today";
    } else if (date.toDateString() === yesterday.toDateString()) {
      return "Yesterday";
    } else {
      return date.toLocaleDateString([], {
        weekday: "long",
        month: "long",
        day: "numeric",
        year:
          date.getFullYear() !== today.getFullYear() ? "numeric" : undefined,
      });
    }
  }

  // Get reply preview for a message
  function getReplyPreview(replyToId: string): {
    senderName: string;
    content: string;
    senderAvatar: string | null;
    hasAttachment: boolean;
  } | null {
    const replyMessage = messages.find((m) => m.eventId === replyToId);
    if (!replyMessage) return null;

    const hasAttachment = ["image", "video", "audio", "file"].includes(
      replyMessage.type,
    );
    let content = replyMessage.content;

    if (hasAttachment && !content) {
      content =
        replyMessage.type === "image"
          ? "Click to see attachment"
          : replyMessage.type === "video"
            ? "Video"
            : replyMessage.type === "audio"
              ? "Audio"
              : "File";
    }

    return {
      senderName: replyMessage.senderName,
      senderAvatar: replyMessage.senderAvatar,
      content: content.slice(0, 50) + (content.length > 50 ? "..." : ""),
      hasAttachment,
    };
  }

  // Scroll to a specific message
  function scrollToMessage(eventId: string) {
    const element = document.getElementById(`message-${eventId}`);
    if (element) {
      element.scrollIntoView({ behavior: "smooth", block: "center" });
      // Highlight briefly
      element.classList.add("bg-primary/20");
      setTimeout(() => element.classList.remove("bg-primary/20"), 2000);
    }
  }
</script>

<div class="relative flex-1 min-h-0">
  <div
    bind:this={containerRef}
    class="h-full overflow-y-auto bg-night"
    onscroll={handleScroll}
  >
    <!-- Load more button -->
    {#if onLoadMore}
      <div class="flex justify-center py-4">
        <button
          class="text-sm text-primary hover:underline disabled:opacity-50"
          onclick={() => onLoadMore?.()}
          disabled={isLoading}
        >
          {isLoading ? "Loading..." : "Load older messages"}
        </button>
      </div>
    {/if}

    <!-- Messages -->
    {#if allVisibleMessages.length === 0}
      <div
        class="flex flex-col items-center justify-center h-full text-light/40"
      >
        <svg
          class="w-16 h-16 mb-4 opacity-50"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="1.5"
        >
          <path
            d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
          />
        </svg>
        <p class="text-lg">No messages yet</p>
        <p class="text-sm">Be the first to send a message!</p>
      </div>
    {:else if virtualizer && enableVirtualization}
      <!-- TanStack Virtual: True DOM recycling -->
      <div class="relative w-full" style="height: {totalSize}px;">
        {#each virtualItems as virtualRow (virtualRow.key)}
          {@const message = allVisibleMessages[virtualRow.index]}
          {@const previousMessage =
            virtualRow.index > 0
              ? allVisibleMessages[virtualRow.index - 1]
              : null}
          {@const isGrouped = shouldGroup(message, previousMessage)}
          {@const showDateSeparator = needsDateSeparator(
            message,
            previousMessage,
          )}

          <div
            class="absolute top-0 left-0 w-full"
            style="transform: translateY({virtualRow.start}px);"
            data-index={virtualRow.index}
            use:measureRow={virtualRow.index}
          >
            <!-- Date separator -->
            {#if showDateSeparator}
              <div class="flex items-center gap-4 px-4 py-2 my-2">
                <div class="flex-1 h-px bg-light/10"></div>
                <span class="text-xs text-light/40 font-medium">
                  {formatDateSeparator(message.timestamp)}
                </span>
                <div class="flex-1 h-px bg-light/10"></div>
              </div>
            {/if}

            <MessageContainer
              {message}
              {isGrouped}
              isOwnMessage={message.sender === $auth.userId}
              currentUserId={$auth.userId || ""}
              onReact={(emoji: string) => onReact?.(message.eventId, emoji)}
              onToggleReaction={(
                emoji: string,
                reactionEventId: string | null,
              ) => onToggleReaction?.(message.eventId, emoji, reactionEventId)}
              onEdit={() => onEdit?.(message)}
              onDelete={() => onDelete?.(message.eventId)}
              onReply={() => onReply?.(message)}
              onScrollToMessage={scrollToMessage}
              replyPreview={message.replyTo
                ? getReplyPreview(message.replyTo)
                : null}
            />
          </div>
        {/each}
      </div>
    {:else}
      <!-- Fallback: Non-virtualized rendering for small lists -->
      <div class="py-4">
        {#each allVisibleMessages as message, i (message.eventId)}
          {@const previousMessage = i > 0 ? allVisibleMessages[i - 1] : null}
          {@const isGrouped = shouldGroup(message, previousMessage)}
          {@const showDateSeparator = needsDateSeparator(
            message,
            previousMessage,
          )}

          <!-- Date separator -->
          {#if showDateSeparator}
            <div class="flex items-center gap-4 px-4 py-2 my-2">
              <div class="flex-1 h-px bg-light/10"></div>
              <span class="text-xs text-light/40 font-medium">
                {formatDateSeparator(message.timestamp)}
              </span>
              <div class="flex-1 h-px bg-light/10"></div>
            </div>
          {/if}

          <MessageContainer
            {message}
            {isGrouped}
            isOwnMessage={message.sender === $auth.userId}
            currentUserId={$auth.userId || ""}
            onReact={(emoji: string) => onReact?.(message.eventId, emoji)}
            onToggleReaction={(emoji: string, reactionEventId: string | null) =>
              onToggleReaction?.(message.eventId, emoji, reactionEventId)}
            onEdit={() => onEdit?.(message)}
            onDelete={() => onDelete?.(message.eventId)}
            onReply={() => onReply?.(message)}
            onScrollToMessage={scrollToMessage}
            replyPreview={message.replyTo
              ? getReplyPreview(message.replyTo)
              : null}
          />
        {/each}
      </div>
    {/if}
  </div>

  <!-- Scroll to bottom button -->
  {#if !shouldAutoScroll && allVisibleMessages.length > 0}
    <button
      class="absolute bottom-4 right-4 p-3 bg-primary text-white rounded-full shadow-lg
             hover:bg-primary/90 transition-all transform hover:scale-105
             animate-in fade-in slide-in-from-bottom-2 duration-200"
      onclick={() => scrollToBottom(true)}
      title="Scroll to bottom"
    >
      <svg
        class="w-5 h-5"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
      >
        <polyline points="6,9 12,15 18,9" />
      </svg>
    </button>
  {/if}
</div>
