/**
 * MessageBubble — рендер одного сообщения с gesture interactions.
 *
 * Гестуры — разведены по двум примитивам во избежание конфликта со
 * скроллом FlatList'а:
 *
 *   1. Swipe-left (reply): PanResponder на Animated.View обёртке
 *      bubble'а. `onMoveShouldSetPanResponder` клеймит responder ТОЛЬКО
 *      когда пользователь сдвинул палец > 6px влево и горизонталь
 *      преобладает над вертикалью. Для вертикального скролла
 *      `onMoveShouldSet` возвращает false — FlatList получает gesture.
 *      Touchdown ничего не клеймит (onStartShouldSetPanResponder
 *      отсутствует).
 *
 *   2. Long-press / tap: через View.onTouchStart/End. Primitive touch
 *      events bubble'ятся независимо от responder'а. Long-press запускаем
 *      timer'ом на 550ms, cancel при `onTouchMove` с достаточной
 *      амплитудой. Tap — короткое касание без move в selection mode.
 *
 *   3. `selectionMode=true` — PanResponder disabled (в selection режиме
 *      свайпы не работают).
 *
 *   4. ReplyQuote — отдельный Pressable над bubble-текстом; tap прыгает
 *      к оригиналу через onJumpToReply.
 *
 *   5. highlight prop — bubble-row мерцает accent-blue фоном, использует
 *      Animated.Value; управляется из ChatScreen после scrollToIndex.
 */
