"use client";

import React, { useEffect, useState, useRef, useMemo, useCallback } from "react";
import { SOCKET_URL, getApiBaseUrl } from "../main/BaseUrl";
import { apiClient } from "@/hooks/useAxios";
import { chatThreadQueryKey } from "@/lib/chat/queryKeys";
import {
  appendMessageToThreadCache,
  normalizeThreadId,
} from "@/lib/chat/threadCache";
import ChatMessageCard, { ChatMessage } from "./ChatMessageCard";
import ChatDateSeparator from "./ChatDateSeparator";
import { groupMessagesByDate } from "@/lib/chat/groupMessagesByDate";
import { dedupeChatMessages, filterResolvedPendingMessages } from "@/lib/chat/dedupeMessages";
import { io } from "socket.io-client";
import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
import { User } from "@/types/types";
import { ChatMessagesSkeleton } from "@/components/ui/ChatSkeletons";
import TypingIndicator from "./TypingIndicator";
import IOSSpinner from "@/components/ui/IOSSpinner";
import { AnimatePresence, motion } from "framer-motion";
import { getStoredUserId } from "@/lib/auth/session";
import { useTimedMessageExpiry } from "@/hooks/useTimedMessageExpiry";
import {
  getBubbleGroupPosition,
  sameVisualGroup,
} from "@/lib/chat/messageGrouping";
import { cn } from "@/lib/utils";
import toast from "react-hot-toast";

function belongsToThread(
  msg: ChatMessage,
  userId: string,
  otherId: string
): boolean {
  const s = String(msg.senderId);
  const r = String(msg.receiverId);
  return (
    (s === userId && r === otherId) || (s === otherId && r === userId)
  );
}

interface ChatMessageListProps {
  userDetail: User | undefined;
  userTwoDetail: User | undefined;
  chatPartnerId: string;
  pendingMessages: ChatMessage[];
  isTyping?: boolean;
  onTypingChange?: (typing: boolean) => void;
  searchQuery?: string;
  selectedMessageId?: string;
  onBubbleClick?: (message: ChatMessage) => void;
  onSwipeReply?: (message: ChatMessage) => void;
  actionMode?: boolean;
  onDismissAction?: () => void;
  deleteMode?: boolean;
  selectedDeleteIds?: string[];
  onToggleDeleteSelect?: (message: ChatMessage) => void;
  getStableKey?: (id: string) => string;
  onExpirePending?: (ids: string[]) => void;
  onIncomingMessage?: (message: ChatMessage) => void;
  /** فضای اضافه وقتی بنر پاسخ/زماندار بالای input است */
  extraBottomRem?: number;
}

