import { useEffect, useRef, type Dispatch, type MutableRefObject, type SetStateAction } from 'react';
import { mergeConversationRequestStatusMessage, mergeRecoveredChatMessages } from '../../mainChatPanel/chatUtils';
import { sortChatConversationSummaries } from '../../mainChatPanel';
import { resolveMergedConversationTitle } from '../../mainChatPanel/conversationTitle';
import { chatGateway } from '../data/chatGateway';
import type {
  ChatConversationRequest,
  ChatConversationSummary,
  ChatMessage,
} from '../../mainChatPanel/types';

const INITIAL_CONVERSATION_REQUEST_PAGE_SIZE = 10;
const OLDER_CONVERSATION_REQUEST_PAGE_SIZE = 10;
const CONVERSATION_DETAIL_RETRY_DELAYS_MS = [0, 250, 800];

function mergeConversationRequests(
  previous: ChatConversationRequest[],
  incoming: ChatConversationRequest[],
  sessionId: string,
) {
  const previousSessionItems = previous.filter((item) => item.sessionId === sessionId);
  const previousByRequestId = new Map(previousSessionItems.map((item) => [item.requestId, item] as const));
  const incomingRequestIds = new Set(incoming.map((item) => item.requestId));
  const mergedIncoming = incoming.map((item) => {
    const previousItem = previousByRequestId.get(item.requestId);

    if (!previousItem) {
      return item;
    }

    const nextUserText = item.userText.trim() || previousItem.userText.trim();
    const nextResponseText = item.responseText.trim() || previousItem.responseText.trim();
    const nextStatusMessage = mergeConversationRequestStatusMessage(previousItem, item);

    return {
      ...item,
      requestOrigin: item.requestOrigin ?? previousItem.requestOrigin ?? null,
      parentRequestId: item.parentRequestId?.trim() || previousItem.parentRequestId?.trim() || null,
      promptContextRef: item.promptContextRef ?? previousItem.promptContextRef ?? null,
      statusMessage: nextStatusMessage,
      userMessageId: item.userMessageId ?? previousItem.userMessageId,
      userText: nextUserText,
      responseMessageId: item.responseMessageId ?? previousItem.responseMessageId,
      responseText: nextResponseText,
      hasResponse: item.hasResponse || previousItem.hasResponse || nextResponseText.length > 0,
      answeredAt: item.answeredAt ?? previousItem.answeredAt,
      terminalAt: item.terminalAt ?? previousItem.terminalAt,
    };
  });
  const preservedLocalItems = previousSessionItems.filter((item) => !incomingRequestIds.has(item.requestId));

  return [...mergedIncoming, ...preservedLocalItems].sort((left, right) => left.createdAt.localeCompare(right.createdAt));
}

type UseConversationRoomDataOptions = {
  activeSessionId: string;
  activeConversationIsDraftOnly?: boolean;
  activeConversationHasLocalActivity?: boolean;
  oldestLoadedMessageId: number | null;
  reloadKey: number;
  shouldForceStickToBottomOnNextLoadRef: MutableRefObject<boolean>;
  connectionState: 'connecting' | 'connected' | 'disconnected';
  captureViewportRestoreSnapshot: (options?: { forceStickToBottom?: boolean }) => void;
  sessionMessageCacheRef: MutableRefObject<Map<string, ChatMessage[]>>;
  messagesRef: MutableRefObject<ChatMessage[]>;
  pendingViewportRestoreRef: MutableRefObject<boolean>;
  shouldRestoreConversationAfterReconnectRef: MutableRefObject<boolean>;
  setConversationItems: Dispatch<SetStateAction<ChatConversationSummary[]>>;
  setMessages: Dispatch<SetStateAction<ChatMessage[]>>;
  setRequestItems: Dispatch<SetStateAction<ChatConversationRequest[]>>;
  setConversationLoadingLabel: Dispatch<SetStateAction<string>>;
  setIsConversationContentLoading: Dispatch<SetStateAction<boolean>>;
  setIsDeferringAuxiliaryChatRequests: Dispatch<SetStateAction<boolean>>;
  setHasOlderMessages: Dispatch<SetStateAction<boolean>>;
  setOldestLoadedMessageId: Dispatch<SetStateAction<number | null>>;
  setIsLoadingOlderMessages: Dispatch<SetStateAction<boolean>>;
  queueViewportPrependRestore: (previousScrollHeight: number, previousScrollTop: number) => void;
  viewportRef: MutableRefObject<HTMLDivElement | null>;
  onMissingConversation?: (sessionId: string) => void;
  shouldPreserveMissingConversation?: (sessionId: string) => boolean;
};

function isMissingConversationError(error: unknown) {
  if (!(error instanceof Error)) {
    return false;
  }

  return /채팅방을 찾을 수 없습니다|404\b|not found/i.test(error.message);
}

