package com.rosetta.messenger.ui.chats.components

import android.graphics.Bitmap
import android.widget.Toast
import com.rosetta.messenger.R
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Block
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.Groups
import androidx.compose.material.icons.filled.Link
import androidx.compose.material.icons.filled.PersonAdd
import android.graphics.BitmapFactory
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.PopupProperties
import com.rosetta.messenger.data.GroupRepository
import com.rosetta.messenger.network.AttachmentType
import com.rosetta.messenger.network.GroupStatus
import com.rosetta.messenger.network.MessageAttachment
import com.rosetta.messenger.network.SearchUser
import com.rosetta.messenger.repository.AvatarRepository
import com.rosetta.messenger.ui.chats.models.*
import com.rosetta.messenger.ui.chats.utils.*
import com.rosetta.messenger.ui.components.AppleEmojiText
import com.rosetta.messenger.ui.components.AvatarImage
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import com.rosetta.messenger.utils.AttachmentFileManager
import com.vanniktech.blurhash.BlurHash
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import androidx.compose.ui.graphics.painter.Painter
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.abs
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
 * Reusable UI components for Chat Detail Screen Extracted from ChatDetailScreen.kt for better
 * organization
 */

/**
 * Telegram-style layout для текста сообщения с временем. Если текст + время помещаются в одну
 * строку - располагает их рядом. Если текст длинный и переносится - время встаёт в правый нижний
 * угол, под последней строкой текста (как в Telegram).
 *
 * @param textContent Composable с текстом сообщения
 * @param timeContent Composable с временем и статусом
 * @param modifier Модификатор для Layout
 * @param spacing Минимальный отступ между текстом и временем (dp)
 */
@Composable
fun TelegramStyleMessageContent(
        textContent: @Composable () -> Unit,
        timeContent: @Composable () -> Unit,
        modifier: Modifier = Modifier,
        spacing: Int = 10,
        fillWidth: Boolean = false // 🔥 Если true - время всегда справа на всю ширину контейнера
) {
        val density = LocalDensity.current
        val spacingPx = with(density) { spacing.dp.roundToPx() }
        val newLineHeightPx = with(density) { 14.dp.roundToPx() }

        Layout(
                content = {
                        textContent()
                        timeContent()
                },
                modifier = modifier
        ) { measurables, constraints ->
                require(measurables.size == 2) {
                        "TelegramStyleMessageContent requires exactly 2 children"
                }

                val textMeasurable = measurables[0]
                val timeMeasurable = measurables[1]

                // Измеряем время с минимальными constraints
                val timePlaceable =
                        timeMeasurable.measure(constraints.copy(minWidth = 0, minHeight = 0))

                // Измеряем текст с полной доступной шириной
                val textConstraints = constraints.copy(minWidth = 0, minHeight = 0)
                val textPlaceable = textMeasurable.measure(textConstraints)

                // Проверяем, помещается ли текст + время в одну строку
                val textWidth = textPlaceable.width
                val timeWidth = timePlaceable.width
                val totalSingleLineWidth = textWidth + spacingPx + timeWidth

                // Если текст занимает всю ширину (с учётом constraints), значит он переносится
                val textWraps = textWidth >= constraints.maxWidth - timeWidth - spacingPx

                // Определяем layout
                val (width, height, timeX, timeY) =
                        if (fillWidth) {
                                // 🔥 Для caption - занимаем всю доступную ширину
                                val w = constraints.maxWidth

                                // 🔥 ИСПРАВЛЕНИЕ: Проверяем помещается ли текст + время в одну
                                // строку
                                val availableForTime = w - textWidth - spacingPx
                                if (availableForTime >= timeWidth) {
                                        // Текст и время помещаются - время справа на одной линии
                                        val h = maxOf(textPlaceable.height, timePlaceable.height)
                                        val tX = w - timeWidth // Время справа
                                        val tY = h - timePlaceable.height // Время внизу строки
                                        // (выровнено по baseline)
                                        LayoutResult(w, h, tX, tY)
                                } else {
                                        // Текст длинный - время на новой строке справа внизу
                                        val h = textPlaceable.height + newLineHeightPx
                                        val tX = w - timeWidth // Время справа
                                        val tY = h - timePlaceable.height // Время внизу
                                        LayoutResult(w, h, tX, tY)
                                }
                        } else if (!textWraps && totalSingleLineWidth <= constraints.maxWidth) {
                                // Текст и время на одной строке
                                val w = totalSingleLineWidth
                                val h = maxOf(textPlaceable.height, timePlaceable.height)
                                val tX = textWidth + spacingPx
                                val tY = h - timePlaceable.height // Время внизу строки
                                LayoutResult(w, h, tX, tY)
                        } else {
                                // Текст переносится - время справа внизу на новой строке
                                val w = maxOf(textWidth, timeWidth)
                                val h = textPlaceable.height + newLineHeightPx
                                val tX = w - timeWidth // Время справа
                                val tY = h - timePlaceable.height // Время внизу
                                LayoutResult(w, h, tX, tY)
                        }

                layout(width, height) {
                        textPlaceable.placeRelative(0, 0)
                        timePlaceable.placeRelative(timeX, timeY)
                }
        }
}

private data class LayoutResult(val width: Int, val height: Int, val timeX: Int, val timeY: Int)

/** Telegram-like date header chip (inline separator and floating top badge). */
@Composable
fun DateHeader(
        dateText: String,
        textColor: Color,
        backgroundColor: Color,
        modifier: Modifier = Modifier,
        verticalPadding: Dp = 12.dp
) {
        Row(
                modifier =
                        modifier
                                .fillMaxWidth()
                                .padding(vertical = verticalPadding),
                horizontalArrangement = Arrangement.Center
        ) {
                Text(
                        text = dateText,
                        fontSize = 12.sp,
                        fontWeight = FontWeight.Medium,
                        color = textColor,
                        modifier =
                                Modifier.background(
                                                color = backgroundColor,
                                                shape = RoundedCornerShape(10.dp)
                                        )
                                        .padding(horizontal = 10.dp, vertical = 3.dp)
                )
        }
}

/**
 * Telegram-style typing indicator — 3 bouncing dots drawn as Canvas circles
 * with sequential wave animation (scale + vertical offset + opacity).
 */
@Composable
fun TypingIndicator(
        isDarkTheme: Boolean,
        typingDisplayName: String = "",
        typingSenderPublicKey: String = ""
) {
        val typingColor =
                if (isDarkTheme) Color(0xFF54A9EB) else Color.White.copy(alpha = 0.7f)
        val senderTypingColor =
                remember(typingSenderPublicKey, isDarkTheme) {
                        if (typingSenderPublicKey.isBlank() || !isDarkTheme) {
                                typingColor
                        } else {
                                groupSenderLabelColor(typingSenderPublicKey, isDarkTheme)
                        }
                }
        val infiniteTransition = rememberInfiniteTransition(label = "typing")
        val normalizedDisplayName = remember(typingDisplayName) { typingDisplayName.trim() }

        // Each dot animates through a 0→1→0 cycle, staggered by 150 ms
        val dotProgresses = List(3) { index ->
                infiniteTransition.animateFloat(
                        initialValue = 0f,
                        targetValue = 1f,
                        animationSpec = infiniteRepeatable(
                                animation = keyframes {
                                        durationMillis = 1200
                                        0f at 0 with LinearEasing
                                        1f at 300 with FastOutSlowInEasing
                                        0f at 600 with FastOutSlowInEasing
                                        0f at 1200 with LinearEasing
                                },
                                repeatMode = RepeatMode.Restart,
                                initialStartOffset = StartOffset(index * 150)
                        ),
                        label = "dot$index"
                )
        }

        Row(verticalAlignment = Alignment.CenterVertically) {
                if (normalizedDisplayName.isBlank()) {
                        Text(
                                text = "typing",
                                fontSize = 13.sp,
                                color = typingColor,
                                maxLines = 1,
                                overflow = TextOverflow.Ellipsis
                        )
                } else {
                        Text(
                                text = "$normalizedDisplayName ",
                                fontSize = 13.sp,
                                color = senderTypingColor,
                                maxLines = 1,
                                overflow = TextOverflow.Ellipsis
                        )
                        Text(
                                text = "typing",
                                fontSize = 13.sp,
                                color = typingColor,
                                maxLines = 1,
                                overflow = TextOverflow.Ellipsis
                        )
                }
                Spacer(modifier = Modifier.width(2.dp))

                // Fixed-size canvas — big enough for bounce, never changes layout
                Canvas(modifier = Modifier.size(width = 18.dp, height = 14.dp)) {
                        val dotRadius = 1.5.dp.toPx()
                        val dotSpacing = 2.5.dp.toPx()
                        val maxBounce = 2.dp.toPx()
                        val centerY = size.height / 2f + 1.dp.toPx()
                        for (i in 0..2) {
                                val p = dotProgresses[i].value
                                val bounce = kotlin.math.sin(p * Math.PI).toFloat()
                                val cx = dotRadius + i * (dotRadius * 2 + dotSpacing)
                                val cy = centerY - bounce * maxBounce
                                val alpha = 0.4f + bounce * 0.6f
                                drawCircle(
                                        color = typingColor.copy(alpha = alpha),
                                        radius = dotRadius,
                                        center = androidx.compose.ui.geometry.Offset(cx, cy)
                                )
                        }
                }
        }
}

@Composable
private fun DeferredAttachmentPlaceholder(
        attachments: List<MessageAttachment>,
        isOutgoing: Boolean,
        isDarkTheme: Boolean
) {
        val hasImage = attachments.any { it.type == AttachmentType.IMAGE }
        val hasVideo = attachments.any { it.type == AttachmentType.VIDEO_CIRCLE }
        val hasVoice = attachments.any { it.type == AttachmentType.VOICE }
        val hasFile = attachments.any { it.type == AttachmentType.FILE }
        val label =
                when {
                        hasImage -> "Loading photo..."
                        hasVideo -> "Loading video..."
                        hasVoice -> "Loading voice..."
                        hasFile -> "Loading attachment..."
                        else -> "Loading attachment..."
                }
        val backgroundColor =
                if (isOutgoing) {
                        Color.White.copy(alpha = 0.14f)
                } else if (isDarkTheme) {
                        Color.White.copy(alpha = 0.08f)
                } else {
                        Color.Black.copy(alpha = 0.06f)
                }
        val textColor =
                if (isOutgoing) {
                        Color.White.copy(alpha = 0.9f)
                } else if (isDarkTheme) {
                        Color(0xFFE6E6E8)
                } else {
                        Color(0xFF4A4A4A)
                }
        Box(
                modifier =
                        Modifier.fillMaxWidth()
                                .heightIn(min = 56.dp)
                                .clip(RoundedCornerShape(12.dp))
                                .background(backgroundColor)
                                .padding(horizontal = 12.dp, vertical = 10.dp),
                contentAlignment = Alignment.CenterStart
        ) {
                Text(
                        text = label,
                        color = textColor,
                        fontSize = 13.sp,
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis
                )
        }
}

