import { useCallback, useEffect, useRef, useState, type TouchEvent } from 'react';
import type {
  ChatConversationSummary,
  ChatMessage,
  ChatRuntimeSnapshot,
} from '../../mainChatPanel/types';

const SCROLL_JUMP_HIDE_THRESHOLD = 24;
const SCROLL_JUMP_MIN_OVERFLOW = 48;
type UseConversationViewportControllerOptions = {
  activeConversation: ChatConversationSummary | null;
  activeQueuedComposerRequestsCount: number;
  activeRuntimeStatus: string | null;
  chatMessageCount: number;
  chatMessageSyncKey: string;
  connectionState: 'connecting' | 'connected' | 'disconnected';
  isConversationContentLoading: boolean;
  messages: ChatMessage[];
  runtimeSnapshot: ChatRuntimeSnapshot | null;
  viewportRef: { current: HTMLDivElement | null };
  hasOlderMessages: boolean;
  isLoadingOlderMessages: boolean;
  onLoadOlderMessages: () => void | Promise<void>;
  mapJobStatusLabel: (
    item: Pick<ChatConversationSummary, 'currentJobStatus' | 'currentJobMessage' | 'currentQueueSize'>,
  ) => string;
  mapSystemStatusMessage: (text: string) => string | null;
  isActivityLogMessage: (message: ChatMessage) => boolean;
};

