"use client";
/**
 * ChatArea 消息列表区
 *
 * 组成（自上而下）：
 *   - AI 披露横幅
 *   - 日期分隔条
 *   - 消息气泡（AI 左 / 用户右）
 *   - AI 正在回复时的加载动画气泡
 *
 * 滚动行为：
 *   - 新消息到达时自动滚到底部
 *   - 用户向上滚动时不强制拉回（保持阅读上下文）
 */
import {
  lazy,
  Suspense,
  type CSSProperties,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
} from "react";
import { LoaderCircle } from "lucide-react";

import type { UiMessage } from "@/stores/chat/ui-message";
import type { ChatAction, CommercialAction } from "@/data/schemas/chat";
import { usePullToRefresh } from "@/hooks/use-pull-to-refresh";

import {
  buildChatRenderItems,
  createChatMessageKeyResolver,
  type ChatMessageKeyResolver,
} from "../chat-render-items";
import { AiDisclosureBanner } from "./ai-disclosure-banner";
import { DateHeader } from "./date-header";
import { MessageBubble } from "./message-bubble";
import styles from "./chat-area.module.css";

const CHAT_AUTO_SCROLL_BOTTOM_THRESHOLD = 96;
const CHAT_PULL_LOADING_OFFSET = 46;
const ReplyingAnimation = lazy(() =>
  import("./lottie-message-bubble").then((module) => ({
    default: module.LottieMessageBubble,
  })),
);

export interface ChatAreaProps {
  characterId: string;
  messages: readonly UiMessage[];
  isReplyingAI: boolean;
  scrollToBottomSignal?: number;
  initialScrollReady?: boolean;
  canLoadMoreHistory?: boolean;
  isLoadingMoreHistory?: boolean;
  isUnlockingMessage?: boolean;
  unlockingMessageId?: string | null;
  onUnlockPrivateMessage?: ChatMessageAction;
  onUnlockVoiceMessage?: ChatMessageAction;
  onUnlockImageMessage?: ChatMessageAction;
  onOpenImage?: (displayMessageId: string) => void;
  onLoadMoreHistory?: () => void;
  onUserAvatarClick?: () => void;
  onCharacterAvatarClick?: () => void;
  onCommercialAction?: (action: CommercialAction) => void;
  onCommercialActionDismiss?: (action: CommercialAction) => void;
  onChatActionViewed?: (action: ChatAction) => void;
  onChatAction?: (action: ChatAction) => void | Promise<void>;
  onChatActionDismiss?: (action: ChatAction) => void;
}

type ChatMessageAction = (
  displayMessageId: string,
  remoteMessageId?: string,
) => void;