/** Message bubble with Telegram-style design and animations */
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MessageBubble(
        message: ChatMessage,
        textSelectionHelper: com.rosetta.messenger.ui.chats.components.TextSelectionHelper? = null,
        isDarkTheme: Boolean,
        hasWallpaper: Boolean = false,
        isSystemSafeChat: Boolean = false,
        isSelectionMode: Boolean = false,
        showTail: Boolean = true,
        showIncomingGroupAvatar: Boolean? = null,
        isGroupStart: Boolean = false,
        isSelected: Boolean = false,
        isHighlighted: Boolean = false,
        isSavedMessages: Boolean = false,
        privateKey: String = "",
        senderPublicKey: String = "",
        senderName: String = "",
        isGroupChat: Boolean = false,
        dialogPublicKey: String = "",
        showGroupSenderLabel: Boolean = false,
        isGroupSenderAdmin: Boolean = false,
        currentUserPublicKey: String = "",
        currentUserUsername: String = "",
        groupRepository: GroupRepository,
        avatarRepository: AvatarRepository? = null,
        onLongClick: () -> Unit = {},
        onClick: () -> Unit = {},
        onSwipeToReply: () -> Unit = {},
        onReplyClick: (String) -> Unit = {},
        onRetry: () -> Unit = {},
        onDelete: () -> Unit = {},
        onCancelPhotoUpload: (attachmentId: String) -> Unit = {},
        onImageClick: (attachmentId: String, bounds: ImageSourceBounds?) -> Unit = { _, _ -> },
        onAvatarClick: (senderPublicKey: String) -> Unit = {},
        onForwardedSenderClick: (senderPublicKey: String) -> Unit = {},
        onMentionClick: (username: String) -> Unit = {},
        onGroupInviteOpen: (SearchUser) -> Unit = {},
        deferHeavyAttachmentsUntilReady: Boolean = false,
        onVoiceWaveGestureActiveChanged: (Boolean) -> Unit = {},
        contextMenuContent: @Composable () -> Unit = {}
) {
        val isTextSelectionOnThisMessage =
                remember(
                        textSelectionHelper?.isInSelectionMode,
                        textSelectionHelper?.selectedMessageId,
                        message.id
                ) {
                        textSelectionHelper?.isInSelectionMode == true &&
                                textSelectionHelper.selectedMessageId == message.id
                }
        var isVoiceWaveGestureActive by remember(message.id) { mutableStateOf(false) }

        // Swipe-to-reply state
        val hapticFeedback = LocalHapticFeedback.current
        var swipeOffset by remember { mutableStateOf(0f) }
        var hapticTriggered by remember { mutableStateOf(false) }
        val swipeThreshold = 80f
        val maxSwipe = 120f

        val animatedOffset by
                animateFloatAsState(
                        targetValue = swipeOffset,
                        animationSpec = spring(dampingRatio = 0.6f, stiffness = 400f),
                        label = "swipeOffset"
                )

        val swipeProgress = (kotlin.math.abs(animatedOffset) / swipeThreshold).coerceIn(0f, 1f)

        // Selection animations
        val selectionAlpha by
                animateFloatAsState(
                        targetValue = if (isSelected && !isTextSelectionOnThisMessage) 0.85f else 1f,
                        animationSpec = tween(150),
                        label = "selectionAlpha"
                )

        // Colors
        val bubbleColor =
                remember(message.isOutgoing, isDarkTheme) {
                        if (message.isOutgoing) {
                                PrimaryBlue
                        } else {
                                if (isDarkTheme) Color(0xFF212121) else Color(0xFFF5F5F5)
                        }
                }

        val textColor =
                remember(message.isOutgoing, isDarkTheme) {
                        if (message.isOutgoing) Color.White
                        else if (isDarkTheme) Color.White else Color(0xFF000000)
                }

        // 🔗 Цвет ссылок: для исходящих (синий фон) - светлый, для входящих - стандартный синий
        val linkColor =
                remember(message.isOutgoing, isDarkTheme) {
                        if (message.isOutgoing) Color(0xFFB3E5FC) // Светло-голубой на синем фоне
                        else Color(0xFF2196F3) // Стандартный Material Blue для входящих
                }
        var textViewRef by remember { mutableStateOf<com.rosetta.messenger.ui.components.AppleEmojiTextView?>(null) }
        val selectionDragEndHandler: (() -> Unit)? = if (textSelectionHelper != null) {
            { textSelectionHelper.hideMagnifier(); textSelectionHelper.endHandleDrag() }
        } else null
        val linksEnabled = !isSelectionMode
        val textClickHandler: (() -> Unit)? = onClick
        val mentionClickHandler: ((String) -> Unit)? =
                if (linksEnabled) {
                        { username -> onMentionClick(username) }
                } else {
                        null
                }
        var suppressBubbleTapUntilMs by remember(message.id) { mutableLongStateOf(0L) }
        val suppressBubbleTapFromSpan: () -> Unit = {
                suppressBubbleTapUntilMs = System.currentTimeMillis() + 450L
        }
        val hasVoiceAttachmentForGesture =
                remember(message.attachments) { message.attachments.any { it.type == AttachmentType.VOICE } }

        val timeColor =
                remember(message.isOutgoing, isDarkTheme) {
                        if (message.isOutgoing) Color.White.copy(alpha = 0.7f)
                        else if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
                }
        val statusColor =
                remember(message.isOutgoing, isDarkTheme, timeColor) {
                        if (message.isOutgoing) Color.White else timeColor
                }

        val isSafeSystemMessage =
                isSystemSafeChat &&
                        !message.isOutgoing &&
                        message.replyData == null &&
                        message.forwardedMessages.isEmpty() &&
                        message.attachments.isEmpty() &&
                        message.text.isNotBlank()

        val groupActionSystemText =
                remember(message.text) { resolveGroupActionSystemText(message.text) }
        val isGroupActionSystemMessage =
                groupActionSystemText != null &&
                        message.replyData == null &&
                        message.forwardedMessages.isEmpty() &&
                        message.attachments.isEmpty()

        if (isGroupActionSystemMessage) {
                GroupActionSystemMessage(
                        text = groupActionSystemText.orEmpty(),
                        isDarkTheme = isDarkTheme,
                        hasWallpaper = hasWallpaper
                )
                return
        }

        // Guard against malformed/empty payloads: don't render sender label without any content.
        val isVisuallyEmptyMessage =
                !isSafeSystemMessage &&
                        message.replyData == null &&
                        message.forwardedMessages.isEmpty() &&
                        message.attachments.isEmpty() &&
                        message.text.isBlank()
        if (isVisuallyEmptyMessage) {
                return
        }

        // Telegram: bubbleRadius = 17dp, smallRad (хвостик) = 6dp
        val bubbleShape =
                remember(message.isOutgoing, showTail, isSafeSystemMessage) {
                        if (isSafeSystemMessage) {
                                RoundedCornerShape(18.dp)
                        } else {
                                RoundedCornerShape(
                                        topStart = TelegramBubbleSpec.bubbleRadius,
                                        topEnd = TelegramBubbleSpec.bubbleRadius,
                                        bottomStart =
                                                if (message.isOutgoing) TelegramBubbleSpec.bubbleRadius
                                                else
                                                        (if (showTail) TelegramBubbleSpec.nearRadius
                                                        else TelegramBubbleSpec.bubbleRadius),
                                        bottomEnd =
                                                if (message.isOutgoing)
                                                        (if (showTail) TelegramBubbleSpec.nearRadius
                                                        else TelegramBubbleSpec.bubbleRadius)
                                                else TelegramBubbleSpec.bubbleRadius
                                )
                        }
                }

        val timeFormat = remember { SimpleDateFormat("HH:mm", Locale.getDefault()) }

        Box(
                modifier =
                        Modifier.fillMaxWidth().pointerInput(isSystemSafeChat, textSelectionHelper?.isActive, isVoiceWaveGestureActive) {
                                if (isSystemSafeChat) return@pointerInput
                                if (textSelectionHelper?.isActive == true) return@pointerInput
                                if (isVoiceWaveGestureActive) return@pointerInput
                                // 🔥 Простой горизонтальный свайп для reply
                                // Используем detectHorizontalDragGestures который лучше работает со
                                // скроллом
                                detectHorizontalDragGestures(
                                        onDragStart = {
                                                if (isVoiceWaveGestureActive) {
                                                        swipeOffset = 0f
                                                        hapticTriggered = false
                                                }
                                        },
                                        onDragEnd = {
                                                if (isVoiceWaveGestureActive) {
                                                        swipeOffset = 0f
                                                        hapticTriggered = false
                                                        return@detectHorizontalDragGestures
                                                }
                                                // Если свайпнули достаточно влево - reply
                                                if (swipeOffset <= -swipeThreshold) {
                                                        onSwipeToReply()
                                                }
                                                swipeOffset = 0f
                                                hapticTriggered = false
                                        },
                                        onDragCancel = {
                                                swipeOffset = 0f
                                                hapticTriggered = false
                                        },
                                        onHorizontalDrag = { change, dragAmount ->
                                                if (isVoiceWaveGestureActive) {
                                                        swipeOffset = 0f
                                                        hapticTriggered = false
                                                        return@detectHorizontalDragGestures
                                                }
                                                // Только свайп влево (отрицательное значение)
                                                if (dragAmount < 0 || swipeOffset < 0) {
                                                        change.consume()
                                                        val newOffset = swipeOffset + dragAmount
                                                        swipeOffset =
                                                                newOffset.coerceIn(-maxSwipe, 0f)
                                                        // Haptic при достижении порога
                                                        if (swipeOffset <= -swipeThreshold && !hapticTriggered) {
                                                                hapticTriggered = true
                                                                hapticFeedback.performHapticFeedback(
                                                                        HapticFeedbackType.LongPress
                                                                )
                                                        } else if (swipeOffset > -swipeThreshold && hapticTriggered) {
                                                                hapticTriggered = false
                                                        }
                                                }
                                        }
                                )
                        }
        ) {
                // 🔥 Reply icon - справа, появляется при свайпе влево
                Box(
                        modifier =
                                Modifier.align(Alignment.CenterEnd)
                                        .padding(end = 16.dp)
                                        .graphicsLayer {
                                                alpha = swipeProgress
                                                scaleX = swipeProgress
                                                scaleY = swipeProgress
                                        }
                ) {
                        Box(
                                modifier =
                                        Modifier.size(36.dp)
                                                .clip(CircleShape)
                                                .background(
                                                        if (swipeProgress >= 1f) PrimaryBlue
                                                        else if (isDarkTheme) Color(0xFF3A3A3A)
                                                        else Color(0xFFE0E0E0)
                                                ),
                                contentAlignment = Alignment.Center
                        ) {
                                Icon(
                                        painter = TelegramIcons.Reply,
                                        contentDescription = "Reply",
                                        tint =
                                                if (swipeProgress >= 1f) Color.White
                                                else if (isDarkTheme) Color.White.copy(alpha = 0.7f)
                                                else Color(0xFF666666),
                                        modifier = Modifier.size(20.dp)
                                )
                        }
                }

                val selectionBackgroundColor by
                        animateColorAsState(
                                targetValue =
                                        if (isSelected && !isTextSelectionOnThisMessage)
                                                PrimaryBlue.copy(alpha = 0.15f)
                                        else Color.Transparent,
                                animationSpec = tween(200),
                                label = "selectionBg"
                        )

                val highlightBackgroundColor by
                        animateColorAsState(
                                targetValue =
                                        if (isHighlighted) PrimaryBlue.copy(alpha = 0.12f)
                                        else Color.Transparent,
                                animationSpec = tween(300),
                                label = "highlightBg"
                        )

                val combinedBackgroundColor =
                        if (isSelected) selectionBackgroundColor else highlightBackgroundColor

                val telegramIncomingAvatarSize = 42.dp
                val telegramIncomingAvatarLane = 48.dp
                val telegramIncomingAvatarInset = 6.dp
                val telegramIncomingBubbleGap = 6.dp
                val shouldShowIncomingGroupAvatar =
                        showIncomingGroupAvatar
                                ?: (isGroupChat &&
                                        !message.isOutgoing &&
                                        showTail &&
                                        senderPublicKey.isNotBlank())

                Row(
                        modifier =
                                Modifier.fillMaxWidth()
                                        .background(combinedBackgroundColor)
                                        .padding(vertical = 2.dp)
                                        .offset { IntOffset(animatedOffset.toInt(), 0) },
                        horizontalArrangement = Arrangement.Start,
                        verticalAlignment = Alignment.CenterVertically
                ) {
                        // Selection checkmark
                        AnimatedVisibility(
                                visible = isSelected && message.isOutgoing,
                                enter =
                                        fadeIn(tween(150)) +
                                                scaleIn(
                                                        initialScale = 0.3f,
                                                        animationSpec = spring(dampingRatio = 0.6f)
                                                ),
                                exit = fadeOut(tween(100)) + scaleOut(targetScale = 0.3f)
                        ) {
                                Box(
                                        modifier =
                                                Modifier.padding(start = 12.dp, end = 4.dp)
                                                        .size(24.dp)
                                                        .clip(CircleShape)
                                                        .background(PrimaryBlue),
                                        contentAlignment = Alignment.Center
                                ) {
                                        Icon(
                                                painter = TelegramIcons.Done,
                                                contentDescription = "Selected",
                                                tint = Color.White,
                                                modifier = Modifier.size(16.dp)
                                        )
                                }
                        }

                AnimatedVisibility(
                                visible = !isSelected || !message.isOutgoing,
                                enter = fadeIn(tween(100)),
                                exit = fadeOut(tween(100))
                        ) {
                                val leadingSpacerWidth =
                                        if (!message.isOutgoing && isGroupChat) 0.dp else 12.dp
                                Spacer(modifier = Modifier.width(leadingSpacerWidth))
                        }

                        if (message.isOutgoing) {
                                Spacer(modifier = Modifier.weight(1f))
                        }

                        if (!message.isOutgoing && isGroupChat) {
                                Box(
                                        modifier =
                                                Modifier.width(telegramIncomingAvatarLane)
                                                        .height(telegramIncomingAvatarSize)
                                                        .align(Alignment.Bottom),
                                        contentAlignment = Alignment.BottomStart
                                ) {
                                        if (shouldShowIncomingGroupAvatar) {
                                                Box(
                                                        modifier =
                                                                Modifier.fillMaxSize()
                                                                        .padding(
                                                                                start =
                                                                                        telegramIncomingAvatarInset
                                                                        ),
                                                        contentAlignment = Alignment.BottomStart
                                                ) {
                                                        val avatarClickHandler: (() -> Unit)? =
                                                                if (!isSelectionMode &&
                                                                                senderPublicKey
                                                                                        .isNotBlank()
                                                                ) {
                                                                        { onAvatarClick(senderPublicKey) }
                                                                } else {
                                                                        null
                                                                }
                                                        AvatarImage(
                                                                publicKey = senderPublicKey,
                                                                avatarRepository = avatarRepository,
                                                                size = telegramIncomingAvatarSize,
                                                                isDarkTheme = isDarkTheme,
                                                                onClick = avatarClickHandler,
                                                                displayName =
                                                                        senderName.ifBlank {
                                                                                senderPublicKey
                                                                        }
                                                        )
                                                }
                                        }
                                }
                        }

                        // Проверяем - есть ли только фотки без текста
                        val hasOnlyMedia =
                                message.attachments.isNotEmpty() &&
                                        message.text.isEmpty() &&
                                        message.replyData == null &&
                                        message.forwardedMessages.isEmpty() &&
                                        message.attachments.all {
                                                it.type ==
                                                        com.rosetta.messenger.network.AttachmentType
                                                                .IMAGE ||
                                                        it.type ==
                                                                com.rosetta.messenger.network
                                                                        .AttachmentType
                                                                        .VIDEO_CIRCLE
                                        }
                        val hasOnlyVideoCircle =
                                hasOnlyMedia &&
                                        message.attachments.all {
                                                it.type ==
                                                        com.rosetta.messenger.network.AttachmentType
                                                                .VIDEO_CIRCLE
                                        }

                        // Фото + caption (как в Telegram)
                        val hasImageWithCaption =
                                message.attachments.isNotEmpty() &&
                                        message.text.isNotEmpty() &&
                                        message.replyData == null &&
                                        message.forwardedMessages.isEmpty() &&
                                        message.attachments.all {
                                                it.type ==
                                                        com.rosetta.messenger.network.AttachmentType
                                                                .IMAGE
                                        }

                        val isCallMessage =
                                message.attachments.isNotEmpty() &&
                                        message.text.isEmpty() &&
                                        message.replyData == null &&
                                        message.forwardedMessages.isEmpty() &&
                                        message.attachments.all {
                                                it.type == AttachmentType.CALL
                                        }
                        val hasVoiceAttachment =
                                message.attachments.any { it.type == AttachmentType.VOICE }
                        val shouldDeferAttachmentRendering =
                                deferHeavyAttachmentsUntilReady &&
                                        message.attachments.isNotEmpty() &&
                                        message.attachments.any {
                                                it.type != AttachmentType.AVATAR
                                        }

                        val isStandaloneGroupInvite =
                                message.attachments.isEmpty() &&
                                        message.replyData == null &&
                                        message.forwardedMessages.isEmpty() &&
                                        message.text.isNotBlank() &&
                                        isGroupInviteCode(message.text)

                        // Для сообщений только с фото - минимальный padding и тонкий border
                        // Для фото + caption - padding только внизу для текста
                        val bubblePadding =
                                when {
                                        isSafeSystemMessage -> PaddingValues(0.dp)
                                        isStandaloneGroupInvite -> PaddingValues(horizontal = 10.dp, vertical = 8.dp)
                                        hasOnlyMedia -> PaddingValues(0.dp)
                                        hasImageWithCaption -> PaddingValues(0.dp)
                                        else -> PaddingValues(horizontal = 10.dp, vertical = 8.dp)
                                }
                        val bubbleBorderWidth =
                                if (hasOnlyMedia && !hasOnlyVideoCircle) 1.dp else 0.dp

                        // Telegram-style: ширина пузырька = ширина фото
                        // Caption переносится на новые строки, не расширяя пузырёк
                        val configuration = LocalConfiguration.current
                        val screenWidthDp = configuration.screenWidthDp
                        val maxPhotoWidth = TelegramBubbleSpec.maxPhotoWidth(screenWidthDp).dp
                        val maxCollageWidth = TelegramBubbleSpec.maxCollageWidth(screenWidthDp).dp
                        val imageCount =
                                message.attachments.count {
                                        it.type ==
                                                com.rosetta.messenger.network.AttachmentType.IMAGE
                                }
                        val isImageCollage = imageCount > 1

                        // Вычисляем ширину фото для ограничения пузырька
                        val photoWidth =
                                if (hasImageWithCaption || hasOnlyMedia) {
                                        if (hasOnlyVideoCircle) {
                                                220.dp
                                        } else if (isImageCollage) {
                                                maxCollageWidth
                                        } else {
                                                val firstImage =
                                                        message.attachments.firstOrNull {
                                                                it.type ==
                                                                        com.rosetta.messenger
                                                                                .network
                                                                                .AttachmentType
                                                                                .IMAGE
                                                        }
                                                if (firstImage != null &&
                                                                firstImage.width > 0 &&
                                                                firstImage.height > 0
                                                ) {
                                                        val ar =
                                                                firstImage.width.toFloat() /
                                                                        firstImage.height
                                                                                .toFloat()
                                                        val maxW =
                                                                TelegramBubbleSpec.maxPhotoWidth(
                                                                                screenWidthDp
                                                                        )
                                                                        .toFloat()
                                                        var w = if (ar >= 1f) maxW else maxW * 0.75f
                                                        var h = w / ar

                                                        if (h > TelegramBubbleSpec.maxPhotoHeight) {
                                                                h =
                                                                        TelegramBubbleSpec.maxPhotoHeight
                                                                                .toFloat()
                                                                w = h * ar
                                                        }
                                                        if (h < TelegramBubbleSpec.minPhotoHeight) {
                                                                h =
                                                                        TelegramBubbleSpec.minPhotoHeight
                                                                                .toFloat()
                                                                w = h * ar
                                                        }
                                                        w.coerceIn(
                                                                        TelegramBubbleSpec
                                                                                .minPhotoWidth
                                                                                .toFloat(),
                                                                        maxW
                                                                )
                                                                .dp
                                                } else {
                                                        maxPhotoWidth
                                                }
                                        }
                                } else {
                                        280.dp
                                }

                        val hasGroupSenderName = showGroupSenderLabel && !message.isOutgoing && senderName.isNotBlank()
                        val bubbleWidthModifier =
                                if (isSafeSystemMessage) {
                                        Modifier.widthIn(min = 220.dp, max = 320.dp)
                                } else if (isStandaloneGroupInvite) {
                                        Modifier.widthIn(min = 180.dp, max = 260.dp)
                                } else if (hasImageWithCaption || hasOnlyMedia) {
                                        Modifier.width(
                                                photoWidth
                                        ) // Для медиа держим фиксированную ширину как в desktop/telegram
                                } else {
                                        Modifier.widthIn(min = if (hasGroupSenderName) 120.dp else 60.dp, max = 280.dp)
                                }

                        Box(
                                modifier =
                                        Modifier.align(Alignment.Bottom)
                                                .padding(
                                                        start =
                                                                if (!message.isOutgoing && isGroupChat)
                                                                        telegramIncomingBubbleGap
                                                                else 0.dp,
                                                        end = 12.dp
                                                )
                                                .then(bubbleWidthModifier)
                                                .graphicsLayer {
                                                        this.alpha = selectionAlpha
                                                }
                                                .combinedClickable(
                                                        indication = null,
                                                        interactionSource =
                                                                remember {
                                                                        MutableInteractionSource()
                                                                },
                                                        onClick = {
                                                                if (System.currentTimeMillis() <= suppressBubbleTapUntilMs) {
                                                                        return@combinedClickable
                                                                }
                                                                onClick()
                                                        },
                                                        onLongClick = onLongClick
                                                )
                                                .then(
                                                        if (isCallMessage) {
                                                                // Звонки без фонового пузырька — у них свой контейнер внутри CallAttachment
                                                                Modifier
                                                        } else if (hasVoiceAttachment) {
                                                                // Для voice не клипуем содержимое пузыря:
                                                                // playback-blob может выходить за границы, как в Telegram.
                                                                Modifier.background(
                                                                                color =
                                                                                        if (isSafeSystemMessage) {
                                                                                                if (isDarkTheme)
                                                                                                        Color(0xFF2A2A2D)
                                                                                                else Color(0xFFF0F0F4)
                                                                                        } else {
                                                                                                bubbleColor
                                                                                        },
                                                                                shape = bubbleShape
                                                                        )
                                                                        .padding(bubblePadding)
                                                        } else {
                                                                Modifier.clip(bubbleShape)
                                                                        .then(
                                                                                if (hasOnlyMedia) {
                                                                                        Modifier.border(
                                                                                                width = bubbleBorderWidth,
                                                                                                color =
                                                                                                        if (message.isOutgoing
                                                                                                        ) {
                                                                                                                Color.White
                                                                                                                        .copy(
                                                                                                                                alpha =
                                                                                                                                        0.15f
                                                                                                                        )
                                                                                                        } else {
                                                                                                                if (isDarkTheme
                                                                                                                )
                                                                                                                        Color.White
                                                                                                                                .copy(
                                                                                                                                        alpha =
                                                                                                                                                0.1f
                                                                                                                                )
                                                                                                                else
                                                                                                                        Color.Black
                                                                                                                                .copy(
                                                                                                                                        alpha =
                                                                                                                                                0.08f
                                                                                                                                )
                                                                                                        },
                                                                                                shape = bubbleShape
                                                                                        )
                                                                                } else if (isSafeSystemMessage) {
                                                                                        Modifier.background(
                                                                                                if (isDarkTheme) Color(0xFF2A2A2D)
                                                                                                else Color(0xFFF0F0F4)
                                                                                        )
                                                                                } else {
                                                                                        Modifier.background(bubbleColor)
                                                                                }
                                                                        )
                                                                        .padding(bubblePadding)
                                                        }
                                                )
                        ) {
                                if (isSafeSystemMessage) {
                                        SafeSystemMessageCard(
                                                text = message.text,
                                                timestamp = message.timestamp,
                                                isDarkTheme = isDarkTheme
                                        )
                                } else {
                                        Column {
                                        if (showGroupSenderLabel &&
                                                        !message.isOutgoing &&
                                                        senderName.isNotBlank()
                                        ) {
                                                val isMediaGroupBubble = hasImageWithCaption || hasOnlyMedia
                                                val senderLabelTopPadding =
                                                        if (isMediaGroupBubble) 8.dp else 0.dp
                                                val senderLabelHorizontalPadding =
                                                        if (isMediaGroupBubble) 10.dp else 0.dp
                                                Row(
                                                        modifier =
                                                                Modifier.padding(
                                                                        start = senderLabelHorizontalPadding,
                                                                        end = senderLabelHorizontalPadding,
                                                                        top = senderLabelTopPadding,
                                                                        bottom = 4.dp
                                                                ),
                                                        verticalAlignment = Alignment.CenterVertically
                                                ) {
                                                        val senderLabelText =
                                                                senderName
                                                                        .replace('\n', ' ')
                                                                        .trim()
                                                        val senderLabelMaxWidth =
                                                                if (isGroupSenderAdmin) 170.dp else 220.dp
                                                        Text(
                                                                text = senderLabelText,
                                                                color =
                                                                        groupSenderLabelColor(
                                                                                senderPublicKey,
                                                                                isDarkTheme
                                                                        ),
                                                                fontSize = 12.sp,
                                                                fontWeight = FontWeight.SemiBold,
                                                                modifier = Modifier.widthIn(max = senderLabelMaxWidth),
                                                                maxLines = 1,
                                                                overflow = TextOverflow.Ellipsis
                                                        )
                                                        if (isGroupSenderAdmin) {
                                                                Spacer(modifier = Modifier.width(4.dp))
                                                                GroupAdminBadge(isDarkTheme = isDarkTheme)
                                                        }
                                                }
                                        }
                                        // 🔥 Forwarded messages (multiple, desktop parity)
                                        if (message.forwardedMessages.isNotEmpty()) {
                                                ForwardedMessagesBubble(
                                                        forwardedMessages = message.forwardedMessages,
                                                        isOutgoing = message.isOutgoing,
                                                        isDarkTheme = isDarkTheme,
                                                        chachaKey = message.chachaKey,
                                                        chachaKeyPlainHex = message.chachaKeyPlainHex,
                                                        privateKey = privateKey,
                                                        dialogPublicKey = dialogPublicKey,
                                                        currentUserPublicKey = currentUserPublicKey,
                                                        senderDisplayName = senderName,
                                                        timestamp = message.timestamp,
                                                        messageStatus = message.status,
                                                        linksEnabled = linksEnabled,
                                                        onImageClick = onImageClick,
                                                        onForwardedSenderClick = onForwardedSenderClick,
                                                        onMentionClick = onMentionClick,
                                                        onTextSpanPressStart = suppressBubbleTapFromSpan,
                                                        onVoiceWaveGestureActiveChanged = { active ->
                                                                isVoiceWaveGestureActive = active
                                                                onVoiceWaveGestureActiveChanged(active)
                                                        }
                                                )
                                                Spacer(modifier = Modifier.height(4.dp))
                                        }

                                        message.replyData?.let { reply ->
                                                ReplyBubble(
                                                        replyData = reply,
                                                        isOutgoing = message.isOutgoing,
                                                        isDarkTheme = isDarkTheme,
                                                        chachaKey = message.chachaKey,
                                                        chachaKeyPlainHex = message.chachaKeyPlainHex,
                                                        privateKey = privateKey,
                                                        onClick = { onReplyClick(reply.messageId) },
                                                        onImageClick = onImageClick,
                                                        onForwardedSenderClick = onForwardedSenderClick
                                                )
                                                Spacer(modifier = Modifier.height(4.dp))
                                        }

                                        // 📎 Attachments (IMAGE, FILE, AVATAR)
                                        if (message.attachments.isNotEmpty()) {
                                                if (shouldDeferAttachmentRendering) {
                                                        DeferredAttachmentPlaceholder(
                                                                attachments = message.attachments,
                                                                isOutgoing = message.isOutgoing,
                                                                isDarkTheme = isDarkTheme
                                                        )
                                                } else {
                                                        val attachmentDisplayStatus =
                                                                if (isSavedMessages) MessageStatus.READ
                                                                else message.status
                                                        MessageAttachments(
                                                                attachments = message.attachments,
                                                                chachaKey = message.chachaKey,
                                                                chachaKeyPlainHex = message.chachaKeyPlainHex,
                                                                privateKey = privateKey,
                                                                isOutgoing = message.isOutgoing,
                                                                isDarkTheme = isDarkTheme,
                                                                senderPublicKey = senderPublicKey,
                                                                senderDisplayName = senderName,
                                                                dialogPublicKey = dialogPublicKey,
                                                                isGroupChat = isGroupChat,
                                                                timestamp = message.timestamp,
                                                                messageStatus = attachmentDisplayStatus,
                                                                avatarRepository = avatarRepository,
                                                                currentUserPublicKey = currentUserPublicKey,
                                                                hasCaption = hasImageWithCaption,
                                                                showTail = showTail, // Передаём для формы
                                                                // пузырька
                                                                isSelectionMode = isSelectionMode,
                                                                onLongClick = onLongClick,
                                                                onCancelUpload = onCancelPhotoUpload,
                                                                // В selection mode блокируем открытие фото
                                                                onImageClick = if (isSelectionMode) { _, _ -> } else onImageClick,
                                                                onVoiceWaveGestureActiveChanged = { active ->
                                                                        isVoiceWaveGestureActive = active
                                                                        onVoiceWaveGestureActiveChanged(active)
                                                                }
                                                        )
                                                }
                                        }

                                        // 🖼️ Caption под фото (Telegram-style)
                                        // Отступ от фото = 9dp, слева = 11dp (исходящие) / 13dp
                                        // (входящие)
                                        if (hasImageWithCaption) {
                                                val captionPaddingStart =
                                                        if (message.isOutgoing)
                                                                TelegramBubbleSpec
                                                                        .captionPaddingStartOutgoing
                                                        else
                                                                TelegramBubbleSpec
                                                                        .captionPaddingStartIncoming
                                                Box(
                                                        modifier =
                                                                Modifier.fillMaxWidth() // 🔥
                                                                        // Растягиваем на ширину
                                                                        // фото для правильного
                                                                        // позиционирования времени
                                                                        .background(bubbleColor)
                                                                        .padding(
                                                                                start =
                                                                                        captionPaddingStart,
                                                                                end =
                                                                                        TelegramBubbleSpec
                                                                                                .captionPaddingEnd,
                                                                                top =
                                                                                        TelegramBubbleSpec
                                                                                                .captionPaddingTop,
                                                                                bottom =
                                                                                        TelegramBubbleSpec
                                                                                                .captionPaddingBottom
                                                                        )
                                                ) {
                                                        TelegramStyleMessageContent(
                                                                textContent = {
                                                                        AppleEmojiText(
                                                                                text = message.text,
                                                                                color = textColor,
                                                                                fontSize = 16.sp,
                                                                                linkColor =
                                                                                        linkColor,
                                                                                enableLinks =
                                                                                        linksEnabled,
                                                                                enableMentions =
                                                                                        true,
                                                                                onMentionClick =
                                                                                        mentionClickHandler,
                                                                                onClickableSpanPressStart =
                                                                                        suppressBubbleTapFromSpan,
                                                                                onClick =
                                                                                        textClickHandler,
                                                                                onLongClick =
                                                                                        onLongClick, // 🔥 Long press для selection
                                                                                onViewCreated = { textViewRef = it },
                                                                                onTextLongPress = if (textSelectionHelper != null && isSelected) { touchX, touchY ->
                                                                                        val info = textViewRef?.getLayoutInfo()
                                                                                        if (info != null) {
                                                                                                textSelectionHelper.startSelection(
                                                                                                        messageId = message.id,
                                                                                                        info = info,
                                                                                                        touchX = touchX,
                                                                                                        touchY = touchY,
                                                                                                        view = textViewRef,
                                                                                                        isOwnMessage = message.isOutgoing
                                                                                                )
                                                                                        }
                                                                                } else null,
                                                                                onSelectionDrag = if (textSelectionHelper != null) { tx, ty ->
                                                                                        textSelectionHelper.moveHandle(
                                                                                                (tx - textSelectionHelper.overlayWindowX),
                                                                                                (ty - textSelectionHelper.overlayWindowY)
                                                                                        )
                                                                                        textSelectionHelper.showMagnifier(
                                                                                                (tx - textSelectionHelper.overlayWindowX),
                                                                                                (ty - textSelectionHelper.overlayWindowY)
                                                                                        )
                                                                                } else null,
                                                                                onSelectionDragEnd = selectionDragEndHandler
                                                                        )
                                                                },
                                                                timeContent = {
                                                                        Row(
                                                                                verticalAlignment =
                                                                                        Alignment
                                                                                                .CenterVertically,
                                                                                horizontalArrangement =
                                                                                        Arrangement
                                                                                                .spacedBy(
                                                                                                        2.dp
                                                                                                )
                                                                        ) {
                                                                                Text(
                                                                                        text =
                                                                                                timeFormat
                                                                                                        .format(
                                                                                                                message.timestamp
                                                                                                        ),
                                                                                        color =
                                                                                                timeColor,
                                                                                        fontSize =
                                                                                                11.sp,
                                                                                        fontStyle =
                                                                                                androidx.compose
                                                                                                        .ui
                                                                                                        .text
                                                                                                        .font
                                                                                                        .FontStyle
                                                                                                        .Italic
                                                                                )
                                                                                if (message.isOutgoing
                                                                                ) {
                                                                                        val displayStatus =
                                                                                                if (isSavedMessages
                                                                                                )
                                                                                                        MessageStatus
                                                                                                                .READ
                                                                                                else
                                                                                                        message.status
                                                                                        AnimatedMessageStatus(
                                                                                                status =
                                                                                                        displayStatus,
                                                                                                timeColor =
                                                                                                        statusColor,
                                                                                                isDarkTheme =
                                                                                                        isDarkTheme,
                                                                                                isOutgoing =
                                                                                                        message.isOutgoing,
                                                                                                timestamp =
                                                                                                        message.timestamp
                                                                                                                .time,
                                                                                                onRetry =
                                                                                                        onRetry,
                                                                                                onDelete =
                                                                                                        onDelete
                                                                                        )
                                                                                }
                                                                        }
                                                                },
                                                                spacing = 8,
                                                                fillWidth =
                                                                        true // 🔥 Время справа на
                                                                // всю ширину фото
                                                                )
                                                }
                                        } else if (message.attachments.isNotEmpty() &&
                                                        message.text.isNotEmpty()
                                        ) {
                                                // Обычное фото + текст (не только изображения)
                                                Spacer(modifier = Modifier.height(8.dp))
                                        }

                                        // Если есть reply/forward - Telegram-style layout
                                        if ((message.replyData != null || message.forwardedMessages.isNotEmpty()) && message.text.isNotEmpty()
                                        ) {
                                                TelegramStyleMessageContent(
                                                        textContent = {
                                                                AppleEmojiText(
                                                                        text = message.text,
                                                                        color = textColor,
                                                                        fontSize = 17.sp,
                                                                        linkColor = linkColor,
                                                                        enableLinks = linksEnabled,
                                                                        enableMentions = true,
                                                                        onMentionClick = mentionClickHandler,
                                                                        onClickableSpanPressStart =
                                                                                suppressBubbleTapFromSpan,
                                                                        onClick = textClickHandler,
                                                                        onLongClick =
                                                                                onLongClick, // 🔥 Long press для selection
                                                                        onViewCreated = { textViewRef = it },
                                                                        onTextLongPress = if (textSelectionHelper != null && !isSelectionMode) { touchX, touchY ->
                                                                                val info = textViewRef?.getLayoutInfo()
                                                                                if (info != null) {
                                                                                        textSelectionHelper.startSelection(
                                                                                                messageId = message.id,
                                                                                                info = info,
                                                                                                touchX = touchX,
                                                                                                touchY = touchY,
                                                                                                view = textViewRef,
                                                                                                isOwnMessage = message.isOutgoing
                                                                                        )
                                                                                }
                                                                        } else null
                                                                )
                                                        },
                                                        timeContent = {
                                                                Row(
                                                                        verticalAlignment =
                                                                                Alignment
                                                                                        .CenterVertically,
                                                                        horizontalArrangement =
                                                                                Arrangement
                                                                                        .spacedBy(
                                                                                                2.dp
                                                                                        )
                                                                ) {
                                                                        Text(
                                                                                text =
                                                                                        timeFormat
                                                                                                .format(
                                                                                                        message.timestamp
                                                                                                ),
                                                                                color = timeColor,
                                                                                fontSize = 11.sp,
                                                                                fontStyle =
                                                                                        androidx.compose
                                                                                                .ui
                                                                                                .text
                                                                                                .font
                                                                                                .FontStyle
                                                                                                .Italic
                                                                        )
                                                                        if (message.isOutgoing) {
                                                                                val displayStatus =
                                                                                        if (isSavedMessages
                                                                                        )
                                                                                                MessageStatus
                                                                                                        .READ
                                                                                        else
                                                                                                message.status
                                                                                AnimatedMessageStatus(
                                                                                        status =
                                                                                                displayStatus,
                                                                                        timeColor =
                                                                                                statusColor,
                                                                                        isDarkTheme =
                                                                                                isDarkTheme,
                                                                                        isOutgoing =
                                                                                                message.isOutgoing,
                                                                                        timestamp =
                                                                                                message.timestamp
                                                                                                        .time,
                                                                                        onRetry =
                                                                                                onRetry,
                                                                                        onDelete =
                                                                                                onDelete
                                                                                )
                                                                        }
                                                                }
                                                        },
                                                        fillWidth = true // 🔥 Время справа с учётом
                                                        // ширины ReplyBubble
                                                        )
                                        } else if (!hasOnlyMedia &&
                                                        !hasImageWithCaption &&
                                                        message.text.isNotEmpty()
                                        ) {
                                                if (isGroupInviteCode(message.text)) {
                                                        val displayStatus =
                                                                if (isSavedMessages) MessageStatus.READ
                                                                else message.status

                                                        GroupInviteInlineCard(
                                                                inviteText = message.text,
                                                                isOutgoing = message.isOutgoing,
                                                                isDarkTheme = isDarkTheme,
                                                                groupRepository = groupRepository,
                                                                accountPublicKey = currentUserPublicKey,
                                                                accountPrivateKey = privateKey,
                                                                actionsEnabled = !isSelectionMode,
                                                                timestamp = message.timestamp,
                                                                messageStatus = displayStatus,
                                                                onRetry = onRetry,
                                                                onDelete = onDelete,
                                                                onOpenGroup = onGroupInviteOpen
                                                        )
                                                } else {
                                                        // Telegram-style: текст + время с автоматическим
                                                        // переносом
                                                        TelegramStyleMessageContent(
                                                                textContent = {
                                                                        AppleEmojiText(
                                                                                text = message.text,
                                                                                color = textColor,
                                                                                fontSize = 17.sp,
                                                                                linkColor = linkColor,
                                                                                enableLinks = linksEnabled,
                                                                                enableMentions = true,
                                                                                onMentionClick = mentionClickHandler,
                                                                                onClickableSpanPressStart =
                                                                                        suppressBubbleTapFromSpan,
                                                                                onClick = textClickHandler,
                                                                                onLongClick =
                                                                                        onLongClick, // 🔥 Long press для selection
                                                                                onViewCreated = { textViewRef = it },
                                                                                onTextLongPress = if (textSelectionHelper != null && isSelected) { touchX, touchY ->
                                                                                        val info = textViewRef?.getLayoutInfo()
                                                                                        if (info != null) {
                                                                                                textSelectionHelper.startSelection(
                                                                                                        messageId = message.id,
                                                                                                        info = info,
                                                                                                        touchX = touchX,
                                                                                                        touchY = touchY,
                                                                                                        view = textViewRef,
                                                                                                        isOwnMessage = message.isOutgoing
                                                                                                )
                                                                                        }
                                                                                } else null,
                                                                                onSelectionDrag = if (textSelectionHelper != null) { tx, ty ->
                                                                                        textSelectionHelper.moveHandle(
                                                                                                (tx - textSelectionHelper.overlayWindowX),
                                                                                                (ty - textSelectionHelper.overlayWindowY)
                                                                                        )
                                                                                        textSelectionHelper.showMagnifier(
                                                                                                (tx - textSelectionHelper.overlayWindowX),
                                                                                                (ty - textSelectionHelper.overlayWindowY)
                                                                                        )
                                                                                } else null,
                                                                                onSelectionDragEnd = selectionDragEndHandler
                                                                        )
                                                                },
                                                                timeContent = {
                                                                        Row(
                                                                                verticalAlignment =
                                                                                        Alignment
                                                                                                .CenterVertically,
                                                                                horizontalArrangement =
                                                                                        Arrangement
                                                                                                .spacedBy(
                                                                                                        2.dp
                                                                                                )
                                                                        ) {
                                                                                Text(
                                                                                        text =
                                                                                                timeFormat
                                                                                                        .format(
                                                                                                                message.timestamp
                                                                                                        ),
                                                                                        color = timeColor,
                                                                                        fontSize = 11.sp,
                                                                                        fontStyle =
                                                                                                androidx.compose
                                                                                                        .ui
                                                                                                        .text
                                                                                                        .font
                                                                                                        .FontStyle
                                                                                                        .Italic
                                                                                )
                                                                                if (message.isOutgoing) {
                                                                                        val displayStatus =
                                                                                                if (isSavedMessages
                                                                                                )
                                                                                                        MessageStatus
                                                                                                                .READ
                                                                                                else
                                                                                                        message.status
                                                                                        AnimatedMessageStatus(
                                                                                                status =
                                                                                                        displayStatus,
                                                                                                timeColor =
                                                                                                        statusColor,
                                                                                                isDarkTheme =
                                                                                                        isDarkTheme,
                                                                                                isOutgoing =
                                                                                                        message.isOutgoing,
                                                                                                timestamp =
                                                                                                        message.timestamp
                                                                                                                .time,
                                                                                                onRetry =
                                                                                                        onRetry,
                                                                                                onDelete =
                                                                                                        onDelete
                                                                                        )
                                                                                }
                                                                        }
                                                                }
                                                        )
                                                }
                                        }
                                }
                                }
                                // 💬 Context menu anchor (DropdownMenu positions relative to this Box)
                                contextMenuContent()
                        }

                        if (!message.isOutgoing) {
                                Spacer(modifier = Modifier.weight(1f))
                                AnimatedVisibility(
                                        visible = isSelected,
                                        enter =
                                                fadeIn(tween(150)) +
                                                        scaleIn(
                                                                initialScale = 0.3f,
                                                                animationSpec =
                                                                        spring(dampingRatio = 0.6f)
                                                        ),
                                        exit = fadeOut(tween(100)) + scaleOut(targetScale = 0.3f)
                                ) {
                                        Box(
                                                modifier =
                                                        Modifier.padding(start = 4.dp, end = 12.dp)
                                                                .size(24.dp)
                                                                .clip(CircleShape)
                                                                .background(PrimaryBlue),
                                                contentAlignment = Alignment.Center
                                        ) {
                                                Icon(
                                                        painter = TelegramIcons.Done,
                                                        contentDescription = "Selected",
                                                        tint = Color.White,
                                                        modifier = Modifier.size(16.dp)
                                                )
                                        }
                                }
                        }
                }
        }
}

