'use client';

import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Link from 'next/link';
import { ArrowLeft, BarChart3, CalendarDays, ImageIcon, Loader2, MessageCircle, Paperclip, Search, Send, Settings, Smile, Trash2, X } from 'lucide-react';
import { AvatarWithPresence } from '@/components/family/avatar-with-presence';
import { FamilyBotChat, BotMiniAppSheet } from '@/components/family/family-bot-chat';
import { ChatRoomAvatarDisplay } from '@/components/family/chat-room-avatar-display';
import { ChatDateSeparator } from '@/components/chat/chat-date-separator';
import { ChatEmojiContent } from '@/components/chat/chat-emoji-content';
import { ChatMessageContextMenu, ChatSelectionContextMenu, SelectableMessageWrapper } from '@/components/chat/chat-message-context-menu';
import { ChatPinnedMessageBanner } from '@/components/chat/chat-pinned-message-banner';
import { ChatDeleteMessageDialog } from '@/components/chat/chat-delete-message-dialog';
import { ChatMessageEditBanner } from '@/components/chat/chat-message-edit-banner';
import { ChatMessageReactions } from '@/components/chat/chat-message-reactions';
import { ChatSelectionToolbar } from '@/components/chat/chat-selection-toolbar';
import { ChatToolsDialog, type ChatToolsTab } from '@/components/chat/chat-tools-dialog';
import { EmojiPicker } from '@/components/chat/emoji-picker';
import { scrollToChatMessage } from '@/components/chat/replied-message-preview';
import { useFamilyOverlay } from '@/components/family/family-overlay-provider';
import { useAuth } from '@/components/id/auth-provider';
import { UserAvatar } from '@/components/id/user-avatar';
import { useToast } from '@/components/id/toast-provider';
import { useRealtime } from '@/components/notifications/realtime-provider';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { useSelectedFamily } from '@/hooks/use-primary-family';
import { useChatDragSelection } from '@/hooks/use-chat-drag-selection';
import { useChatMessageSelection } from '@/hooks/use-chat-message-selection';
import { FamilyGroupSelector } from '@/components/family/family-group-selector';
import {
  apiFetch,
  BotChatMessage,
  BotChatMeta,
  ChatMessage,
  ChatRoom,
  deleteChatMessage,
  deleteChatRoom,
  editChatMessage,
  fetchBotChatMessages,
  fetchChatMessages,
  fetchChatRooms,
  getApiErrorMessage,
  markChatRoomRead,
  sendBotMessage,
  sendChatMessage,
  setChatMessagePinned,
  toggleChatMessageReaction,
  uploadMediaObject,
  voteChatPoll
} from '@/lib/api';
import {
  getChatListPreviewText,
  getMessageCopyText,
  getMessagePreviewText,
  isChatInteractiveTarget,
  isSystemMessage
} from '@/lib/chat-message-utils';
import { detectChatMessageType, parseMessageMetadata, resolveChatContentType } from '@/lib/chat-media';
import { getSingleEmojiNative, isSingleEmojiMessage } from '@/lib/chat-emoji-display';
import { shouldShowChatDateSeparator } from '@/lib/chat-date-utils';
import { encryptE2EBlob, e2eKindFromMessageType } from '@/lib/e2e-chat';
import { findMemberRoom, findOrCreateMemberChat, roomDisplayLabel } from '@/lib/family-chat';
import { getE2EMessageText, useE2EChat } from '@/hooks/use-e2e-chat';
import { cn } from '@/lib/utils';

interface PresignedUploadResponse {
  uploadUrl: string;
  storageKey: string;
  expiresAt?: string;
  uploadToken?: string;
}

function formatMessageTime(value: string) {
  return new Intl.DateTimeFormat('ru-RU', { hour: '2-digit', minute: '2-digit' }).format(new Date(value));
}