import React, { useRef, useEffect } from 'react';
import {
  View, Text, Pressable, ViewStyle, Animated, PanResponder,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';

import type { Message } from '@/lib/types';
import { relTime } from '@/lib/dates';
import { Avatar } from '@/components/Avatar';
import { AttachmentPreview } from '@/components/chat/AttachmentPreview';
import { ReplyQuote } from '@/components/chat/ReplyQuote';

export const PEER_AVATAR_SLOT = 34;
const SWIPE_THRESHOLD  = 60;
const LONG_PRESS_MS    = 550;
const TAP_MAX_MOVEMENT = 8;
const TAP_MAX_ELAPSED  = 300;

export interface MessageBubbleProps {
  msg:          Message;
  peerName:     string;
  peerAddress?: string;
  withSenderMeta?: boolean;
  showName:     boolean;
  showAvatar:   boolean;

  onReply?:        (m: Message) => void;
  onLongPress?:    (m: Message) => void;
  onTap?:          (m: Message) => void;
  onOpenProfile?:  () => void;
  onJumpToReply?:  (originalId: string) => void;

  selectionMode?: boolean;
  selected?:      boolean;
  /** Mgnt-управляемый highlight: row мерцает accent-фоном ~1-2 секунды. */
  highlighted?:   boolean;
}

// ─── Bubble styles ──────────────────────────────────────────────────

const bubbleBase: ViewStyle = {
  borderRadius: 18,
  paddingHorizontal: 14,
  paddingTop: 8,
  paddingBottom: 6,
};

const peerBubble: ViewStyle = {
  ...bubbleBase,
  backgroundColor: '#1a1a1a',
  borderBottomLeftRadius: 6,
};

const ownBubble: ViewStyle = {
  ...bubbleBase,
  backgroundColor: '#1d9bf0',
  borderBottomRightRadius: 6,
};

const bubbleText = { color: '#ffffff', fontSize: 15, lineHeight: 20 } as const;

// ─── Main ───────────────────────────────────────────────────────────

export function MessageBubble(props: MessageBubbleProps) {
  if (props.msg.mine) return <RowShell {...props} variant="own" />;
  if (!props.withSenderMeta) return <RowShell {...props} variant="peer-compact" />;
  return <RowShell {...props} variant="group-peer" />;
}

type Variant = 'own' | 'peer-compact' | 'group-peer';

function RowShell({
  msg, peerName, peerAddress, showName, showAvatar,
  onReply, onLongPress, onTap, onOpenProfile, onJumpToReply,
  selectionMode, selected, highlighted, variant,
}: MessageBubbleProps & { variant: Variant }) {
  const translateX = useRef(new Animated.Value(0)).current;
  const startTs    = useRef(0);
  const moved      = useRef(false);
  const lpTimer    = useRef<ReturnType<typeof setTimeout> | null>(null);

  const clearLp = () => {
    if (lpTimer.current) { clearTimeout(lpTimer.current); lpTimer.current = null; }
  };

  // Touch start — запускаем long-press timer (НЕ клеймим responder).
  const onTouchStart = () => {
    startTs.current = Date.now();
    moved.current = false;
    clearLp();
    if (onLongPress) {
      lpTimer.current = setTimeout(() => {
        if (!moved.current) onLongPress(msg);
        lpTimer.current = null;
      }, LONG_PRESS_MS);
    }
  };

  const onTouchMove = (e: { nativeEvent: { pageX: number; pageY: number } }) => {
    // Если пользователь двигает палец — отменяем long-press timer.
    // Малые движения (< TAP_MAX_MOVEMENT) игнорируем — устраняют
    // fale-cancel от дрожания пальца.
    // Здесь нет точного dx/dy от gesture-системы, используем primitive
    // touch coords отсчитываемые по абсолютным координатам. Проще —
    // всегда отменяем на first move (PanResponder ниже отнимет
    // responder если leftward).
    moved.current = true;
    clearLp();
  };

  const onTouchEnd = () => {
    const elapsed = Date.now() - startTs.current;
    clearLp();
    // Короткий tap без движения → в selection mode toggle.
    if (!moved.current && elapsed < TAP_MAX_ELAPSED && selectionMode) {
      onTap?.(msg);
    }
  };

  // Swipe-to-reply: PanResponder клеймит ТОЛЬКО leftward-dominant move.
  // Для vertical scroll / rightward swipe / start-touch возвращает false,
  // FlatList / AnimatedSlot получают gesture.
  const panResponder = useRef(
    PanResponder.create({
      onMoveShouldSetPanResponder: (_e, g) => {
        if (selectionMode) return false;
        // Leftward > 6px и горизонталь преобладает.
        return g.dx < -6 && Math.abs(g.dx) > Math.abs(g.dy) * 1.5;
      },
      onPanResponderGrant: () => {
        // Как только мы заклеймили gesture, отменяем long-press
        // (пользователь явно свайпает, не удерживает).
        clearLp();
        moved.current = true;
      },
      onPanResponderMove: (_e, g) => {
        translateX.setValue(Math.min(0, g.dx));
      },
      onPanResponderRelease: (_e, g) => {
        if (g.dx <= -SWIPE_THRESHOLD) onReply?.(msg);
        Animated.spring(translateX, {
          toValue: 0, friction: 6, tension: 80, useNativeDriver: true,
        }).start();
      },
      onPanResponderTerminate: () => {
        Animated.spring(translateX, {
          toValue: 0, friction: 6, tension: 80, useNativeDriver: true,
        }).start();
      },
    }),
  ).current;

  // Highlight fade: при переключении highlighted=true крутим короткую
  // анимацию "flash + fade out" через Animated.Value (0→1→0 за ~1.8s).
  const highlightAnim = useRef(new Animated.Value(0)).current;
  useEffect(() => {
    if (!highlighted) return;
    highlightAnim.setValue(0);
    Animated.sequence([
      Animated.timing(highlightAnim, { toValue: 1, duration: 150, useNativeDriver: false }),
      Animated.delay(1400),
      Animated.timing(highlightAnim, { toValue: 0, duration: 450, useNativeDriver: false }),
    ]).start();
  }, [highlighted, highlightAnim]);

  const highlightBg = highlightAnim.interpolate({
    inputRange: [0, 1],
    outputRange: ['rgba(29,155,240,0)', 'rgba(29,155,240,0.22)'],
  });

  const isMine = variant === 'own';
  const hasAttachment = !!msg.attachment;
  const hasReply      = !!msg.replyTo;
  const attachmentOnly = hasAttachment && !msg.text.trim();
  const bubbleStyle = attachmentOnly
    ? { ...(isMine ? ownBubble : peerBubble), padding: 4 }
    : (isMine ? ownBubble : peerBubble);

  const bubbleNode = (
    <Animated.View
      {...panResponder.panHandlers}
      style={{
        transform: [{ translateX }],
        maxWidth: hasAttachment ? '80%' : '85%',
        minWidth: hasAttachment || hasReply ? 220 : undefined,
      }}
    >
      <View style={bubbleStyle}>
        {msg.replyTo && (
          <ReplyQuote
            author={msg.replyTo.author}
            preview={msg.replyTo.text}
            own={isMine}
            onJump={() => onJumpToReply?.(msg.replyTo!.id)}
          />
        )}
        {msg.attachment && (
          <AttachmentPreview attachment={msg.attachment} own={isMine} />
        )}
        {msg.text.trim() ? (
          <Text style={bubbleText}>{msg.text}</Text>
        ) : null}
        <BubbleFooter
          edited={!!msg.edited}
          time={relTime(msg.timestamp)}
          own={isMine}
          read={!!msg.read}
        />
      </View>
    </Animated.View>
  );

  const contentRow =
    variant === 'own' ? (
      <View style={{ flexDirection: 'row', justifyContent: 'flex-end' }}>
        {bubbleNode}
      </View>
    ) : variant === 'peer-compact' ? (
      <View style={{ flexDirection: 'row', alignItems: 'flex-start' }}>
        {bubbleNode}
      </View>
    ) : (
      <View>
        {showName && (
          <Pressable
            onPress={onOpenProfile}
            hitSlop={4}
            style={{ marginLeft: PEER_AVATAR_SLOT, marginBottom: 3 }}
          >
            <Text style={{ color: '#8b8b8b', fontSize: 12 }} numberOfLines={1}>
              {peerName}
            </Text>
          </Pressable>
        )}
        <View style={{ flexDirection: 'row', alignItems: 'flex-end' }}>
          <View style={{ width: PEER_AVATAR_SLOT, alignItems: 'flex-start' }}>
            {showAvatar ? (
              <Pressable onPress={onOpenProfile} hitSlop={4}>
                <Avatar name={peerName} address={peerAddress} size={26} />
              </Pressable>
            ) : null}
          </View>
          {bubbleNode}
        </View>
      </View>
    );

  return (
    <Animated.View
      onTouchStart={onTouchStart}
      onTouchMove={onTouchMove}
      onTouchEnd={onTouchEnd}
      onTouchCancel={() => { clearLp(); moved.current = true; }}
      style={{
        paddingHorizontal: 8,
        marginBottom: 6,
        // Selection & highlight накладываются: highlight flash побеждает
        // когда анимация > 0, иначе статичный selection-tint.
        backgroundColor: selected ? 'rgba(29,155,240,0.12)' : highlightBg,
        position: 'relative',
      }}
    >
      {contentRow}
      {selectionMode && (
        <CheckDot
          selected={!!selected}
          onPress={() => onTap?.(msg)}
        />
      )}
    </Animated.View>
  );
}

// ─── Clickable check-dot ────────────────────────────────────────────

function CheckDot({ selected, onPress }: { selected: boolean; onPress: () => void }) {
  return (
    <Pressable
      onPress={onPress}
      hitSlop={12}
      style={{
        position: 'absolute',
        right: 4,
        top: 0, bottom: 0,
        alignItems: 'center',
        justifyContent: 'center',
      }}
    >
      <View
        style={{
          width: 20,
          height: 20,
          borderRadius: 10,
          backgroundColor: selected ? '#1d9bf0' : 'rgba(0,0,0,0.55)',
          borderWidth: 2,
          borderColor: selected ? '#1d9bf0' : '#6b6b6b',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        {selected && <Ionicons name="checkmark" size={12} color="#ffffff" />}
      </View>
    </Pressable>
  );
}

// ─── Footer ─────────────────────────────────────────────────────────

interface FooterProps {
  edited: boolean;
  time:   string;
  own?:   boolean;
  read?:  boolean;
}

function BubbleFooter({ edited, time, own, read }: FooterProps) {
  const textColor = own ? 'rgba(255,255,255,0.78)' : '#8b8b8b';
  const dotColor  = own ? 'rgba(255,255,255,0.55)' : '#5a5a5a';
  return (
    <View
      style={{
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'flex-end',
        marginTop: 2,
        gap: 4,
      }}
    >
      {edited && (
        <>
          <Text style={{ color: textColor, fontSize: 11 }}>Edited</Text>
          <Text style={{ color: dotColor, fontSize: 11 }}>·</Text>
        </>
      )}
      <Text style={{ color: textColor, fontSize: 11 }}>{time}</Text>
      {own && (
        <Ionicons
          name={read ? 'checkmark-circle' : 'checkmark-circle-outline'}
          size={13}
          color={read ? '#ffffff' : 'rgba(255,255,255,0.78)'}
          style={{ marginLeft: 2 }}
        />
      )}
    </View>
  );
}