private val GROUP_INVITE_REGEX = Regex("^#group:[A-Za-z0-9+/=:]+$")

/**
 * Truncate text safely without breaking :emoji_xxx: codes.
 * If the truncation point falls inside an emoji code, move it to before that code.
 */
private fun truncateEmojiSafe(text: String, maxLen: Int): String {
    if (text.length <= maxLen) return text
    var cutAt = maxLen
    // Check if we're inside an :emoji_xxx: tag
    val lastColon = text.lastIndexOf(':', cutAt - 1)
    if (lastColon >= 0) {
        val sub = text.substring(lastColon)
        // If there's an opening :emoji_ but no closing : within our range, we're mid-tag
        if (sub.startsWith(":emoji_") && !sub.substring(1).contains(':')) {
            cutAt = lastColon
        }
    }
    return text.substring(0, cutAt).trimEnd() + "..."
}
private const val GROUP_ACTION_JOINED = "\$a=Group joined"
private const val GROUP_ACTION_CREATED = "\$a=Group created"

private fun resolveGroupActionSystemText(text: String): String? {
        return when (text.trim()) {
                GROUP_ACTION_JOINED -> "You joined the group"
                GROUP_ACTION_CREATED -> "Group created"
                else -> null
        }
}

@Composable
private fun GroupActionSystemMessage(text: String, isDarkTheme: Boolean, hasWallpaper: Boolean) {
        val messageColor = if (isDarkTheme || hasWallpaper) Color.White else Color(0xFF2E7D32)
        Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp),
                horizontalArrangement = Arrangement.Center,
                verticalAlignment = Alignment.CenterVertically
        ) {
                Text(
                        text = text,
                        color = messageColor,
                        fontSize = 12.sp,
                        fontWeight = FontWeight.SemiBold,
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis
                )
        }
}

