import { useEffect, useRef, useState } from 'react';

import dayjs from 'dayjs';
import { observer } from 'mobx-react';

import { rpc } from '../../../api/request';
import { showAlert } from '../../../components/AlertRoot';
import { IS_DEV, VERSION } from '../../../config';
import { usePresentation } from '../../../presentation/PresentationContext';
import { channelStore } from '../../../stores/channelStore';
import store from '../../../stores/store';
import MessageItem from './MessageItem';
import MessagePinnedItem from './MessagePinnedItem';
import MessagePopover, {
  MessagePopoverRefProps,
} from './MessagePopover';
import MessageReactionPopover, {
  MessageReactionPopoverRefProps,
} from './MessageReactionPopover';

const SCROLL_DEBOUNCE_MS = 300;
const SMOOTH_SCROLL_MAX_DISTANCE_PX = 320;
const SCROLL_TO_BOTTOM_VISIBLE_PX = 400;

const MessagesBlock = observer(() => {
  const {
    components: {
      MessagesSkeleton,
      DateChip,
      ScrollToBottomButton,
      CloseConversationButton,
    },
  } = usePresentation();

  const pageRef = useRef<HTMLDivElement | null>(null);
  const messagePopoverRef = useRef<MessagePopoverRefProps | null>(null);
  const messageReactionPopoverRef =
    useRef<MessageReactionPopoverRefProps | null>(null);
  const scrollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const [showScrollDown, setShowScrollDown] = useState(false);

  const scrollToBottom = (force = false) => {
    if (scrollTimeoutRef.current) {
      clearTimeout(scrollTimeoutRef.current);
    }
    scrollTimeoutRef.current = setTimeout(() => {
      scrollTimeoutRef.current = null;
      requestAnimationFrame(() => {
        const el = pageRef.current;
        if (!el) return;
        const maxScrollTop = Math.max(0, el.scrollHeight - el.clientHeight);
        const distanceToBottom = maxScrollTop - el.scrollTop;
        el.scrollTo({
          top: maxScrollTop,
          behavior:
            !force &&
            distanceToBottom > 0 &&
            distanceToBottom <= SMOOTH_SCROLL_MAX_DISTANCE_PX
              ? 'smooth'
              : force
              ? 'smooth'
              : 'auto',
        });
      });
    }, SCROLL_DEBOUNCE_MS);
  };

  useEffect(() => {
    if (channelStore.channel?._id) {
      channelStore.loadMessages();
      if (!channelStore.channel?.closed) {
        channelStore.startMessagesPolling(scrollToBottom);
      } else {
        channelStore.stopMessagesPolling();
      }
    }
    return () => {
      channelStore.stopMessagesPolling();
      if (scrollTimeoutRef.current) {
        clearTimeout(scrollTimeoutRef.current);
      }
    };
  }, [channelStore.channel?._id, channelStore.channel?.closed]);

  const initialScrollToBottomCalled = useRef(false);

  // Reset the "initial scroll" flag whenever the user switches to a different
  // channel. In desktop mode <MessagesBlock /> stays mounted across channel
  // changes, so without this reset the autoscroll-to-bottom would only fire
  // for the very first opened chat.
  useEffect(() => {
    initialScrollToBottomCalled.current = false;
  }, [channelStore.channel?._id]);

  useEffect(() => {
    if (channelStore.displayMessages?.length && pageRef.current) {
      if (!initialScrollToBottomCalled.current) {
        initialScrollToBottomCalled.current = true;
        scrollToBottom();
      }
    }
  }, [channelStore.displayMessages?.length, channelStore.channel?._id]);

  useEffect(() => {
    if (!channelStore.pendingScrollToBottom) return;
    if (!channelStore.displayMessages?.length) return;
    scrollToBottom();
    channelStore.clearPendingScrollToBottom();
  }, [channelStore.displayMessages]);

  // Track "near bottom" for floating scroll-down button.
  useEffect(() => {
    const el = pageRef.current;
    if (!el) return;
    const onScroll = () => {
      const distanceToBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
      setShowScrollDown(distanceToBottom > SCROLL_TO_BOTTOM_VISIBLE_PX);
    };
    el.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => el.removeEventListener('scroll', onScroll);
  }, [channelStore.channel?._id]);

  const scrollByMessage = (_id: string | undefined) => {
    if (!_id) return;
    const element = document.getElementById(`msg-${_id}`);
    if (!element || !pageRef.current) return;
    setTimeout(() => {
      if (!pageRef.current) return;
      const rect = element.getBoundingClientRect();
      const parentRect = pageRef.current.getBoundingClientRect();
      pageRef.current.scrollTo({
        top:
          pageRef.current.scrollTop + (rect.top - parentRect.top) - 80,
        behavior: 'smooth',
      });
      element.classList.add('tg-fade-in');
    }, 100);
  };

  const messages = channelStore.displayMessages ?? [];

  // Понимаем, что чат «групповой» (более одного собеседника или canal type !=
  // 1-on-1 поддержки), чтобы показывать имена авторов над входящими.
  const otherMembersCount = (channelStore.channel?.members ?? []).filter(
    (m) => m.owner != store.userID
  ).length;
  const isGroupLike = otherMembersCount > 1;

  // Группировка сообщений: чтобы у группы было одно имя автора сверху,
  // считаем newAuthorBlock — первое сообщение этого автора в дне.
  const grouped = messages.map((msg, i) => {
    const prev = messages[i - 1];
    const next = messages[i + 1];

    const sameDayAsPrev =
      prev?.created_at &&
      msg.created_at &&
      dayjs.unix(prev.created_at).isSame(dayjs.unix(msg.created_at), 'day');

    const newDay = !prev || !sameDayAsPrev;

    const sameOwnerAsPrev = prev && prev.owner === msg.owner && sameDayAsPrev;
    const sameOwnerAsNext =
      next &&
      next.owner === msg.owner &&
      next.created_at &&
      msg.created_at &&
      dayjs.unix(next.created_at).isSame(dayjs.unix(msg.created_at), 'day');

    return {
      msg,
      newDay,
      groupedTop: !!sameOwnerAsPrev,
      groupedBottom: !!sameOwnerAsNext,
    };
  });

  const scrollUnread =
    channelStore.channel?._id &&
    channelStore.channel.unreadCounts &&
    store.userID
      ? channelStore.channel.unreadCounts[store.userID as any] ?? 0
      : 0;

  return (
    <>
      {!!channelStore.pinnedMessage && (
        <MessagePinnedItem
          onPin={() => {
            rpc
              .pinnedMessage({ message: channelStore.pinnedMessage!._id })
              .then(() => channelStore.loadMessages());
          }}
          onClick={() => scrollByMessage(channelStore.pinnedMessage!._id)}
          msg={channelStore.pinnedMessage}
        />
      )}
      <div className="relative flex-1 min-h-0 tg-chat-bg">
        {IS_DEV && (
          <div className="absolute top-1 left-2 text-[10px] text-red-500/60 select-none pointer-events-none z-10">
            {VERSION}
          </div>
        )}

        <div
          ref={pageRef}
          className="absolute inset-0 overflow-y-auto tg-scroll"
        >
          <div className="flex flex-col py-3 px-3 pb-4 min-h-full">
            {channelStore.messagesLoading && !messages.length && (
              <MessagesSkeleton />
            )}

            {grouped.map(({ msg, newDay, groupedTop, groupedBottom }) => (
              <div key={msg._id} className="contents">
                {newDay && msg.created_at && <DateChip ts={msg.created_at} />}
                <MessageItem
                  onReplayClick={(m) => scrollByMessage(m._id)}
                  onReactionClick={(m) => {
                    messageReactionPopoverRef.current?.open({
                      msg: m,
                      onChange: () => channelStore.loadMessages(),
                    });
                  }}
                  onOpenMenu={() => {
                    messagePopoverRef.current?.open({
                      msg,
                      onChange: () => channelStore.loadMessages(),
                      onPinned: () => {
                        rpc
                          .pinnedMessage({ message: msg._id })
                          .then(() => channelStore.loadMessages());
                      },
                      onDelete: () => {
                        rpc
                          .channelDeleteMessage({
                            message: msg._id,
                            channel: channelStore.channel?._id,
                          })
                          .then(() => channelStore.loadMessages());
                      },
                      onReply: (m) => channelStore.setReplyMsg(m),
                    });
                  }}
                  onChange={() => channelStore.loadMessages()}
                  msg={msg}
                  showAuthorName={isGroupLike}
                  groupedTop={groupedTop}
                  groupedBottom={groupedBottom}
                />
              </div>
            ))}

            {!channelStore.channel?.closed &&
              channelStore.channel?.type == 'support' &&
              channelStore.channel?.members?.find(
                (m) => m.owner == store.userID
              ) && (
                <CloseConversationButton
                  onClick={() => {
                    showAlert({
                      title: 'Закрыть обращение?',
                      content: 'Диалог будет переведён в статус закрытого.',
                      buttons: [
                        {
                          text: 'Закрыть',
                          onClick: () => {
                            rpc
                              .channelClose({
                                channel: channelStore.channel?._id,
                              })
                              .then(() => {
                                channelStore.loadMessages();
                                channelStore.refreshChannel();
                              });
                          },
                        },
                        { text: 'Отмена' },
                      ],
                    });
                  }}
                />
              )}
          </div>
        </div>

        <ScrollToBottomButton
          visible={showScrollDown}
          onClick={() => scrollToBottom(true)}
          unread={scrollUnread}
        />

        <MessageReactionPopover ref={messageReactionPopoverRef} />
        <MessagePopover ref={messagePopoverRef} />
      </div>
    </>
  );
});

export default MessagesBlock;
