import type { MessageWithMeta } from '@/contexts';
import { Button } from '@/components/ui/button';
import { ArrowDown, Loader2 } from 'lucide-react';
import { memo, useCallback, useEffect, useMemo, useRef, useState, useTransition, useDeferredValue } from 'react';
import { useVirtualizer } from '@tanstack/react-virtual';
import { RoomMessageBubble } from './RoomMessageBubble';
import { getSenderModelId } from './sender';

interface RoomMessageListProps {
  roomId: string;
  messages: MessageWithMeta[];
  messagesEndRef: React.RefObject<HTMLDivElement | null>;
  onInlineEdit?: (message: MessageWithMeta, newContent: string) => void;
  onViewHistory?: (message: MessageWithMeta) => void;
  onRevoke?: (message: MessageWithMeta) => void;
  onReply?: (message: MessageWithMeta) => void;
  onMention?: (name: string, type: 'user' | 'ai') => void;
  onOpenUserCard?: (payload: {
    username: string;
    displayName?: string | null;
    avatarUrl?: string | null;
    userId: string;
    point: { x: number; y: number };
  }) => void;
  onLoadMore?: () => void;
  hasMore?: boolean;
  isLoadingMore?: boolean;
  onOpenThread?: (message: MessageWithMeta) => void;
  onCreateThread?: (message: MessageWithMeta) => void;
  /** Called with the count of currently rendered (visible) rows whenever it changes. */
  onRenderedCountChange?: (count: number) => void;
}

interface MessageRow {
  type: 'divider' | 'message';
  label?: string;
  message?: MessageWithMeta;
  grouped?: boolean;
  replyMessage?: MessageWithMeta | null;
  /** Unique key for the virtualizer */
  key: string;
}

function getDateKey(iso: string): string {
  return new Date(iso).toDateString();
}

function formatDateDivider(iso: string): string {
  const date = new Date(iso);
  const now = new Date();
  const today = now.toDateString();
  const yesterday = new Date(now.getTime() - 86400000).toDateString();
  const key = date.toDateString();

  if (key === today) return 'Today';
  if (key === yesterday) return 'Yesterday';

  return date.toLocaleDateString(undefined, {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  });
}

function getSenderKey(message: MessageWithMeta): string {
  const userUid = message.sender_id;
  if (userUid) return `user:${userUid}`;
  if (message.sender_type === 'ai') {
    const modelId = getSenderModelId(message);
    if (modelId) return `ai-model:${modelId}`;
    return `ai-streaming:${message.id}`;
  }
  return `sender:${message.sender_type}`;
}

/** Estimated height for a message row in pixels (used as initial guess before measurement) */
const ESTIMATED_ROW_HEIGHT = 40;
/** Estimated height for a date divider row in pixels */
const ESTIMATED_DIVIDER_HEIGHT = 30;