private fun isGroupInviteCode(text: String): Boolean {
        val normalized = text.trim()
        if (!normalized.startsWith("#group:")) return false
        return GROUP_INVITE_REGEX.matches(normalized)
}

private fun groupSenderLabelColor(publicKey: String, isDarkTheme: Boolean): Color {
        // Match nickname color with avatar initials color.
        return com.rosetta.messenger.ui.chats.getAvatarColor(publicKey, isDarkTheme).textColor
}

@Composable
private fun GroupAdminBadge(isDarkTheme: Boolean) {
        var showInfo by remember { mutableStateOf(false) }
        val iconTint = Color(0xFFF6C445)
        val popupBackground = if (isDarkTheme) Color(0xFF2E2E31) else Color(0xFFF2F2F5)
        val popupTextColor = if (isDarkTheme) Color(0xFFE3E3E6) else Color(0xFF2B2B2F)

        Box {
                Icon(
                        painter = painterResource(id = R.drawable.ic_arrow_badge_down_filled),
                        contentDescription = "Admin",
                        tint = iconTint,
                        modifier =
                                Modifier.size(14.dp).clickable(
                                        indication = null,
                                        interactionSource =
                                                remember { MutableInteractionSource() }
                                ) { showInfo = true }
                )

                DropdownMenu(
                        expanded = showInfo,
                        onDismissRequest = { showInfo = false },
                        offset = DpOffset(x = 0.dp, y = 6.dp),
                        modifier =
                                Modifier.background(
                                        color = popupBackground,
                                        shape = RoundedCornerShape(14.dp)
                                )
                ) {
                        Text(
                                text = "This user is administrator of\nthis group.",
                                color = popupTextColor,
                                fontSize = 14.sp,
                                lineHeight = 20.sp,
                                modifier =
                                        Modifier.padding(
                                                horizontal = 14.dp,
                                                vertical = 10.dp
                                        )
                        )
                }
        }
}

@Composable
private fun GroupInviteInlineCard(
        inviteText: String,
        isOutgoing: Boolean,
        isDarkTheme: Boolean,
        groupRepository: GroupRepository,
        accountPublicKey: String,
        accountPrivateKey: String,
        actionsEnabled: Boolean,
        timestamp: Date,
        messageStatus: MessageStatus,
        onRetry: () -> Unit,
        onDelete: () -> Unit,
        onOpenGroup: (SearchUser) -> Unit
) {
        val context = LocalContext.current
        val scope = rememberCoroutineScope()

        val normalizedInvite = remember(inviteText) { inviteText.trim() }
        val parsedInvite = remember(normalizedInvite) { groupRepository.parseInviteString(normalizedInvite) }

        val cachedInfo = remember(normalizedInvite) { parsedInvite?.let { groupRepository.getCachedInviteInfo(it.groupId) } }
        var status by remember(normalizedInvite) { mutableStateOf(cachedInfo?.status ?: GroupStatus.NOT_JOINED) }
        var membersCount by remember(normalizedInvite) { mutableStateOf(cachedInfo?.membersCount ?: 0) }
        var statusLoading by remember(normalizedInvite) { mutableStateOf(cachedInfo == null) }
        var actionLoading by remember(normalizedInvite) { mutableStateOf(false) }

        LaunchedEffect(normalizedInvite, accountPublicKey) {
                if (parsedInvite == null) {
                        status = GroupStatus.INVALID
                        membersCount = 0
                        statusLoading = false
                        return@LaunchedEffect
                }

                if (cachedInfo == null) {
                        statusLoading = true
                }

                val localGroupExists =
                        withContext(Dispatchers.IO) {
                                if (accountPublicKey.isBlank()) {
                                        false
                                } else {
                                        groupRepository.getGroup(accountPublicKey, parsedInvite.groupId) != null
                                }
                        }

                val inviteInfo =
                        withContext(Dispatchers.IO) {
                                groupRepository.requestInviteInfo(parsedInvite.groupId)
                        }

                membersCount = inviteInfo?.membersCount ?: 0
                val newStatus = when {
                        localGroupExists -> GroupStatus.JOINED
                        inviteInfo != null -> inviteInfo.status
                        else -> GroupStatus.NOT_JOINED
                }
                status = newStatus
                groupRepository.cacheInviteInfo(parsedInvite.groupId, newStatus, membersCount)

                statusLoading = false
        }

        val title =
                parsedInvite?.title?.trim().takeUnless { it.isNullOrBlank() }
                        ?: "Group Invite"

        val subtitle =
                if (statusLoading) {
                        "Checking invite..."
                } else {
                        when (status) {
                                GroupStatus.NOT_JOINED ->
                                        if (membersCount > 0) {
                                                "$membersCount members • Invite to join this group"
                                        } else {
                                                "Invite to join this group"
                                        }
                                GroupStatus.JOINED ->
                                        if (membersCount > 0) {
                                                "$membersCount members • You are already a member"
                                        } else {
                                                "You are already a member of this group"
                                        }
                                GroupStatus.BANNED -> "You are banned in this group"
                                GroupStatus.INVALID -> "This group invite is invalid"
                        }
                }

        val subtitleColor =
                if (isOutgoing) Color.White.copy(alpha = 0.82f)
                else if (isDarkTheme) Color(0xFFA9AFBA)
                else Color(0xFF70757F)
        val timeColor =
                if (isOutgoing) Color.White.copy(alpha = 0.74f)
                else if (isDarkTheme) Color(0xFF8E8E93)
                else Color(0xFF666666)
        val statusColor = if (isOutgoing) Color.White else timeColor
        val timeFormat = remember { SimpleDateFormat("HH:mm", Locale.getDefault()) }

        val accentColor =
                when (status) {
                        GroupStatus.NOT_JOINED ->
                                if (isOutgoing) Color.White else Color(0xFF228BE6)
                        GroupStatus.JOINED ->
                                if (isOutgoing) Color(0xFFD9FAD6) else Color(0xFF34C759)
                        GroupStatus.BANNED, GroupStatus.INVALID ->
                                if (isOutgoing) Color(0xFFFFD7D7) else Color(0xFFFF3B30)
                }
        val actionLabel =
                when (status) {
                        GroupStatus.NOT_JOINED -> "Join Group"
                        GroupStatus.JOINED -> "Open Group"
                        GroupStatus.INVALID -> "Invalid"
                        GroupStatus.BANNED -> "Banned"
                }
        val actionIcon =
                when (status) {
                        GroupStatus.NOT_JOINED -> Icons.Default.PersonAdd
                        GroupStatus.JOINED -> Icons.Default.Check
                        GroupStatus.INVALID -> Icons.Default.Link
                        GroupStatus.BANNED -> Icons.Default.Block
                }
        val actionEnabled = actionsEnabled && !statusLoading && !actionLoading && status != GroupStatus.INVALID && status != GroupStatus.BANNED

        fun openParsedGroup() {
                val parsed = parsedInvite ?: return
                onOpenGroup(
                        SearchUser(
                                publicKey = groupRepository.toGroupDialogPublicKey(parsed.groupId),
                                title = parsed.title.ifBlank { "Group" },
                                username = "",
                                verified = 0,
                                online = 0
                        )
                )
        }

        fun handleAction() {
                if (!actionEnabled) return
                if (parsedInvite == null) return

                if (status == GroupStatus.JOINED) {
                        if (accountPublicKey.isBlank() || accountPrivateKey.isBlank()) {
                                openParsedGroup()
                                return
                        }

                        scope.launch {
                                actionLoading = true
                                val restoreResult =
                                        withContext(Dispatchers.IO) {
                                                groupRepository.ensureLocalGroupFromInvite(
                                                        accountPublicKey = accountPublicKey,
                                                        accountPrivateKey = accountPrivateKey,
                                                        inviteString = normalizedInvite
                                                )
                                        }
                                actionLoading = false

                                if (restoreResult.success) {
                                        status = GroupStatus.JOINED
                                        groupRepository.cacheInviteInfo(
                                                parsedInvite.groupId,
                                                GroupStatus.JOINED,
                                                membersCount
                                        )
                                        openParsedGroup()
                                } else {
                                        Toast.makeText(
                                                context,
                                                restoreResult.error ?: "Failed to restore group access",
                                                Toast.LENGTH_SHORT
                                        ).show()
                                }
                        }
                        return
                }

                if (accountPublicKey.isBlank() || accountPrivateKey.isBlank()) {
                        Toast.makeText(context, "Account is not ready", Toast.LENGTH_SHORT).show()
                        return
                }

                scope.launch {
                        actionLoading = true
                        val joinResult =
                                withContext(Dispatchers.IO) {
                                        groupRepository.joinGroup(
                                                accountPublicKey = accountPublicKey,
                                                accountPrivateKey = accountPrivateKey,
                                                inviteString = normalizedInvite
                                        )
                                }
                        actionLoading = false

                        if (joinResult.success) {
                                status = GroupStatus.JOINED
                                groupRepository.cacheInviteInfo(parsedInvite.groupId, GroupStatus.JOINED, membersCount)
                                openParsedGroup()
                        } else {
                                status = joinResult.status
                                val errorMessage =
                                        when (joinResult.status) {
                                                GroupStatus.BANNED -> "You are banned in this group"
                                                GroupStatus.INVALID -> "This invite is invalid"
                                                else -> joinResult.error ?: "Failed to join group"
                                        }
                                Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show()
                        }
                }
        }

        Column {
                // Icon + Title row
                Row(
                        verticalAlignment = Alignment.CenterVertically
                ) {
                        // Group icon circle
                        Box(
                                modifier = Modifier
                                        .size(40.dp)
                                        .clip(CircleShape)
                                        .background(
                                                if (isOutgoing) Color.White.copy(alpha = 0.15f)
                                                else accentColor.copy(alpha = 0.12f)
                                        ),
                                contentAlignment = Alignment.Center
                        ) {
                                Icon(
                                        imageVector = Icons.Default.Groups,
                                        contentDescription = null,
                                        tint = if (isOutgoing) Color.White else accentColor,
                                        modifier = Modifier.size(20.dp)
                                )
                        }

                        Spacer(modifier = Modifier.width(10.dp))

                        Column(modifier = Modifier.weight(1f)) {
                                Text(
                                        text = title,
                                        color = if (isOutgoing) Color.White else if (isDarkTheme) Color.White else Color(0xFF1A1A1A),
                                        fontSize = 15.sp,
                                        fontWeight = FontWeight.SemiBold,
                                        maxLines = 1,
                                        overflow = TextOverflow.Ellipsis
                                )
                                Spacer(modifier = Modifier.height(1.dp))
                                Text(
                                        text = subtitle,
                                        color = subtitleColor,
                                        fontSize = 12.sp,
                                        maxLines = 1,
                                        overflow = TextOverflow.Ellipsis
                                )
                        }
                }

                Spacer(modifier = Modifier.height(8.dp))

                // Action button (full width)
                Surface(
                        modifier = Modifier
                                .fillMaxWidth()
                                .clip(RoundedCornerShape(8.dp))
                                .clickable(
                                        enabled = actionEnabled,
                                        onClick = ::handleAction
                                ),
                        color = if (isOutgoing) Color.White.copy(alpha = 0.18f)
                                else accentColor.copy(alpha = 0.10f),
                        shape = RoundedCornerShape(8.dp)
                ) {
                        Row(
                                modifier = Modifier.padding(vertical = 7.dp),
                                horizontalArrangement = Arrangement.Center,
                                verticalAlignment = Alignment.CenterVertically
                        ) {
                                Spacer(modifier = Modifier.weight(1f))
                                if (actionLoading || statusLoading) {
                                        CircularProgressIndicator(
                                                modifier = Modifier.size(13.dp),
                                                strokeWidth = 1.5.dp,
                                                color = if (isOutgoing) Color.White else accentColor
                                        )
                                } else {
                                        Icon(
                                                imageVector = actionIcon,
                                                contentDescription = null,
                                                tint = if (isOutgoing) Color.White else accentColor,
                                                modifier = Modifier.size(14.dp)
                                        )
                                }
                                Spacer(modifier = Modifier.width(6.dp))
                                Text(
                                        text = actionLabel,
                                        color = if (isOutgoing) Color.White else accentColor,
                                        fontSize = 13.sp,
                                        fontWeight = FontWeight.SemiBold
                                )
                                Spacer(modifier = Modifier.weight(1f))
                        }
                }

                Spacer(modifier = Modifier.height(4.dp))

                // Time + status row
                Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.End,
                        verticalAlignment = Alignment.CenterVertically
                ) {
                        Text(
                                text = timeFormat.format(timestamp),
                                color = timeColor,
                                fontSize = 11.sp
                        )
                        if (isOutgoing) {
                                Spacer(modifier = Modifier.width(2.dp))
                                AnimatedMessageStatus(
                                        status = messageStatus,
                                        timeColor = statusColor,
                                        isDarkTheme = isDarkTheme,
                                        isOutgoing = true,
                                        timestamp = timestamp.time,
                                        onRetry = onRetry,
                                        onDelete = onDelete
                                )
                        }
                }
        }
}