export function MiniFamilyChat() {
  const { user, token, isPinLocked } = useAuth();
  const { showToast } = useToast();
  const { subscribe } = useRealtime();
  const {
    group,
    groupSummaries,
    hasFamily,
    selectedGroupId,
    setSelectedGroupId,
    presenceByUserId
  } = useSelectedFamily(Boolean(user && token && !isPinLocked));
  const {
    miniChatOpen,
    openMiniChat,
    closeMiniChat,
    pendingRoomId,
    pendingMember,
    clearPending
  } = useFamilyOverlay();

  const [view, setView] = useState<'picker' | 'chat' | 'bot'>('picker');
  const [rooms, setRooms] = useState<ChatRoom[]>([]);
  const [activeRoom, setActiveRoom] = useState<ChatRoom | null>(null);
  const [activeBot, setActiveBot] = useState<{ botRef: string; name: string } | null>(null);
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [botMessages, setBotMessages] = useState<BotChatMessage[]>([]);
  const [draft, setDraft] = useState('');
  const [loadingRooms, setLoadingRooms] = useState(false);
  const [loadingMessages, setLoadingMessages] = useState(false);
  const [sending, setSending] = useState(false);
  const [openingMemberId, setOpeningMemberId] = useState<string | null>(null);
  const [deletingRoomId, setDeletingRoomId] = useState<string | null>(null);
  const [miniAppUrl, setMiniAppUrl] = useState<string | null>(null);
  const [miniAppTitle, setMiniAppTitle] = useState('Mini App');
  const [botChatMeta, setBotChatMeta] = useState<BotChatMeta | null>(null);
  const [editingMessageId, setEditingMessageId] = useState<string | null>(null);
  const [deleteMessagesDialogOpen, setDeleteMessagesDialogOpen] = useState(false);
  const [pendingDeleteMessageIds, setPendingDeleteMessageIds] = useState<string[]>([]);
  const [deletingMessages, setDeletingMessages] = useState(false);
  const [emojiOpen, setEmojiOpen] = useState(false);
  const [pollOpen, setPollOpen] = useState(false);
  const [pollQuestion, setPollQuestion] = useState('');
  const [pollOptions, setPollOptions] = useState(['', '']);
  const [chatToolsOpen, setChatToolsOpen] = useState(false);
  const [chatToolsTab, setChatToolsTab] = useState<ChatToolsTab>('search');
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const autoScrollStateRef = useRef<{ id: string | null; count: number }>({ id: null, count: 0 });
  const fileInputRef = useRef<HTMLInputElement>(null);
  const previousGroupIdRef = useRef<string | null>(null);

  const visibleMessages = useMemo(
    () => messages.filter((message) => !message.isDeleted),
    [messages]
  );
  const pinnedMessage = useMemo(
    () =>
      visibleMessages
        .filter((message) => message.isPinned)
        .sort((left, right) => new Date(right.pinnedAt ?? right.createdAt).getTime() - new Date(left.pinnedAt ?? left.createdAt).getTime())[0] ?? null,
    [visibleMessages]
  );

  const {
    selectionMode,
    selectedMessageIds,
    enterSelectionMode,
    exitSelectionMode,
    toggleSelectedMessage,
    addSelectedMessage,
    handleSelectionPointer
  } = useChatMessageSelection(visibleMessages);

  const { handleMessagePointerDown, handleDragEnterMessage, isDragging } = useChatDragSelection({
    selectionMode,
    enterSelectionMode,
    addSelectedMessage
  });

  const { peerE2eKey, peerKeyLoading, e2ePayloads, e2eErrors, encryptOutgoing, isE2E: activeRoomIsE2E } = useE2EChat({
    room: activeRoom,
    messages,
    userId: user?.id,
    token
  });
  const pinnedVisibleText = pinnedMessage
    ? getE2EMessageText({
        message: pinnedMessage,
        isE2E: activeRoomIsE2E,
        peerE2eKey,
        peerKeyLoading,
        e2ePayload: pinnedMessage.isEncrypted ? e2ePayloads[pinnedMessage.id] : null,
        e2eError: e2eErrors[pinnedMessage.id]
      })
    : '';
  const pinnedPreview = pinnedMessage ? getMessagePreviewText(pinnedMessage, pinnedVisibleText) : '';

  const loadRooms = useCallback(async () => {
    if (!group || !token) return;
    setLoadingRooms(true);
    try {
      const response = await fetchChatRooms(group.id, token);
      setRooms(response.rooms ?? []);
    } catch (error) {
      showToast(getApiErrorMessage(error, 'Не удалось загрузить чаты') ?? 'Ошибка');
    } finally {
      setLoadingRooms(false);
    }
  }, [group, showToast, token]);

  const openBotChat = useCallback(
    async (botRef: string, botName: string) => {
      if (!token) return;
      setActiveBot({ botRef, name: botName });
      setActiveRoom(null);
      setView('bot');
      setLoadingMessages(true);
      try {
        const response = await fetchBotChatMessages(botRef, token);
        setBotMessages(response.messages ?? []);
      } catch (error) {
        showToast(getApiErrorMessage(error, 'Не удалось загрузить чат с ботом') ?? 'Ошибка');
      } finally {
        setLoadingMessages(false);
      }
    },
    [showToast, token]
  );

  const openRoom = useCallback(
    async (room: ChatRoom) => {
      if (room.type === 'BOT' && room.botUsername && user) {
        await openBotChat(room.botUsername, roomDisplayLabel(room, user.id));
        return;
      }
      setActiveRoom(room);
      setActiveBot(null);
      setView('chat');
      setLoadingMessages(true);
      try {
        const response = await fetchChatMessages(room.id, token);
        setMessages((response.messages ?? []).filter((message) => !message.isDeleted));
      } catch (error) {
        showToast(getApiErrorMessage(error, 'Не удалось загрузить сообщения') ?? 'Ошибка');
      } finally {
        setLoadingMessages(false);
      }
    },
    [openBotChat, showToast, token, user]
  );

  const openMemberChat = useCallback(
    async (memberUserId: string, memberName: string, options?: { isBot?: boolean; botUsername?: string }) => {
      if (!group || !token || !user) return;
      if (options?.isBot && options.botUsername) {
        await openBotChat(options.botUsername, memberName);
        return;
      }
      const existing = findMemberRoom(rooms, memberUserId, user.id, options);
      if (existing) {
        await openRoom(existing);
        return;
      }
      setOpeningMemberId(memberUserId);
      try {
        const room = await findOrCreateMemberChat(group.id, memberUserId, memberName, user.id, token);
        await loadRooms();
        await openRoom(room);
      } catch (error) {
        showToast(getApiErrorMessage(error, 'Не удалось открыть чат') ?? 'Ошибка');
      } finally {
        setOpeningMemberId(null);
      }
    },
    [group, loadRooms, openBotChat, openRoom, rooms, showToast, token, user]
  );

  useEffect(() => {
    if (!miniChatOpen || !hasFamily) return;
    void loadRooms();
  }, [group?.id, hasFamily, loadRooms, miniChatOpen]);

  useEffect(() => {
    if (!group?.id) return;
    if (previousGroupIdRef.current && previousGroupIdRef.current !== group.id && miniChatOpen) {
      setView('picker');
      setActiveRoom(null);
      setActiveBot(null);
      setMessages([]);
      setBotMessages([]);
      setDraft('');
    }
    previousGroupIdRef.current = group.id;
  }, [group?.id, miniChatOpen]);

  useEffect(() => {
    if (!miniChatOpen) {
      setView('picker');
      setActiveRoom(null);
      setActiveBot(null);
      setMessages([]);
      setBotMessages([]);
      setDraft('');
      exitSelectionMode();
      setEmojiOpen(false);
      setPollOpen(false);
      clearPending();
      return;
    }

    if (pendingRoomId && rooms.length) {
      const room = rooms.find((item) => item.id === pendingRoomId);
      if (room) {
        void openRoom(room);
        clearPending();
      }
    } else if (pendingMember && user) {
      void openMemberChat(pendingMember.userId, pendingMember.name, {
        isBot: pendingMember.isBot,
        botUsername: pendingMember.botUsername
      });
      clearPending();
    }
  }, [clearPending, exitSelectionMode, miniChatOpen, openMemberChat, openRoom, pendingMember, pendingRoomId, rooms, user]);

  useEffect(() => {
    exitSelectionMode();
    setEditingMessageId(null);
    setDraft('');
    setEmojiOpen(false);
  }, [activeRoom?.id, exitSelectionMode]);

  useEffect(() => {
    if (!activeBot) return;
    return subscribe((event) => {
      if (event.type !== 'bot_message' && event.type !== 'bot_message_edited') return;
      const payload = event.payload as {
        text?: string | null;
        messageId?: number;
        messageType?: string;
        editedAt?: string;
      };
      if (!payload?.messageId) return;
      setBotMessages((current) => {
        if (event.type === 'bot_message_edited') {
          return current.map((item) =>
            item.messageId === payload.messageId
              ? { ...item, text: payload.text ?? item.text, createdAt: payload.editedAt ?? item.createdAt }
              : item
          );
        }
        const exists = current.some((item) => item.messageId === payload.messageId && item.direction === 'out');
        if (exists) return current;
        return [
          ...current,
          {
            id: `out-live-${payload.messageId}`,
            direction: 'out' as const,
            text: payload.text ?? '',
            messageType: payload.messageType ?? 'text',
            messageId: payload.messageId ?? 0,
            createdAt: new Date().toISOString()
          }
        ];
      });
    });
  }, [activeBot, subscribe]);

  useEffect(() => {
    if (!activeRoom) return;
    return subscribe((event) => {
      const payload = event.payload;
      if (payload?.roomId && payload.roomId !== activeRoom.id) return;
      if (event.type === 'chat_message' && payload?.message) {
        const message = payload.message as ChatMessage;
        if (message.isDeleted) return;
        setMessages((current) => {
          if (current.some((item) => item.id === message.id)) return current;
          return [...current, message];
        });
      }
      if (event.type === 'chat_message_updated') {
        const updated = payload?.message as ChatMessage | undefined;
        if (updated) {
          if (updated.isDeleted) {
            setMessages((current) => current.filter((item) => item.id !== updated.id));
          } else {
            setMessages((current) => current.map((item) => (item.id === updated.id ? updated : item)));
          }
        }
      }
      if (event.type === 'chat_message_deleted') {
        const deleted = payload?.message as ChatMessage | undefined;
        if (deleted?.id) {
          setMessages((current) => current.filter((item) => item.id !== deleted.id));
        }
      }
    });
  }, [activeRoom, subscribe]);

  useEffect(() => {
    autoScrollStateRef.current = { id: null, count: 0 };
  }, [activeRoom?.id]);

  useEffect(() => {
    const lastMessage = visibleMessages[visibleMessages.length - 1];
    const previous = autoScrollStateRef.current;
    const next = { id: lastMessage?.id ?? null, count: visibleMessages.length };
    autoScrollStateRef.current = next;
    if (!next.id) return;
    const lastMessageChanged = previous.id !== next.id;
    const messageWasAppended = next.count >= previous.count;
    if (lastMessageChanged && messageWasAppended) {
      messagesEndRef.current?.scrollIntoView({ behavior: previous.id ? 'smooth' : 'auto' });
    }
  }, [visibleMessages]);

  useEffect(() => {
    if (!activeRoom || !token || !messages.length) return;
    const last = messages[messages.length - 1];
    const timer = window.setTimeout(() => {
      void markChatRoomRead(activeRoom.id, last.id, token).catch(() => {});
    }, 400);
    return () => window.clearTimeout(timer);
  }, [activeRoom, messages, token]);

  function cancelEditMessage() {
    setEditingMessageId(null);
    setDraft('');
  }

  function requestDeleteMessages(messageIds: string[]) {
    if (!messageIds.length) return;
    setPendingDeleteMessageIds(messageIds);
    setDeleteMessagesDialogOpen(true);
  }

  async function confirmDeleteMessages() {
    if (!token || !pendingDeleteMessageIds.length) return;
    const ids = [...pendingDeleteMessageIds];
    setDeletingMessages(true);
    setMessages((current) => current.filter((item) => !ids.includes(item.id)));
    try {
      for (const messageId of ids) {
        await deleteChatMessage(messageId, token);
        if (editingMessageId === messageId) cancelEditMessage();
      }
      setDeleteMessagesDialogOpen(false);
      setPendingDeleteMessageIds([]);
      exitSelectionMode();
      showToast(ids.length === 1 ? 'Сообщение удалено' : 'Сообщения удалены');
    } catch (error) {
      if (view === 'bot' && activeBot) {
        const response = await fetchBotChatMessages(activeBot.botRef, token);
        setBotMessages(response.messages ?? []);
      } else if (activeRoom && activeRoom.type !== 'BOT') {
        const response = await fetchChatMessages(activeRoom.id, token);
        setMessages((response.messages ?? []).filter((message) => !message.isDeleted));
      }
      showToast(getApiErrorMessage(error, 'Не удалось удалить сообщение') ?? 'Ошибка');
    } finally {
      setDeletingMessages(false);
    }
  }

  async function handleSend() {
    if (!token || !draft.trim()) return;
    if (view === 'bot' && activeBot) {
      setSending(true);
      try {
        const text = draft.trim();
        setBotMessages((current) => [
          ...current,
          {
            id: `in-local-${Date.now()}`,
            direction: 'in',
            text,
            messageType: 'text',
            messageId: 0,
            createdAt: new Date().toISOString()
          }
        ]);
        setDraft('');
        await sendBotMessage(activeBot.botRef, text, token);
        const response = await fetchBotChatMessages(activeBot.botRef, token);
        setBotMessages(response.messages ?? []);
      } catch (error) {
        showToast(getApiErrorMessage(error, 'Не удалось отправить сообщение') ?? 'Ошибка');
      } finally {
        setSending(false);
      }
      return;
    }

    if (!activeRoom) return;

    if (editingMessageId) {
      setSending(true);
      try {
        const updated = await editChatMessage(editingMessageId, draft.trim(), token);
        setMessages((current) => current.map((item) => (item.id === updated.id ? updated : item)));
        cancelEditMessage();
      } catch (error) {
        showToast(getApiErrorMessage(error, 'Не удалось изменить сообщение') ?? 'Ошибка');
      } finally {
        setSending(false);
      }
      return;
    }

    setSending(true);
    try {
      let content = draft.trim();
      let isEncrypted = false;
      if (activeRoomIsE2E) {
        content = await encryptOutgoing({ kind: 'text', text: content });
        isEncrypted = true;
      }
      const message = await sendChatMessage(activeRoom.id, { type: 'TEXT', content, isEncrypted }, token);
      setMessages((current) => [...current, message]);
      setDraft('');
      void loadRooms();
    } catch (error) {
      showToast(getApiErrorMessage(error, 'Не удалось отправить сообщение') ?? 'Ошибка');
    } finally {
      setSending(false);
    }
  }

  function insertEmojiToDraft(nativeEmoji: string) {
    setDraft((current) => `${current}${nativeEmoji}`);
  }

  function getSelectedMessages() {
    const selected = new Set(selectedMessageIds);
    return visibleMessages.filter((message) => selected.has(message.id));
  }

  async function handleBulkCopy() {
    const selected = getSelectedMessages();
    if (!selected.length) return;
    const lines = selected
      .map((message) =>
        getMessageCopyText(
          message,
          getE2EMessageText({
            message,
            isE2E: activeRoomIsE2E,
            peerE2eKey,
            peerKeyLoading,
            e2ePayload: message.isEncrypted ? e2ePayloads[message.id] : null,
            e2eError: e2eErrors[message.id]
          })
        )
      )
      .filter(Boolean);
    if (!lines.length) {
      showToast('Нечего копировать');
      return;
    }
    await navigator.clipboard.writeText(lines.join('\n\n'));
    showToast('Сообщения скопированы');
    exitSelectionMode();
  }

  async function handleBulkDelete() {
    const selected = getSelectedMessages().filter((message) => message.senderId === user?.id);
    if (!selected.length) {
      showToast('Можно удалять только свои сообщения');
      return;
    }
    requestDeleteMessages(selected.map((message) => message.id));
  }

  async function handleTogglePin(message: ChatMessage) {
    if (!token) return;
    try {
      const updated = await setChatMessagePinned(message.id, !message.isPinned, token);
      setMessages((current) => current.map((item) => (item.id === updated.id ? updated : item)));
      showToast(updated.isPinned ? 'Сообщение закреплено' : 'Сообщение откреплено');
    } catch (error) {
      showToast(getApiErrorMessage(error, 'Не удалось изменить закрепление') ?? 'Ошибка');
    }
  }

  function handleSelectionReply() {
    const selected = getSelectedMessages();
    if (selected.length !== 1) {
      showToast('Ответить можно только на одно сообщение');
      return;
    }
    showToast('Ответ доступен в полном виде семьи');
  }

  function handleSelectionPin() {
    const selected = getSelectedMessages();
    if (selected.length !== 1) {
      showToast('Закрепить можно только одно сообщение');
      return;
    }
    void handleTogglePin(selected[0]!);
  }

  async function uploadChatFile(file: File) {
    if (!token || !activeRoom || sending) return;
    setSending(true);
    try {
      const contentType = resolveChatContentType(file);
      const messageType = detectChatMessageType(file);
      const metadata = { fileName: file.name, fileSize: file.size };

      let uploadFile = file;
      let uploadContentType = contentType;
      let messageContent: string | undefined = messageType === 'FILE' ? file.name : undefined;
      let isEncrypted = false;

      if (activeRoomIsE2E) {
        if (!user?.id || !peerE2eKey) {
          showToast('Секретный чат недоступен без ключей E2E у обоих участников');
          return;
        }
        const encryptedBuffer = await encryptE2EBlob(user.id, peerE2eKey, await file.arrayBuffer());
        uploadFile = new File([encryptedBuffer], `${file.name}.lendryenc`, { type: 'application/octet-stream' });
        uploadContentType = 'application/octet-stream';
        messageContent = await encryptOutgoing({
          kind: e2eKindFromMessageType(messageType),
          fileName: file.name,
          mimeType: contentType,
          fileSize: file.size
        });
        isEncrypted = true;
      }

      const presigned = await apiFetch<PresignedUploadResponse>(
        `/media/chat/${activeRoom.id}/media/upload-url`,
        {
          method: 'POST',
          body: JSON.stringify({ contentType: uploadContentType, fileName: uploadFile.name })
        },
        token
      );
      await uploadMediaObject(presigned, uploadFile, uploadContentType);
      const message = await sendChatMessage(
        activeRoom.id,
        {
          type: messageType,
          storageKey: presigned.storageKey,
          mimeType: uploadContentType,
          content: messageContent,
          isEncrypted,
          metadataJson: JSON.stringify(activeRoomIsE2E ? { e2e: true, ...metadata } : metadata)
        },
        token
      );
      setMessages((current) => [...current, message]);
      void loadRooms();
    } catch (error) {
      showToast(getApiErrorMessage(error, 'Не удалось отправить файл') ?? 'Ошибка');
    } finally {
      setSending(false);
    }
  }

  async function submitPoll() {
    if (!token || !activeRoom) return;
    const options = pollOptions.map((item) => item.trim()).filter(Boolean);
    if (!pollQuestion.trim() || options.length < 2) {
      showToast('Укажите вопрос и минимум 2 варианта');
      return;
    }
    setSending(true);
    try {
      const message = await sendChatMessage(
        activeRoom.id,
        { type: 'POLL', poll: { question: pollQuestion.trim(), options } },
        token
      );
      setMessages((current) => [...current, message]);
      setPollOpen(false);
      setPollQuestion('');
      setPollOptions(['', '']);
      void loadRooms();
    } catch (error) {
      showToast(getApiErrorMessage(error, 'Не удалось создать опрос') ?? 'Ошибка');
    } finally {
      setSending(false);
    }
  }

  async function handleDeleteRoom(roomId: string) {
    if (!token) return;
    const room = rooms.find((item) => item.id === roomId);
    if (!room || room.type === 'GENERAL') return;
    if (!window.confirm('Удалить этот чат? Все сообщения будут удалены без возможности восстановления.')) return;
    setDeletingRoomId(roomId);
    try {
      await deleteChatRoom(roomId, token);
      if (activeRoom?.id === roomId) {
        setActiveRoom(null);
        setMessages([]);
        setView('picker');
      }
      await loadRooms();
      showToast('Чат удалён');
    } catch (error) {
      showToast(getApiErrorMessage(error, 'Не удалось удалить чат') ?? 'Ошибка');
    } finally {
      setDeletingRoomId(null);
    }
  }

  const otherMembers = useMemo(
    () => (group?.members ?? []).filter((member) => member.userId !== user?.id),
    [group?.members, user?.id]
  );

  if (!user || !token || isPinLocked) return null;

  return (
    <>
      <button
        type="button"
        onClick={() => (miniChatOpen ? closeMiniChat() : openMiniChat())}
        className={cn(
          'fixed bottom-5 right-5 z-50 flex h-12 w-12 items-center justify-center rounded-full bg-[#3390ec] text-white shadow-lg transition hover:bg-[#2b7fd4] lg:bottom-6 lg:right-6',
          miniChatOpen && 'scale-95'
        )}
        aria-label={miniChatOpen ? 'Закрыть чат семьи' : 'Открыть чат семьи'}
      >
        {miniChatOpen ? <X className="h-5 w-5" /> : <MessageCircle className="h-5 w-5" />}
      </button>

      {miniChatOpen ? (
        <div className="fixed bottom-20 right-5 z-50 flex w-[min(92vw,380px)] flex-col overflow-hidden rounded-[24px] border border-[#eceef4] bg-white shadow-2xl lg:bottom-[5.5rem] lg:right-6">
          <div className="flex items-center gap-2 border-b border-[#eceef4] px-4 py-3">
            {view === 'chat' || view === 'bot' ? (
              <Button type="button" variant="ghost" size="icon" className="h-8 w-8 shrink-0" onClick={() => setView('picker')}>
                <ArrowLeft className="h-4 w-4" />
              </Button>
            ) : null}
            <div className="min-w-0 flex-1">
              <p className="truncate text-sm font-semibold">
                {view === 'bot' && activeBot
                  ? activeBot.name
                  : view === 'chat' && activeRoom && user
                    ? roomDisplayLabel(activeRoom, user.id)
                    : 'Чат семьи'}
              </p>
              {view === 'chat' && activeRoom && user ? (
                <div className="mt-1 flex items-center gap-2">
                  <ChatRoomAvatarDisplay room={activeRoom} viewerUserId={user.id} token={token} size="sm" />
                  <span className="truncate text-xs text-[#667085]">{activeRoom.members.length} участников</span>
                </div>
              ) : null}
              {hasFamily ? (
                <FamilyGroupSelector
                  groups={groupSummaries}
                  selectedGroupId={selectedGroupId}
                  onSelect={setSelectedGroupId}
                  href={`/family/${group!.id}`}
                  nameClassName="text-xs text-[#667085]"
                />
              ) : (
                <p className="text-xs text-[#667085]">Семья не создана</p>
              )}
            </div>
            {view === 'bot' && botChatMeta?.manageWebAppUrl && user?.id === botChatMeta.botOwnerId ? (
              <Button
                type="button"
                variant="ghost"
                size="icon"
                className="h-8 w-8 shrink-0"
                aria-label="Настройки бота"
                title="Настройки бота"
                onClick={() => {
                  setMiniAppTitle('Настройки бота');
                  setMiniAppUrl(botChatMeta.manageWebAppUrl!);
                }}
              >
                <Settings className="h-4 w-4" />
              </Button>
            ) : null}
            {view === 'chat' && activeRoom ? (
              <>
                <Button
                  type="button"
                  variant="ghost"
                  size="icon"
                  className="h-8 w-8 shrink-0"
                  aria-label="Поиск по сообщениям"
                  onClick={() => {
                    setChatToolsTab('search');
                    setChatToolsOpen(true);
                  }}
                >
                  <Search className="h-4 w-4" />
                </Button>
                <Button
                  type="button"
                  variant="ghost"
                  size="icon"
                  className="h-8 w-8 shrink-0"
                  aria-label="Перейти к дате"
                  onClick={() => {
                    setChatToolsTab('calendar');
                    setChatToolsOpen(true);
                  }}
                >
                  <CalendarDays className="h-4 w-4" />
                </Button>
                <Button
                  type="button"
                  variant="ghost"
                  size="icon"
                  className="h-8 w-8 shrink-0"
                  aria-label="Медиа чата"
                  onClick={() => {
                    setChatToolsTab('media');
                    setChatToolsOpen(true);
                  }}
                >
                  <ImageIcon className="h-4 w-4" />
                </Button>
              </>
            ) : null}
            {view === 'chat' && activeRoom && activeRoom.type !== 'GENERAL' ? (
              <Button
                type="button"
                variant="ghost"
                size="icon"
                className="h-8 w-8 shrink-0 text-red-600"
                disabled={deletingRoomId === activeRoom.id}
                onClick={() => void handleDeleteRoom(activeRoom.id)}
              >
                {deletingRoomId === activeRoom.id ? <Loader2 className="h-4 w-4 animate-spin" /> : <Trash2 className="h-4 w-4" />}
              </Button>
            ) : null}
            <Button type="button" variant="ghost" size="icon" className="h-8 w-8 shrink-0" onClick={closeMiniChat}>
              <X className="h-4 w-4" />
            </Button>
          </div>

          {!hasFamily ? (
            <div className="space-y-3 p-5 text-sm text-[#667085]">
              <p>Создайте семейную группу, чтобы общаться с близкими из любого раздела.</p>
              <Link href="/family" className="inline-flex text-[#3390ec] hover:underline" onClick={closeMiniChat}>
                Перейти к созданию семьи
              </Link>
            </div>
          ) : view === 'picker' ? (
            <div className="max-h-[min(60vh,460px)] overflow-y-auto p-3">
              <section className="mb-4">
                <p className="mb-2 px-1 text-xs font-semibold uppercase tracking-wide text-[#667085]">Чаты</p>
                {loadingRooms ? (
                  <div className="flex items-center gap-2 px-2 py-3 text-sm text-[#667085]">
                    <Loader2 className="h-4 w-4 animate-spin" />
                    Загрузка...
                  </div>
                ) : rooms.length ? (
                  <div className="space-y-1">
                    {rooms.map((room) => (
                      <div key={room.id} className="group flex items-stretch rounded-xl transition hover:bg-[#f4f5f8]">
                        <button
                          type="button"
                          className="flex min-w-0 flex-1 items-center gap-3 px-2 py-2 text-left"
                          onClick={() => void openRoom(room)}
                        >
                          <div className="flex h-9 w-9 shrink-0 items-center justify-center overflow-hidden rounded-full">
                            {user ? (
                              <ChatRoomAvatarDisplay room={room} viewerUserId={user.id} token={token} size="sm" />
                            ) : (
                              <div className="flex h-9 w-9 items-center justify-center rounded-full bg-[#eef4ff] text-xs font-semibold text-[#3390ec]">
                                {room.type === 'GENERAL' ? 'В' : room.name.slice(0, 2).toUpperCase()}
                              </div>
                            )}
                          </div>
                          <div className="min-w-0 flex-1">
                            <p className="truncate text-sm font-medium">{user ? roomDisplayLabel(room, user.id) : room.name}</p>
                            <p className="truncate text-xs text-[#667085]">
                              {getChatListPreviewText(room.lastMessage, 'Нет сообщений')}
                            </p>
                          </div>
                        </button>
                        {room.type !== 'GENERAL' ? (
                          <button
                            type="button"
                            aria-label="Удалить чат"
                            disabled={deletingRoomId === room.id}
                            className="mr-1 flex h-8 w-8 shrink-0 self-center items-center justify-center rounded-lg text-[#667085] opacity-0 transition hover:bg-red-50 hover:text-red-600 group-hover:opacity-100 disabled:opacity-50"
                            onClick={() => void handleDeleteRoom(room.id)}
                          >
                            {deletingRoomId === room.id ? <Loader2 className="h-4 w-4 animate-spin" /> : <Trash2 className="h-3.5 w-3.5" />}
                          </button>
                        ) : null}
                      </div>
                    ))}
                  </div>
                ) : (
                  <p className="px-2 py-2 text-sm text-[#667085]">Чаты пока не созданы</p>
                )}
              </section>

              <section>
                <p className="mb-2 px-1 text-xs font-semibold uppercase tracking-wide text-[#667085]">Написать участнику</p>
                <div className="space-y-1">
                  {otherMembers.map((member) => {
                    const presence = presenceByUserId.get(member.userId);
                    return (
                      <button
                        key={member.id}
                        type="button"
                        disabled={openingMemberId === member.userId}
                        className="flex w-full items-center gap-3 rounded-xl px-2 py-2 text-left transition hover:bg-[#f4f5f8] disabled:opacity-60"
                        onClick={() =>
                          void openMemberChat(member.userId, member.displayName, {
                            isBot: member.isBot,
                            botUsername: member.botUsername
                          })
                        }
                      >
                        <AvatarWithPresence
                          userId={member.userId}
                          displayName={member.displayName}
                          hasAvatar={member.hasAvatar}
                          token={token}
                          isVerified={member.isVerified}
                          verificationIcon={member.verificationIcon}
                          online={presence?.online}
                          className="h-9 w-9"
                        />
                        <div className="min-w-0 flex-1">
                          <p className="truncate text-sm font-medium">
                            {member.displayName}
                            {member.isBot ? ' 🤖' : ''}
                          </p>
                          <p className={cn('text-xs', member.isBot ? 'text-[#3390ec]' : presence?.online ? 'text-emerald-600' : 'text-[#667085]')}>
                            {member.isBot ? 'Бот' : presence?.online ? 'В сети' : 'Личный чат'}
                          </p>
                        </div>
                        {openingMemberId === member.userId ? <Loader2 className="h-4 w-4 animate-spin text-[#667085]" /> : null}
                      </button>
                    );
                  })}
                  {!otherMembers.length ? (
                    <p className="px-2 py-2 text-sm text-[#667085]">Пригласите участников в семью</p>
                  ) : null}
                </div>
              </section>

              <Link
                href={`/family/${group!.id}`}
                className="mt-3 block rounded-xl bg-[#f4f5f8] px-3 py-2 text-center text-xs font-medium text-[#3390ec] hover:bg-[#eceef4]"
                onClick={closeMiniChat}
              >
                Открыть семью полностью
              </Link>
            </div>
          ) : view === 'bot' && activeBot ? (
            <FamilyBotChat
              botRef={activeBot.botRef}
              botName={activeBot.name}
              token={token}
              className="max-h-[min(60vh,460px)]"
              onBotMetaChange={setBotChatMeta}
              onOpenMiniApp={(url) => {
                setMiniAppTitle('Mini App');
                setMiniAppUrl(url);
              }}
            />
          ) : (
            <>
              {pinnedMessage ? (
                <ChatPinnedMessageBanner
                  senderName={pinnedMessage.senderName}
                  preview={pinnedPreview}
                  className="px-3 py-2"
                  onClick={() => {
                    if (!scrollToChatMessage(pinnedMessage.id)) {
                      showToast('Закреплённое сообщение пока не загружено в ленте');
                    }
                  }}
                />
              ) : null}
              <div
                className={cn('flex-1 space-y-2 overflow-y-auto bg-[#eef1f6] p-3', isDragging && 'select-none')}
                style={{ maxHeight: 'min(50vh,380px)' }}
              >
                {loadingMessages ? (
                  <div className="flex items-center justify-center gap-2 py-8 text-sm text-[#667085]">
                    <Loader2 className="h-4 w-4 animate-spin" />
                    Загрузка...
                  </div>
                ) : visibleMessages.length ? (
                  visibleMessages.map((message, messageIndex) => {
                    const previousMessage = visibleMessages[messageIndex - 1];
                    const showDateSeparator = shouldShowChatDateSeparator(message.createdAt, previousMessage?.createdAt);

                    if (isSystemMessage(message)) {
                      return (
                        <div key={message.id}>
                          {showDateSeparator ? <ChatDateSeparator date={message.createdAt} /> : null}
                          <div id={`msg-${message.id}`} className="flex justify-center py-1">
                            <p className="rounded-full bg-black/[0.06] px-3 py-1 text-[11px] text-[#667085]">
                              {getMessagePreviewText(message)}
                            </p>
                          </div>
                        </div>
                      );
                    }

                    const mine = message.senderId === user.id;
                    const e2ePayload = message.isEncrypted ? e2ePayloads[message.id] : null;
                    const visibleText = getE2EMessageText({
                      message,
                      isE2E: activeRoomIsE2E,
                      peerE2eKey,
                      peerKeyLoading,
                      e2ePayload,
                      e2eError: e2eErrors[message.id]
                    });
                    const emojiContent =
                      message.type === 'EMOJI' || e2ePayload?.kind === 'emoji'
                        ? e2ePayload?.emojiId ?? message.content
                        : null;
                    const isSelected = selectedMessageIds.includes(message.id);
                    const isLargeEmoji = isSingleEmojiMessage({
                      type: message.type,
                      content: emojiContent ?? message.content,
                      visibleText
                    });
                    const largeEmojiNative = isLargeEmoji
                      ? getSingleEmojiNative({ type: message.type, content: emojiContent ?? message.content, visibleText })
                      : '';

                    const bubble = (
                      <div
                        className={cn(
                          'relative',
                          isLargeEmoji
                            ? 'bg-transparent px-0 py-0 shadow-none'
                            : cn(
                                'max-w-[85%] rounded-[16px] px-3 py-2 text-sm shadow-sm',
                                mine ? 'bg-[#effdde]' : 'bg-white',
                                !selectionMode && 'max-w-[85%]'
                              )
                        )}
                      >
                        {!mine && !isLargeEmoji ? (
                          <p className="mb-0.5 text-[11px] font-semibold text-[#3390ec]">{message.senderName}</p>
                        ) : null}
                        {message.type === 'POLL' && message.poll ? (
                          <div className="space-y-1.5">
                            <p className="font-medium">{message.poll.question}</p>
                            {message.poll.options.map((option) => {
                              const total = message.poll!.options.reduce((sum, item) => sum + item.voteCount, 0) || 1;
                              const percent = Math.round((option.voteCount / total) * 100);
                              return (
                                <button
                                  key={option.id}
                                  type="button"
                                  data-chat-interactive="true"
                                  className="relative w-full overflow-hidden rounded-lg border border-[#dce3ec] px-2 py-1.5 text-left text-xs"
                                  onClick={() =>
                                    void voteChatPoll(message.id, [option.id], token).then((updated) => {
                                      setMessages((current) => current.map((item) => (item.id === updated.id ? updated : item)));
                                    })
                                  }
                                >
                                  <div className="absolute inset-y-0 left-0 bg-[#3390ec]/15" style={{ width: `${percent}%` }} />
                                  <span className="relative flex justify-between gap-2">
                                    <span>{option.text}</span>
                                    <span>{percent}%</span>
                                  </span>
                                </button>
                              );
                            })}
                          </div>
                        ) : message.storageKey ? (
                          <p className="text-sm">{getMessagePreviewText(message, visibleText)}</p>
                        ) : isLargeEmoji ? (
                          <div className="relative inline-flex min-w-[64px] flex-col items-center">
                            <span className="text-[48px] leading-none">{largeEmojiNative}</span>
                            <div className="absolute bottom-0 right-0 rounded-full bg-black/45 px-1.5 py-0.5 text-[10px] text-white">
                              {formatMessageTime(message.createdAt)}
                            </div>
                          </div>
                        ) : emojiContent ? (
                          <div className="py-0.5">
                            <ChatEmojiContent content={emojiContent} size={28} />
                          </div>
                        ) : (
                          <div className="whitespace-pre-wrap break-words text-sm leading-relaxed">
                            <ChatEmojiContent content={visibleText} size={18} />
                          </div>
                        )}
                        {!isLargeEmoji ? (
                          <p className="mt-1 text-[10px] text-[#a8adbc]">{formatMessageTime(message.createdAt)}</p>
                        ) : null}
                        {!isLargeEmoji ? (
                          <ChatMessageReactions
                            reactions={message.reactions}
                            onToggle={(emoji) => {
                              if (!token) return;
                              void toggleChatMessageReaction(message.id, emoji, token).then((updated) => {
                                setMessages((current) => current.map((item) => (item.id === updated.id ? updated : item)));
                              });
                            }}
                            compact
                          />
                        ) : null}
                      </div>
                    );

                    return (
                      <div key={message.id}>
                        {showDateSeparator ? <ChatDateSeparator date={message.createdAt} /> : null}
                        <div
                          id={`msg-${message.id}`}
                          className={cn('group flex gap-1.5', mine ? 'justify-end' : 'justify-start', isDragging && 'select-none')}
                          onMouseDown={(event) => {
                            if (isChatInteractiveTarget(event.target)) return;
                            handleMessagePointerDown(message.id, event);
                          }}
                          onMouseEnter={() => handleDragEnterMessage(message.id)}
                          onClick={(event) => {
                            if (selectionMode) return;
                            if (event.ctrlKey || event.metaKey || event.shiftKey) {
                              handleSelectionPointer(message.id, event);
                            }
                          }}
                        >
                          {!mine ? (
                            <UserAvatar
                              userId={message.senderId}
                              displayName={message.senderName}
                              hasAvatar={message.senderHasAvatar}
                              token={token}
                              isVerified={message.senderIsVerified}
                              verificationIcon={message.senderVerificationIcon}
                              className="z-10 -mr-2 -mt-0.5 h-7 w-7 shrink-0 self-start ring-2 ring-[#eef1f6]"
                              badgeSize="xs"
                            />
                          ) : null}
                          {selectionMode ? (
                            <ChatSelectionContextMenu
                              count={selectedMessageIds.length}
                              canPin={selectedMessageIds.length === 1}
                              pinned={getSelectedMessages()[0]?.isPinned}
                              onReply={handleSelectionReply}
                              onCopy={() => void handleBulkCopy()}
                              onForward={() => showToast('Пересылка доступна в полном виде семьи')}
                              onDelete={() => void handleBulkDelete()}
                              onTogglePin={handleSelectionPin}
                              onCancel={exitSelectionMode}
                            >
                              <SelectableMessageWrapper
                                selected={isSelected}
                                selectionMode
                                align={mine ? 'right' : 'left'}
                                onClick={() => toggleSelectedMessage(message.id)}
                              >
                                {bubble}
                              </SelectableMessageWrapper>
                            </ChatSelectionContextMenu>
                          ) : (
                            <ChatMessageContextMenu
                              message={message}
                              userId={user.id}
                              isE2E={activeRoomIsE2E}
                              visibleText={visibleText}
                              onEdit={() => {
                                setEditingMessageId(message.id);
                                setDraft(message.content ?? '');
                              }}
                              onDelete={() => requestDeleteMessages([message.id])}
                              onForward={() => showToast('Пересылка доступна в полном виде семьи')}
                              onTogglePin={() => void handleTogglePin(message)}
                              onSelect={() => enterSelectionMode(message.id)}
                              onToggleReaction={async (emoji) => {
                                if (!token) return;
                                const updated = await toggleChatMessageReaction(message.id, emoji, token);
                                setMessages((current) => current.map((item) => (item.id === updated.id ? updated : item)));
                              }}
                              onCopy={() => showToast('Текст скопирован')}
                            >
                              {bubble}
                            </ChatMessageContextMenu>
                          )}
                          {mine ? (
                            <UserAvatar
                              userId={user.id}
                              displayName={user.displayName}
                              hasAvatar={Boolean(user.hasAvatar)}
                              token={token}
                              isVerified={user.isVerified}
                              verificationIcon={user.verificationIcon}
                              className="z-10 -ml-2 -mt-0.5 h-7 w-7 shrink-0 self-start ring-2 ring-[#eef1f6]"
                              badgeSize="xs"
                            />
                          ) : null}
                        </div>
                      </div>
                    );
                  })
                ) : (
                  <p className="py-8 text-center text-sm text-[#667085]">Сообщений пока нет</p>
                )}
                <div ref={messagesEndRef} />
              </div>

              {selectionMode ? (
                <ChatSelectionToolbar
                  count={selectedMessageIds.length}
                  deleting={deletingMessages}
                  onCancel={exitSelectionMode}
                  onCopy={() => void handleBulkCopy()}
                  onForward={() => showToast('Пересылка доступна в полном виде семьи')}
                  onDelete={() => void handleBulkDelete()}
                />
              ) : (
                <div className="flex flex-col gap-2 border-t border-[#eceef4] p-3">
                  {editingMessageId ? (
                    <ChatMessageEditBanner
                      preview={draft.trim() || messages.find((item) => item.id === editingMessageId)?.content || undefined}
                      onCancel={cancelEditMessage}
                    />
                  ) : null}
                  {emojiOpen ? (
                    <div className="mb-1">
                      <EmojiPicker className="w-full" onSelect={(nativeEmoji) => insertEmojiToDraft(nativeEmoji)} />
                    </div>
                  ) : null}
                  <div className="flex items-end gap-1">
                    <Button
                      type="button"
                      size="icon"
                      variant="ghost"
                      className="h-9 w-9 shrink-0 rounded-xl"
                      onClick={() => fileInputRef.current?.click()}
                      disabled={Boolean(editingMessageId)}
                    >
                      <Paperclip className="h-4 w-4" />
                    </Button>
                    {!activeRoomIsE2E ? (
                      <Button
                        type="button"
                        size="icon"
                        variant="ghost"
                        className="h-9 w-9 shrink-0 rounded-xl"
                        onClick={() => setPollOpen(true)}
                        disabled={Boolean(editingMessageId)}
                      >
                        <BarChart3 className="h-4 w-4" />
                      </Button>
                    ) : null}
                    <Button
                      type="button"
                      size="icon"
                      variant="ghost"
                      className="h-9 w-9 shrink-0 rounded-xl"
                      onClick={() => setEmojiOpen((value) => !value)}
                      disabled={Boolean(editingMessageId)}
                    >
                      <Smile className="h-4 w-4" />
                    </Button>
                    <Input
                      value={draft}
                      onChange={(event) => setDraft(event.target.value)}
                      placeholder={editingMessageId ? 'Измените сообщение' : 'Сообщение'}
                      className="min-h-[40px] flex-1 rounded-xl"
                      onKeyDown={(event) => {
                        if (event.key === 'Enter' && !event.shiftKey) {
                          event.preventDefault();
                          void handleSend();
                        }
                        if (event.key === 'Escape') {
                          if (editingMessageId) {
                            cancelEditMessage();
                          }
                        }
                      }}
                    />
                    <Button
                      className="h-9 w-9 shrink-0 rounded-full p-0"
                      disabled={sending || !draft.trim()}
                      onClick={() => void handleSend()}
                    >
                      {sending ? <Loader2 className="h-4 w-4 animate-spin" /> : <Send className="h-4 w-4" />}
                    </Button>
                  </div>
                </div>
              )}
            </>
          )}
        </div>
      ) : null}
      <ChatToolsDialog
        open={chatToolsOpen}
        onOpenChange={setChatToolsOpen}
        messages={visibleMessages}
        defaultTab={chatToolsTab}
        onJumpToMessage={scrollToChatMessage}
        getMessagePreview={(message) =>
          getMessagePreviewText(
            message,
            getE2EMessageText({
              message,
              isE2E: activeRoomIsE2E,
              peerE2eKey,
              peerKeyLoading,
              e2ePayload: message.isEncrypted ? e2ePayloads[message.id] : null,
              e2eError: e2eErrors[message.id]
            })
          )
        }
      />
      <ChatDeleteMessageDialog
        open={deleteMessagesDialogOpen}
        count={pendingDeleteMessageIds.length}
        loading={deletingMessages}
        onOpenChange={(open) => {
          setDeleteMessagesDialogOpen(open);
          if (!open) {
            setPendingDeleteMessageIds([]);
            exitSelectionMode();
          }
        }}
        onConfirm={() => void confirmDeleteMessages()}
      />
      <Dialog open={pollOpen} onOpenChange={setPollOpen}>
        <DialogContent className="rounded-[24px] sm:max-w-[360px]">
          <DialogHeader>
            <DialogTitle>Создать опрос</DialogTitle>
          </DialogHeader>
          <Input placeholder="Вопрос" value={pollQuestion} onChange={(event) => setPollQuestion(event.target.value)} />
          <div className="mt-3 space-y-2">
            {pollOptions.map((option, index) => (
              <Input
                key={index}
                placeholder={`Вариант ${index + 1}`}
                value={option}
                onChange={(event) =>
                  setPollOptions((current) => current.map((item, i) => (i === index ? event.target.value : item)))
                }
              />
            ))}
          </div>
          <Button variant="secondary" className="mt-2 w-full rounded-xl" onClick={() => setPollOptions((current) => [...current, ''])}>
            Добавить вариант
          </Button>
          <Button className="mt-3 w-full rounded-xl" disabled={sending} onClick={() => void submitPoll()}>
            {sending ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : null}
            Отправить опрос
          </Button>
        </DialogContent>
      </Dialog>
      <input
        ref={fileInputRef}
        type="file"
        accept="image/*,audio/*,video/*,.pdf,.doc,.docx,.txt,.zip"
        className="hidden"
        onChange={(event) => {
          const file = event.target.files?.[0];
          if (file) void uploadChatFile(file);
          event.target.value = '';
        }}
      />
      <BotMiniAppSheet
        url={miniAppUrl}
        title={miniAppTitle}
        authToken={token}
        onClose={() => setMiniAppUrl(null)}
      />
    </>
  );
}