export function ChatArea({
  characterId,
  messages,
  isReplyingAI,
  scrollToBottomSignal = 0,
  initialScrollReady = true,
  canLoadMoreHistory = false,
  isLoadingMoreHistory = false,
  isUnlockingMessage,
  unlockingMessageId,
  onUnlockPrivateMessage,
  onUnlockVoiceMessage,
  onUnlockImageMessage,
  onOpenImage,
  onLoadMoreHistory,
  onUserAvatarClick,
  onCharacterAvatarClick,
  onCommercialAction,
  onCommercialActionDismiss,
  onChatActionViewed,
  onChatAction,
  onChatActionDismiss,
}: ChatAreaProps) {
  const scrollRef = useRef<HTMLDivElement>(null);
  const contentRef = useRef<HTMLDivElement>(null);
  const initialScrollSettledRef = useRef(false);
  const shouldStickToBottomRef = useRef(true);
  const previousScrollToBottomSignalRef = useRef(scrollToBottomSignal);
  const loadMoreAnchorRef = useRef<{
    scrollHeight: number;
    scrollTop: number;
  } | null>(null);
  const getMessageKey = useMemo(() => createChatMessageKeyResolver(), []);
  const {
    isPulling,
    isRefreshing,
    isReleaseReady,
    pullDistance,
    touchHandlers,
  } = usePullToRefresh(
    (scrollNode) => {
      if (!onLoadMoreHistory) return;
      loadMoreAnchorRef.current = {
        scrollHeight: scrollNode.scrollHeight,
        scrollTop: scrollNode.scrollTop,
      };
      shouldStickToBottomRef.current = false;
      onLoadMoreHistory();
    },
    {
      disabled:
        !initialScrollReady || !canLoadMoreHistory || !onLoadMoreHistory,
      refreshing: isLoadingMoreHistory,
    },
  );
  const pullOffset = isRefreshing
    ? CHAT_PULL_LOADING_OFFSET
    : pullDistance;
  const pullStyle = {
    "--chat-pull-offset": `${pullOffset}px`,
  } as CSSProperties;
  const showPullIndicator = pullOffset > 0;

  useLayoutEffect(() => {
    if (!initialScrollReady) {
      initialScrollSettledRef.current = false;
      previousScrollToBottomSignalRef.current = scrollToBottomSignal;
      return;
    }

    const scrollNode = scrollRef.current;
    if (!scrollNode) return;

    const shouldForceScrollToBottom =
      previousScrollToBottomSignalRef.current !== scrollToBottomSignal;
    previousScrollToBottomSignalRef.current = scrollToBottomSignal;

    if (shouldForceScrollToBottom) {
      loadMoreAnchorRef.current = null;
      initialScrollSettledRef.current = true;
      shouldStickToBottomRef.current = true;
      scrollToBottom(scrollNode);
      const frameId = requestAnimationFrame(() => {
        if (
          scrollRef.current === scrollNode &&
          shouldStickToBottomRef.current
        ) {
          scrollToBottom(scrollNode);
        }
      });
      return () => cancelAnimationFrame(frameId);
    }

    if (loadMoreAnchorRef.current && !isLoadingMoreHistory) {
      const anchor = loadMoreAnchorRef.current;
      loadMoreAnchorRef.current = null;
      scrollNode.scrollTop = Math.max(
        0,
        anchor.scrollTop + scrollNode.scrollHeight - anchor.scrollHeight,
      );
      return;
    }

    if (!initialScrollSettledRef.current) {
      initialScrollSettledRef.current = true;
      shouldStickToBottomRef.current = true;
    }

    if (!shouldStickToBottomRef.current) return;

    scrollToBottom(scrollNode);
    const frameId = requestAnimationFrame(() => {
      if (
        scrollRef.current === scrollNode &&
        shouldStickToBottomRef.current
      ) {
        scrollToBottom(scrollNode);
      }
    });

    return () => cancelAnimationFrame(frameId);
  }, [
    initialScrollReady,
    isLoadingMoreHistory,
    isReplyingAI,
    messages,
    scrollToBottomSignal,
  ]);

  useEffect(() => {
    const scrollNode = scrollRef.current;
    const contentNode = contentRef.current;
    if (!scrollNode || !contentNode || typeof ResizeObserver === "undefined") {
      return;
    }

    const observer = new ResizeObserver(() => {
      if (
        initialScrollSettledRef.current &&
        shouldStickToBottomRef.current
      ) {
        scrollToBottom(scrollNode);
      }
    });
    observer.observe(scrollNode);
    observer.observe(contentNode);

    return () => observer.disconnect();
  }, []);

  return (
    <main
      ref={scrollRef}
      className={styles.area}
      aria-label="Chat messages"
      data-pulling={isPulling ? "true" : "false"}
      style={pullStyle}
      {...touchHandlers}
      onScroll={(event) => {
        shouldStickToBottomRef.current = isNearBottom(event.currentTarget);
      }}
    >
      <div
        className={styles.pullIndicator}
        aria-hidden={!showPullIndicator}
      >
        <LoaderCircle
          size={17}
          aria-hidden="true"
          data-spinning={isRefreshing ? "true" : "false"}
        />
        <span>
          {isRefreshing
            ? "Loading earlier messages..."
            : isReleaseReady
              ? "Release to load earlier messages"
              : "Pull down for earlier messages"}
        </span>
      </div>
      <div ref={contentRef} className={styles.content}>
        <AiDisclosureBanner />

        {renderMessagesWithDateHeaders(
          characterId,
          messages,
          getMessageKey,
          isUnlockingMessage,
          unlockingMessageId,
          onUnlockPrivateMessage,
          onUnlockVoiceMessage,
          onUnlockImageMessage,
          onOpenImage,
          onUserAvatarClick,
          onCharacterAvatarClick,
          onCommercialAction,
          onCommercialActionDismiss,
          onChatActionViewed,
          onChatAction,
          onChatActionDismiss,
        )}

        {isReplyingAI ? (
          <Suspense fallback={null}>
            <ReplyingAnimation />
          </Suspense>
        ) : null}
      </div>
    </main>
  );
}

