/**
 * Chat detail screen — верстка по референсу (X-style Messages).
 *
 * Структура:
 *   [Header: back + avatar + name + typing-status | ⋯]
 *   [FlatList: MessageBubble + DaySeparator, group-aware]
 *   [Composer: floating, supports edit/reply banner]
 *
 * Весь presentational код вынесен в components/chat/*:
 *   - MessageBubble (own/peer rendering)
 *   - DaySeparator  (day label между группами)
 *   - buildRows     (чистая функция группировки)
 * Date-форматирование — lib/dates.ts.
 */
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
import {
  View, Text, FlatList, KeyboardAvoidingView, Platform, Alert, Pressable,
} from 'react-native';
import { router, useLocalSearchParams } from 'expo-router';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import * as Clipboard from 'expo-clipboard';

import { useStore } from '@/lib/store';
import { useMessages } from '@/hooks/useMessages';
import { encryptMessage } from '@/lib/crypto';
import { sendEnvelope } from '@/lib/api';
import { getWSClient } from '@/lib/ws';
import { appendMessage, loadMessages } from '@/lib/storage';
import { randomId } from '@/lib/utils';
import type { Message } from '@/lib/types';

import { Avatar }        from '@/components/Avatar';
import { Header }        from '@/components/Header';
import { IconButton }    from '@/components/IconButton';
import { Composer, ComposerMode } from '@/components/Composer';
import { AttachmentMenu } from '@/components/chat/AttachmentMenu';
import { VideoCircleRecorder } from '@/components/chat/VideoCircleRecorder';
import { clearContactNotifications } from '@/hooks/useNotifications';
import { MessageBubble } from '@/components/chat/MessageBubble';
import { DaySeparator }  from '@/components/chat/DaySeparator';
import { buildRows, Row } from '@/components/chat/rows';
import type { Attachment } from '@/lib/types';

function shortAddr(a: string, n = 6) {
  if (!a) return '—';
  return a.length <= n * 2 + 1 ? a : `${a.slice(0, n)}…${a.slice(-n)}`;
}