const ChatMessageList = ({
  userDetail,
  userTwoDetail,
  chatPartnerId,
  pendingMessages,
  isTyping = false,
  onTypingChange,
  searchQuery = "",
  selectedMessageId,
  onBubbleClick,
  onSwipeReply,
  actionMode = false,
  onDismissAction,
  deleteMode = false,
  selectedDeleteIds = [],
  onToggleDeleteSelect,
  getStableKey = (id) => id,
  onExpirePending,
  onIncomingMessage,
  extraBottomRem = 0,
}: ChatMessageListProps) => {
  const scrollRef = useRef<HTMLDivElement | null>(null);
  const prevScrollHeight = useRef(0);
  const prevPendingCountRef = useRef(0);
  const [toEnd, setToEnd] = useState(true);
  const queryClient = useQueryClient();
  const hasInitializedRef = useRef(false);
  const seenMessageKeysRef = useRef(new Set<string>());
  const prevMessageCountRef = useRef(0);
  const messageRefs = useRef(new Map<string, HTMLDivElement>());
  const highlightTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const [highlightedMessageId, setHighlightedMessageId] = useState<string | null>(
    null
  );

  const senderId = normalizeThreadId(userDetail?._id ?? getStoredUserId());
  const receiverId = normalizeThreadId(
    userTwoDetail?._id ?? chatPartnerId
  );
  const threadReady = Boolean(senderId && receiverId);
  const queryKey = chatThreadQueryKey(senderId, receiverId);

  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
    isLoading,
    isError,
    refetch,
  } = useInfiniteQuery({
      queryKey,
      queryFn: async ({ pageParam = 1, queryKey: key }) => {
        const [, sid, rid] = key as ReturnType<typeof chatThreadQueryKey>;
        if (!sid || !rid) {
          return { messages: [], nextPage: undefined };
        }

        try {
          const response = await apiClient.get(
            `/chat?senderId=${sid}&receiverId=${rid}&page=${pageParam}&limit=50`,
            { baseURL: getApiBaseUrl() }
          );
          const msgs = (response.data.messages || []) as ChatMessage[];
          return {
            messages: msgs,
            nextPage:
              pageParam < response.data.totalPages ? pageParam + 1 : undefined,
          };
        } catch (error) {
          console.error("Error fetching messages:", error);
          throw error;
        }
      },
      enabled: threadReady,
      initialPageParam: 1,
      getNextPageParam: (lastPage) => lastPage.nextPage,
      staleTime: 60_000,
      gcTime: 10 * 60 * 1000,
      refetchOnMount: true,
    });

  const hasCachedMessages = Boolean(
    data?.pages?.some((page) => page.messages.length > 0)
  );
  const showSkeleton =
    !threadReady ||
    (isLoading && !hasCachedMessages && pendingMessages.length === 0);

  const scrollToBottom = useCallback((behavior: ScrollBehavior = "auto") => {
    const el = scrollRef.current;
    if (!el) return;
    const run = () => {
      el.scrollTo({ top: el.scrollHeight, behavior });
    };
    requestAnimationFrame(() => requestAnimationFrame(run));
  }, []);

  const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
    if (!scrollRef.current) return;

    const isAtTopOfScroll = e.currentTarget.scrollTop === 0;
    const isAtBottomOfScroll =
      Math.abs(
        e.currentTarget.scrollHeight -
          (e.currentTarget.scrollTop + e.currentTarget.clientHeight)
      ) < 48;

    if (isAtTopOfScroll && hasNextPage && !isFetchingNextPage) {
      prevScrollHeight.current = e.currentTarget.scrollHeight;
      fetchNextPage().then(() => {
        if (scrollRef.current) {
          scrollRef.current.scrollTop =
            scrollRef.current.scrollHeight - prevScrollHeight.current;
        }
      });
    }

    setToEnd(isAtBottomOfScroll);
  };

  useEffect(() => {
    if (!threadReady) return;
    const socket = io(SOCKET_URL, {
      transports: ["websocket", "polling"],
      reconnection: true,
      reconnectionAttempts: Infinity,
      reconnectionDelay: 800,
    });

    // عضویت در روم چت روی هر اتصال/اتصال مجدد
    const joinRoom = () => {
      socket.emit("joinChat", { userId: senderId, receiverId });
      socket.emit("joinUser", { userId: senderId });
    };
    socket.on("connect", joinRoom);
    if (socket.connected) joinRoom();

    const belongs = (msg: ChatMessage) =>
      belongsToThread(msg, senderId, receiverId) ||
      (msg.senderId === senderId && msg.receiverId === receiverId) ||
      (msg.senderId === receiverId && msg.receiverId === senderId);

    socket.on("newMessage", (message: ChatMessage) => {
      if (!belongs(message)) return;

      queryClient.setQueryData(
        chatThreadQueryKey(senderId, receiverId),
        (oldData) => appendMessageToThreadCache(oldData, message)
      );
      onIncomingMessage?.(message);
      setToEnd(true);

      if (message.senderId === receiverId) {
        socket.emit("messageSeen", {
          messageId: message._id,
          senderId: message.senderId,
          receiverId: senderId,
        });
      }
    });

    socket.on(
      "messageStatusUpdate",
      ({ messageId, status }: { messageId: string; status: ChatMessage["status"] }) => {
        queryClient.setQueryData(
          chatThreadQueryKey(senderId, receiverId),
          (oldData: { pages: { messages: ChatMessage[] }[] } | undefined) => {
            if (!oldData) return oldData;
            return {
              ...oldData,
              pages: oldData.pages.map((page) => ({
                ...page,
                messages: page.messages.map((m) =>
                  m._id === messageId ? { ...m, status } : m
                ),
              })),
            };
          }
        );
      }
    );

    let typingHideTimer: ReturnType<typeof setTimeout> | null = null;
    socket.on("userTyping", ({ userId }: { userId: string }) => {
      if (userId === receiverId) {
        onTypingChange?.(true);
        if (typingHideTimer) clearTimeout(typingHideTimer);
        typingHideTimer = setTimeout(() => onTypingChange?.(false), 3500);
      }
    });

    socket.on("userStoppedTyping", ({ userId }: { userId: string }) => {
      if (userId === receiverId) {
        if (typingHideTimer) clearTimeout(typingHideTimer);
        onTypingChange?.(false);
      }
    });

    socket.on(
      "messagesDeleted",
      ({ messageIds }: { messageIds: string[] }) => {
        if (!messageIds?.length) return;
        const idSet = new Set(messageIds);
        queryClient.setQueryData(
          chatThreadQueryKey(senderId, receiverId),
          (oldData: { pages: { messages: ChatMessage[] }[] } | undefined) => {
            if (!oldData) return oldData;
            return {
              ...oldData,
              pages: oldData.pages.map((page) => ({
                ...page,
                messages: page.messages.filter((m) => !idSet.has(m._id)),
              })),
            };
          }
        );
      }
    );

    return () => {
      if (typingHideTimer) clearTimeout(typingHideTimer);
      socket.off("connect", joinRoom);
      socket.off("newMessage");
      socket.off("messageStatusUpdate");
      socket.off("messagesDeleted");
      socket.off("userTyping");
      socket.off("userStoppedTyping");
      socket.disconnect();
    };
  }, [threadReady, senderId, receiverId, queryClient, onTypingChange, onIncomingMessage]);

  const allMessages = useMemo(() => {
    const server =
      data?.pages.flatMap((page) => page.messages) || [];
    const serverIds = new Set(server.map((m) => m._id));
    const pendingOnly = filterResolvedPendingMessages(server, pendingMessages).filter(
      (p) => !serverIds.has(p._id)
    );
    const merged = dedupeChatMessages([...server, ...pendingOnly]);
    const q = searchQuery.trim().toLowerCase();
    if (!q) return merged;
    return merged.filter((m) =>
      (m.content || "").toLowerCase().includes(q)
    );
  }, [data, pendingMessages, searchQuery]);

  const grouped = useMemo(
    () => groupMessagesByDate(allMessages),
    [allMessages]
  );

  useEffect(() => {
    if (!toEnd) return;
    scrollToBottom("auto");
  }, [
    data,
    toEnd,
    pendingMessages,
    isTyping,
    allMessages.length,
    extraBottomRem,
    scrollToBottom,
  ]);

  useEffect(() => {
    if (pendingMessages.length > prevPendingCountRef.current) {
      setToEnd(true);
      scrollToBottom("smooth");
      const retry = setTimeout(() => scrollToBottom("auto"), 180);
      prevPendingCountRef.current = pendingMessages.length;
      return () => clearTimeout(retry);
    }
    prevPendingCountRef.current = pendingMessages.length;
  }, [pendingMessages, scrollToBottom]);

  useTimedMessageExpiry(allMessages, queryKey, onExpirePending);

  useEffect(() => {
    if (allMessages.length === 0) return;
    if (!hasInitializedRef.current) {
      allMessages.forEach((m) =>
        seenMessageKeysRef.current.add(getStableKey(m._id))
      );
      hasInitializedRef.current = true;
      prevMessageCountRef.current = allMessages.length;
    }
  }, [allMessages, getStableKey]);

  const messageIndexMap = useMemo(() => {
    const map = new Map<string, number>();
    allMessages.forEach((m, i) => map.set(m._id, i));
    return map;
  }, [allMessages]);

  const registerMessageRef = useCallback(
    (id: string, el: HTMLDivElement | null) => {
      if (el) messageRefs.current.set(id, el);
      else messageRefs.current.delete(id);
    },
    []
  );

  const scrollToMessage = useCallback(
    async (targetId: string) => {
      if (!targetId) return;
      setToEnd(false);

      const scrollToTarget = (): boolean => {
        const container = scrollRef.current;
        const el = messageRefs.current.get(targetId);
        if (!container || !el) return false;

        const containerRect = container.getBoundingClientRect();
        const elRect = el.getBoundingClientRect();
        const offset =
          elRect.top -
          containerRect.top -
          container.clientHeight / 2 +
          elRect.height / 2;

        container.scrollTo({
          top: container.scrollTop + offset,
          behavior: "smooth",
        });
        return true;
      };

      const flashHighlight = () => {
        setHighlightedMessageId(targetId);
        if (highlightTimerRef.current) clearTimeout(highlightTimerRef.current);
        highlightTimerRef.current = setTimeout(
          () => setHighlightedMessageId(null),
          1800
        );
      };

      const waitForPaint = () =>
        new Promise<void>((resolve) => {
          requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
        });

      if (scrollToTarget()) {
        flashHighlight();
        return;
      }

      const isLoaded = (id: string) => allMessages.some((m) => m._id === id);

      if (isLoaded(targetId)) {
        await waitForPaint();
        if (scrollToTarget()) {
          flashHighlight();
          return;
        }
      }

      let pagesLoaded = 0;
      let hasMore = hasNextPage;
      while (hasMore && pagesLoaded < 15 && !isLoaded(targetId)) {
        const result = await fetchNextPage();
        pagesLoaded += 1;
        hasMore = result.hasNextPage ?? false;
        await waitForPaint();
      }

      await waitForPaint();
      if (scrollToTarget()) {
        flashHighlight();
        return;
      }

      toast.error("پیام اصلی یافت نشد");
    },
    [allMessages, fetchNextPage, hasNextPage]
  );

  useEffect(() => {
    return () => {
      if (highlightTimerRef.current) clearTimeout(highlightTimerRef.current);
    };
  }, []);

  return (
    <div
      dir="rtl"
      className={cn(
        "chat-thread-messages custom-scrollbar relative flex min-h-0 flex-1 flex-col overflow-y-auto bg-transparent px-2 pt-[calc(3.75rem+env(safe-area-inset-top))] sm:px-3"
      )}
      style={{
        paddingBottom: `calc(${5.5 + extraBottomRem}rem + env(safe-area-inset-bottom, 0px))`,
      }}
      onScroll={handleScroll}
      onClick={(e) => {
        if (!actionMode) return;
        if (!(e.target as Element).closest("[data-chat-bubble]")) {
          onDismissAction?.();
        }
      }}
      ref={scrollRef}
    >
      <AnimatePresence>
        {actionMode && (
          <motion.div
            key="action-dim"
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{ duration: 0.18 }}
            className="pointer-events-none absolute inset-0 z-[5] bg-black/15 dark:bg-black/35"
            aria-hidden
          />
        )}
      </AnimatePresence>
      {isFetchingNextPage && (
        <div className="flex justify-center py-2">
          <IOSSpinner size={18} color="#8e8e93" />
        </div>
      )}

      {showSkeleton ? (
        <ChatMessagesSkeleton />
      ) : isError ? (
        <div className="flex flex-1 flex-col items-center justify-center gap-3 py-12 text-center">
          <p className="text-sm text-neutral-500">بارگذاری پیام‌ها ناموفق بود.</p>
          <button
            type="button"
            onClick={() => void refetch()}
            className="rounded-full bg-[#2aabee] px-4 py-2 text-sm text-white"
          >
            تلاش مجدد
          </button>
        </div>
      ) : grouped.length === 0 ? (
        <p className="py-12 text-center text-sm text-neutral-500">
          هنوز پیامی رد و بدل نشده است.
        </p>
      ) : (
        <div className="flex flex-col pb-1">
          {grouped.map((item, index) => {
            if (item.type === "date") {
              return (
                <ChatDateSeparator
                  key={`date-${item.label}-${index}`}
                  label={item.label}
                />
              );
            }

            const msg = item.message;
            const msgIndex = messageIndexMap.get(msg._id) ?? 0;
            const groupPosition = getBubbleGroupPosition(allMessages, msgIndex);
            const prevMsg = allMessages[msgIndex - 1];
            const isGroupedWithPrev =
              !!prevMsg && sameVisualGroup(prevMsg, msg);
            const isIncoming =
              String(msg.senderId) !==
              String(userDetail?._id ?? getStoredUserId());
            const stableKey = getStableKey(msg._id);
            const isNew =
              hasInitializedRef.current &&
              !seenMessageKeysRef.current.has(stableKey);

            if (isNew) {
              seenMessageKeysRef.current.add(stableKey);
            }

            return (
              <ChatMessageCard
                key={stableKey}
                message={msg}
                userDetail={userDetail}
                groupPosition={groupPosition}
                isGroupedWithPrev={isGroupedWithPrev}
                showAvatar={
                  isIncoming &&
                  (groupPosition === "last" || groupPosition === "single")
                }
                partnerAvatar={userTwoDetail?.profile_image ?? undefined}
                isNew={isNew}
                selected={selectedMessageId === msg._id}
                onBubbleClick={deleteMode ? undefined : onBubbleClick}
                onSwipeReply={deleteMode ? undefined : onSwipeReply}
                deleteMode={deleteMode}
                selectedForDelete={selectedDeleteIds.includes(msg._id)}
                onToggleDeleteSelect={onToggleDeleteSelect}
                onScrollToReply={scrollToMessage}
                highlighted={highlightedMessageId === msg._id}
                registerRef={registerMessageRef}
              />
            );
          })}
        </div>
      )}

      <AnimatePresence>
        {isTyping && !showSkeleton && (
          <TypingIndicator userName={userTwoDetail?.first_name} />
        )}
      </AnimatePresence>
    </div>
  );
};

export default ChatMessageList;