function scrollToBottom(scrollNode: HTMLElement): void {
  scrollNode.scrollTop = Math.max(
    0,
    scrollNode.scrollHeight - scrollNode.clientHeight,
  );
}

function isNearBottom(scrollNode: HTMLElement): boolean {
  const distanceFromBottom =
    scrollNode.scrollHeight - scrollNode.scrollTop - scrollNode.clientHeight;
  return distanceFromBottom <= CHAT_AUTO_SCROLL_BOTTOM_THRESHOLD;
}

/** 渲染消息列表（按日期分组插入分隔条） */
function renderMessagesWithDateHeaders(
  characterId: string,
  messages: readonly UiMessage[],
  getMessageKey: ChatMessageKeyResolver,
  isUnlockingMessage?: boolean,
  unlockingMessageId?: string | null,
  onUnlockPrivateMessage?: ChatMessageAction,
  onUnlockVoiceMessage?: ChatMessageAction,
  onUnlockImageMessage?: ChatMessageAction,
  onOpenImage?: (displayMessageId: string) => void,
  onUserAvatarClick?: () => void,
  onCharacterAvatarClick?: () => void,
  onCommercialAction?: (action: CommercialAction) => void,
  onCommercialActionDismiss?: (action: CommercialAction) => void,
  onChatActionViewed?: (action: ChatAction) => void,
  onChatAction?: (action: ChatAction) => void | Promise<void>,
  onChatActionDismiss?: (action: ChatAction) => void,
) {
  return buildChatRenderItems(messages, getMessageKey).map((item) =>
    item.type === "date" ? (
      <DateHeader key={item.key} date={item.date} />
    ) : (
      <MessageBubble
        characterId={characterId}
        key={item.key}
        displayMessageId={item.message.displayId}
        remoteMessageId={item.message.remoteId}
        content={item.message.content}
        imageUrl={item.message.imageUrl}
        imagePaywalled={item.message.imagePaywalled}
        audioUrl={item.message.audioUrl}
        isFromAI={item.message.isFromAI}
        locked={item.message.locked}
        lockReason={item.message.lockReason}
        lockedPrivate={item.message.lockedPrivate}
        privateMessageHint={item.message.privateMessageHint}
        commercialAction={item.message.commercialAction}
        chatAction={item.message.chatAction}
        paymentGuidance={item.message.paymentGuidance}
        isUnlockingMessage={
          isUnlockingMessage === true &&
          item.message.displayId === unlockingMessageId
        }
        onUnlockPrivateMessage={onUnlockPrivateMessage}
        onUnlockVoiceMessage={onUnlockVoiceMessage}
        onUnlockImageMessage={onUnlockImageMessage}
        onOpenImage={onOpenImage}
        onUserAvatarClick={onUserAvatarClick}
        onCharacterAvatarClick={onCharacterAvatarClick}
        onCommercialAction={onCommercialAction}
        onCommercialActionDismiss={onCommercialActionDismiss}
        onChatActionViewed={onChatActionViewed}
        onChatAction={onChatAction}
        onChatActionDismiss={onChatActionDismiss}
      />
    ),
  );
}