@Composable
private fun SafeSystemMessageCard(text: String, timestamp: Date, isDarkTheme: Boolean) {
        val contentColor = if (isDarkTheme) Color(0xFFE8E9EE) else Color(0xFF1E1F23)
        val timeColor = if (isDarkTheme) Color(0xFFB3B7C0) else Color(0xFF737983)
        val annotatedText = remember(text) { buildSafeSystemAnnotatedText(text) }
        val timeFormat = remember { SimpleDateFormat("HH:mm", Locale.getDefault()) }

        Column(
                modifier =
                        Modifier.fillMaxWidth().padding(start = 14.dp, end = 14.dp, top = 12.dp, bottom = 8.dp)
        ) {
                Text(
                        text = annotatedText,
                        color = contentColor,
                        fontSize = 16.sp,
                        lineHeight = 20.sp
                )
                Spacer(modifier = Modifier.height(6.dp))
                Text(
                        text = timeFormat.format(timestamp),
                        color = timeColor,
                        fontSize = 11.sp,
                        modifier = Modifier.align(Alignment.End)
                )
        }
}

private fun buildSafeSystemAnnotatedText(text: String) = buildAnnotatedString {
        val boldStyle = SpanStyle(fontWeight = FontWeight.SemiBold)
        val lines = text.lines()

        lines.forEachIndexed { index, line ->
                when {
                        index == 0 && line.isNotBlank() -> {
                                withStyle(boldStyle) { append(line) }
                        }
                        line.startsWith("We detected a login to your account from ") -> {
                                val prefix = "We detected a login to your account from "
                                val marker = " a new device by seed phrase"
                                val tail = line.removePrefix(prefix)
                                val markerIndex = tail.indexOf(marker)

                                if (markerIndex > 0) {
                                        val ip = tail.substring(0, markerIndex)
                                        append(prefix)
                                        withStyle(boldStyle) { append(ip) }
                                        append(" a new device ")
                                        withStyle(boldStyle) { append("by seed phrase") }
                                        append(tail.substring(markerIndex + marker.length))
                                } else {
                                        append(line)
                                }
                        }
                        line.startsWith("Arch:") ||
                                line.startsWith("IP:") ||
                                line.startsWith("Device:") ||
                                line.startsWith("ID:") -> {
                                val separatorIndex = line.indexOf(':')
                                if (separatorIndex > 0) {
                                        withStyle(boldStyle) { append(line.substring(0, separatorIndex + 1)) }
                                        if (separatorIndex + 1 < line.length) {
                                                append(line.substring(separatorIndex + 1))
                                        }
                                } else {
                                        append(line)
                                }
                        }
                        else -> append(line)
                }

                if (index < lines.lastIndex) append('\n')
        }
}

/** Animated message status indicator */
@Composable
fun AnimatedMessageStatus(
        status: MessageStatus,
        timeColor: Color,
        isDarkTheme: Boolean = false,
        isOutgoing: Boolean = false,
        timestamp: Long = 0L,
        onRetry: () -> Unit = {},
        onDelete: () -> Unit = {}
) {
        // Force recomposition when SENDING message exceeds 80s timeout
        var timeoutTick by remember { mutableLongStateOf(0L) }
        if (status == MessageStatus.SENDING && timestamp > 0) {
                val remainingMs = (timestamp + 80_000L) - System.currentTimeMillis()
                if (remainingMs > 0) {
                        LaunchedEffect(timestamp) {
                                kotlinx.coroutines.delay(remainingMs + 100)
                                timeoutTick++
                        }
                }
        }
        @Suppress("UNUSED_EXPRESSION") timeoutTick // read to subscribe

        val isTimedOut =
                status == MessageStatus.SENDING &&
                        timestamp > 0 &&
                        !isMessageDeliveredByTime(timestamp)
        val effectiveStatus = if (isTimedOut) MessageStatus.ERROR else status

        val targetColor =
                when (effectiveStatus) {
                        MessageStatus.READ -> if (isDarkTheme || isOutgoing) Color.White else Color(0xFF4FC3F7)
                        MessageStatus.ERROR -> Color(0xFFE53935)
                        else -> timeColor
                }

        val animatedColor by
                animateColorAsState(
                        targetValue = targetColor,
                        animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing),
                        label = "statusColor"
                )

        var previousStatus by remember { mutableStateOf(effectiveStatus) }
        var shouldAnimate by remember { mutableStateOf(false) }

        LaunchedEffect(effectiveStatus) {
                if (previousStatus != effectiveStatus) {
                        shouldAnimate = true
                        previousStatus = effectiveStatus
                }
        }

        val scale by
                animateFloatAsState(
                        targetValue = if (shouldAnimate) 1.2f else 1f,
                        animationSpec =
                                spring(
                                        dampingRatio = Spring.DampingRatioMediumBouncy,
                                        stiffness = Spring.StiffnessLow
                                ),
                        finishedListener = { shouldAnimate = false },
                        label = "statusScale"
                )

        var showErrorMenu by remember { mutableStateOf(false) }
        val iconSize = with(LocalDensity.current) { 14.sp.toDp() }
        val statusSlotWidth = iconSize + 6.dp

        Box(
                modifier = Modifier.width(statusSlotWidth).height(iconSize),
                contentAlignment = Alignment.CenterStart
        ) {
                Crossfade(
                        targetState = effectiveStatus,
                        animationSpec = tween(durationMillis = 200),
                        label = "statusIcon"
                ) { currentStatus ->
                        if (currentStatus == MessageStatus.ERROR) {
                                // Telegram-style: red filled circle with white "!" inside
                                Box(
                                        modifier =
                                                Modifier.padding(start = 4.dp)
                                                        .size(iconSize)
                                                        .align(Alignment.CenterStart)
                                                        .scale(scale)
                                                        .background(
                                                                color = Color(0xFFE53935),
                                                                shape = CircleShape
                                                        )
                                                        .clickable {
                                                                showErrorMenu = true
                                                        },
                                        contentAlignment = Alignment.Center
                                ) {
                                        Text(
                                                text = "!",
                                                color = Color.White,
                                                fontSize = 10.sp,
                                                fontWeight = androidx.compose.ui.text.font.FontWeight.Bold,
                                                lineHeight = 10.sp
                                        )
                                }
                        } else {
                                if (currentStatus == MessageStatus.READ) {
                                        Box(
                                                modifier =
                                                        Modifier.width(statusSlotWidth)
                                                                .height(iconSize)
                                                                .scale(scale)
                                        ) {
                                                Icon(
                                                        painter = TelegramIcons.Done,
                                                        contentDescription = null,
                                                        tint = animatedColor,
                                                        modifier =
                                                                Modifier.size(iconSize)
                                                                        .align(Alignment.CenterStart)
                                                )
                                                Icon(
                                                        painter = TelegramIcons.Done,
                                                        contentDescription = null,
                                                        tint = animatedColor,
                                                        modifier =
                                                                Modifier.size(iconSize)
                                                                        .align(Alignment.CenterStart)
                                                                        .offset(x = 4.dp)
                                                )
                                        }
                                } else {
                                        Icon(
                                                        painter =
                                                        when (currentStatus) {
                                                                MessageStatus.SENDING ->
                                                                        TelegramIcons.Clock
                                                                MessageStatus.SENT ->
                                                                        TelegramIcons.Clock
                                                                MessageStatus.DELIVERED ->
                                                                        TelegramIcons.Done
                                                                else -> TelegramIcons.Clock
                                                        },
                                                contentDescription = null,
                                                tint = animatedColor,
                                                modifier = Modifier.size(iconSize).scale(scale)
                                        )
                                }
                        }
                }

                val menuBgColor = if (isDarkTheme) Color(0xFF272829) else Color.White
                val menuTextColor = if (isDarkTheme) Color.White else Color(0xFF222222)
                val menuIconColor = if (isDarkTheme) Color.White.copy(alpha = 0.47f) else Color(0xFF676B70)

                MaterialTheme(
                        colorScheme = MaterialTheme.colorScheme.copy(
                                surface = menuBgColor,
                                onSurface = menuTextColor
                        )
                ) {
                        DropdownMenu(
                                expanded = showErrorMenu,
                                onDismissRequest = { showErrorMenu = false },
                                modifier = Modifier
                                        .defaultMinSize(minWidth = 196.dp)
                                        .background(menuBgColor)
                        ) {
                                Box(
                                        modifier = Modifier
                                                .fillMaxWidth()
                                                .defaultMinSize(minHeight = 48.dp)
                                                .clickable {
                                                        showErrorMenu = false
                                                        onRetry()
                                                }
                                                .padding(horizontal = 18.dp),
                                        contentAlignment = Alignment.CenterStart
                                ) {
                                        Row(verticalAlignment = Alignment.CenterVertically) {
                                                Icon(
                                                        painter = TelegramIcons.Retry,
                                                        contentDescription = null,
                                                        tint = menuIconColor,
                                                        modifier = Modifier.size(24.dp)
                                                )
                                                Spacer(modifier = Modifier.width(19.dp))
                                                Text(
                                                        text = "Retry",
                                                        color = menuTextColor,
                                                        fontSize = 16.sp
                                                )
                                        }
                                }
                                Box(
                                        modifier = Modifier
                                                .fillMaxWidth()
                                                .defaultMinSize(minHeight = 48.dp)
                                                .clickable {
                                                        showErrorMenu = false
                                                        onDelete()
                                                }
                                                .padding(horizontal = 18.dp),
                                        contentAlignment = Alignment.CenterStart
                                ) {
                                        Row(verticalAlignment = Alignment.CenterVertically) {
                                                Icon(
                                                        painter = TelegramIcons.Delete,
                                                        contentDescription = null,
                                                        tint = Color(0xFFFF3B30),
                                                        modifier = Modifier.size(24.dp)
                                                )
                                                Spacer(modifier = Modifier.width(19.dp))
                                                Text(
                                                        text = "Delete",
                                                        color = Color(0xFFFF3B30),
                                                        fontSize = 16.sp
                                                )
                                        }
                                }
                        }
                }
        }
}

/** Reply bubble inside message - Telegram style with image preview */
@Composable
fun ReplyBubble(
        replyData: ReplyData,
        isOutgoing: Boolean,
        isDarkTheme: Boolean,
        chachaKey: String = "",
        chachaKeyPlainHex: String = "",
        privateKey: String = "",
        onClick: () -> Unit = {},
        onImageClick: (attachmentId: String, bounds: ImageSourceBounds?) -> Unit = { _, _ -> },
        onForwardedSenderClick: (senderPublicKey: String) -> Unit = {}
) {
        val context = androidx.compose.ui.platform.LocalContext.current
        val backgroundColor =
                if (isOutgoing) {
                        Color.Black.copy(alpha = 0.15f)
                } else {
                        Color.Black.copy(alpha = 0.08f)
                }

        val borderColor = if (isOutgoing) Color.White else PrimaryBlue
        val nameColor = if (isOutgoing) Color.White else PrimaryBlue
        val replyTextColor =
                if (isOutgoing) {
                        Color.White.copy(alpha = 0.85f)
                } else {
                        if (isDarkTheme) Color.White else Color.Black
                }

        // 🖼️ Проверяем есть ли изображение в attachments
        val imageAttachment = replyData.attachments.firstOrNull { it.type == AttachmentType.IMAGE }
        val hasImage = imageAttachment != null

        // Загружаем полноценную картинку вместо blur preview
        var imageBitmap by remember { mutableStateOf<Bitmap?>(null) }
        // Blurhash preview для fallback
        var blurPreviewBitmap by remember { mutableStateOf<Bitmap?>(null) }

        // Сначала загружаем blurhash preview (более высокое разрешение для чёткости)
        LaunchedEffect(imageAttachment?.preview) {
                if (imageAttachment != null && imageAttachment.preview.isNotEmpty()) {
                        withContext(Dispatchers.IO) {
                                try {
                                        val blurhash =
                                                if (imageAttachment.preview.contains("::")) {
                                                        imageAttachment.preview.substringAfter("::")
                                                } else if (!imageAttachment.preview.startsWith(
                                                                "http"
                                                        ) && imageAttachment.preview.length < 50
                                                ) {
                                                        imageAttachment.preview
                                                } else {
                                                        ""
                                                }
                                        if (blurhash.isNotEmpty()) {
                                                blurPreviewBitmap =
                                                        BlurHash.decode(blurhash, 64, 64)
                                        }
                                } catch (e: Exception) {}
                        }
                }
        }

        // Загружаем полноценную картинку
        LaunchedEffect(imageAttachment?.id) {
                if (imageAttachment != null) {
                        // 1. Проверяем in-memory кэш (фото уже загружено в чате)
                        val cached = ImageBitmapCache.get("img_${imageAttachment.id}")
                        if (cached != null) {
                                imageBitmap = cached
                                return@LaunchedEffect
                        }

                        withContext(Dispatchers.IO) {
                                try {
                                        // 2. Загружаем из localUri (для недавно отправленных/полученных фото)
                                        if (imageAttachment.localUri.isNotEmpty()) {
                                                try {
                                                        val uri = android.net.Uri.parse(imageAttachment.localUri)
                                                        context.contentResolver.openInputStream(uri)?.use { stream ->
                                                                val bitmap = BitmapFactory.decodeStream(stream)
                                                                if (bitmap != null) {
                                                                        imageBitmap = bitmap
                                                                        return@withContext
                                                                }
                                                        }
                                                } catch (_: Exception) {}
                                        }

                                        // 3. Пробуем из blob
                                        if (imageAttachment.blob.isNotEmpty()) {
                                                val decoded = base64ToBitmap(imageAttachment.blob)
                                                if (decoded != null) {
                                                        imageBitmap = decoded
                                                        return@withContext
                                                }
                                        }

                                        // 4. Загружаем из AttachmentFileManager
                                        val localBlob =
                                                AttachmentFileManager.readAttachment(
                                                        context,
                                                        imageAttachment.id,
                                                        replyData.senderPublicKey,
                                                        replyData.recipientPrivateKey
                                                )
                                        if (localBlob != null) {
                                                imageBitmap = base64ToBitmap(localBlob)
                                                if (imageBitmap != null) return@withContext
                                        }
                                } catch (e: Exception) {}
                        }

                        // 5. Ждём пока другой composable загрузит фото в кэш
                        if (imageBitmap == null) {
                                val awaited = ImageBitmapCache.awaitCached("img_${imageAttachment.id}")
                                if (awaited != null) {
                                        imageBitmap = awaited
                                        return@LaunchedEffect
                                }
                        }

                        // 6. CDN download — для форвардов, где фото загружено на CDN
                        if (imageBitmap == null && imageAttachment.preview.isNotEmpty()) {
                                val downloadTag = getDownloadTag(imageAttachment.preview)
                                val effectiveChachaKey = replyData.chachaKey.ifEmpty { chachaKey }
                                val bitmap = downloadAndDecryptImage(
                                        attachmentId = imageAttachment.id,
                                        downloadTag = downloadTag,
                                        chachaKey = effectiveChachaKey,
                                        privateKey = privateKey,
                                        cacheKey = "img_${imageAttachment.id}",
                                        context = context,
                                        senderPublicKey = replyData.senderPublicKey,
                                        recipientPrivateKey = replyData.recipientPrivateKey,
                                        chachaKeyPlainHex = replyData.chachaKeyPlainHex.ifEmpty {
                                                chachaKeyPlainHex
                                        }
                                )
                                if (bitmap != null) imageBitmap = bitmap
                        }
                }
        }

        Row(
                modifier =
                        Modifier.fillMaxWidth()
                                .height(IntrinsicSize.Min)
                                .clip(RoundedCornerShape(4.dp))
                                .clickable(onClick = onClick)
                                .background(backgroundColor)
        ) {
                // Вертикальная полоска слева
                Box(modifier = Modifier.width(3.dp).fillMaxHeight().background(borderColor))

                // Контент reply
                Column(
                        modifier =
                                Modifier.weight(1f)
                                        .padding(
                                                start = 8.dp,
                                                end = 10.dp,
                                                top = 4.dp,
                                                bottom = 4.dp
                                        )
                ) {
                        // Заголовок (имя отправителя / Forwarded from)
                        if (replyData.isForwarded && replyData.forwardedFromName.isNotEmpty()) {
                                Row(
                                        verticalAlignment = Alignment.CenterVertically,
                                        modifier = Modifier.clickable(enabled = replyData.senderPublicKey.isNotEmpty()) {
                                                onForwardedSenderClick(replyData.senderPublicKey)
                                        }
                                ) {
                                        Text(
                                                text = "Forwarded from ",
                                                color = nameColor,
                                                fontSize = 14.sp,
                                                fontWeight = FontWeight.Normal,
                                                maxLines = 1
                                        )
                                        Text(
                                                text = replyData.forwardedFromName,
                                                color = nameColor,
                                                fontSize = 14.sp,
                                                fontWeight = FontWeight.Bold,
                                                maxLines = 1,
                                                overflow = TextOverflow.Ellipsis
                                        )
                                }
                        } else {
                                Text(
                                        text =
                                                if (replyData.isForwarded) "Forwarded message"
                                                else replyData.senderName,
                                        color = nameColor,
                                        fontSize = 14.sp,
                                        fontWeight = FontWeight.Medium,
                                        maxLines = 1,
                                        overflow = TextOverflow.Ellipsis
                                )
                        }

                        // Текст сообщения
                        if (replyData.text.isNotEmpty()) {
                                AppleEmojiText(
                                        text = truncateEmojiSafe(replyData.text, 100),
                                        color = replyTextColor,
                                        fontSize = 14.sp,
                                        maxLines = 2,
                                        overflow = android.text.TextUtils.TruncateAt.END,
                                        enableLinks = false
                                )
                        } else if (!hasImage) {
                                val displayText = when {
                                        replyData.attachments.any { it.type == AttachmentType.VOICE } -> "Voice Message"
                                        replyData.attachments.any { it.type == AttachmentType.VIDEO_CIRCLE } -> "Video Message"
                                        replyData.attachments.any { it.type == AttachmentType.FILE } -> "File"
                                        replyData.attachments.any { it.type == AttachmentType.CALL } -> "Call"
                                        else -> "..."
                                }
                                AppleEmojiText(
                                        text = displayText,
                                        color = replyTextColor,
                                        fontSize = 14.sp,
                                        maxLines = 1,
                                        overflow = android.text.TextUtils.TruncateAt.END,
                                        enableLinks = false
                                )
                        }

                        // 🖼️ Большое фото внизу пузырька
                        if (hasImage) {
                                Spacer(modifier = Modifier.height(4.dp))

                                // Aspect ratio из attachment
                                val imgWidth = imageAttachment?.width ?: 0
                                val imgHeight = imageAttachment?.height ?: 0
                                val aspectRatio = if (imgWidth > 0 && imgHeight > 0) {
                                        imgWidth.toFloat() / imgHeight.toFloat()
                                } else {
                                        1.5f
                                }

                                var photoBoxBounds by remember { mutableStateOf<ImageSourceBounds?>(null) }

                                Box(
                                        modifier =
                                                Modifier.fillMaxWidth()
                                                        .aspectRatio(aspectRatio.coerceIn(0.5f, 2.5f))
                                                        .heightIn(max = 180.dp)
                                                        .clip(RoundedCornerShape(6.dp))
                                                        .background(Color.Gray.copy(alpha = 0.2f))
                                                        .onGloballyPositioned { coords ->
                                                                val rect = coords.boundsInWindow()
                                                                photoBoxBounds = ImageSourceBounds(
                                                                        left = rect.left,
                                                                        top = rect.top,
                                                                        width = rect.width,
                                                                        height = rect.height,
                                                                        cornerRadius = 6f,
                                                                        thumbnailBitmap = imageBitmap ?: blurPreviewBitmap
                                                                )
                                                        }
                                                        .clickable {
                                                                if (imageAttachment != null) {
                                                                        onImageClick(imageAttachment.id, photoBoxBounds)
                                                                }
                                                        }
                                ) {
                                        if (imageBitmap != null) {
                                                Image(
                                                        bitmap = imageBitmap!!.asImageBitmap(),
                                                        contentDescription = "Photo",
                                                        modifier = Modifier.fillMaxSize(),
                                                        contentScale = ContentScale.Crop
                                                )
                                        } else if (blurPreviewBitmap != null) {
                                                Image(
                                                        bitmap = blurPreviewBitmap!!.asImageBitmap(),
                                                        contentDescription = "Photo",
                                                        modifier = Modifier.fillMaxSize(),
                                                        contentScale = ContentScale.Crop
                                                )
                                        } else {
                                                Box(
                                                        modifier = Modifier.fillMaxSize(),
                                                        contentAlignment = Alignment.Center
                                                ) {
                                                        Icon(
                                                                painter = TelegramIcons.Photos,
                                                                contentDescription = null,
                                                                tint = Color.White.copy(alpha = 0.7f),
                                                                modifier = Modifier.size(28.dp)
                                                        )
                                                }
                                        }
                                }
                        }
                }
        }
}