export function useConversationRoomData({
  activeSessionId,
  activeConversationIsDraftOnly = false,
  activeConversationHasLocalActivity = false,
  oldestLoadedMessageId,
  reloadKey,
  shouldForceStickToBottomOnNextLoadRef,
  connectionState,
  captureViewportRestoreSnapshot,
  sessionMessageCacheRef,
  messagesRef,
  pendingViewportRestoreRef,
  shouldRestoreConversationAfterReconnectRef,
  setConversationItems,
  setMessages,
  setRequestItems,
  setConversationLoadingLabel,
  setIsConversationContentLoading,
  setIsDeferringAuxiliaryChatRequests,
  setHasOlderMessages,
  setOldestLoadedMessageId,
  setIsLoadingOlderMessages,
  queueViewportPrependRestore,
  viewportRef,
  onMissingConversation,
  shouldPreserveMissingConversation,
}: UseConversationRoomDataOptions) {
  const previousSessionIdRef = useRef('');

  useEffect(() => {
    if (!activeSessionId.trim()) {
      previousSessionIdRef.current = '';
      setMessages([]);
      setRequestItems([]);
      setIsConversationContentLoading(false);
      setIsLoadingOlderMessages(false);
      setHasOlderMessages(false);
      setOldestLoadedMessageId(null);
      return;
    }

    if (activeConversationIsDraftOnly && !activeConversationHasLocalActivity) {
      previousSessionIdRef.current = activeSessionId;
      setMessages([]);
      setRequestItems((previous) => previous.filter((item) => item.sessionId !== activeSessionId));
      setConversationLoadingLabel('첫 요청을 보내면 대화가 저장됩니다.');
      setIsConversationContentLoading(false);
      setIsLoadingOlderMessages(false);
      setHasOlderMessages(false);
      setOldestLoadedMessageId(null);
      return;
    }

    let isCancelled = false;
    const requestedSessionId = activeSessionId;

    const waitForRetry = (delayMs: number) =>
      new Promise<void>((resolve) => {
        window.setTimeout(resolve, delayMs);
      });

    const loadConversationDetail = async () => {
      const isSessionChanged = previousSessionIdRef.current !== requestedSessionId;
      const shouldForceStickToBottom = isSessionChanged || shouldForceStickToBottomOnNextLoadRef.current;

      previousSessionIdRef.current = requestedSessionId;
      captureViewportRestoreSnapshot({
        forceStickToBottom: shouldForceStickToBottom,
      });
      shouldForceStickToBottomOnNextLoadRef.current = false;
      pendingViewportRestoreRef.current = true;
      setConversationLoadingLabel('대화 내용을 불러오는 중입니다.');
      const cachedMessages = isSessionChanged ? [] : (sessionMessageCacheRef.current.get(requestedSessionId) ?? []);

      if (cachedMessages.length > 0) {
        setMessages(cachedMessages);
      }

      setIsConversationContentLoading(true);
      setIsDeferringAuxiliaryChatRequests(true);

      try {
        let response: Awaited<ReturnType<typeof chatGateway.getConversationDetail>> | null = null;
        let lastError: unknown = null;

        for (const delayMs of CONVERSATION_DETAIL_RETRY_DELAYS_MS) {
          if (delayMs > 0) {
            await waitForRetry(delayMs);
          }

          if (isCancelled) {
            return;
          }

          try {
            response = await chatGateway.getConversationDetail(requestedSessionId, {
              limit: INITIAL_CONVERSATION_REQUEST_PAGE_SIZE,
            });
            break;
          } catch (error) {
            lastError = error;
          }
        }

        if (!response) {
          throw lastError ?? new Error('대화 내용을 불러오지 못했습니다.');
        }

        if (!isCancelled && response.item.sessionId === requestedSessionId) {
          setConversationItems((previous) => {
            const previousItem = previous.find((item) => item.sessionId === response.item.sessionId) ?? null;
            const exists = previousItem != null;
            const mergedItem = previousItem
              ? {
                  ...response.item,
                  title: resolveMergedConversationTitle(previousItem.title, response.item.title),
                }
              : response.item;

            if (!exists) {
              return sortChatConversationSummaries([mergedItem, ...previous]);
            }

            return sortChatConversationSummaries(
              previous.map((item) => (item.sessionId === response.item.sessionId ? mergedItem : item)),
            );
          });

          const baseMessages =
            isSessionChanged
              ? []
              : requestedSessionId === activeSessionId
              ? messagesRef.current
              : (sessionMessageCacheRef.current.get(requestedSessionId) ?? []);
          const mergedMessages = mergeRecoveredChatMessages(baseMessages, response.messages);

          sessionMessageCacheRef.current.set(requestedSessionId, mergedMessages);
          setMessages(mergedMessages);
          setRequestItems((previous) => {
            const preservedOtherSessions = previous.filter((item) => item.sessionId !== requestedSessionId);
            return [...preservedOtherSessions, ...mergeConversationRequests(previous, response.requests, requestedSessionId)];
          });
          setHasOlderMessages(response.hasOlderMessages);
          setOldestLoadedMessageId(response.oldestLoadedMessageId);
        }
      } catch (error) {
        if (!isCancelled) {
          if (cachedMessages.length === 0 && isMissingConversationError(error)) {
            if (shouldPreserveMissingConversation?.(requestedSessionId)) {
              setMessages([]);
              setHasOlderMessages(false);
              setOldestLoadedMessageId(null);
              setConversationLoadingLabel('새 채팅방을 준비하는 중입니다.');
              return;
            }

            sessionMessageCacheRef.current.delete(requestedSessionId);
            setConversationItems((previous) => previous.filter((item) => item.sessionId !== requestedSessionId));
            setMessages([]);
            setRequestItems((previous) => previous.filter((item) => item.sessionId !== requestedSessionId));
            setHasOlderMessages(false);
            setOldestLoadedMessageId(null);
            setConversationLoadingLabel('삭제되었거나 만료된 채팅방입니다.');
            onMissingConversation?.(requestedSessionId);
            return;
          }

          setMessages(cachedMessages);
          setHasOlderMessages(false);
          setOldestLoadedMessageId(cachedMessages[0]?.id ?? null);
          setConversationLoadingLabel(
            cachedMessages.length > 0
              ? '저장된 대화 내용을 먼저 보여주고 있습니다. 서버 연결을 다시 확인해 주세요.'
              : '대화 내용을 다시 불러오지 못했습니다.',
          );
        }
      } finally {
        if (!isCancelled) {
          setIsConversationContentLoading(false);
          setIsDeferringAuxiliaryChatRequests(false);
        }
      }
    };

    void loadConversationDetail();

    return () => {
      isCancelled = true;
    };
  }, [
    activeSessionId,
    activeConversationHasLocalActivity,
    activeConversationIsDraftOnly,
    captureViewportRestoreSnapshot,
    messagesRef,
    pendingViewportRestoreRef,
    reloadKey,
    sessionMessageCacheRef,
    shouldForceStickToBottomOnNextLoadRef,
    setConversationItems,
    setConversationLoadingLabel,
    setIsConversationContentLoading,
    setIsDeferringAuxiliaryChatRequests,
    setHasOlderMessages,
    setMessages,
    setOldestLoadedMessageId,
    onMissingConversation,
    shouldPreserveMissingConversation,
    setRequestItems,
  ]);

  useEffect(() => {
    if (connectionState === 'connected') {
      shouldRestoreConversationAfterReconnectRef.current = false;
    }
  }, [connectionState, shouldRestoreConversationAfterReconnectRef]);

  const loadOlderMessages = async () => {
    const requestedSessionId = activeSessionId.trim();
    const oldestVisibleMessageId = oldestLoadedMessageId;

    if (!requestedSessionId || oldestVisibleMessageId == null) {
      return;
    }

    setIsLoadingOlderMessages(true);

    try {
      const response = await chatGateway.getConversationDetail(requestedSessionId, {
        limit: OLDER_CONVERSATION_REQUEST_PAGE_SIZE,
        beforeMessageId: oldestVisibleMessageId,
      });

      if (
        response.item.sessionId !== requestedSessionId ||
        (response.messages.length === 0 && response.requests.length === 0)
      ) {
        setHasOlderMessages(response.hasOlderMessages);
        setOldestLoadedMessageId(response.oldestLoadedMessageId);
        return;
      }

      const viewport = viewportRef.current;
      const previousScrollHeight = viewport?.scrollHeight ?? 0;
      const previousScrollTop = viewport?.scrollTop ?? 0;
      const nextMessages = mergeRecoveredChatMessages(messagesRef.current, response.messages);

      queueViewportPrependRestore(previousScrollHeight, previousScrollTop);
      sessionMessageCacheRef.current.set(requestedSessionId, nextMessages);
      setMessages(nextMessages);
      setRequestItems((previous) => {
        const preservedOtherSessions = previous.filter((item) => item.sessionId !== requestedSessionId);
        return [...preservedOtherSessions, ...mergeConversationRequests(previous, response.requests, requestedSessionId)];
      });
      setHasOlderMessages(response.hasOlderMessages);
      setOldestLoadedMessageId(response.oldestLoadedMessageId);
    } finally {
      setIsLoadingOlderMessages(false);
    }
  };

  return {
    loadOlderMessages,
  };
}