export function useConversationViewportController({
  activeConversation,
  activeQueuedComposerRequestsCount,
  activeRuntimeStatus,
  chatMessageCount,
  chatMessageSyncKey,
  connectionState,
  isConversationContentLoading,
  messages,
  runtimeSnapshot,
  viewportRef,
  hasOlderMessages,
  isLoadingOlderMessages,
  onLoadOlderMessages,
  mapJobStatusLabel,
  mapSystemStatusMessage,
  isActivityLogMessage,
}: UseConversationViewportControllerOptions) {
  const [activeSystemStatus, setActiveSystemStatus] = useState<string | null>(null);
  const [isSystemStatusPending, setIsSystemStatusPending] = useState(false);
  const [showScrollToBottom, setShowScrollToBottom] = useState(false);
  const systemStatusTimerRef = useRef<number | null>(null);
  const restoreAutoScrollFrameRef = useRef<number | null>(null);
  const showScrollToBottomRef = useRef(false);
  const shouldStickToBottomRef = useRef(true);
  const lastViewportScrollTopRef = useRef(0);
  const autoScrollSuspendedUntilRef = useRef(0);
  const pendingViewportRestoreRef = useRef(false);
  const pendingPrependRestoreRef = useRef<{
    previousScrollHeight: number;
    previousScrollTop: number;
  } | null>(null);
  const viewportRestoreSnapshotRef = useRef<{
    shouldStickToBottom: boolean;
    offsetFromBottom: number;
  } | null>(null);
  const touchStartYRef = useRef<number | null>(null);
  const touchPullActiveRef = useRef(false);
  const [pullToLoadDistance, setPullToLoadDistance] = useState(0);
  const [isPullToLoadArmed, setIsPullToLoadArmed] = useState(false);

  const clearSystemStatusTimer = useCallback(() => {
    if (systemStatusTimerRef.current !== null) {
      window.clearTimeout(systemStatusTimerRef.current);
      systemStatusTimerRef.current = null;
    }
  }, []);

  const syncShowScrollToBottom = useCallback((nextValue: boolean) => {
    if (showScrollToBottomRef.current === nextValue) {
      return;
    }

    showScrollToBottomRef.current = nextValue;
    setShowScrollToBottom(nextValue);
  }, []);

  const scrollViewportToBottom = useCallback(
    (behavior: ScrollBehavior = 'auto') => {
      const viewport = viewportRef.current;

      if (!viewport) {
        return;
      }

      viewport.scrollTo({
        top: viewport.scrollHeight,
        behavior,
      });
    },
    [viewportRef],
  );

  const isAutoScrollSuspended = useCallback(() => autoScrollSuspendedUntilRef.current > Date.now(), []);

  const releaseAutoScrollSuspension = useCallback(() => {
    autoScrollSuspendedUntilRef.current = 0;
  }, []);

  const scheduleViewportBottomSync = useCallback(
    (frameCount = 6) => {
      if (restoreAutoScrollFrameRef.current !== null) {
        window.cancelAnimationFrame(restoreAutoScrollFrameRef.current);
        restoreAutoScrollFrameRef.current = null;
      }

      const run = (remainingFrames: number) => {
        restoreAutoScrollFrameRef.current = window.requestAnimationFrame(() => {
          if (!shouldStickToBottomRef.current || isConversationContentLoading || isAutoScrollSuspended()) {
            restoreAutoScrollFrameRef.current = null;
            return;
          }

          syncShowScrollToBottom(false);
          scrollViewportToBottom('auto');

          if (remainingFrames <= 1) {
            restoreAutoScrollFrameRef.current = null;
            return;
          }

          run(remainingFrames - 1);
        });
      };

      run(frameCount);
    },
    [isAutoScrollSuspended, isConversationContentLoading, scrollViewportToBottom, syncShowScrollToBottom],
  );

  const handleViewportScroll = useCallback(() => {
    const viewport = viewportRef.current;

    if (!viewport) {
      return;
    }

    const remainingDistance = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
    const maxScrollDistance = Math.max(0, viewport.scrollHeight - viewport.clientHeight);
    const currentScrollTop = viewport.scrollTop;
    const scrollDelta = currentScrollTop - lastViewportScrollTopRef.current;
    const isNearBottom = remainingDistance <= SCROLL_JUMP_HIDE_THRESHOLD;
    const isScrollingUp = scrollDelta < -2;

    if (isNearBottom) {
      releaseAutoScrollSuspension();
    } else if (isScrollingUp) {
      autoScrollSuspendedUntilRef.current = Date.now() + 1600;
    }

    const shouldStickToBottom = isNearBottom && !isAutoScrollSuspended();
    shouldStickToBottomRef.current = shouldStickToBottom;
    lastViewportScrollTopRef.current = currentScrollTop;

    if (maxScrollDistance < SCROLL_JUMP_MIN_OVERFLOW || shouldStickToBottom) {
      syncShowScrollToBottom(false);
      return;
    }

    syncShowScrollToBottom(true);
  }, [
    isAutoScrollSuspended,
    releaseAutoScrollSuspension,
    syncShowScrollToBottom,
    viewportRef,
  ]);

  const captureViewportRestoreSnapshot = useCallback((options?: { forceStickToBottom?: boolean }) => {
    if (options?.forceStickToBottom) {
      viewportRestoreSnapshotRef.current = {
        shouldStickToBottom: true,
        offsetFromBottom: 0,
      };
      shouldStickToBottomRef.current = true;
      return;
    }

    const viewport = viewportRef.current;

    if (!viewport) {
      viewportRestoreSnapshotRef.current = {
        shouldStickToBottom: shouldStickToBottomRef.current,
        offsetFromBottom: 0,
      };
      return;
    }

    viewportRestoreSnapshotRef.current = {
      shouldStickToBottom: shouldStickToBottomRef.current,
      offsetFromBottom: Math.max(0, viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight),
    };
  }, [viewportRef]);

  const queueViewportPrependRestore = useCallback((previousScrollHeight: number, previousScrollTop: number) => {
    pendingPrependRestoreRef.current = {
      previousScrollHeight,
      previousScrollTop,
    };
  }, []);

  useEffect(() => {
    if (!shouldStickToBottomRef.current || isAutoScrollSuspended()) {
      return;
    }

    scrollViewportToBottom('auto');
  }, [chatMessageCount, chatMessageSyncKey, isAutoScrollSuspended, scrollViewportToBottom]);

  useEffect(() => {
    if (isConversationContentLoading || !shouldStickToBottomRef.current || isAutoScrollSuspended()) {
      return;
    }

    scheduleViewportBottomSync();

    return () => {
      if (restoreAutoScrollFrameRef.current !== null) {
        window.cancelAnimationFrame(restoreAutoScrollFrameRef.current);
        restoreAutoScrollFrameRef.current = null;
      }
    };
  }, [
    activeConversation?.sessionId,
    chatMessageSyncKey,
    isAutoScrollSuspended,
    isConversationContentLoading,
    scheduleViewportBottomSync,
  ]);

  useEffect(() => {
    const pendingPrependRestore = pendingPrependRestoreRef.current;

    if (!pendingPrependRestore) {
      return;
    }

    pendingPrependRestoreRef.current = null;

    const frameId = window.requestAnimationFrame(() => {
      const viewport = viewportRef.current;

      if (!viewport) {
        return;
      }

      const nextScrollTop =
        pendingPrependRestore.previousScrollTop + (viewport.scrollHeight - pendingPrependRestore.previousScrollHeight);
      viewport.scrollTop = Math.max(0, nextScrollTop);
      handleViewportScroll();
    });

    return () => {
      window.cancelAnimationFrame(frameId);
    };
  }, [chatMessageCount, chatMessageSyncKey, handleViewportScroll, viewportRef]);

  useEffect(() => {
    if (isConversationContentLoading || !pendingViewportRestoreRef.current) {
      return;
    }

    const restoreSnapshot = viewportRestoreSnapshotRef.current;

    pendingViewportRestoreRef.current = false;
    viewportRestoreSnapshotRef.current = null;

    const frameId = window.requestAnimationFrame(() => {
      const viewport = viewportRef.current;

      if (!viewport) {
        return;
      }

      if (!restoreSnapshot || restoreSnapshot.shouldStickToBottom) {
        releaseAutoScrollSuspension();
        shouldStickToBottomRef.current = true;
        syncShowScrollToBottom(false);
        scrollViewportToBottom('auto');
        return;
      }

      shouldStickToBottomRef.current = false;
      viewport.scrollTop = Math.max(
        0,
        viewport.scrollHeight - viewport.clientHeight - restoreSnapshot.offsetFromBottom,
      );
      handleViewportScroll();
    });

    return () => {
      window.cancelAnimationFrame(frameId);
    };
  }, [
    chatMessageCount,
    handleViewportScroll,
    isConversationContentLoading,
    releaseAutoScrollSuspension,
    scrollViewportToBottom,
    syncShowScrollToBottom,
    viewportRef,
  ]);

  const handleViewportTouchStart = useCallback((event: TouchEvent<HTMLDivElement>) => {
    const viewport = viewportRef.current;

    if (!viewport || isLoadingOlderMessages) {
      touchStartYRef.current = null;
      touchPullActiveRef.current = false;
      return;
    }

    const isAtTop = viewport.scrollTop <= 0;

    if (isAtTop && hasOlderMessages) {
      touchStartYRef.current = event.touches[0]?.clientY ?? null;
      touchPullActiveRef.current = true;
      return;
    }

    touchStartYRef.current = event.touches[0]?.clientY ?? null;
    touchPullActiveRef.current = false;
  }, [hasOlderMessages, isLoadingOlderMessages, viewportRef]);

  const handleViewportTouchMove = useCallback((event: TouchEvent<HTMLDivElement>) => {
    if (!touchPullActiveRef.current || touchStartYRef.current == null) {
      return;
    }

    const viewport = viewportRef.current;
    const currentY = event.touches[0]?.clientY ?? null;

    if (!viewport || currentY == null) {
      touchPullActiveRef.current = false;
      touchStartYRef.current = null;
      setPullToLoadDistance(0);
      setIsPullToLoadArmed(false);
      return;
    }

    if (viewport.scrollTop > 0) {
      touchPullActiveRef.current = false;
      touchStartYRef.current = null;
      setPullToLoadDistance(0);
      setIsPullToLoadArmed(false);
      return;
    }

    const deltaY = Math.max(0, currentY - touchStartYRef.current);
    const nextDistance = Math.min(96, deltaY * 0.45);

    if (nextDistance > 0) {
      event.preventDefault();
    }

    setPullToLoadDistance(nextDistance);
    setIsPullToLoadArmed(nextDistance >= 52);
  }, [viewportRef]);

  const resetPullToLoad = useCallback(() => {
    touchPullActiveRef.current = false;
    touchStartYRef.current = null;
    setPullToLoadDistance(0);
    setIsPullToLoadArmed(false);
  }, []);

  const handleViewportTouchEnd = useCallback(() => {
    const shouldLoadOlder = touchPullActiveRef.current && isPullToLoadArmed && hasOlderMessages && !isLoadingOlderMessages;

    resetPullToLoad();

    if (shouldLoadOlder) {
      void onLoadOlderMessages();
    }
  }, [
    hasOlderMessages,
    isLoadingOlderMessages,
    isPullToLoadArmed,
    onLoadOlderMessages,
    resetPullToLoad,
  ]);

  useEffect(() => {
    const handleViewportInteractionReset = () => {
      resetPullToLoad();
    };

    const handleVisibilityChange = () => {
      if (document.visibilityState !== 'visible') {
        resetPullToLoad();
        return;
      }

      window.requestAnimationFrame(() => {
        resetPullToLoad();
        handleViewportScroll();
      });
    };

    window.addEventListener('resize', handleViewportInteractionReset);
    window.addEventListener('pageshow', handleViewportInteractionReset);
    window.addEventListener('blur', handleViewportInteractionReset);
    document.addEventListener('visibilitychange', handleVisibilityChange);

    return () => {
      window.removeEventListener('resize', handleViewportInteractionReset);
      window.removeEventListener('pageshow', handleViewportInteractionReset);
      window.removeEventListener('blur', handleViewportInteractionReset);
      document.removeEventListener('visibilitychange', handleVisibilityChange);
    };
  }, [handleViewportScroll, resetPullToLoad]);

  useEffect(() => {
    if (connectionState === 'disconnected') {
      clearSystemStatusTimer();
      setActiveSystemStatus('워크서버 연결이 끊어졌습니다. 실제 처리 상태를 다시 확인하는 중입니다.');
      setIsSystemStatusPending(false);
      return clearSystemStatusTimer;
    }

    if (activeRuntimeStatus) {
      clearSystemStatusTimer();
      setActiveSystemStatus(activeRuntimeStatus);
      setIsSystemStatusPending(true);
      return clearSystemStatusTimer;
    }

    if (activeConversation?.currentJobStatus) {
      clearSystemStatusTimer();
      setActiveSystemStatus(mapJobStatusLabel(activeConversation));
      setIsSystemStatusPending(
        activeConversation.currentJobStatus === 'queued' || activeConversation.currentJobStatus === 'started',
      );
      return clearSystemStatusTimer;
    }

    if (activeQueuedComposerRequestsCount > 0) {
      clearSystemStatusTimer();
      setActiveSystemStatus(`실제 대기열 ${activeQueuedComposerRequestsCount}건`);
      setIsSystemStatusPending(true);
      return clearSystemStatusTimer;
    }

    clearSystemStatusTimer();
    const latestMessage = messages[messages.length - 1];

    if (!latestMessage || isActivityLogMessage(latestMessage) || latestMessage.author !== 'system') {
      if (activeSystemStatus == null && !isSystemStatusPending) {
        return clearSystemStatusTimer;
      }

      systemStatusTimerRef.current = window.setTimeout(() => {
        setActiveSystemStatus(null);
        setIsSystemStatusPending(false);
        systemStatusTimerRef.current = null;
      }, 450);
      return clearSystemStatusTimer;
    }

    const isTerminalStatus =
      latestMessage.text.startsWith('요청 처리가 끝났습니다.') ||
      latestMessage.text.startsWith('즉시 요청 처리가 끝났습니다.') ||
      latestMessage.text.startsWith('요청 처리 중 오류가 발생했습니다.');

    const nextStatus = mapSystemStatusMessage(latestMessage.text);
    if (!nextStatus || isTerminalStatus) {
      if (activeSystemStatus == null && !isSystemStatusPending) {
        return clearSystemStatusTimer;
      }

      systemStatusTimerRef.current = window.setTimeout(() => {
        setActiveSystemStatus(null);
        setIsSystemStatusPending(false);
        systemStatusTimerRef.current = null;
      }, 450);
      return clearSystemStatusTimer;
    }

    setActiveSystemStatus(nextStatus);
    setIsSystemStatusPending(true);

    return clearSystemStatusTimer;
  }, [
    activeConversation,
    activeQueuedComposerRequestsCount,
    activeRuntimeStatus,
    clearSystemStatusTimer,
    connectionState,
    isActivityLogMessage,
    isSystemStatusPending,
    mapJobStatusLabel,
    mapSystemStatusMessage,
    messages,
    runtimeSnapshot,
    activeSystemStatus,
  ]);

  useEffect(() => {
    return () => {
      clearSystemStatusTimer();

      if (restoreAutoScrollFrameRef.current !== null) {
        window.cancelAnimationFrame(restoreAutoScrollFrameRef.current);
      }
    };
  }, [clearSystemStatusTimer]);

  return {
    activeSystemStatus,
    captureViewportRestoreSnapshot,
    handleViewportScroll,
    isSystemStatusPending,
    pendingViewportRestoreRef,
    scrollViewportToBottom,
    setActiveSystemStatus,
    setIsSystemStatusPending,
    setShowScrollToBottom: syncShowScrollToBottom,
    shouldStickToBottomRef,
    showScrollToBottom,
    handleViewportTouchEnd,
    handleViewportTouchMove,
    handleViewportTouchStart,
    isPullToLoadArmed,
    pullToLoadDistance,
    queueViewportPrependRestore,
    releaseAutoScrollSuspension,
  };
}