/** Forwarded messages bubble — renders ALL forwarded messages like Desktop (Telegram-style) */
@Composable
fun ForwardedMessagesBubble(
        forwardedMessages: List<ReplyData>,
        isOutgoing: Boolean,
        isDarkTheme: Boolean,
        chachaKey: String = "",
        chachaKeyPlainHex: String = "",
        privateKey: String = "",
        dialogPublicKey: String = "",
        currentUserPublicKey: String = "",
        senderDisplayName: String = "",
        timestamp: Date = Date(),
        messageStatus: MessageStatus = MessageStatus.READ,
        linksEnabled: Boolean = true,
        onImageClick: (attachmentId: String, bounds: ImageSourceBounds?) -> Unit = { _, _ -> },
        onForwardedSenderClick: (senderPublicKey: String) -> Unit = {},
        onMentionClick: (username: String) -> Unit = {},
        onTextSpanPressStart: (() -> Unit)? = null,
        onVoiceWaveGestureActiveChanged: (Boolean) -> Unit = {}
) {
        val configuration = androidx.compose.ui.platform.LocalConfiguration.current
        val backgroundColor =
                if (isOutgoing) Color.Black.copy(alpha = 0.1f)
                else Color.Black.copy(alpha = 0.05f)
        val borderColor = if (isOutgoing) Color.White.copy(alpha = 0.6f) else PrimaryBlue

        // Color palette for different senders
        val senderColors = listOf(
                Color(0xFF3390EC), // blue
                Color(0xFF4CAF50), // green
                Color(0xFFE65100), // orange
                Color(0xFF9C27B0), // purple
                Color(0xFFE53935), // red
                Color(0xFF00ACC1), // cyan
        )

        // Map sender names to consistent colors
        val senderColorMap = remember(forwardedMessages) {
                val uniqueSenders = forwardedMessages.map { it.forwardedFromName }.distinct()
                uniqueSenders.mapIndexed { index, name ->
                        name to senderColors[index % senderColors.size]
                }.toMap()
        }

        val maxBubbleWidth = (configuration.screenWidthDp * 0.65f).dp

        Column(
                modifier = Modifier
                        .widthIn(max = maxBubbleWidth)
                        .clip(RoundedCornerShape(4.dp))
                        .background(backgroundColor)
        ) {
                Row(modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min)) {
                        // Single vertical border line for entire block
                        Box(modifier = Modifier.width(3.dp).fillMaxHeight().background(borderColor))

                        Column(
                                modifier = Modifier
                                        .weight(1f)
                                        .padding(start = 8.dp, end = 8.dp, top = 4.dp, bottom = 4.dp)
                        ) {
                                forwardedMessages.forEachIndexed { index, fwd ->
                                        if (index > 0) {
                                                Spacer(modifier = Modifier.height(6.dp))
                                                Divider(
                                                        color = if (isDarkTheme) Color.White.copy(alpha = 0.1f)
                                                                else Color.Black.copy(alpha = 0.08f),
                                                        thickness = 0.5.dp
                                                )
                                                Spacer(modifier = Modifier.height(6.dp))
                                        }

                                        val nameColor = if (isOutgoing) {
                                                Color.White
                                        } else {
                                                senderColorMap[fwd.forwardedFromName] ?: PrimaryBlue
                                        }

                                        // "Forwarded from [Name]" — clickable to open profile
                                        Text(
                                                text = buildAnnotatedString {
                                                        withStyle(SpanStyle(
                                                                color = nameColor.copy(alpha = 0.7f),
                                                                fontWeight = FontWeight.Normal
                                                        )) { append("Forwarded from ") }
                                                        withStyle(SpanStyle(
                                                                color = nameColor,
                                                                fontWeight = FontWeight.Bold
                                                        )) { append(fwd.forwardedFromName.ifEmpty { "User" }) }
                                                },
                                                fontSize = 13.sp,
                                                maxLines = 1,
                                                overflow = TextOverflow.Ellipsis,
                                                modifier = Modifier.clickable(enabled = fwd.senderPublicKey.isNotEmpty()) {
                                                        onForwardedSenderClick(fwd.senderPublicKey)
                                                }
                                        )

                                        // Attachments (images) — before text (text acts as caption)
                                        val imageAttachments = fwd.attachments.filter { it.type == AttachmentType.IMAGE }
                                        if (imageAttachments.isNotEmpty()) {
                                                Spacer(modifier = Modifier.height(4.dp))
                                                imageAttachments.forEach { imgAtt ->
                                                        ForwardedImagePreview(
                                                                attachment = imgAtt,
                                                                senderPublicKey = fwd.senderPublicKey,
                                                                recipientPrivateKey = fwd.recipientPrivateKey,
                                                                chachaKey = fwd.chachaKey.ifEmpty { chachaKey },
                                                                privateKey = privateKey,
                                                                onImageClick = onImageClick,
                                                                chachaKeyPlainHex = fwd.chachaKeyPlainHex
                                                        )
                                                }
                                        }

                                        val nonImageAttachments =
                                                fwd.attachments.filter {
                                                        it.type != AttachmentType.IMAGE &&
                                                                it.type != AttachmentType.MESSAGES
                                                }
                                        if (nonImageAttachments.isNotEmpty()) {
                                                Spacer(modifier = Modifier.height(4.dp))
                                                MessageAttachments(
                                                        attachments = nonImageAttachments,
                                                        chachaKey = fwd.chachaKey.ifEmpty { chachaKey },
                                                        chachaKeyPlainHex =
                                                                fwd.chachaKeyPlainHex.ifEmpty {
                                                                        chachaKeyPlainHex
                                                                },
                                                        privateKey = fwd.recipientPrivateKey.ifEmpty { privateKey },
                                                        isOutgoing = isOutgoing,
                                                        isDarkTheme = isDarkTheme,
                                                        senderPublicKey = fwd.senderPublicKey,
                                                        senderDisplayName =
                                                                fwd.forwardedFromName.ifEmpty {
                                                                        senderDisplayName
                                                                },
                                                        dialogPublicKey = dialogPublicKey,
                                                        timestamp = timestamp,
                                                        messageStatus = messageStatus,
                                                        currentUserPublicKey = currentUserPublicKey,
                                                        onVoiceWaveGestureActiveChanged =
                                                                onVoiceWaveGestureActiveChanged
                                                )
                                        }

                                        // Message text (below image, like a caption)
                                        if (fwd.text.isNotEmpty()) {
                                                Spacer(modifier = Modifier.height(2.dp))
                                                val textColor = if (isOutgoing) Color.White.copy(alpha = 0.9f)
                                                        else if (isDarkTheme) Color.White else Color.Black
                                                AppleEmojiText(
                                                        text = fwd.text,
                                                        color = textColor,
                                                        fontSize = 14.sp,
                                                        maxLines = 50,
                                                        overflow = android.text.TextUtils.TruncateAt.END,
                                                        enableLinks = linksEnabled,
                                                        enableMentions = true,
                                                        onMentionClick = if (linksEnabled) onMentionClick else null,
                                                        onClickableSpanPressStart = onTextSpanPressStart
                                                )
                                        }
                                }
                        }
                }
        }
}

/** Image preview inside a forwarded message — decrypts exactly like desktop */
@Composable
private fun ForwardedImagePreview(
        attachment: MessageAttachment,
        senderPublicKey: String,
        recipientPrivateKey: String,
        chachaKey: String,
        privateKey: String,
        onImageClick: (attachmentId: String, bounds: ImageSourceBounds?) -> Unit,
        chachaKeyPlainHex: String = ""
) {
        val context = androidx.compose.ui.platform.LocalContext.current
        val configuration = androidx.compose.ui.platform.LocalConfiguration.current
        val cacheKey = "img_${attachment.id}"

        var imageBitmap by remember(attachment.id) {
                mutableStateOf(ImageBitmapCache.get(cacheKey))
        }
        var blurPreviewBitmap by remember(attachment.id) { mutableStateOf<android.graphics.Bitmap?>(null) }

        // Extract CDN tag and blurhash from preview (format: "UUID::blurhash")
        val downloadTag = remember(attachment.preview) { getDownloadTag(attachment.preview) }
        val blurhashPreview = remember(attachment.preview) { getPreview(attachment.preview) }

        // Load blur preview
        LaunchedEffect(attachment.id) {
                if (blurhashPreview.isNotEmpty()) {
                        withContext(Dispatchers.IO) {
                                try {
                                        val bitmap = BlurHash.decode(blurhashPreview, 32, 32)
                                        if (bitmap != null) blurPreviewBitmap = bitmap
                                } catch (_: Exception) {}
                        }
                }
        }

        // Main image loading — Desktop pipeline:
        // 1. decryptKeyFromSender(chachaKey, privateKey) → plainKeyAndNonce
        // 2. password = plainKeyAndNonce.toString('utf-8')  (bytesToJsUtf8String)
        // 3. decodeWithPassword(password, encryptedBlob) = PBKDF2 + AES-CBC + inflate
        LaunchedEffect(attachment.id) {
                // Skip if already loaded
                val cached = ImageBitmapCache.get(cacheKey)
                if (cached != null) { imageBitmap = cached; return@LaunchedEffect }

                // 🚀 Optimistic forward: если есть localUri, показываем сразу локальный файл
                if (attachment.localUri.isNotEmpty()) {
                        val localBitmap =
                                withContext(Dispatchers.IO) {
                                        runCatching {
                                                context.contentResolver
                                                        .openInputStream(
                                                                android.net.Uri.parse(
                                                                        attachment.localUri
                                                                )
                                                        )
                                                        ?.use { input ->
                                                                BitmapFactory.decodeStream(input)
                                                        }
                                        }.getOrNull()
                                }
                        if (localBitmap != null) {
                                imageBitmap = localBitmap
                                ImageBitmapCache.put(cacheKey, localBitmap)
                                return@LaunchedEffect
                        }
                }

                withContext(Dispatchers.IO) {
                        // Try local file cache first
                        try {
                                val localBlob = AttachmentFileManager.readAttachment(
                                        context, attachment.id, senderPublicKey, recipientPrivateKey
                                )
                                if (localBlob != null) {
                                        val bitmap = base64ToBitmap(localBlob)
                                        if (bitmap != null) {
                                                imageBitmap = bitmap
                                                ImageBitmapCache.put(cacheKey, bitmap)
                                                return@withContext
                                        }
                                }
                        } catch (_: Exception) {}
                }

                // CDN download — exactly like desktop useAttachment.ts
                if (imageBitmap == null) {
                        val bitmap = downloadAndDecryptImage(
                                attachmentId = attachment.id,
                                downloadTag = downloadTag,
                                chachaKey = chachaKey,
                                privateKey = privateKey,
                                cacheKey = cacheKey,
                                context = context,
                                senderPublicKey = senderPublicKey,
                                recipientPrivateKey = recipientPrivateKey,
                                chachaKeyPlainHex = chachaKeyPlainHex
                        )
                        if (bitmap != null) imageBitmap = bitmap
                }

                // Ждём пока другой composable загрузит фото в кэш
                if (imageBitmap == null) {
                        ImageBitmapCache.awaitCached(cacheKey)?.let { imageBitmap = it; return@LaunchedEffect
                        }
                }
        }

        // Forwarded images use smaller max size to fit inside the forward bubble
        val maxPhotoWidthDp = (configuration.screenWidthDp * 0.55f).toInt().dp
        val minPhotoWidthDp = 100.dp
        val minPhotoHeightDp = 80.dp
        val maxPhotoHeightDp = 280.dp
        val (previewWidth, previewHeight) =
                remember(
                        attachment.width,
                        attachment.height,
                        configuration.screenWidthDp
                ) {
                        val actualWidth = attachment.width.takeIf { it > 0 } ?: 0
                        val actualHeight = attachment.height.takeIf { it > 0 } ?: 0

                        if (actualWidth > 0 && actualHeight > 0) {
                                val ar = actualWidth.toFloat() / actualHeight.toFloat()
                                var w = if (ar >= 1f) {
                                        maxPhotoWidthDp.value
                                } else {
                                        maxPhotoWidthDp.value * 0.75f
                                }
                                var h = w / ar

                                if (h > maxPhotoHeightDp.value) {
                                        h = maxPhotoHeightDp.value
                                        w = h * ar
                                }
                                if (h < minPhotoHeightDp.value) {
                                        h = minPhotoHeightDp.value
                                        w = h * ar
                                }

                                w = w.coerceIn(minPhotoWidthDp.value, maxPhotoWidthDp.value)
                                h = h.coerceIn(minPhotoHeightDp.value, maxPhotoHeightDp.value)
                                w.dp to h.dp
                        } else {
                                val fallbackWidth = maxPhotoWidthDp
                                val fallbackHeight = (maxPhotoWidthDp.value * 0.75f)
                                        .coerceIn(minPhotoHeightDp.value, maxPhotoHeightDp.value)
                                        .dp
                                fallbackWidth to fallbackHeight
                        }
                }

        var photoBoxBounds by remember { mutableStateOf<ImageSourceBounds?>(null) }

        Box(
                modifier = Modifier
                        .width(previewWidth)
                        .height(previewHeight)
                        .clip(RoundedCornerShape(6.dp))
                        .background(Color.Gray.copy(alpha = 0.2f))
                        .onGloballyPositioned { coords ->
                                val bounds = coords.boundsInWindow()
                                photoBoxBounds = ImageSourceBounds(
                                        left = bounds.left, top = bounds.top,
                                        width = bounds.width, height = bounds.height
                                )
                        }
                        .clickable { onImageClick(attachment.id, photoBoxBounds) }
        ) {
                if (imageBitmap != null) {
                        Image(
                                bitmap = imageBitmap!!.asImageBitmap(),
                                contentDescription = "Photo",
                                modifier = Modifier.fillMaxSize(),
                                contentScale = ContentScale.Crop
                        )
                } else if (blurPreviewBitmap != null) {
                        Image(
                                bitmap = blurPreviewBitmap!!.asImageBitmap(),
                                contentDescription = "Photo",
                                modifier = Modifier.fillMaxSize(),
                                contentScale = ContentScale.Crop
                        )
                } else {
                        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                                Icon(
                                        painter = TelegramIcons.Photos,
                                        contentDescription = null,
                                        tint = Color.White.copy(alpha = 0.7f),
                                        modifier = Modifier.size(28.dp)
                                )
                        }
                }
        }
}