export const RoomMessageList = memo(function RoomMessageList({
  roomId,
  messages,
  messagesEndRef,
  onInlineEdit,
  onViewHistory,
  onRevoke,
  onReply,
  onMention,
  onOpenUserCard,
  onLoadMore,
  hasMore = false,
  isLoadingMore = false,
  onOpenThread,
  onCreateThread,
  onRenderedCountChange,
}: RoomMessageListProps) {
  const scrollContainerRef = useRef<HTMLDivElement>(null);
  const topSentinelRef = useRef<HTMLDivElement>(null);
  const prevScrollHeightRef = useRef<number | null>(null);
  const [showScrollToBottom, setShowScrollToBottom] = useState(false);
  const [isUserScrolling, setIsUserScrolling] = useState(false);
  const scrollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const isRestoringScrollRef = useRef(false);
  const [, startScrollTransition] = useTransition();

  // Record the ID of the first visible message before loading, for more precise scroll position restoration
  const firstVisibleMessageIdRef = useRef<string | null>(null);

  // Defer messages so React can prioritize scroll/interaction state updates.
  // When messages arrive rapidly (e.g. WS stream), React renders the deferred
  // version in a lower-priority work window, preventing scroll jank.
  const deferredMessages = useDeferredValue(messages);

  const messageMap = useMemo(() => {
    const map = new Map<string, MessageWithMeta>();
    deferredMessages.forEach((message) => map.set(message.id, message));
    return map;
  }, [deferredMessages]);

  // Build rows: date dividers + messages
  // Uses deferredMessages so row computation is deprioritized during rapid message updates
  const rows = useMemo<MessageRow[]>(() => {
    const result: MessageRow[] = [];
    let lastDateKey: string | null = null;
    let lastSenderKey: string | null = null;

    for (const message of deferredMessages) {
      const dateKey = getDateKey(message.send_at);
      const senderKey = getSenderKey(message);

      if (dateKey !== lastDateKey) {
        result.push({
          type: 'divider',
          label: formatDateDivider(message.send_at),
          key: `divider-${dateKey}-${message.id}`,
        });
        lastDateKey = dateKey;
        lastSenderKey = null;
      }

      const grouped = senderKey === lastSenderKey;
      result.push({
        type: 'message',
        message,
        grouped,
        replyMessage: message.in_reply_to ? messageMap.get(message.in_reply_to) ?? null : null,
        key: message.id,
      });
      lastSenderKey = senderKey;
    }
    return result;
  }, [deferredMessages, messageMap]);

  const scrollToBottom = useCallback((smooth = true) => {
    messagesEndRef.current?.scrollIntoView({ behavior: smooth ? 'smooth' : 'auto' });
  }, [messagesEndRef]);

  // Track user scroll to detect if user is at bottom.
  // Wrapped in startTransition so React knows these state updates are non-urgent
  // and can be interrupted if a higher-priority update (e.g., new message) comes in.
  const handleScroll = useCallback(() => {
    const container = scrollContainerRef.current;
    if (!container) return;
    const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
    const nearBottom = distanceFromBottom < 100;

    startScrollTransition(() => {
      setShowScrollToBottom(!nearBottom);
      setIsUserScrolling(true);
    });

    // Reset user scrolling flag after a delay
    if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
    scrollTimeoutRef.current = setTimeout(() => {
      setIsUserScrolling(false);
    }, 500);
  }, []);

  useEffect(() => {
    const container = scrollContainerRef.current;
    if (!container) return;
    container.addEventListener('scroll', handleScroll, { passive: true });
    return () => {
      container.removeEventListener('scroll', handleScroll);
      if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
    };
  }, [handleScroll]);

  // Auto-scroll to bottom when new messages arrive (only if user was already at bottom).
  // Uses deferredMessages.length so auto-scroll waits for the deferred render to settle.
  useEffect(() => {
    if (!isUserScrolling && deferredMessages.length > 0) {
      scrollToBottom(false);
    }
  }, [deferredMessages.length, isUserScrolling, scrollToBottom]);

  // Virtualizer
  const virtualizer = useVirtualizer({
    count: rows.length,
    getScrollElement: () => scrollContainerRef.current,
    estimateSize: (index) => {
      const row = rows[index];
      if (row?.type === 'divider') return ESTIMATED_DIVIDER_HEIGHT;
      return ESTIMATED_ROW_HEIGHT;
    },
    overscan: 5,
    gap: 0,
  });

  const virtualItems = virtualizer.getVirtualItems();

  // IntersectionObserver for load more (only when scrolled to top)
  useEffect(() => {
    const sentinel = topSentinelRef.current;
    const container = scrollContainerRef.current;
    if (!sentinel || !container || !onLoadMore) return;

    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0]?.isIntersecting && !isLoadingMore && hasMore) {
          const scrollContainer = scrollContainerRef.current;
          if (scrollContainer) {
            prevScrollHeightRef.current = scrollContainer.scrollHeight;
            // Record the ID of the first visible message
            const virtualItems = virtualizer.getVirtualItems();
            if (virtualItems.length > 0) {
              const firstVisibleRow = rows[virtualItems[0].index];
              if (firstVisibleRow?.type === 'message' && firstVisibleRow.message) {
                firstVisibleMessageIdRef.current = firstVisibleRow.message.id;
              }
            }
          }
          isRestoringScrollRef.current = true;
          onLoadMore();
        }
      },
      { root: container, threshold: 0.1 },
    );

    observer.observe(sentinel);
    return () => observer.disconnect();
  }, [onLoadMore, hasMore, isLoadingMore, rows]);

  // Maintain scroll position after loading more messages
  useEffect(() => {
    // Only run this effect when we're restoring scroll from a load-more operation
    if (!isRestoringScrollRef.current) return;

    const container = scrollContainerRef.current;
    if (!container || prevScrollHeightRef.current === null) {
      isRestoringScrollRef.current = false;
      return;
    }

    const newScrollHeight = container.scrollHeight;
    const delta = newScrollHeight - prevScrollHeightRef.current;

    // Method 1: Try to find the previously recorded first visible message
    if (firstVisibleMessageIdRef.current) {
      const messageElement = container.querySelector(`[data-message-id="${firstVisibleMessageIdRef.current}"]`);
      if (messageElement) {
        // Use scrollIntoView to precisely scroll to the previously visible message
        messageElement.scrollIntoView({ block: 'start' });
        prevScrollHeightRef.current = null;
        firstVisibleMessageIdRef.current = null;
        isRestoringScrollRef.current = false;
        return;
      }
    }

    // Method 2: Fallback to the previous scrollHeight delta method
    if (delta > 0) {
      container.scrollTop += delta;
    }

    prevScrollHeightRef.current = null;
    firstVisibleMessageIdRef.current = null;
    isRestoringScrollRef.current = false;
  }, [deferredMessages.length]);

  // Report rendered count to parent (for performance monitor)
  useEffect(() => {
    onRenderedCountChange?.(virtualItems.length);
  }, [virtualItems.length, onRenderedCountChange]);

  // Empty state
  if (messages.length === 0) {
    return (
      <div className="flex flex-1 items-center justify-center px-6 py-12">
        <div className="rounded-lg border border-dashed border-border/70 bg-muted/20 px-6 py-8 text-center">
          <p className="text-sm font-medium text-foreground">No messages yet</p>
          <p className="mt-1 text-xs text-muted-foreground">Start the conversation below.</p>
        </div>
        <div ref={messagesEndRef} />
      </div>
    );
  }

  return (
    <div className="relative min-h-0 flex-1">
      <div ref={scrollContainerRef} className="h-full overflow-y-auto">
        <div
          className="relative"
          style={{
            height: `${virtualizer.getTotalSize()}px`,
            width: '100%',
          }}
        >
          {/* Top sentinel for load more */}
          <div ref={topSentinelRef} className="absolute top-0 h-1 w-full" />

          {isLoadingMore && (
            <div className="sticky top-0 z-10 flex items-center justify-center bg-background/80 py-3 backdrop-blur-sm">
              <Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
            </div>
          )}

          {virtualItems.map((virtualRow) => {
            const row = rows[virtualRow.index];
            if (!row) return null;

            if (row.type === 'divider') {
              return (
                <div
                  key={row.key}
                  className="absolute left-0 w-full"
                  style={{
                    height: `${virtualRow.size}px`,
                    transform: `translateY(${virtualRow.start}px)`,
                  }}
                >
                  <div className="flex items-center gap-3 px-4 py-2">
                    <div className="h-px flex-1 bg-border/70" />
                    <span className="text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
                      {row.label}
                    </span>
                    <div className="h-px flex-1 bg-border/70" />
                  </div>
                </div>
              );
            }

            return (
              <div
                key={row.key}
                className="absolute left-0 w-full"
                style={{
                  position: 'absolute',
                  top: 0,
                  left: 0,
                  width: '100%',
                  transform: `translateY(${virtualRow.start}px)`,
                }}
              >
                <div
                  ref={(el) => {
                    if (el) virtualizer.measureElement(el);
                  }}
                  data-index={virtualRow.index}
                  data-message-id={row.message?.id}
                >
                  <RoomMessageBubble
                    roomId={roomId}
                    message={row.message!}
                    replyMessage={row.replyMessage ?? null}
                    grouped={row.grouped}
                    showDate={!row.grouped}
                    onInlineEdit={onInlineEdit}
                    onViewHistory={onViewHistory}
                    onRevoke={onRevoke}
                    onReply={onReply}
                    onMention={onMention}
                    onOpenUserCard={onOpenUserCard}
                    onOpenThread={onOpenThread}
                    onCreateThread={onCreateThread}
                  />
                </div>
              </div>
            );
          })}

          {/* Bottom sentinel for auto-scroll */}
          <div
            ref={messagesEndRef}
            className="absolute left-0 w-full"
            style={{
              height: '1px',
              transform: `translateY(${virtualizer.getTotalSize()}px)`,
            }}
          />
        </div>
      </div>

      {showScrollToBottom && (
        <Button
          variant="secondary"
          size="icon"
          className="absolute bottom-4 right-4 z-10 h-9 w-9 rounded-full shadow-md border border-border/70"
          onClick={() => scrollToBottom(true)}
          title="Scroll to bottom"
        >
          <ArrowDown className="h-4 w-4" />
        </Button>
      )}
    </div>
  );
});
