'use client';

/**
 * Virtualized message list with date dividers.
 */

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

interface MessageListProps {
  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;
}

interface MessageRow {
  type: 'divider' | 'message';
  label?: string;
  message?: MessageWithMeta;
  grouped?: boolean;
  replyMessage?: MessageWithMeta | null;
  key: string;
}

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

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}`;
}

function estimateMessageRowHeight(message: MessageWithMeta): number {
  const lineCount = message.content.split(/\r?\n/).reduce((total, line) => {
    return total + Math.max(1, Math.ceil(line.trim().length / 90));
  }, 0);
  const baseHeight = 24;
  const lineHeight = 20;
  const replyHeight = message.in_reply_to ? 36 : 0;
  return baseHeight + Math.min(lineCount, 5) * lineHeight + replyHeight;
}

const ESTIMATED_DIVIDER_HEIGHT = 30;

export const MessageList = memo(function MessageList({
  roomId,
  messages,
  messagesEndRef,
  onInlineEdit,
  onViewHistory,
  onRevoke,
  onReply,
  onMention,
  onOpenUserCard,
  onLoadMore,
  hasMore = false,
  isLoadingMore = false,
  onOpenThread,
  onCreateThread,
}: MessageListProps) {
  const scrollContainerRef = useRef<HTMLDivElement>(null);
  const topSentinelRef = useRef<HTMLDivElement>(null);
  const prevScrollHeightRef = useRef<number | null>(null);
  const [showScrollToBottom, setShowScrollToBottom] = useState(false);
  const scrollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const isRestoringScrollRef = useRef(false);
  const firstVisibleMessageIdRef = useRef<string | null>(null);
  const isInitialLoadRef = useRef(true);
  const wasNearBottomRef = useRef(true);

  // Reset initial load flag when switching rooms
  useEffect(() => {
    isInitialLoadRef.current = true;
    wasNearBottomRef.current = true;
  }, [roomId]);

  const replyMap = useMemo(() => {
    const map = new Map<string, MessageWithMeta>();
    messages.forEach((m) => {
      if (m.id) map.set(m.id, m);
    });
    return map;
  }, [messages]);

  const rows = useMemo<MessageRow[]>(() => {
    const result: MessageRow[] = [];
    let lastDateKey: string | null = null;
    let lastSenderKey: string | null = null;
    let lastMessageTime: number | null = null;
    const GROUP_GAP_MS = 5 * 60 * 1000; // 5 minutes

    for (const message of messages) {
      const dateKey = getDateKey(message.send_at);
      const senderKey = getSenderKey(message);
      const msgTime = new Date(message.send_at).getTime();

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

      // Group if: same sender AND within 5-minute gap (Discord-style)
      const sameSender = senderKey === lastSenderKey;
      const withinTimeGap = lastMessageTime !== null && (msgTime - lastMessageTime) < GROUP_GAP_MS;
      const grouped = sameSender && withinTimeGap;

      result.push({
        type: 'message',
        message,
        grouped,
        replyMessage: message.in_reply_to ? replyMap.get(message.in_reply_to) ?? null : null,
        key: message.id,
      });
      lastSenderKey = senderKey;
      lastMessageTime = msgTime;
    }
    return result;
  }, [messages, replyMap]);

  const scrollToBottom = useCallback((smooth = true) => {
    const container = scrollContainerRef.current;
    if (container) {
      container.scrollTo({ top: container.scrollHeight, behavior: smooth ? 'smooth' : 'auto' });
    }
  }, []);

  const handleScroll = useCallback(() => {
    const container = scrollContainerRef.current;
    if (!container) return;

    const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
    const nearBottom = distanceFromBottom < 100;
    wasNearBottomRef.current = nearBottom;

    requestAnimationFrame(() => {
      setShowScrollToBottom(!nearBottom);
    });

    if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
    scrollTimeoutRef.current = setTimeout(() => {
      const c = scrollContainerRef.current;
      if (c) {
        const dist = c.scrollHeight - c.scrollTop - c.clientHeight;
        if (dist < 100) setShowScrollToBottom(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]);

  useEffect(() => {
    if (messages.length === 0) return;
    const container = scrollContainerRef.current;
    if (!container) return;

    // On initial load, jump to bottom instantly (no animation)
    if (isInitialLoadRef.current) {
      isInitialLoadRef.current = false;
      wasNearBottomRef.current = true;
      // Use requestAnimationFrame to wait for virtualizer to layout
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          scrollToBottom(false);
        });
      });
      return;
    }

    // For new messages: auto-scroll only if user was near bottom
    const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
    if (distanceFromBottom < 150) {
      wasNearBottomRef.current = true;
      requestAnimationFrame(() => scrollToBottom(false));
    }
  }, [messages.length, scrollToBottom]);

  const virtualizer = useVirtualizer({
    count: rows.length,
    getScrollElement: () => scrollContainerRef.current,
    estimateSize: (index) => {
      const row = rows[index];
      if (row?.type === 'divider') return ESTIMATED_DIVIDER_HEIGHT;
      if (row?.type === 'message' && row.message) return estimateMessageRowHeight(row.message);
      return 60;
    },
    overscan: 30,
    gap: 0,
  });

  const virtualItems = virtualizer.getVirtualItems();

  // IntersectionObserver for load more
  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) {
          prevScrollHeightRef.current = container.scrollHeight;
          const items = virtualizer.getVirtualItems();
          if (items.length > 0) {
            const firstVisibleRow = rows[items[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, virtualizer]);

  // Maintain scroll position after loading more messages
  useEffect(() => {
    if (!isRestoringScrollRef.current) return;
    const container = scrollContainerRef.current;
    if (!container || prevScrollHeightRef.current === null) {
      isRestoringScrollRef.current = false;
      return;
    }

    const delta = container.scrollHeight - prevScrollHeightRef.current;

    if (firstVisibleMessageIdRef.current) {
      const el = container.querySelector(`[data-message-id="${firstVisibleMessageIdRef.current}"]`);
      if (el) {
        el.scrollIntoView({ block: 'start' });
        prevScrollHeightRef.current = null;
        firstVisibleMessageIdRef.current = null;
        isRestoringScrollRef.current = false;
        return;
      }
    }

    if (delta > 0) container.scrollTop += delta;

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

  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%',
          }}
        >
          <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 top-0 w-full"
                style={{
                  transform: `translateY(${virtualRow.start}px)`,
                }}
              >
                <div
                  ref={(el) => {
                    if (el) virtualizer.measureElement(el);
                  }}
                  id={row.message ? `msg-${row.message.id}` : undefined}
                  data-index={virtualRow.index}
                  data-message-id={row.message?.id}
                >
                  <MessageBubble
                    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>
            );
          })}

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