export default function ChatScreen() {
  const { id: contactAddress } = useLocalSearchParams<{ id: string }>();
  const insets    = useSafeAreaInsets();
  const keyFile   = useStore(s => s.keyFile);
  const contacts  = useStore(s => s.contacts);
  const messages  = useStore(s => s.messages);
  const setMsgs   = useStore(s => s.setMessages);
  const appendMsg = useStore(s => s.appendMessage);
  const clearUnread = useStore(s => s.clearUnread);

  // При открытии чата: сбрасываем unread-счётчик и dismiss'им банер.
  useEffect(() => {
    if (!contactAddress) return;
    clearUnread(contactAddress);
    clearContactNotifications(contactAddress);
  }, [contactAddress, clearUnread]);

  const contact  = contacts.find(c => c.address === contactAddress);
  const chatMsgs = messages[contactAddress ?? ''] ?? [];
  const listRef  = useRef<FlatList>(null);

  const [text,         setText]         = useState('');
  const [sending,      setSending]      = useState(false);
  const [peerTyping,   setPeerTyping]   = useState(false);
  const [composeMode,  setComposeMode]  = useState<ComposerMode>({ kind: 'new' });
  const [pendingAttach, setPendingAttach] = useState<Attachment | null>(null);
  const [attachMenuOpen, setAttachMenuOpen] = useState(false);
  const [videoCircleOpen, setVideoCircleOpen] = useState(false);
  /**
   * ID сообщения, которое сейчас подсвечено (после jump-to-reply). На
   * ~2 секунды backgroundColor bubble'а мерцает accent-цветом.
   * `null` — ничего не подсвечено.
   */
  const [highlightedId, setHighlightedId] = useState<string | null>(null);
  const highlightClearTimer = useRef<ReturnType<typeof setTimeout> | null>(null);

  // ── Selection mode ───────────────────────────────────────────────────
  // Активируется первым long-press'ом на bubble'е. Header меняется на
  // toolbar с Forward/Delete/Cancel. Tap по bubble'у в selection mode
  // toggle'ит принадлежность к выборке. Cancel сбрасывает всё.
  const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
  const selectionMode = selectedIds.size > 0;

  useMessages(contact?.x25519Pub ?? '');

  // ── Typing indicator от peer'а ─────────────────────────────────────────
  useEffect(() => {
    if (!keyFile?.x25519_pub) return;
    const ws = getWSClient();
    let timer: ReturnType<typeof setTimeout> | null = null;
    const off = ws.subscribe('typing:' + keyFile.x25519_pub, (frame) => {
      if (frame.event !== 'typing') return;
      const d = frame.data as { from?: string } | undefined;
      if (!contact?.x25519Pub || d?.from !== contact.x25519Pub) return;
      setPeerTyping(true);
      if (timer) clearTimeout(timer);
      timer = setTimeout(() => setPeerTyping(false), 3_000);
    });
    return () => { off(); if (timer) clearTimeout(timer); };
  }, [keyFile?.x25519_pub, contact?.x25519Pub]);

  // Throttled типinginisi-ping собеседнику.
  const lastTypingSent = useRef(0);
  const onChange = useCallback((t: string) => {
    setText(t);
    if (!contact?.x25519Pub || !t.trim()) return;
    const now = Date.now();
    if (now - lastTypingSent.current < 2_000) return;
    lastTypingSent.current = now;
    getWSClient().sendTyping(contact.x25519Pub);
  }, [contact?.x25519Pub]);

  // Восстановить сообщения из persistent-storage при первом заходе в чат.
  //
  // Важно: НЕ перезаписываем store пустым массивом — это стёрло бы
  // содержимое, которое уже лежит в zustand (только что полученные по
  // WS сообщения пока монтировались). Если в кэше что-то есть — мержим:
  // берём max(cached, in-store) по id.
  useEffect(() => {
    if (!contactAddress) return;
    loadMessages(contactAddress).then(cached => {
      if (!cached || cached.length === 0) return; // кэш пуст → оставляем store
      const existing = useStore.getState().messages[contactAddress] ?? [];
      const byId = new Map<string, Message>();
      for (const m of cached as Message[]) byId.set(m.id, m);
      for (const m of existing)            byId.set(m.id, m); // store-версия свежее
      const merged = Array.from(byId.values()).sort((a, b) => a.timestamp - b.timestamp);
      setMsgs(contactAddress, merged);
    });
  }, [contactAddress, setMsgs]);

  const name = contact?.username
    ? `@${contact.username}`
    : contact?.alias ?? shortAddr(contactAddress ?? '');

  // ── Compose actions ────────────────────────────────────────────────────
  const cancelCompose = useCallback(() => {
    setComposeMode({ kind: 'new' });
    setText('');
    setPendingAttach(null);
  }, []);

  // buildRows выдаёт chronological [old → new]. FlatList работает
  // inverted, поэтому reverse'им: newest = data[0] = снизу экрана.
  // Определено тут (не позже) чтобы handlers типа onJumpToReply могли
  // искать индексы по id без forward-declaration.
  const rows = useMemo(() => {
    const chrono = buildRows(chatMsgs);
    return [...chrono].reverse();
  }, [chatMsgs]);

  /**
   * Core send logic. Принимает явные text + attachment чтобы избегать
   * race'а со state updates при моментальной отправке голоса/видео.
   * Если передано null/undefined — берём из текущего state.
   */
  const sendCore = useCallback(async (
    textArg: string | null = null,
    attachArg: Attachment | null | undefined = undefined,
  ) => {
    if (!keyFile || !contact) return;
    const actualText   = textArg !== null ? textArg : text;
    const actualAttach = attachArg !== undefined ? attachArg : pendingAttach;
    const hasText   = !!actualText.trim();
    const hasAttach = !!actualAttach;
    if (!hasText && !hasAttach) return;
    if (!contact.x25519Pub) {
      Alert.alert('No encryption key yet', 'The contact has not published their key. Try later.');
      return;
    }

    if (composeMode.kind === 'edit') {
      const target = chatMsgs.find(m => m.text === composeMode.text && m.mine);
      if (!target) { cancelCompose(); return; }
      const updated: Message = { ...target, text: actualText.trim(), edited: true };
      setMsgs(contact.address, chatMsgs.map(m => m.id === target.id ? updated : m));
      cancelCompose();
      return;
    }

    setSending(true);
    try {
      if (hasText) {
        const { nonce, ciphertext } = encryptMessage(
          actualText.trim(), keyFile.x25519_priv, contact.x25519Pub,
        );
        await sendEnvelope({
          senderPub:    keyFile.x25519_pub,
          recipientPub: contact.x25519Pub,
          senderEd25519Pub: keyFile.pub_key,
          nonce, ciphertext,
        });
      }

      const msg: Message = {
        id:         randomId(),
        from:       keyFile.x25519_pub,
        text:       actualText.trim(),
        timestamp:  Math.floor(Date.now() / 1000),
        mine:       true,
        read:       false,
        edited:     false,
        attachment: actualAttach ?? undefined,
        replyTo:    composeMode.kind === 'reply'
          ? { id: composeMode.msgId, text: composeMode.preview, author: composeMode.author }
          : undefined,
      };
      appendMsg(contact.address, msg);
      await appendMessage(contact.address, msg);
      setText('');
      setPendingAttach(null);
      setComposeMode({ kind: 'new' });
    } catch (e: any) {
      Alert.alert('Send failed', e?.message ?? 'Unknown error');
    } finally {
      setSending(false);
    }
  }, [
    text, keyFile, contact, composeMode, chatMsgs,
    setMsgs, cancelCompose, appendMsg, pendingAttach,
  ]);

  // UI send button
  const send = useCallback(() => sendCore(), [sendCore]);

  // ── Selection handlers ───────────────────────────────────────────────
  // Long-press — входим в selection mode и сразу отмечаем это сообщение.
  const onMessageLongPress = useCallback((m: Message) => {
    setSelectedIds(prev => {
      const next = new Set(prev);
      next.add(m.id);
      return next;
    });
  }, []);

  // Tap в selection mode — toggle принадлежности.
  const onMessageTap = useCallback((m: Message) => {
    if (!selectionMode) return;
    setSelectedIds(prev => {
      const next = new Set(prev);
      if (next.has(m.id)) next.delete(m.id); else next.add(m.id);
      return next;
    });
  }, [selectionMode]);

  const cancelSelection = useCallback(() => setSelectedIds(new Set()), []);

  // ── Swipe-to-reply ──────────────────────────────────────────────────
  const onMessageReply = useCallback((m: Message) => {
    if (selectionMode) return;
    setComposeMode({
      kind:    'reply',
      msgId:   m.id,
      author:  m.mine ? 'You' : name,
      preview: m.text || (m.attachment ? `(${m.attachment.kind})` : ''),
    });
  }, [name, selectionMode]);

  // ── Profile navigation (tap на аватарке / имени peer'а) ──────────────
  const onOpenPeerProfile = useCallback(() => {
    if (!contactAddress) return;
    router.push(`/(app)/profile/${contactAddress}` as never);
  }, [contactAddress]);

  // ── Jump to reply: tap по quoted-блоку в bubble'е ────────────────────
  // Скроллим FlatList к оригинальному сообщению и зажигаем highlight
  // на ~2 секунды (highlightedId state + useEffect-driven анимация в
  // MessageBubble.highlightAnim).
  const onJumpToReply = useCallback((originalId: string) => {
    const idx = rows.findIndex(r => r.kind === 'msg' && r.msg.id === originalId);
    if (idx < 0) {
      // Сообщение не найдено (возможно удалено или ушло за пагинацию).
      // Silently no-op.
      return;
    }
    try {
      listRef.current?.scrollToIndex({
        index: idx,
        animated: true,
        viewPosition: 0.3, // оригинал — чуть выше середины экрана, не прямо в центре
      });
    } catch {
      // scrollToIndex может throw'нуть если индекс за пределами рендера;
      // fallback: scrollToOffset на приблизительную позицию.
    }
    setHighlightedId(originalId);
    if (highlightClearTimer.current) clearTimeout(highlightClearTimer.current);
    highlightClearTimer.current = setTimeout(() => {
      setHighlightedId(null);
      highlightClearTimer.current = null;
    }, 2000);
  }, [rows]);

  useEffect(() => {
    return () => {
      if (highlightClearTimer.current) clearTimeout(highlightClearTimer.current);
    };
  }, []);

  // ── Selection actions ────────────────────────────────────────────────
  const deleteSelected = useCallback(() => {
    if (selectedIds.size === 0 || !contact) return;
    Alert.alert(
      `Delete ${selectedIds.size} message${selectedIds.size > 1 ? 's' : ''}?`,
      'This removes them from your device. Other participants keep their copies.',
      [
        { text: 'Cancel', style: 'cancel' },
        {
          text: 'Delete',
          style: 'destructive',
          onPress: () => {
            setMsgs(contact.address, chatMsgs.filter(m => !selectedIds.has(m.id)));
            setSelectedIds(new Set());
          },
        },
      ],
    );
  }, [selectedIds, contact, chatMsgs, setMsgs]);

  const forwardSelected = useCallback(() => {
    // Forward UI ещё не реализован — показываем stub. Пример потока:
    //   1. открыть "Forward to…" screen со списком контактов
    //   2. для каждого выбранного контакта — sendEnvelope с оригинальным
    //      текстом, timestamp=now
    Alert.alert(
      `Forward ${selectedIds.size} message${selectedIds.size > 1 ? 's' : ''}`,
      'Contact-picker screen is coming in the next iteration. For now, copy the text and paste.',
      [{ text: 'OK' }],
    );
  }, [selectedIds]);

  // Copy доступен только когда выделено ровно одно сообщение.
  const copySelected = useCallback(async () => {
    if (selectedIds.size !== 1) return;
    const id = [...selectedIds][0];
    const msg = chatMsgs.find(m => m.id === id);
    if (!msg) return;
    await Clipboard.setStringAsync(msg.text);
    setSelectedIds(new Set());
  }, [selectedIds, chatMsgs]);

  // В group-чатах над peer-сообщениями рисуется имя отправителя и его
  // аватар (group = несколько участников). В DM (direct) и каналах
  // отправитель ровно один, поэтому имя/аватар не нужны — убираем.
  const withSenderMeta = contact?.kind === 'group';

  const renderRow = ({ item }: { item: Row }) => {
    if (item.kind === 'sep') return <DaySeparator label={item.label} />;
    return (
      <MessageBubble
        msg={item.msg}
        peerName={name}
        peerAddress={contactAddress}
        withSenderMeta={withSenderMeta}
        showName={item.showName}
        showAvatar={item.showAvatar}
        onReply={onMessageReply}
        onLongPress={onMessageLongPress}
        onTap={onMessageTap}
        onOpenProfile={onOpenPeerProfile}
        onJumpToReply={onJumpToReply}
        selectionMode={selectionMode}
        selected={selectedIds.has(item.msg.id)}
        highlighted={highlightedId === item.msg.id}
      />
    );
  };

  return (
    <KeyboardAvoidingView
      style={{ flex: 1, backgroundColor: '#000000' }}
      behavior={Platform.OS === 'ios' ? 'padding' : undefined}
      // Увеличенный offset: composer поднимается выше клавиатуры с заметным
      // зазором (20px на iOS, 10px на Android) — пользователь не видит
      // прилипания к верхнему краю клавиатуры.
      keyboardVerticalOffset={Platform.OS === 'ios' ? 20 : 10}
    >
      {/* Header — использует общий компонент <Header>, чтобы соблюдать
          правила шапки приложения (left slot / centered title / right slot). */}
      <View style={{ paddingTop: insets.top, backgroundColor: '#000000' }}>
        {selectionMode ? (
          <Header
            divider
            left={<IconButton icon="close" size={36} onPress={cancelSelection} />}
            title={`${selectedIds.size} selected`}
            right={
              <>
                {selectedIds.size === 1 && (
                  <IconButton icon="copy-outline" size={36} onPress={copySelected} />
                )}
                <IconButton icon="arrow-redo-outline" size={36} onPress={forwardSelected} />
                <IconButton icon="trash-outline"      size={36} onPress={deleteSelected} />
              </>
            }
          />
        ) : (
          <Header
            divider
            left={<IconButton icon="chevron-back" size={36} onPress={() => router.back()} />}
            title={
              <Pressable
                onPress={onOpenPeerProfile}
                hitSlop={4}
                style={{ flexDirection: 'row', alignItems: 'center', gap: 8, maxWidth: '100%' }}
              >
                <Avatar name={name} address={contactAddress} size={28} />
                <View style={{ minWidth: 0, flexShrink: 1 }}>
                  <Text
                    numberOfLines={1}
                    style={{
                      color: '#ffffff',
                      fontSize: 15,
                      fontWeight: '700',
                      letterSpacing: -0.2,
                    }}
                  >
                    {name}
                  </Text>
                  {peerTyping && (
                    <Text style={{ color: '#1d9bf0', fontSize: 11, fontWeight: '500' }}>
                      typing…
                    </Text>
                  )}
                  {!peerTyping && !contact?.x25519Pub && (
                    <Text style={{ color: '#f0b35a', fontSize: 11 }}>
                      waiting for key
                    </Text>
                  )}
                </View>
              </Pressable>
            }
            right={<IconButton icon="ellipsis-horizontal" size={36} />}
          />
        )}
      </View>

      {/* Messages — inverted: data[0] рендерится снизу, последующее —
          выше. Это стандартный chat-паттерн: FlatList сразу монтируется
          с "scroll position at bottom" без ручного scrollToEnd, и новые
          сообщения (добавляемые в начало reversed-массива) появляются
          внизу естественно. Никаких jerk'ов при открытии. */}
      <FlatList
        ref={listRef}
        data={rows}
        inverted
        keyExtractor={r => r.kind === 'sep' ? r.id : r.msg.id}
        renderItem={renderRow}
        contentContainerStyle={{ paddingVertical: 10 }}
        showsVerticalScrollIndicator={false}
        // Lazy render: only mount ~1.5 screens of bubbles initially,
        // render further batches as the user scrolls older. Keeps
        // initial paint fast on chats with thousands of messages.
        initialNumToRender={25}
        maxToRenderPerBatch={12}
        windowSize={10}
        removeClippedSubviews
        ListEmptyComponent={() => (
          <View style={{
            flex: 1, alignItems: 'center', justifyContent: 'center',
            paddingHorizontal: 32, gap: 10,
            transform: [{ scaleY: -1 }],  // inverted flips cells; un-flip empty state
          }}>
            <Avatar name={name} address={contactAddress} size={72} />
            <Text style={{ color: '#ffffff', fontSize: 16, fontWeight: '700', marginTop: 10 }}>
              Say hi to {name}
            </Text>
            <Text style={{ color: '#8b8b8b', fontSize: 13, textAlign: 'center', lineHeight: 20 }}>
              Your messages are end-to-end encrypted.
            </Text>
          </View>
        )}
      />

      {/* Composer — floating, прибит к низу. */}
      <View style={{ paddingBottom: Math.max(insets.bottom, 4) + 6, backgroundColor: '#000000' }}>
        <Composer
          mode={composeMode}
          onCancelMode={cancelCompose}
          text={text}
          onChangeText={onChange}
          onSend={send}
          sending={sending}
          onAttach={() => setAttachMenuOpen(true)}
          attachment={pendingAttach}
          onClearAttach={() => setPendingAttach(null)}
          onFinishVoice={(att) => {
            // Voice отправляется сразу — sendCore получает attachment
            // явным аргументом, минуя state-задержку.
            sendCore('', att);
          }}
          onStartVideoCircle={() => setVideoCircleOpen(true)}
        />
      </View>

      <AttachmentMenu
        visible={attachMenuOpen}
        onClose={() => setAttachMenuOpen(false)}
        onPick={(att) => setPendingAttach(att)}
      />

      <VideoCircleRecorder
        visible={videoCircleOpen}
        onClose={() => setVideoCircleOpen(false)}
        onFinish={(att) => {
          // Video-circle тоже отправляется сразу.
          sendCore('', att);
        }}
      />
    </KeyboardAvoidingView>
  );
}