/** Message skeleton loader with shimmer animation */
@Composable
fun MessageSkeletonList(
        isDarkTheme: Boolean,
        isGroupChat: Boolean,
        modifier: Modifier = Modifier
) {
        val transition = rememberInfiniteTransition(label = "telegramSkeleton")
        val shimmerProgress by
                transition.animateFloat(
                        initialValue = -1f,
                        targetValue = 2f,
                        animationSpec =
                                infiniteRepeatable(
                                        animation = tween(1300, easing = LinearEasing),
                                        repeatMode = RepeatMode.Restart
                                ),
                        label = "telegramSkeletonProgress"
                )

        // Deterministic pseudo-random widths (fraction of bubbleMaxWidth)
        val widthRandom = remember {
                floatArrayOf(0.55f, 0.72f, 0.38f, 0.65f, 0.48f, 0.80f, 0.42f, 0.68f,
                        0.35f, 0.75f, 0.58f, 0.44f, 0.70f, 0.52f, 0.82f, 0.40f,
                        0.63f, 0.77f, 0.46f, 0.60f, 0.34f, 0.71f, 0.50f, 0.66f)
        }
        // Realistic message bubble heights in dp (single-line to ~3-line)
        val heightRandom = remember {
                intArrayOf(44, 52, 40, 60, 36, 56, 44, 48, 64, 40, 52, 44,
                        36, 68, 52, 44, 60, 36, 48, 56, 40, 64, 44, 52)
        }
        // false = incoming (left), true = outgoing (right)
        val sidePattern = remember {
                booleanArrayOf(false, false, true, true, true, false, true,
                        false, false, true, true, false, true, true, false,
                        false, false, true, true, true, false, false, true, true)
        }

        val incomingShape = remember {
                RoundedCornerShape(
                        topStart = TelegramBubbleSpec.bubbleRadius,
                        topEnd = TelegramBubbleSpec.bubbleRadius,
                        bottomStart = TelegramBubbleSpec.nearRadius,
                        bottomEnd = TelegramBubbleSpec.bubbleRadius
                )
        }
        val outgoingShape = remember {
                RoundedCornerShape(
                        topStart = TelegramBubbleSpec.bubbleRadius,
                        topEnd = TelegramBubbleSpec.bubbleRadius,
                        bottomStart = TelegramBubbleSpec.bubbleRadius,
                        bottomEnd = TelegramBubbleSpec.nearRadius
                )
        }

        BoxWithConstraints(modifier = modifier.fillMaxSize()) {
                val density = LocalDensity.current
                val maxWidthPx = with(density) { maxWidth.toPx() }.coerceAtLeast(1f)
                val gradientWidthPx = with(density) { 200.dp.toPx() }
                val shimmerX = shimmerProgress * maxWidthPx

                val color0 = if (isDarkTheme) Color(0x36FFFFFF) else Color(0x1F000000)
                val color1 = if (isDarkTheme) Color(0x1CFFFFFF) else Color(0x12000000)
                val outlineColor = if (isDarkTheme) Color(0x40FFFFFF) else Color(0x24FFFFFF)

                val shimmerBrush =
                        Brush.linearGradient(
                                colorStops = arrayOf(
                                        0f to color1,
                                        0.4f to color0,
                                        0.6f to color0,
                                        1f to color1
                                ),
                                start = Offset(shimmerX - gradientWidthPx, 0f),
                                end = Offset(shimmerX, 0f)
                        )

                val bottomInset = 58.dp
                val avatarSize = 36.dp
                val avatarGap = 6.dp
                val containerWidth = this@BoxWithConstraints.maxWidth
                val bubbleMaxWidth = containerWidth * 0.78f - if (isGroupChat) (avatarSize + avatarGap) else 0.dp

                val availableHeight = (maxHeight - bottomInset).coerceAtLeast(0.dp)
                var usedHeight = 0.dp
                var rowCount = 0
                while (rowCount < heightRandom.size && usedHeight < availableHeight) {
                        usedHeight += (heightRandom[rowCount].dp + 4.dp)
                        rowCount++
                }
                if (rowCount < 6) rowCount = 6

                Column(
                        modifier =
                                Modifier.align(Alignment.BottomStart)
                                        .fillMaxWidth()
                                        .padding(start = 8.dp, end = 8.dp, bottom = bottomInset),
                        verticalArrangement = Arrangement.spacedBy(4.dp)
                ) {
                        repeat(rowCount) { index ->
                                val isOutgoing = sidePattern[index % sidePattern.size]
                                val bubbleWidth = bubbleMaxWidth * widthRandom[index % widthRandom.size]
                                val bubbleHeight = heightRandom[index % heightRandom.size].dp
                                val shape = if (isOutgoing) outgoingShape else incomingShape

                                Row(
                                        modifier = Modifier.fillMaxWidth(),
                                        horizontalArrangement = if (isOutgoing) Arrangement.End else Arrangement.Start,
                                        verticalAlignment = Alignment.Bottom
                                ) {
                                        if (!isOutgoing && isGroupChat) {
                                                Box(
                                                        modifier =
                                                                Modifier.size(avatarSize)
                                                                        .clip(CircleShape)
                                                                        .background(shimmerBrush)
                                                                        .border(1.dp, outlineColor, CircleShape)
                                                )
                                                Spacer(modifier = Modifier.width(avatarGap))
                                        }
                                        Box(
                                                modifier =
                                                        Modifier.width(bubbleWidth)
                                                                .height(bubbleHeight)
                                                                .clip(shape)
                                                                .background(shimmerBrush)
                                                                .border(1.dp, outlineColor, shape)
                                        )
                                }
                        }
                }
        }
}

// ═══════════════════════════════════════════════════════════
// 📌 PINNED MESSAGE BANNER (Telegram-style)
// ═══════════════════════════════════════════════════════════

/**
 * Telegram-style pinned message banner — отображается под хедером чата.
 * При клике скроллит к текущему pinned сообщению и переключает на следующее.
 */
@Composable
fun PinnedMessageBanner(
        pinnedCount: Int,
        currentIndex: Int,
        messagePreview: String,
        isDarkTheme: Boolean,
        onBannerClick: () -> Unit,
        onCloseClick: () -> Unit
) {
    val bannerBg = if (isDarkTheme) Color(0xFF212121) else Color(0xFFFFFFFF)
    val textColor = if (isDarkTheme) Color.White else Color.Black
    val secondaryColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93)
    val accentColor = PrimaryBlue

    // 📌 Animated text transition (slide up/down like Telegram)
    var previousIndex by remember { mutableStateOf(currentIndex) }
    var previousPreview by remember { mutableStateOf(messagePreview) }
    val slideDirection = if (currentIndex > previousIndex) 1 else -1 // 1 = вверх, -1 = вниз

    val transition = updateTransition(targetState = currentIndex, label = "pinnedTransition")
    val offsetY by transition.animateFloat(
            transitionSpec = { tween(durationMillis = 200, easing = FastOutSlowInEasing) },
            label = "offsetY"
    ) { targetIdx ->
        if (targetIdx == previousIndex) 0f else 0f
    }

    // Track text for outgoing animation
    LaunchedEffect(currentIndex) {
        previousIndex = currentIndex
        previousPreview = messagePreview
    }

    Box(
            modifier = Modifier
                    .fillMaxWidth()
                    .background(bannerBg)
                    .clickable(
                            indication = null,
                            interactionSource = remember { MutableInteractionSource() }
                    ) { onBannerClick() }
    ) {
        Row(
                modifier = Modifier
                        .fillMaxWidth()
                        .padding(start = 16.dp, end = 8.dp, top = 8.dp, bottom = 8.dp),
                verticalAlignment = Alignment.CenterVertically
        ) {
            // 📌 Telegram-style PinnedLineView (animated vertical segments indicator)
            PinnedLineIndicator(
                    totalCount = pinnedCount,
                    selectedIndex = currentIndex,
                    accentColor = accentColor,
                    isDarkTheme = isDarkTheme,
                    modifier = Modifier
                            .width(2.dp)
                            .height(32.dp)
            )

            Spacer(modifier = Modifier.width(12.dp))

            // Текст с анимацией
            Column(
                    modifier = Modifier
                            .weight(1f)
                            .clipToBounds()
            ) {
                // Title: "Pinned Message" или "Pinned Message #N"
                AnimatedContent(
                        targetState = currentIndex,
                        transitionSpec = {
                            val direction = if (targetState > initialState) 1 else -1
                            (slideInVertically { height -> direction * height } + fadeIn(tween(200)))
                                    .togetherWith(slideOutVertically { height -> -direction * height } + fadeOut(tween(150)))
                        },
                        label = "pinnedTitle"
                ) { idx ->
                    Text(
                            text = if (pinnedCount > 1) "Pinned Message #${pinnedCount - idx}" else "Pinned Message",
                            color = accentColor,
                            fontSize = 13.sp,
                            fontWeight = FontWeight.SemiBold,
                            maxLines = 1
                    )
                }
                // Preview text
                AnimatedContent(
                        targetState = messagePreview,
                        transitionSpec = {
                            (slideInVertically { height -> height } + fadeIn(tween(200)))
                                    .togetherWith(slideOutVertically { height -> -height } + fadeOut(tween(150)))
                        },
                        label = "pinnedPreview"
                ) { preview ->
                    Text(
                            text = preview,
                            color = textColor,
                            fontSize = 13.sp,
                            maxLines = 1,
                            overflow = TextOverflow.Ellipsis
                    )
                }
            }

            // Кнопка закрытия (unpin)
            Box(
                    modifier = Modifier
                            .size(32.dp)
                            .clip(CircleShape)
                            .clickable(
                                    indication = null,
                                    interactionSource = remember { MutableInteractionSource() }
                            ) { onCloseClick() },
                    contentAlignment = Alignment.Center
            ) {
                Icon(
                        painter = TelegramIcons.Close,
                        contentDescription = "Unpin message",
                        tint = secondaryColor,
                        modifier = Modifier.size(16.dp)
                )
            }
        }

        // Bottom divider
        Box(
                modifier = Modifier
                        .align(Alignment.BottomCenter)
                        .fillMaxWidth()
                        .height(0.5.dp)
                        .background(
                                if (isDarkTheme) Color.White.copy(alpha = 0.08f)
                                else Color.Black.copy(alpha = 0.06f)
                        )
        )
    }
}

/**
 * 📌 Telegram-style vertical line indicator for pinned messages.
 * Shows segments for each pin (max 3 visible), active segment highlighted.
 * Animates position and count transitions with 220ms cubic-bezier.
 * Based on Telegram's PinnedLineView.java
 */
@Composable
private fun PinnedLineIndicator(
        totalCount: Int,
        selectedIndex: Int,
        accentColor: Color,
        isDarkTheme: Boolean,
        modifier: Modifier = Modifier
) {
    // Inactive color = accent with ~44% alpha (matches Telegram: alpha * 112/255)
    val inactiveColor = accentColor.copy(alpha = 0.44f)
    val activeColor = accentColor

    // Animate position changes
    // Invert: index 0 (newest, DESC) → bottom segment, index N-1 (oldest) → top segment (Telegram-style)
    val visualPosition = (totalCount - 1 - selectedIndex).coerceAtLeast(0)
    val animatedPosition by animateFloatAsState(
            targetValue = visualPosition.toFloat(),
            animationSpec = tween(durationMillis = 220, easing = CubicBezierEasing(0.25f, 0.1f, 0.25f, 1f)),
            label = "pinnedLinePosition"
    )
    val animatedCount by animateFloatAsState(
            targetValue = totalCount.toFloat(),
            animationSpec = tween(durationMillis = 220, easing = CubicBezierEasing(0.25f, 0.1f, 0.25f, 1f)),
            label = "pinnedLineCount"
    )

    if (totalCount <= 1) {
        // Single pin — just a solid accent line
        Box(
                modifier = modifier
                        .clip(RoundedCornerShape(1.dp))
                        .background(activeColor)
        )
    } else {
        Canvas(modifier = modifier) {
            val viewPadding = 2.dp.toPx()
            val maxVisible = 3
            val visibleCount = minOf(totalCount, maxVisible)
            val lineH = (size.height - viewPadding * 2) / visibleCount.toFloat()
            if (lineH <= 0f) return@Canvas
            val linePadding = 1.dp.toPx()
            val cornerRadius = size.width / 2f

            // Calculate scroll offset only when totalCount > maxVisible
            var startOffset = 0f
            if (totalCount > maxVisible) {
                // Keep selected segment visible in the viewport
                startOffset = (animatedPosition - 1) * lineH
                if (startOffset < 0f) startOffset = 0f
                val maxOffset = (totalCount - maxVisible).toFloat() * lineH
                if (startOffset > maxOffset) startOffset = maxOffset
            }

            // Draw visible segments
            val start = maxOf(0, ((startOffset) / lineH).toInt() - 1)
            val end = minOf(start + maxVisible + 2, totalCount)

            for (i in start until end) {
                val y = viewPadding + i * lineH - startOffset
                if (y + lineH < 0f || y > size.height) continue

                drawRoundRect(
                        color = inactiveColor,
                        topLeft = Offset(0f, y + linePadding),
                        size = Size(size.width, lineH - linePadding * 2),
                        cornerRadius = CornerRadius(cornerRadius, cornerRadius)
                )
            }

            // Draw active (selected) segment on top
            val activeY = viewPadding + animatedPosition * lineH - startOffset
            drawRoundRect(
                    color = activeColor,
                    topLeft = Offset(0f, activeY + linePadding),
                    size = Size(size.width, lineH - linePadding * 2),
                    cornerRadius = CornerRadius(cornerRadius, cornerRadius)
            )

            // Fade edges when scrollable (>3 pins)
            if (totalCount > maxVisible) {
                // Top fade
                drawRect(
                        brush = Brush.verticalGradient(
                                colors = listOf(Color.Transparent.copy(alpha = 0.6f), Color.Transparent),
                                startY = 0f,
                                endY = 4.dp.toPx()
                        ),
                        size = Size(size.width, 4.dp.toPx()),
                        blendMode = BlendMode.DstOut
                )
                // Bottom fade
                drawRect(
                        brush = Brush.verticalGradient(
                                colors = listOf(Color.Transparent, Color.Transparent.copy(alpha = 0.6f)),
                                startY = size.height - 4.dp.toPx(),
                                endY = size.height
                        ),
                        topLeft = Offset(0f, size.height - 4.dp.toPx()),
                        size = Size(size.width, 4.dp.toPx()),
                        blendMode = BlendMode.DstOut
                )
            }
        }
    }
}

// ═══════════════════════════════════════════════════════════
// 💬 MESSAGE CONTEXT MENU (Telegram-style long press menu)
// ═══════════════════════════════════════════════════════════

/**
 * Telegram-style context menu — появляется при long press на сообщении.
 * Содержит: Reply, Copy, Forward, Pin/Unpin, Delete.
 */
