import {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import { Loader2, MessageSquare, ChevronDown, Pin } from "lucide-react";
import type { MessageNewService } from "@/socket";
import MessageItem, { DateDivider, formatDate } from "./message-item";
import MessageComposer from "./composer";
import type { Thread } from "./thread-sidebar";

type Props = {
  roomId: string;
  roomName: string;
  currentUserId?: string;
  messages: MessageNewService[];
  loading: boolean;
  hasMore: boolean;
  onLoadMore: () => void;
  onSend: (content: string, inReplyTo?: string) => Promise<void>;
  onTyping?: (typing: boolean) => void;
  onPinToggle?: (messageId: string, pinned: boolean) => void;
  onDelete?: (messageId: string) => void;
  onEdit?: (messageId: string, content: string) => void;
  onStartThread?: (messageId: string, seq: number) => void;
  onViewThread?: (threadId: string, seq: number) => void;
  onReactionToggle?: (
    messageId: string,
    emoji: string,
    add: boolean,
  ) => void;
  streamingMessages?: Map<
    string,
    { content: string; displayName?: string }
  >;
  typingText?: string | null;
  threads?: Thread[];
};

function shouldShowHeader(
  current: MessageNewService,
  prev?: MessageNewService,
): boolean {
  if (!prev) return true;
  if (current.sender.id !== prev.sender.id) return true;
  const curTime = current.send_at ? new Date(current.send_at).getTime() : 0;
  const prvTime = prev.send_at ? new Date(prev.send_at).getTime() : 0;
  if (!curTime || !prvTime) return true;
  return curTime - prvTime > 5 * 60 * 1000;
}

function shouldShowDate(
  current: MessageNewService,
  prev?: MessageNewService,
): boolean {
  if (!prev) return true;
  const cur = current.send_at ? new Date(current.send_at) : new Date();
  const prv = prev.send_at ? new Date(prev.send_at) : new Date();
  if (isNaN(cur.getTime()) || isNaN(prv.getTime())) return true;
  return cur.toDateString() !== prv.toDateString();
}

type RenderedItem =
  | { type: "loading" | "more_loading" | "notice_beginning" | "notice_scroll" }
  | { type: "date"; date: string; id: string }
  | { type: "message"; msg: MessageNewService; isCompact: boolean; showHeader: boolean };

export default function MessageView({
  roomId,
  roomName,
  currentUserId,
  messages,
  loading,
  hasMore,
  onLoadMore,
  onSend,
  onTyping,
  onPinToggle,
  onDelete,
  onEdit,
  onStartThread,
  onViewThread,
  onReactionToggle,
  streamingMessages = new Map(),
  typingText,
  threads,
}: Props) {
  const scrollRef = useRef<HTMLDivElement>(null);
  const bottomRef = useRef<HTMLDivElement>(null);
  const [atBottom, setAtBottom] = useState(true);
  const [newMsgCount, setNewMsgCount] = useState(0);
  const [initialLoaded, setInitialLoaded] = useState(false);
  const prevMsgCountRef = useRef(0);
  const [replyTarget, setReplyTarget] = useState<MessageNewService | null>(null);

  const pinnedMessages = useMemo(
    () => messages.filter((m) => (m as Record<string, unknown>).pinned),
    [messages],
  );

  const renderedItems = useMemo<RenderedItem[]>(() => {
    const result: RenderedItem[] = [];

    if (loading && messages.length === 0) {
      result.push({ type: "loading" });
      return result;
    }

    if (!loading && !hasMore) {
      result.push({ type: "notice_beginning" });
    } else if (!loading && hasMore) {
      result.push({ type: "notice_scroll" });
    } else if (loading) {
      result.push({ type: "more_loading" });
    }

    let prevMsg: MessageNewService | undefined;

    for (const msg of messages) {
      if (shouldShowDate(msg, prevMsg)) {
        result.push({ type: "date", date: msg.send_at, id: `d-${msg.id}` });
        prevMsg = undefined;
      }

      const showHeader = shouldShowHeader(msg, prevMsg);
      result.push({
        type: "message",
        msg,
        isCompact: !showHeader,
        showHeader,
      });
      prevMsg = msg;
    }

    return result;
  }, [messages, loading, hasMore]);

  const messageCount = useMemo(
    () => renderedItems.filter((i) => i.type === "message").length,
    [renderedItems],
  );

  /* eslint-disable react-hooks/set-state-in-effect */
  useEffect(() => {
    prevMsgCountRef.current = 0;
    setNewMsgCount(0);
    setInitialLoaded(false);
  }, [roomId]);
  /* eslint-enable react-hooks/set-state-in-effect */

  // Scroll to bottom after initial messages load
  useEffect(() => {
    if (!initialLoaded && !loading && messageCount > 0) {
      // eslint-disable-next-line react-hooks/set-state-in-effect -- one-time scroll after load
      setInitialLoaded(true);
      requestAnimationFrame(() => {
        if (scrollRef.current) {
          scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
        }
      });
    }
  }, [initialLoaded, loading, messageCount]);

  // Auto-scroll on new messages (after initial load)
  useEffect(() => {
    const prev = prevMsgCountRef.current;
    prevMsgCountRef.current = messageCount;

    if (!initialLoaded) return;

    if (messageCount > prev && prev > 0 && !loading) {
      if (atBottom) {
        bottomRef.current?.scrollIntoView({ behavior: "smooth" });
        /* eslint-disable react-hooks/set-state-in-effect */
        setNewMsgCount(0);
        /* eslint-enable react-hooks/set-state-in-effect */
      } else {
        setNewMsgCount((c) => c + (messageCount - prev));
      }
    }
  }, [messageCount, loading, atBottom, initialLoaded]);

  const handleScroll = useCallback(() => {
    const el = scrollRef.current;
    if (!el) return;

    const threshold = 80;
    const isAtBottom =
      el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
    setAtBottom(isAtBottom);
    if (isAtBottom) setNewMsgCount(0);

    if (el.scrollTop < threshold && hasMore && !loading) {
      onLoadMore();
    }
  }, [hasMore, loading, onLoadMore]);

  const handleScrollToBottom = useCallback(() => {
    bottomRef.current?.scrollIntoView({ behavior: "smooth" });
    setNewMsgCount(0);
    setAtBottom(true);
  }, []);

  if (!roomId) {
    return (
      <div className="flex min-h-0 flex-1 flex-col items-center justify-center">
        <div className="text-center">
          <div className="mx-auto grid size-20 place-items-center rounded-2xl bg-muted/20 ring-1 ring-border/30">
            <MessageSquare className="size-8 text-muted-foreground/15" />
          </div>
          <h3 className="mt-5 text-lg font-semibold text-foreground">
            Select a channel
          </h3>
          <p className="mt-1.5 text-sm text-muted-foreground/40">
            Choose a channel from the sidebar to start chatting
          </p>
        </div>
      </div>
    );
  }

  return (
    <div className="flex min-h-0 flex-1 flex-col">
      {pinnedMessages.length > 0 && (
        <button
          className="flex shrink-0 w-full cursor-pointer items-center gap-2 border-b border-warning/10 bg-warning/[0.03] px-4 py-2 text-left transition-colors hover:bg-warning/[0.06]"
          type="button"
          title={`${pinnedMessages.length} pinned message${pinnedMessages.length > 1 ? "s" : ""}`}
        >
          <Pin className="size-3.5 text-warning/50" />
          <span className="truncate text-[12px] font-medium text-warning/60">
            {pinnedMessages.length} pinned message
            {pinnedMessages.length > 1 ? "s" : ""}
          </span>
        </button>
      )}

      <div className="relative min-h-0 flex-1">
        <div
          ref={scrollRef}
          className="absolute inset-0 overflow-y-auto overscroll-contain scroll-smooth"
          onScroll={handleScroll}
        >
          {renderedItems.map((item) => {
            switch (item.type) {
              case "loading":
                return (
                  <div
                    key="loading"
                    className="flex items-center justify-center py-20"
                  >
                    <Loader2 className="size-5 animate-spin text-muted-foreground/30" />
                  </div>
                );
              case "more_loading":
                return (
                  <div key="more_loading" className="flex justify-center py-3">
                    <Loader2 className="size-4 animate-spin text-muted-foreground/30" />
                  </div>
                );
              case "notice_beginning":
                return (
                  <div key="notice_beginning" className="flex flex-col items-center gap-2 px-4 py-6">
                    <div className="grid size-10 place-items-center rounded-2xl bg-muted/30 ring-1 ring-border/20">
                      <MessageSquare className="size-4 text-muted-foreground/20" />
                    </div>
                    <span className="text-[12px] font-medium text-muted-foreground/30">
                      Beginning of <span className="text-muted-foreground/50">#{roomName}</span>
                    </span>
                    <div className="h-px w-24 bg-gradient-to-r from-transparent via-border/30 to-transparent mt-1" />
                  </div>
                );
              case "notice_scroll":
                return (
                  <div
                    key="notice_scroll"
                    className="flex items-center justify-center gap-2 py-4 text-[11px] text-muted-foreground/30"
                  >
                    <div className="h-px w-8 bg-gradient-to-r from-transparent to-border/30" />
                    <span>Scroll up for older messages</span>
                    <div className="h-px w-8 bg-gradient-to-l from-transparent to-border/30" />
                  </div>
                );
              case "date":
                return <DateDivider key={item.id} date={formatDate(item.date)} />;
              case "message":
                return (
                  <MessageItem
                    currentUserId={currentUserId}
                    isCompact={item.isCompact}
                    key={item.msg.id}
                    message={item.msg}
                    messages={messages}
                    onDelete={onDelete}
                    onEdit={onEdit}
                    onPinToggle={onPinToggle}
                    onReactionToggle={onReactionToggle}
                    onStartThread={onStartThread}
                    onViewThread={onViewThread}
                    onReply={(msg) => setReplyTarget(msg)}
                    roomId={roomId}
                    showHeader={item.showHeader}
                    threads={threads}
                  />
                );
              default:
                return null;
            }
          })}

          {Array.from(streamingMessages.entries()).map(([id, stream]) => (
            <div className="group relative flex gap-3 px-4 py-1.5" key={`stream-${id}`}>
              <div className="grid size-9 shrink-0 place-items-center rounded-full bg-gradient-to-br from-primary/20 to-primary/5">
                <Loader2 className="size-4 animate-spin text-primary/60" />
              </div>
              <div className="min-w-0 flex-1">
                <div className="flex items-baseline gap-2">
                  <span className="text-sm font-semibold text-foreground">
                    {stream.displayName ?? "AI"}
                  </span>
                  <span className="rounded-full bg-primary/[0.06] px-1.5 py-[1px] text-[10px] font-medium text-primary/70">
                    streaming
                  </span>
                </div>
                {stream.content ? (
                  <p className="mt-0.5 whitespace-pre-wrap break-words text-sm text-foreground/80">
                    {stream.content}
                    <span className="ml-0.5 inline-block h-3.5 w-0.5 animate-pulse bg-primary/40 align-text-bottom" />
                  </p>
                ) : (
                  <div className="mt-1.5 flex gap-1">
                    <span className="inline-block size-1.5 animate-bounce rounded-full bg-muted-foreground/20 [animation-delay:0ms]" />
                    <span className="inline-block size-1.5 animate-bounce rounded-full bg-muted-foreground/20 [animation-delay:150ms]" />
                    <span className="inline-block size-1.5 animate-bounce rounded-full bg-muted-foreground/20 [animation-delay:300ms]" />
                  </div>
                )}
              </div>
            </div>
          ))}

          <div ref={bottomRef} className="mb-4" />
        </div>

        {newMsgCount > 0 && (
          <button
            className="absolute bottom-3 left-1/2 z-20 flex -translate-x-1/2 cursor-pointer items-center gap-1.5 rounded-full bg-primary px-4 py-1.5 text-xs font-medium text-primary-foreground shadow-lg transition-[background-color,border-color,color,opacity,box-shadow,transform] duration-200 hover:bg-primary/90 hover:shadow-xl"
            onClick={handleScrollToBottom}
          >
            <ChevronDown className="size-3.5" />
            {newMsgCount} new message{newMsgCount > 1 ? "s" : ""}
          </button>
        )}
      </div>

      {typingText && (
        <div
          aria-live="polite"
          className="flex h-6 shrink-0 items-center gap-1.5 px-4"
        >
          <span className="flex gap-0.5">
            <span className="inline-block size-1 animate-bounce rounded-full bg-muted-foreground/30 [animation-delay:0ms]" />
            <span className="inline-block size-1 animate-bounce rounded-full bg-muted-foreground/30 [animation-delay:150ms]" />
            <span className="inline-block size-1 animate-bounce rounded-full bg-muted-foreground/30 [animation-delay:300ms]" />
          </span>
          <span className="truncate text-[11px] text-muted-foreground/50">
            {typingText}
          </span>
        </div>
      )}

      <MessageComposer
        onCancelReply={() => setReplyTarget(null)}
        onSend={async (content) => {
          await onSend(content, replyTarget?.id);
          setReplyTarget(null);
        }}
        onTyping={onTyping}
        replyTarget={replyTarget}
        roomName={roomName}
      />
    </div>
  );
}