@Composable
fun MessageContextMenu(
        expanded: Boolean,
        onDismiss: () -> Unit,
        isDarkTheme: Boolean,
        isPinned: Boolean,
        isOutgoing: Boolean,
        hasText: Boolean = true,
        isSystemAccount: Boolean = false,
        onReply: () -> Unit,
        onCopy: () -> Unit,
        onForward: () -> Unit,
        onPin: () -> Unit,
        onDelete: () -> Unit
) {
    val menuBgColor = if (isDarkTheme) Color(0xFF272829) else Color.White
    val textColor = if (isDarkTheme) Color.White else Color(0xFF222222)
    val iconColor = if (isDarkTheme) Color.White.copy(alpha = 0.47f) else Color(0xFF676B70)

    MaterialTheme(
            colorScheme = MaterialTheme.colorScheme.copy(
                    surface = menuBgColor,
                    onSurface = textColor
            )
    ) {
        DropdownMenu(
                expanded = expanded,
                onDismissRequest = onDismiss,
                modifier = Modifier
                        .defaultMinSize(minWidth = 196.dp)
                        .background(menuBgColor),
                properties = PopupProperties(
                        focusable = false,
                        dismissOnBackPress = true,
                        dismissOnClickOutside = true
                )
        ) {
            // Reply
            if (!isSystemAccount) {
                ContextMenuItem(
                        icon = TelegramIcons.Reply,
                        text = "Reply",
                        onClick = {
                            onDismiss()
                            onReply()
                        },
                        tintColor = iconColor,
                        textColor = textColor
                )
            }

            // Copy (только если есть текст)
            if (hasText) {
                ContextMenuItemWithVector(
                        icon = Icons.Default.ContentCopy,
                        text = "Copy",
                        onClick = {
                            onDismiss()
                            onCopy()
                        },
                        tintColor = iconColor,
                        textColor = textColor
                )
            }

            // Forward
            if (!isSystemAccount) {
                ContextMenuItem(
                        icon = TelegramIcons.Reply,
                        text = "Forward",
                        onClick = {
                            onDismiss()
                            onForward()
                        },
                        tintColor = iconColor,
                        textColor = textColor,
                        mirrorIcon = true
                )
            }

            // Pin / Unpin
            ContextMenuItem(
                    icon = if (isPinned) TelegramIcons.Unpin else TelegramIcons.Pin,
                    text = if (isPinned) "Unpin" else "Pin",
                    onClick = {
                        onDismiss()
                        onPin()
                    },
                    tintColor = iconColor,
                    textColor = textColor
            )

            // Delete
            ContextMenuItem(
                    icon = TelegramIcons.Delete,
                    text = "Delete",
                    onClick = {
                        onDismiss()
                        onDelete()
                    },
                    tintColor = Color(0xFFFF3B30),
                    textColor = Color(0xFFFF3B30)
            )
        }
    }
}

@Composable
private fun ContextMenuItem(
        icon: Painter,
        text: String,
        onClick: () -> Unit,
        tintColor: Color,
        textColor: Color,
        mirrorIcon: Boolean = false
) {
    Box(
            modifier = Modifier
                    .fillMaxWidth()
                    .defaultMinSize(minWidth = 196.dp, minHeight = 48.dp)
                    .clickable(onClick = onClick)
                    .padding(horizontal = 18.dp),
            contentAlignment = Alignment.CenterStart
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) {
            Icon(
                    painter = icon,
                    contentDescription = null,
                    tint = tintColor,
                    modifier = Modifier
                            .size(24.dp)
                            .then(
                                    if (mirrorIcon) Modifier.graphicsLayer { scaleX = -1f }
                                    else Modifier
                            )
            )
            Spacer(modifier = Modifier.width(19.dp))
            Text(
                    text = text,
                    color = textColor,
                    fontSize = 16.sp
            )
        }
    }
}

@Composable
private fun ContextMenuItemWithVector(
        icon: ImageVector,
        text: String,
        onClick: () -> Unit,
        tintColor: Color,
        textColor: Color
) {
    Box(
            modifier = Modifier
                    .fillMaxWidth()
                    .defaultMinSize(minWidth = 196.dp, minHeight = 48.dp)
                    .clickable(onClick = onClick)
                    .padding(horizontal = 18.dp),
            contentAlignment = Alignment.CenterStart
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) {
            Icon(
                    imageVector = icon,
                    contentDescription = null,
                    tint = tintColor,
                    modifier = Modifier.size(24.dp)
            )
            Spacer(modifier = Modifier.width(19.dp))
            Text(
                    text = text,
                    color = textColor,
                    fontSize = 16.sp
            )
        }
    }
}

/** Telegram-style kebab menu */
@Composable
fun KebabMenu(
        expanded: Boolean,
        onDismiss: () -> Unit,
        isDarkTheme: Boolean,
        isSavedMessages: Boolean,
        isGroupChat: Boolean = false,
        isSystemAccount: Boolean = false,
        isBlocked: Boolean,
        onChatOpenMetricsClick: (() -> Unit)? = null,
        onSearchInChatClick: () -> Unit = {},
        onGroupInfoClick: () -> Unit = {},
        onSearchMembersClick: () -> Unit = {},
        onLeaveGroupClick: () -> Unit = {},
        onBlockClick: () -> Unit,
        onUnblockClick: () -> Unit,
        onDeleteClick: () -> Unit
) {
        val menuBgColor = if (isDarkTheme) Color(0xFF272829) else Color.White
        val textColor = if (isDarkTheme) Color.White else Color(0xFF222222)
        val iconColor = if (isDarkTheme) Color.White.copy(alpha = 0.47f) else Color(0xFF676B70)
        val dividerColor = if (isDarkTheme) Color(0xFF1C1D1F) else Color(0xFFF5F5F5)

        MaterialTheme(
                colorScheme = MaterialTheme.colorScheme.copy(
                        surface = menuBgColor,
                        onSurface = textColor
                )
        ) {
                DropdownMenu(
                        expanded = expanded,
                        onDismissRequest = onDismiss,
                        modifier = Modifier
                                .defaultMinSize(minWidth = 196.dp)
                                .background(menuBgColor),
                        properties =
                                PopupProperties(
                                        focusable = true,
                                        dismissOnBackPress = true,
                                        dismissOnClickOutside = true
                                )
                ) {
                        ContextMenuItemWithVector(
                                icon = TablerIcons.Search,
                                text = "Search",
                                onClick = onSearchInChatClick,
                                tintColor = iconColor,
                                textColor = textColor
                        )
                        onChatOpenMetricsClick?.let { onMetricsClick ->
                                Divider(color = dividerColor)
                                KebabMenuItem(
                                        icon = TelegramIcons.Info,
                                        text = "Chat Open Metrics",
                                        onClick = onMetricsClick,
                                        tintColor = iconColor,
                                        textColor = textColor
                                )
                        }

                        if (isGroupChat) {
                                Divider(color = dividerColor)
                                ContextMenuItemWithVector(
                                        icon = TablerIcons.Search,
                                        text = "Search Members",
                                        onClick = onSearchMembersClick,
                                        tintColor = iconColor,
                                        textColor = textColor
                                )
                                KebabMenuItem(
                                        icon = TelegramIcons.Info,
                                        text = "Group Info",
                                        onClick = onGroupInfoClick,
                                        tintColor = iconColor,
                                        textColor = textColor
                                )
                                Divider(color = dividerColor)
                                KebabMenuItem(
                                        icon = TelegramIcons.Leave,
                                        text = "Leave Group",
                                        onClick = onLeaveGroupClick,
                                        tintColor = Color(0xFFFF3B30),
                                        textColor = Color(0xFFFF3B30)
                                )
                        } else {
                                if (!isSavedMessages && !isSystemAccount) {
                                        KebabMenuItem(
                                                icon = if (isBlocked) TelegramIcons.Done else TelegramIcons.Block,
                                                text = if (isBlocked) "Unblock User" else "Block User",
                                                onClick = { if (isBlocked) onUnblockClick() else onBlockClick() },
                                                tintColor = iconColor,
                                                textColor = textColor
                                        )
                                }

                                // Delete chat
                                KebabMenuItem(
                                        icon = TelegramIcons.Delete,
                                        text = "Delete Chat",
                                        onClick = onDeleteClick,
                                        tintColor = Color(0xFFFF3B30),
                                        textColor = Color(0xFFFF3B30)
                                )
                        }
                }
        }
}

@Composable
private fun KebabMenuItem(
        icon: Painter,
        text: String,
        onClick: () -> Unit,
        tintColor: Color,
        textColor: Color
) {
        // Telegram-style: 48dp height, 18dp horizontal padding
        Box(
                modifier = Modifier
                        .fillMaxWidth()
                        .defaultMinSize(minWidth = 196.dp, minHeight = 48.dp)
                        .clickable(onClick = onClick)
                        .padding(horizontal = 18.dp),
                contentAlignment = Alignment.CenterStart
        ) {
                Row(
                        verticalAlignment = Alignment.CenterVertically
                ) {
                        Icon(
                                painter = icon,
                                contentDescription = null,
                                tint = tintColor,
                                modifier = Modifier.size(24.dp)
                        )
                        Spacer(modifier = Modifier.width(19.dp))
                        Text(
                                text = text,
                                color = textColor,
                                fontSize = 16.sp
                        )
                }
        }
}

/** 🖼️ Превью изображения для reply в input bar (как в Telegram) */
@Composable
fun ReplyImagePreview(
        attachment: MessageAttachment,
        modifier: Modifier = Modifier,
        senderPublicKey: String = "",
        recipientPrivateKey: String = ""
) {
        val context = androidx.compose.ui.platform.LocalContext.current
        var previewBitmap by remember { mutableStateOf<android.graphics.Bitmap?>(null) }
        var fullImageBitmap by remember { mutableStateOf<android.graphics.Bitmap?>(null) }

        // Сначала загружаем blurhash preview
        LaunchedEffect(attachment.preview) {
                if (attachment.preview.isNotEmpty()) {
                        withContext(Dispatchers.IO) {
                                try {
                                        // Получаем blurhash из preview (может быть в формате
                                        // "tag::blurhash")
                                        val blurhash =
                                                if (attachment.preview.contains("::")) {
                                                        attachment.preview.split("::").lastOrNull()
                                                                ?: ""
                                                } else {
                                                        attachment.preview
                                                }
                                        if (blurhash.isNotEmpty() && !blurhash.startsWith("http")) {
                                                previewBitmap = BlurHash.decode(blurhash, 40, 40)
                                        }
                                } catch (e: Exception) {
                                        // Ignore blurhash decode errors
                                }
                        }
                }
        }

        // Потом пробуем загрузить полноценную картинку
        LaunchedEffect(attachment.id) {
                if (senderPublicKey.isNotEmpty() && recipientPrivateKey.isNotEmpty()) {
                        withContext(Dispatchers.IO) {
                                try {
                                        // Пробуем сначала из blob
                                        if (attachment.blob.isNotEmpty()) {
                                                val decoded = base64ToBitmap(attachment.blob)
                                                if (decoded != null) {
                                                        fullImageBitmap = decoded
                                                        return@withContext
                                                }
                                        }

                                        // Если blob нет - загружаем из локального файла
                                        val localBlob =
                                                AttachmentFileManager.readAttachment(
                                                        context,
                                                        attachment.id,
                                                        senderPublicKey,
                                                        recipientPrivateKey
                                                )

                                        if (localBlob != null) {
                                                val decoded = base64ToBitmap(localBlob)
                                                fullImageBitmap = decoded
                                        }
                                } catch (e: Exception) {}
                        }
                }
        }

        Box(
                modifier =
                        modifier.clip(RoundedCornerShape(4.dp))
                                .background(Color.Gray.copy(alpha = 0.3f))
        ) {
                if (fullImageBitmap != null) {
                        // Полноценная картинка если загружена
                        Image(
                                bitmap = fullImageBitmap!!.asImageBitmap(),
                                contentDescription = "Photo preview",
                                modifier = Modifier.fillMaxSize(),
                                contentScale = ContentScale.Crop
                        )
                } else if (previewBitmap != null) {
                        // Blurhash preview если полная не загружена
                        Image(
                                bitmap = previewBitmap!!.asImageBitmap(),
                                contentDescription = "Photo preview",
                                modifier = Modifier.fillMaxSize(),
                                contentScale = ContentScale.Crop
                        )
                } else {
                        // Placeholder с иконкой только если нет ничего
                        Box(
                                modifier = Modifier.fillMaxSize(),
                                contentAlignment = Alignment.Center
                        ) {
                                Icon(
                                        painter = TelegramIcons.Photos,
                                        contentDescription = null,
                                        tint = Color.White.copy(alpha = 0.7f),
                                        modifier = Modifier.size(20.dp)
                                )
                        }
                }
        }
}
/** Profile photo menu for avatar — Telegram style */
@Composable
fun ProfilePhotoMenu(
        expanded: Boolean,
        onDismiss: () -> Unit,
        isDarkTheme: Boolean,
        onQrCodeClick: (() -> Unit)? = null,
        onSetPhotoClick: () -> Unit,
        onDeletePhotoClick: (() -> Unit)? = null,
        hasAvatar: Boolean = false
) {
        // Telegram popup colors
        val menuBgColor = if (isDarkTheme) Color(0xFF272829) else Color.White
        val textColor = if (isDarkTheme) Color.White else Color(0xFF222222)
        val iconColor = if (isDarkTheme) Color.White.copy(alpha = 0.47f) else Color(0xFF676B70)
        val dividerColor = if (isDarkTheme) Color(0xFF1C1D1F) else Color(0xFFF5F5F5)

        MaterialTheme(
                colorScheme = MaterialTheme.colorScheme.copy(
                        surface = menuBgColor,
                        onSurface = textColor
                )
        ) {
                DropdownMenu(
                        expanded = expanded,
                        onDismissRequest = onDismiss,
                        modifier = Modifier
                                .defaultMinSize(minWidth = 196.dp)
                                .background(menuBgColor),
                        properties =
                                PopupProperties(
                                        focusable = true,
                                        dismissOnBackPress = true,
                                        dismissOnClickOutside = true
                                )
                ) {
                        onQrCodeClick?.let { onQrClick ->
                                ProfilePhotoMenuItem(
                                        icon = androidx.compose.ui.graphics.vector.rememberVectorPainter(TablerIcons.Scan),
                                        text = "QR Code",
                                        onClick = onQrClick,
                                        tintColor = iconColor,
                                        textColor = textColor
                                )
                        }

                        ProfilePhotoMenuItem(
                                icon = TelegramIcons.AddPhoto,
                                text = if (hasAvatar) "Set Profile Photo" else "Add Photo",
                                onClick = onSetPhotoClick,
                                tintColor = iconColor,
                                textColor = textColor
                        )

                        if (hasAvatar && onDeletePhotoClick != null) {
                                ProfilePhotoMenuItem(
                                        icon = TelegramIcons.Delete,
                                        text = "Delete Photo",
                                        onClick = onDeletePhotoClick,
                                        tintColor = Color(0xFFFF3B30),
                                        textColor = Color(0xFFFF3B30)
                                )
                        }
                }
        }
}

/** Other user profile menu with block option */
@Composable
fun OtherProfileMenu(
        expanded: Boolean,
        onDismiss: () -> Unit,
        isDarkTheme: Boolean,
        isBlocked: Boolean,
        isSystemAccount: Boolean = false,
        onBlockClick: () -> Unit,
        onClearChatClick: () -> Unit
) {
        val menuBgColor = if (isDarkTheme) Color(0xFF272829) else Color.White
        val textColor = if (isDarkTheme) Color.White else Color(0xFF222222)
        val iconColor = if (isDarkTheme) Color.White.copy(alpha = 0.47f) else Color(0xFF676B70)
        val dividerColor = if (isDarkTheme) Color(0xFF1C1D1F) else Color(0xFFF5F5F5)

        MaterialTheme(
                colorScheme = MaterialTheme.colorScheme.copy(
                        surface = menuBgColor,
                        onSurface = textColor
                )
        ) {
                DropdownMenu(
                        expanded = expanded,
                        onDismissRequest = onDismiss,
                        modifier = Modifier
                                .defaultMinSize(minWidth = 196.dp)
                                .background(menuBgColor),
                        properties =
                                PopupProperties(
                                        focusable = true,
                                        dismissOnBackPress = true,
                                        dismissOnClickOutside = true
                                )
                ) {
                        if (!isSystemAccount) {
                                ProfilePhotoMenuItem(
                                        icon = if (isBlocked) TelegramIcons.Done else TelegramIcons.Block,
                                        text = if (isBlocked) "Unblock User" else "Block User",
                                        onClick = onBlockClick,
                                        tintColor = iconColor,
                                        textColor = textColor
                                )
                        }

                        ProfilePhotoMenuItem(
                                icon = TelegramIcons.Delete,
                                text = "Clear Chat History",
                                onClick = onClearChatClick,
                                tintColor = Color(0xFFFF3B30),
                                textColor = Color(0xFFFF3B30)
                        )
                }
        }
}

@Composable
private fun ProfilePhotoMenuItem(
        icon: Painter,
        text: String,
        onClick: () -> Unit,
        tintColor: Color,
        textColor: Color
) {
        // Telegram: 48dp height, 18dp horizontal padding, 16sp text, icon→text gap 43dp
        Box(
                modifier = Modifier
                        .fillMaxWidth()
                        .defaultMinSize(minWidth = 196.dp, minHeight = 48.dp)
                        .clickable(onClick = onClick)
                        .padding(horizontal = 18.dp),
                contentAlignment = Alignment.CenterStart
        ) {
                Row(
                        verticalAlignment = Alignment.CenterVertically
                ) {
                        Icon(
                                painter = icon,
                                contentDescription = null,
                                tint = tintColor,
                                modifier = Modifier.size(24.dp)
                        )
                        Spacer(modifier = Modifier.width(19.dp))
                        Text(
                                text = text,
                                color = textColor,
                                fontSize = 16.sp
                        )
                }
        }
}
