package com.rosetta.messenger.ui.settings

import android.app.Activity
import android.content.Intent
import android.graphics.BitmapFactory
import android.net.Uri
import android.util.Base64
import android.util.LruCache
import android.webkit.MimeTypeMap
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.core.content.FileProvider
import androidx.core.view.WindowCompat
import androidx.compose.animation.Crossfade
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Block
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition
import com.rosetta.messenger.R
import com.rosetta.messenger.crypto.CryptoManager
import com.rosetta.messenger.crypto.MessageCrypto
import com.rosetta.messenger.data.MessageRepository
import com.rosetta.messenger.data.PreferencesManager
import com.rosetta.messenger.database.MessageEntity
import com.rosetta.messenger.database.RosettaDatabase
import com.rosetta.messenger.network.AttachmentType
import com.rosetta.messenger.network.MessageAttachment
import com.rosetta.messenger.network.SearchUser
import com.rosetta.messenger.network.TransportManager
import com.rosetta.messenger.repository.AvatarRepository
import com.rosetta.messenger.ui.chats.ChatViewModel
import com.rosetta.messenger.ui.chats.ChatsListViewModel
import com.rosetta.messenger.ui.chats.components.ImageViewerScreen
import com.rosetta.messenger.ui.chats.components.ViewableImage
import com.rosetta.messenger.ui.components.AvatarImage
import com.rosetta.messenger.ui.components.BlurredAvatarBackground
import com.rosetta.messenger.ui.components.SharedMediaFastScrollOverlay
import com.rosetta.messenger.ui.components.VerifiedBadge
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import com.rosetta.messenger.utils.AttachmentFileManager
import com.rosetta.messenger.utils.AvatarFileManager
import com.rosetta.messenger.ui.icons.TelegramIcons
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.flowOf
import androidx.compose.runtime.snapshotFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext
import org.json.JSONArray
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.math.ceil
import kotlin.math.roundToInt

// Collapsing header constants
private val EXPANDED_HEADER_HEIGHT_OTHER = 280.dp
private val COLLAPSED_HEADER_HEIGHT_OTHER = 64.dp
private val AVATAR_SIZE_EXPANDED_OTHER = 120.dp
private val AVATAR_SIZE_COLLAPSED_OTHER = 36.dp
private const val MEDIA_THUMB_EDGE_PX = 240

private object SharedMediaBitmapCache {
    private val maxMemoryKb = (Runtime.getRuntime().maxMemory() / 1024).toInt()
    private val cacheSizeKb = (maxMemoryKb / 16).coerceAtLeast(1024)
    private val cache =
            object : LruCache<String, android.graphics.Bitmap>(cacheSizeKb) {
                override fun sizeOf(key: String, value: android.graphics.Bitmap): Int {
                    return value.byteCount / 1024
                }
            }

    fun get(key: String): android.graphics.Bitmap? = cache.get(key)

    fun put(key: String, bitmap: android.graphics.Bitmap) {
        if (cache.get(key) == null) cache.put(key, bitmap)
    }
}

/**
 * Вычисляет средний цвет bitmap (для случаев когда Palette не может извлечь swatch)
 * Используется для белых/чёрных/однотонных изображений
 */
private fun calculateAverageColor(bitmap: android.graphics.Bitmap): Color {
    var redSum = 0L
    var greenSum = 0L
    var blueSum = 0L
    
    // Сэмплируем каждый 4-й пиксель для производительности
    val step = 4
    var sampledCount = 0
    
    for (y in 0 until bitmap.height step step) {
        for (x in 0 until bitmap.width step step) {
            val pixel = bitmap.getPixel(x, y)
            redSum += android.graphics.Color.red(pixel)
            greenSum += android.graphics.Color.green(pixel)
            blueSum += android.graphics.Color.blue(pixel)
            sampledCount++
        }
    }
    
    if (sampledCount == 0) return Color.White
    
    return Color(
        red = (redSum / sampledCount) / 255f,
        green = (greenSum / sampledCount) / 255f,
        blue = (blueSum / sampledCount) / 255f
    )
}

@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
@Composable
fun OtherProfileScreen(
        user: SearchUser,
        isDarkTheme: Boolean,
        preferencesManager: PreferencesManager,
        messageRepository: MessageRepository,
        onBack: () -> Unit,
        onSwipeBackEnabledChanged: (Boolean) -> Unit = {},
        avatarRepository: AvatarRepository? = null,
        currentUserPublicKey: String = "",
        currentUserPrivateKey: String = "",
        backgroundBlurColorId: String = "avatar",
        onWriteMessage: (SearchUser) -> Unit = {},
        onCall: (SearchUser) -> Unit = {}
) {
    var isBlocked by remember { mutableStateOf(false) }
    var showAvatarMenu by remember { mutableStateOf(false) }
    var showImageViewer by remember { mutableStateOf(false) }
    var showAvatarViewer by remember { mutableStateOf(false) }
    var avatarViewerBitmap by remember { mutableStateOf<android.graphics.Bitmap?>(null) }
    var avatarViewerTimestamp by remember { mutableStateOf(0L) }
    var imageViewerInitialIndex by remember { mutableIntStateOf(0) }
    var selectedTab by remember { mutableStateOf(OtherProfileTab.MEDIA) }
    var tabSwitchJob by remember { mutableStateOf<Job?>(null) }
    val pagerState = rememberPagerState(
            initialPage = 0,
            pageCount = { OtherProfileTab.entries.size }
    )

    // Pager swipe → update tab + control swipe-back
    LaunchedEffect(pagerState) {
        snapshotFlow { pagerState.currentPage }.collect { page ->
            selectedTab = OtherProfileTab.entries[page]
            // Swipe-back only on first tab (Media); on other tabs pager handles swipe
            onSwipeBackEnabledChanged(page == 0 && !showImageViewer && !showAvatarViewer)
        }
    }

    LaunchedEffect(showImageViewer, showAvatarViewer) {
        onSwipeBackEnabledChanged(!showImageViewer && !showAvatarViewer && pagerState.currentPage == 0)
    }

    val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
    val avatarColors = getAvatarColor(user.publicKey, isDarkTheme)
    val isSafetyProfile = remember(user.publicKey) {
        user.publicKey == MessageRepository.SYSTEM_SAFE_PUBLIC_KEY
    }
    val isSystemAccount = remember(user.publicKey) {
        MessageRepository.isSystemAccount(user.publicKey)
    }
    val context = LocalContext.current
    val view = LocalView.current
    val window = remember { (view.context as? Activity)?.window }

    // 🎨 Статус бар - прозрачный с белыми иконками (поверх аватара)
    DisposableEffect(Unit) {
        val insetsController = window?.let { WindowCompat.getInsetsController(it, view) }
        // Сохраняем оригинальные значения
        val originalStatusBarColor = window?.statusBarColor ?: 0
        val originalLightStatusBars = insetsController?.isAppearanceLightStatusBars ?: false
        
        // Прозрачный статус бар с белыми иконками
        window?.statusBarColor = android.graphics.Color.TRANSPARENT
        insetsController?.isAppearanceLightStatusBars = false
        
        onDispose {
            // Восстанавливаем при выходе
            window?.statusBarColor = originalStatusBarColor
            insetsController?.isAppearanceLightStatusBars = originalLightStatusBars
        }
    }

    // 🔥 ChatsListViewModel для блокировки/разблокировки
    val chatsListViewModel: ChatsListViewModel = hiltViewModel()
    val coroutineScope = rememberCoroutineScope()

    // 🔕 Mute state
    var notificationsEnabled by remember { mutableStateOf(true) }

    // 🔥 Загружаем статус блокировки при открытии экрана
    LaunchedEffect(user.publicKey) { isBlocked = chatsListViewModel.isUserBlocked(user.publicKey) }

    // 🎹 Для закрытия клавиатуры
    val keyboardController = LocalSoftwareKeyboardController.current
    val focusManager = LocalFocusManager.current

    // Закрываем клавиатуру при открытии экрана
    LaunchedEffect(Unit) {
        val imm = context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE)
                as android.view.inputmethod.InputMethodManager
        imm.hideSoftInputFromWindow(view.windowToken, 0)
        focusManager.clearFocus()
    }

    // 🗑️ Для удаления диалога
    val database = remember { RosettaDatabase.getDatabase(context) }
    val messageDao = remember { database.messageDao() }
    val dialogDao = remember { database.dialogDao() }

    val activeAccountPublicKey =
            remember(currentUserPublicKey) { currentUserPublicKey.trim() }
    val activeAccountPrivateKey =
            remember(currentUserPrivateKey) { currentUserPrivateKey.trim() }

    // 🔕 Load mute state from preferences
    LaunchedEffect(activeAccountPublicKey, user.publicKey) {
        if (activeAccountPublicKey.isNotBlank()) {
            notificationsEnabled = !preferencesManager.isChatMuted(activeAccountPublicKey, user.publicKey)
        }
    }

    val dialogKey =
            remember(activeAccountPublicKey, user.publicKey) {
                if (activeAccountPublicKey.isBlank()) {
                    ""
                } else if (activeAccountPublicKey < user.publicKey) {
                    "$activeAccountPublicKey:${user.publicKey}"
                } else {
                    "${user.publicKey}:$activeAccountPublicKey"
                }
            }

    val sharedMessagesFlow =
            remember(messageDao, activeAccountPublicKey, dialogKey) {
                if (activeAccountPublicKey.isBlank() || dialogKey.isBlank()) {
                    flowOf(emptyList())
                } else {
                    messageDao.getMessagesFlow(activeAccountPublicKey, dialogKey)
                }
            }
    val sharedMessages by sharedMessagesFlow.collectAsState(initial = emptyList())

    val sharedContent by
            produceState(
                    initialValue = OtherProfileSharedContent(),
                    key1 = sharedMessages,
                    key2 = activeAccountPublicKey,
                    key3 = activeAccountPrivateKey
            ) {
                value =
                        withContext(Dispatchers.IO) {
                            buildOtherProfileSharedContent(
                                    messages = sharedMessages,
                                    accountPublicKey = activeAccountPublicKey,
                                    accountPrivateKey = activeAccountPrivateKey,
                                    defaultFallbackPublicKey = user.publicKey
                            )
                        }
            }
    val viewerSourcePhotos = remember(sharedContent.mediaPhotos) { sharedContent.mediaPhotos }
    val mediaViewerImages =
            remember(viewerSourcePhotos, user, activeAccountPublicKey) {
                viewerSourcePhotos.map { media ->
                    ViewableImage(
                            attachmentId = media.attachmentId,
                            preview = media.preview,
                            blob = media.blob,
                            chachaKey = media.chachaKey,
                            senderPublicKey = activeAccountPublicKey,
                            senderName = user.title.ifBlank { user.username.ifBlank { "User" } },
                            timestamp = Date(media.timestamp),
                            width = media.width,
                            height = media.height,
                            caption = media.caption,
                            transportTag = media.transportTag,
                            transportServer = media.transportServer
                    )
                }
            }

    // 🔥 Закрываем клавиатуру при открытии экрана
    LaunchedEffect(Unit) {
        keyboardController?.hide()
        focusManager.clearFocus()
    }

    // �🟢 Наблюдаем за онлайн статусом пользователя в реальном времени
    val onlineStatus by
            messageRepository
                    .observeUserOnlineStatus(user.publicKey)
                    .collectAsState(initial = false to 0L)
    val isOnline = onlineStatus.first

    // Scroll state for collapsing header animation
    val density = LocalDensity.current
    val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
    val expandedHeightPx = with(density) { (EXPANDED_HEADER_HEIGHT_OTHER + statusBarHeight).toPx() }
    val collapsedHeightPx =
            with(density) { (COLLAPSED_HEADER_HEIGHT_OTHER + statusBarHeight).toPx() }

    var scrollOffset by remember { mutableFloatStateOf(0f) }
    val maxScrollOffset = expandedHeightPx - collapsedHeightPx

    val collapseProgress by remember {
        derivedStateOf { (scrollOffset / maxScrollOffset).coerceIn(0f, 1f) }
    }

    // ═══════════════════════════════════════════════════════════════
    // TELEGRAM-STYLE AVATAR EXPANSION (Drop/Blob effect)
    // При свайпе вниз от верха списка - аватарка расширяется
    // Порог snap = 33% (как в Telegram: expandProgress >= 0.33f)
    // ═══════════════════════════════════════════════════════════════
    var overscrollOffset by remember { mutableFloatStateOf(0f) }
    val maxOverscroll = with(density) { 250.dp.toPx() }  // Максимальное расширение
    val snapThreshold = maxOverscroll * 0.05f  // 🔥 5% - мгновенный snap!

    // Track dragging state
    var isDragging by remember { mutableStateOf(false) }

    // isPulledDown = зафиксировано в раскрытом состоянии (как Telegram)
    var isPulledDown by remember { mutableStateOf(false) }

    // Velocity для учёта скорости свайпа
    var lastVelocity by remember { mutableFloatStateOf(0f) }

    // Haptic feedback
    val hapticFeedback = LocalHapticFeedback.current
    var hasTriggeredExpandHaptic by remember { mutableStateOf(false) }

    // Проверяем наличие аватара у пользователя
    val avatars by
            avatarRepository?.getAvatars(user.publicKey, allDecode = false)?.collectAsState()
                    ?: remember { mutableStateOf(emptyList()) }
    val hasAvatar = avatars.isNotEmpty()

    // ═══════════════════════════════════════════════════════════════
    // SNAP ANIMATION - как Telegram's expandAnimator
    // При отпускании пальца: snap к 0 или к max в зависимости от порога
    // 🔥 FIX: isPulledDown имеет ВЫСШИЙ ПРИОРИТЕТ
    // ═══════════════════════════════════════════════════════════
    val targetOverscroll = when {
        isPulledDown -> maxOverscroll   // 🔥 ВЫСШИЙ ПРИОРИТЕТ: snap сработал!
        isDragging -> overscrollOffset  // Во время drag (до порога)
        overscrollOffset > snapThreshold -> maxOverscroll
        else -> 0f
    }

    // Плавная spring анимация для snap (без bounce для гладкости)
    val animatedOverscroll by animateFloatAsState(
        targetValue = targetOverscroll,
        animationSpec = if (isDragging && !isPulledDown) {
            spring(stiffness = Spring.StiffnessHigh)
        } else {
            spring(
                dampingRatio = Spring.DampingRatioNoBouncy,
                stiffness = Spring.StiffnessMedium
            )
        },
        label = "overscroll"
    )

    // ExpansionProgress для передачи в overlay
    // 🔥 FIX: Когда isPulledDown=true - ВСЕГДА используем animatedOverscroll
    val expansionProgress = when {
        collapseProgress > 0.1f -> 0f
        isPulledDown -> (animatedOverscroll / maxOverscroll).coerceIn(0f, 1f)  // 🔥 AUTO-EXPAND!
        isDragging -> (overscrollOffset / maxOverscroll).coerceIn(0f, 1f)
        else -> (animatedOverscroll / maxOverscroll).coerceIn(0f, 1f)
    }

    // Haptic при достижении порога (как Telegram)
    // 🔥 FIX: Также автоматически snap к expanded
    LaunchedEffect(expansionProgress, isDragging) {
        if (expansionProgress >= 0.10f && !hasTriggeredExpandHaptic && isDragging) {
            hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
            hasTriggeredExpandHaptic = true
            // 🔥 AUTO-SNAP!
            isPulledDown = true
        } else if (expansionProgress < 0.02f) {
            hasTriggeredExpandHaptic = false
        }
    }

    // Коллапс при отсутствии аватара
    LaunchedEffect(hasAvatar) {
        if (!hasAvatar) {
            isPulledDown = false
            overscrollOffset = 0f
        }
    }

    // ═══════════════════════════════════════════════════════════════
    // NESTED SCROLL - Telegram style with overscroll support
    // ═══════════════════════════════════════════════════════════════
    val nestedScrollConnection = remember(hasAvatar) {
        object : NestedScrollConnection {
            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                val delta = available.y
                isDragging = true

                // Тянем вверх (delta < 0)
                if (delta < 0) {
                    // Сначала убираем overscroll
                    if (overscrollOffset > 0 || isPulledDown) {
                        // 🔥 FIX: Если isPulledDown=true - НЕ меняем overscrollOffset напрямую
                        // Только сбрасываем isPulledDown и даём анимации плавно свернуть
                        if (isPulledDown) {
                            // При достаточном свайпе вверх - плавно сворачиваем
                            if (delta < -10f) {
                                isPulledDown = false
                                // 🔥 НЕ сбрасываем overscrollOffset - пусть animatedOverscroll плавно вернётся к 0
                            }
                            return Offset(0f, delta)  // Consume весь delta
                        }
                        val newOffset = (overscrollOffset + delta).coerceAtLeast(0f)
                        val consumed = overscrollOffset - newOffset
                        overscrollOffset = newOffset
                        return Offset(0f, -consumed)
                    }
                    // Затем коллапсируем header
                    if (scrollOffset < maxScrollOffset) {
                        val newScrollOffset = (scrollOffset - delta).coerceIn(0f, maxScrollOffset)
                        val consumed = newScrollOffset - scrollOffset
                        scrollOffset = newScrollOffset
                        return Offset(0f, -consumed)
                    }
                }

                // Тянем вниз (delta > 0) - раскрываем header
                if (delta > 0 && scrollOffset > 0) {
                    val newScrollOffset = (scrollOffset - delta).coerceAtLeast(0f)
                    val consumed = scrollOffset - newScrollOffset
                    scrollOffset = newScrollOffset
                    return Offset(0f, consumed)
                }

                return Offset.Zero
            }

            override fun onPostScroll(
                    consumed: Offset,
                    available: Offset,
                    source: NestedScrollSource
            ): Offset {
                // Overscroll при свайпе вниз от верха (только если есть аватар)
                if (available.y > 0 && scrollOffset == 0f && hasAvatar) {
                    // Telegram: сопротивление если ещё не isPulledDown
                    val resistance = if (isPulledDown) 1f else 0.5f
                    val delta = available.y * resistance
                    overscrollOffset = (overscrollOffset + delta).coerceAtMost(maxOverscroll)
                    return Offset(0f, available.y)
                }
                return Offset.Zero
            }

            override suspend fun onPreFling(available: Velocity): Velocity {
                lastVelocity = available.y
                return Velocity.Zero
            }

            override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
                isDragging = false

                // Telegram: snap логика с учётом velocity
                val velocityThreshold = 1000f

                when {
                    overscrollOffset > snapThreshold || (lastVelocity > velocityThreshold && overscrollOffset > snapThreshold * 0.5f) -> {
                        // Snap to expanded
                        isPulledDown = true
                    }
                    lastVelocity < -velocityThreshold && overscrollOffset > 0 -> {
                        // Fast swipe up - snap to collapsed
                        isPulledDown = false
                    }
                    else -> {
                        // Normal case - snap based on threshold
                        isPulledDown = overscrollOffset > snapThreshold
                    }
                }

                return Velocity.Zero
            }
        }
    }

    // Handle back gesture
    BackHandler { onBack() }

    // ══════════════════════════════════════════════════════
    // PRE-COMPUTE media grid state (must be in Composable context)
    // ══════════════════════════════════════════════════════
    val mediaColumns = 3
    val mediaSpacing = 1.dp
    val mediaScreenWidth = LocalConfiguration.current.screenWidthDp.dp
    val mediaCellSize = (mediaScreenWidth - mediaSpacing * (mediaColumns - 1)) / mediaColumns
    val mediaDecodeSemaphore = remember { Semaphore(4) }
    var rootHeightPx by remember { mutableIntStateOf(0) }
    var sharedTabsBottomPx by remember { mutableIntStateOf(0) }
    val profileListState = rememberLazyListState()
    var mediaFastScrollHintDismissed by rememberSaveable(user.publicKey) { mutableStateOf(false) }
    // Use stable key for bitmap cache - don't recreate on size change
    val mediaBitmapStates = remember { mutableStateMapOf<String, android.graphics.Bitmap?>() }
    // Pre-compute indexed rows to avoid O(n) indexOf calls
    val mediaIndexedRows = remember(sharedContent.mediaPhotos) {
        sharedContent.mediaPhotos.chunked(mediaColumns).mapIndexed { idx, row -> idx to row }
    }
    val mediaRowsCount = remember(sharedContent.mediaPhotos.size) {
        ceil(sharedContent.mediaPhotos.size / mediaColumns.toFloat()).toInt().coerceAtLeast(1)
    }
    val mediaRowHeightPx = with(density) { (mediaCellSize + mediaSpacing).toPx() }
    val mediaMaxScrollPx by remember(sharedContent.mediaPhotos.size, mediaRowHeightPx, profileListState.layoutInfo) {
        derivedStateOf {
            val viewportHeight = (profileListState.layoutInfo.viewportEndOffset - profileListState.layoutInfo.viewportStartOffset)
                .toFloat()
                .coerceAtLeast(1f)
            (mediaRowsCount * mediaRowHeightPx - viewportHeight).coerceAtLeast(1f)
        }
    }
    val tabPagerScrollPx by remember(selectedTab, profileListState.layoutInfo, mediaMaxScrollPx) {
        derivedStateOf {
            if (selectedTab != OtherProfileTab.MEDIA) return@derivedStateOf 0f
            val tabPagerInfo =
                    profileListState.layoutInfo.visibleItemsInfo.firstOrNull { it.key == "tab_pager" }
            when {
                tabPagerInfo != null -> (-tabPagerInfo.offset).toFloat().coerceIn(0f, mediaMaxScrollPx)
                profileListState.layoutInfo.visibleItemsInfo.any { it.key == "bottom_spacer" } -> mediaMaxScrollPx
                else -> 0f
            }
        }
    }
    val mediaFastScrollProgress by remember(
        selectedTab,
        tabPagerScrollPx,
        mediaMaxScrollPx
    ) {
        derivedStateOf {
            if (selectedTab != OtherProfileTab.MEDIA) {
                0f
            } else {
                (tabPagerScrollPx / mediaMaxScrollPx).coerceIn(0f, 1f)
            }
        }
    }
    val mediaFastScrollMonthLabel by remember(
        selectedTab,
        sharedContent.mediaPhotos,
        tabPagerScrollPx,
        mediaRowHeightPx,
        mediaRowsCount
    ) {
        derivedStateOf {
            if (selectedTab != OtherProfileTab.MEDIA || sharedContent.mediaPhotos.isEmpty()) return@derivedStateOf ""
            val currentOffsetPx = tabPagerScrollPx
            val rowIndex = if (mediaRowHeightPx <= 0f) 0 else (currentOffsetPx / mediaRowHeightPx).toInt()
                .coerceIn(0, mediaRowsCount - 1)
            val itemIndex = (rowIndex * mediaColumns).coerceIn(0, sharedContent.mediaPhotos.lastIndex)
            formatMediaMonthLabel(sharedContent.mediaPhotos[itemIndex].timestamp)
        }
    }
    val overlayTopPx = sharedTabsBottomPx.coerceIn(0, rootHeightPx)
    val overlayHeightPx = (rootHeightPx - overlayTopPx).coerceAtLeast(0)
    val mediaFastScrollVisible by remember(
        selectedTab,
        sharedContent.mediaPhotos.size,
        mediaMaxScrollPx,
        overlayHeightPx
    ) {
        derivedStateOf {
            selectedTab == OtherProfileTab.MEDIA &&
                sharedContent.mediaPhotos.isNotEmpty() &&
                mediaMaxScrollPx > 24f &&
                profileListState.firstVisibleItemIndex >= 1 &&
                overlayHeightPx > 0
        }
    }

    Box(
            modifier =
                    Modifier.fillMaxSize()
                            .background(backgroundColor)
                            .onSizeChanged { rootHeightPx = it.height }
                            .nestedScroll(nestedScrollConnection)
    ) {
        // Scrollable content
        LazyColumn(
                state = profileListState,
                modifier =
                        Modifier.fillMaxSize()
                                .padding(
                                        top =
                                                with(density) {
                                                    (expandedHeightPx - scrollOffset).toDp()
                                                }
                                )
        ) {
            item {
                // ═══════════════════════════════════════════════════════════
                // ✉️ MESSAGE + 📞 CALL — первые элементы
                // ═══════════════════════════════════════════════════════════
                Spacer(modifier = Modifier.height(12.dp))

                Row(
                        modifier = Modifier
                                .fillMaxWidth()
                                .padding(horizontal = 16.dp),
                        horizontalArrangement = Arrangement.spacedBy(10.dp)
                ) {
                    // Write Message
                    Button(
                            onClick = { onWriteMessage(user) },
                            modifier = Modifier
                                    .weight(1f)
                                    .height(48.dp),
                            shape = RoundedCornerShape(12.dp),
                            colors = ButtonDefaults.buttonColors(
                                    containerColor = PrimaryBlue,
                                    contentColor = Color.White
                            ),
                            elevation = ButtonDefaults.buttonElevation(
                                    defaultElevation = 0.dp,
                                    pressedElevation = 0.dp
                            )
                    ) {
                        Icon(
                                painter = TelegramIcons.Message,
                                contentDescription = null,
                                modifier = Modifier.size(20.dp),
                                tint = Color.White
                        )
                        Spacer(modifier = Modifier.width(8.dp))
                        Text(
                                text = "Message",
                                fontSize = 15.sp,
                                fontWeight = FontWeight.SemiBold,
                                color = Color.White
                        )
                    }

                    if (!isSafetyProfile && !isSystemAccount) {
                        // Call
                        Button(
                                onClick = { onCall(user) },
                                modifier = Modifier
                                        .weight(1f)
                                        .height(48.dp),
                                shape = RoundedCornerShape(12.dp),
                                colors = ButtonDefaults.buttonColors(
                                        containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFE8E8ED),
                                        contentColor = if (isDarkTheme) Color.White else Color.Black
                                ),
                                elevation = ButtonDefaults.buttonElevation(
                                        defaultElevation = 0.dp,
                                        pressedElevation = 0.dp
                                )
                        ) {
                            Icon(
                                    imageVector = TablerIcons.Phone,
                                    contentDescription = null,
                                    modifier = Modifier.size(20.dp),
                                    tint = if (isDarkTheme) Color.White else PrimaryBlue
                            )
                            Spacer(modifier = Modifier.width(8.dp))
                            Text(
                                    text = "Call",
                                    fontSize = 15.sp,
                                    fontWeight = FontWeight.SemiBold,
                                    color = if (isDarkTheme) Color.White else Color.Black
                            )
                        }
                    }
                }

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

                // ═══════════════════════════════════════════════════════════
                // Разделитель секций
                // ═══════════════════════════════════════════════════════════
                Box(
                        modifier = Modifier
                                .fillMaxWidth()
                                .height(8.dp)
                                .background(if (isDarkTheme) Color(0xFF0F0F0F) else Color(0xFFF0F0F0))
                )

                // ═══════════════════════════════════════════════════════════
                // 📋 INFORMATION SECTION — после кнопок
                // ═══════════════════════════════════════════════════════════
                TelegramSectionTitle(
                        title = "Information",
                        isDarkTheme = isDarkTheme,
                        color = PrimaryBlue
                )

                if (user.username.isNotBlank()) {
                    TelegramCopyField(
                            value = "@${user.username}",
                            fullValue = user.username,
                            label = "Username",
                            isDarkTheme = isDarkTheme
                    )

                    Divider(
                            color = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE0E0E0),
                            thickness = 0.5.dp,
                            modifier = Modifier.padding(start = 16.dp)
                    )
                }

                TelegramCopyField(
                        value = user.publicKey.take(16) + "..." + user.publicKey.takeLast(6),
                        fullValue = user.publicKey,
                        label = "Public Key",
                        isDarkTheme = isDarkTheme
                )

                // ═══════════════════════════════════════════════════════════
                // 🔔 NOTIFICATIONS SECTION
                // ═══════════════════════════════════════════════════════════
                if (!isSafetyProfile) {
                    TelegramToggleItem(
                            icon = if (notificationsEnabled) TelegramIcons.Notifications else TelegramIcons.Mute,
                            title = "Notifications",
                            subtitle = if (notificationsEnabled) "On" else "Off",
                            isEnabled = notificationsEnabled,
                            onToggle = {
                                notificationsEnabled = !notificationsEnabled
                                coroutineScope.launch {
                                    preferencesManager.setChatMuted(
                                        activeAccountPublicKey,
                                        user.publicKey,
                                        !notificationsEnabled
                                    )
                                }
                            },
                            isDarkTheme = isDarkTheme
                    )
                }

            }

            stickyHeader(key = "shared_tabs") {
                Box(
                        modifier = Modifier
                                .fillMaxWidth()
                                .background(backgroundColor)
                                .onGloballyPositioned { coords ->
                                    sharedTabsBottomPx =
                                            (coords.positionInRoot().y + coords.size.height).roundToInt()
                                }
                ) {
                    OtherProfileSharedTabs(
                            selectedTab = selectedTab,
                            onTabSelected = { tab ->
                                val targetPage = OtherProfileTab.entries.indexOf(tab)
                                if (targetPage == -1) return@OtherProfileSharedTabs
                                selectedTab = tab
                                tabSwitchJob?.cancel()
                                tabSwitchJob = coroutineScope.launch {
                                    runCatching { pagerState.animateScrollToPage(targetPage) }
                                }
                            },
                            isDarkTheme = isDarkTheme
                    )
                }
            }

            // ══════════════════════════════════════════════════════
            // TAB CONTENT — HorizontalPager for swipe between tabs
            // On first tab (Media) swipe-right triggers swipe-back;
            // on other tabs swipe-right goes to the previous tab.
            // ══════════════════════════════════════════════════════
            item(key = "tab_pager") {
                HorizontalPager(
                        state = pagerState,
                        modifier = Modifier.fillMaxWidth(),
                        verticalAlignment = Alignment.Top
                ) { page ->
                    when (page) {
                        // ── MEDIA ──
                        0 -> {
                            if (sharedContent.mediaPhotos.isEmpty()) {
                                OtherProfileEmptyState(
                                        animationAssetPath = "lottie/saved.json",
                                        title = "No shared media yet",
                                        subtitle = "Photos from your chat will appear here.",
                                        isDarkTheme = isDarkTheme
                                )
                            } else {
                                Column(modifier = Modifier.fillMaxWidth()) {
                                    mediaIndexedRows.forEach { (rowIdx, rowPhotos) ->
                                        Row(
                                                modifier = Modifier.fillMaxWidth(),
                                                horizontalArrangement = Arrangement.spacedBy(mediaSpacing)
                                        ) {
                                            rowPhotos.forEachIndexed { colIdx, media ->
                                                val globalIndex = rowIdx * mediaColumns + colIdx

                                                val cachedBitmap = mediaBitmapStates[media.key]
                                                        ?: SharedMediaBitmapCache.get(media.key)

                                                if (cachedBitmap == null && !mediaBitmapStates.containsKey(media.key)) {
                                                    LaunchedEffect(media.key) {
                                                        mediaDecodeSemaphore.withPermit {
                                                            val bitmap = withContext(Dispatchers.IO) {
                                                                resolveSharedPhotoBitmap(
                                                                        context = context,
                                                                        media = media,
                                                                        accountPublicKey = activeAccountPublicKey,
                                                                        accountPrivateKey = activeAccountPrivateKey
                                                                )
                                                            }
                                                            mediaBitmapStates[media.key] = bitmap
                                                        }
                                                    }
                                                }

                                                val resolvedBitmap = cachedBitmap ?: mediaBitmapStates[media.key]
                                                val model = remember(media.localUri, media.blob) {
                                                    resolveSharedMediaModel(media.localUri, media.blob)
                                                }
                                                val previewBitmap = remember(media.preview) {
                                                    if (media.preview.isNotBlank()) {
                                                        // Extract blurhash from "UUID::blurhash" format
                                                        val blurhash = if (media.preview.contains("::")) {
                                                            media.preview.substringAfter("::")
                                                        } else {
                                                            media.preview
                                                        }
                                                        runCatching {
                                                            com.vanniktech.blurhash.BlurHash.decode(blurhash, 32, 32)
                                                        }.getOrNull()
                                                            ?: runCatching {
                                                                val bytes = Base64.decode(media.preview, Base64.DEFAULT)
                                                                BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
                                                            }.getOrNull()
                                                    } else null
                                                }
                                                val isLoaded = resolvedBitmap != null || model != null
                                                val imageAlpha by animateFloatAsState(
                                                        targetValue = if (isLoaded) 1f else 0f,
                                                        animationSpec = tween(300),
                                                        label = "media_fade"
                                                )

                                                Box(
                                                        modifier = Modifier
                                                                .size(mediaCellSize)
                                                                .clip(RoundedCornerShape(0.dp))
                                                                .clickable(
                                                                        enabled = model != null || resolvedBitmap != null || media.attachmentId.isNotBlank()
                                                                ) {
                                                                    imageViewerInitialIndex = globalIndex
                                                                    showImageViewer = true
                                                                },
                                                        contentAlignment = Alignment.Center
                                                ) {
                                                    if (previewBitmap != null) {
                                                        Image(
                                                                bitmap = previewBitmap.asImageBitmap(),
                                                                contentDescription = null,
                                                                modifier = Modifier.fillMaxSize(),
                                                                contentScale = ContentScale.Crop
                                                        )
                                                    } else {
                                                        Box(
                                                                modifier = Modifier
                                                                        .fillMaxSize()
                                                                        .background(
                                                                                if (isDarkTheme) Color(0xFF1E1E1E)
                                                                                else Color(0xFFECECEC)
                                                                        )
                                                        )
                                                    }
                                                    if (resolvedBitmap != null) {
                                                        Image(
                                                                bitmap = resolvedBitmap.asImageBitmap(),
                                                                contentDescription = "Shared media",
                                                                modifier = Modifier.fillMaxSize().graphicsLayer { alpha = imageAlpha },
                                                                contentScale = ContentScale.Crop
                                                        )
                                                    } else if (model != null) {
                                                        coil.compose.AsyncImage(
                                                                model = model,
                                                                contentDescription = "Shared media",
                                                                modifier = Modifier.fillMaxSize().graphicsLayer { alpha = imageAlpha },
                                                                contentScale = ContentScale.Crop
                                                        )
                                                    }
                                                }
                                            }
                                            repeat(mediaColumns - rowPhotos.size) {
                                                Spacer(modifier = Modifier.size(mediaCellSize))
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        // ── FILES ──
                        1 -> {
                            if (sharedContent.files.isEmpty()) {
                                OtherProfileEmptyState(
                                        animationAssetPath = "lottie/folder.json",
                                        title = "No shared files",
                                        subtitle = "Documents from this chat will appear here.",
                                        isDarkTheme = isDarkTheme
                                )
                            } else {
                                val fileTextColor = if (isDarkTheme) Color.White else Color.Black
                                val fileSecondary = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
                                val fileDivider = if (isDarkTheme) Color(0xFF333333) else Color(0xFFD9DDE5)

                                Column(modifier = Modifier.fillMaxWidth()) {
                                    sharedContent.files.forEachIndexed { index, file ->
                                        Column {
                                            Row(
                                                    modifier = Modifier
                                                            .fillMaxWidth()
                                                            .clickable {
                                                                val opened = openSharedFile(context, file)
                                                                if (!opened) {
                                                                    Toast.makeText(context, "File is not available on this device", Toast.LENGTH_SHORT).show()
                                                                }
                                                            }
                                                            .padding(horizontal = 20.dp, vertical = 12.dp),
                                                    verticalAlignment = Alignment.CenterVertically
                                            ) {
                                                Box(
                                                        modifier = Modifier.size(36.dp).clip(CircleShape)
                                                                .background(PrimaryBlue.copy(alpha = if (isDarkTheme) 0.25f else 0.12f)),
                                                        contentAlignment = Alignment.Center
                                                ) {
                                                    Icon(painter = TelegramIcons.File, contentDescription = null, tint = PrimaryBlue, modifier = Modifier.size(18.dp))
                                                }
                                                Spacer(modifier = Modifier.width(12.dp))
                                                Column(modifier = Modifier.weight(1f)) {
                                                    Text(text = file.fileName, color = fileTextColor, fontSize = 15.sp, fontWeight = FontWeight.Medium, maxLines = 1, overflow = TextOverflow.Ellipsis)
                                                    Spacer(modifier = Modifier.height(2.dp))
                                                    Text(text = "${formatFileSize(file.sizeBytes)} • ${formatTimestamp(file.timestamp)}", color = fileSecondary, fontSize = 12.sp)
                                                }
                                                Spacer(modifier = Modifier.width(8.dp))
                                                Icon(imageVector = TablerIcons.ChevronRight, contentDescription = null, tint = fileSecondary.copy(alpha = 0.6f), modifier = Modifier.size(16.dp))
                                            }
                                            if (index != sharedContent.files.lastIndex) {
                                                Divider(color = fileDivider, thickness = 0.5.dp)
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        // ── LINKS ──
                        2 -> {
                            if (sharedContent.links.isEmpty()) {
                                OtherProfileEmptyState(
                                        animationAssetPath = "lottie/earth.json",
                                        title = "No shared links",
                                        subtitle = "Links from your messages will appear here.",
                                        isDarkTheme = isDarkTheme
                                )
                            } else {
                                val linkSecondary = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
                                val linkDivider = if (isDarkTheme) Color(0xFF333333) else Color(0xFFD9DDE5)

                                Column(modifier = Modifier.fillMaxWidth()) {
                                    sharedContent.links.forEachIndexed { index, link ->
                                        Column {
                                            Column(
                                                    modifier = Modifier
                                                            .fillMaxWidth()
                                                            .clickable {
                                                                val normalizedLink = if (link.url.startsWith("http://", ignoreCase = true) || link.url.startsWith("https://", ignoreCase = true)) link.url else "https://${link.url}"
                                                                val opened = runCatching { context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(normalizedLink))) }.isSuccess
                                                                if (!opened) {
                                                                    Toast.makeText(context, "Unable to open this link", Toast.LENGTH_SHORT).show()
                                                                }
                                                            }
                                                            .padding(horizontal = 20.dp, vertical = 12.dp)
                                            ) {
                                                Text(text = link.url, color = PrimaryBlue, fontSize = 15.sp, fontWeight = FontWeight.Medium, maxLines = 1, overflow = TextOverflow.Ellipsis, textDecoration = TextDecoration.Underline)
                                                Spacer(modifier = Modifier.height(3.dp))
                                                Text(text = formatTimestamp(link.timestamp), color = linkSecondary, fontSize = 12.sp)
                                            }
                                            if (index != sharedContent.links.lastIndex) {
                                                Divider(color = linkDivider, thickness = 0.5.dp)
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            item(key = "bottom_spacer") {
                Spacer(modifier = Modifier.height(32.dp))
            }
        }

        if (overlayHeightPx > 0) {
            Box(
                    modifier = Modifier
                            .align(Alignment.TopEnd)
                            .offset { IntOffset(0, overlayTopPx) }
                            .fillMaxWidth()
                            .height(with(density) { overlayHeightPx.toDp() })
                            .clipToBounds()
            ) {
                SharedMediaFastScrollOverlay(
                        visible = mediaFastScrollVisible,
                        progress = mediaFastScrollProgress,
                        monthLabel = mediaFastScrollMonthLabel,
                        isDarkTheme = isDarkTheme,
                        showHint = mediaFastScrollVisible && !mediaFastScrollHintDismissed,
                        onHintDismissed = { mediaFastScrollHintDismissed = true },
                        onDragProgressChanged = { fraction ->
                            if (!mediaFastScrollVisible) return@SharedMediaFastScrollOverlay
                            val targetOffset = (mediaMaxScrollPx * fraction).roundToInt()
                            coroutineScope.launch {
                                profileListState.scrollToItem(index = 2, scrollOffset = targetOffset)
                            }
                        }
                )
            }
        }

        // ═══════════════════════════════════════════════════════════
        // 🎨 COLLAPSING HEADER with METABALL EFFECT
        // ═══════════════════════════════════════════════════════════
        CollapsingOtherProfileHeader(
                name = user.title.ifEmpty { "Unknown User" },
                username = user.username,
                publicKey = user.publicKey,
                verified = user.verified,
                isOnline = isOnline,
                avatarColors = avatarColors,
                collapseProgress = collapseProgress,
                expansionProgress = expansionProgress,
                hasAvatar = hasAvatar,
                onBack = onBack,
                isDarkTheme = isDarkTheme,
                showAvatarMenu = showAvatarMenu,
                onAvatarMenuChange = { showAvatarMenu = it },
                isBlocked = isBlocked,
                isSystemAccount = isSystemAccount,
                onBlockToggle = {
                    coroutineScope.launch {
                        if (isBlocked) {
                            chatsListViewModel.unblockUser(user.publicKey)
                        } else {
                            chatsListViewModel.blockUser(user.publicKey)
                        }
                        // Обновляем локальное состояние
                        isBlocked = !isBlocked
                    }
                },
                avatarRepository = avatarRepository,
                onClearChat = {
                    CoroutineScope(Dispatchers.IO).launch {
                        try {
                            val account = activeAccountPublicKey
                            if (account.isBlank()) return@launch

                            messageDao.deleteMessagesBetweenUsers(account, account, user.publicKey)
                            dialogDao.deleteDialog(account, user.publicKey)
                            ChatViewModel.clearCacheForOpponent(user.publicKey)
                        } catch (_: Exception) {
                        }
                    }
                },
                onAvatarLongPress = {
                    coroutineScope.launch {
                        when {
                            hasAvatar && avatars.isNotEmpty() -> {
                                val first = avatars.first()
                                avatarViewerTimestamp = first.timestamp
                                val bitmap = withContext(Dispatchers.IO) {
                                    AvatarFileManager.base64ToBitmap(first.base64Data)
                                }
                                if (bitmap != null) {
                                    avatarViewerBitmap = bitmap
                                    showAvatarViewer = true
                                }
                            }
                            user.publicKey == MessageRepository.SYSTEM_SAFE_PUBLIC_KEY -> {
                                val bitmap = withContext(Dispatchers.IO) {
                                    BitmapFactory.decodeResource(context.resources, R.drawable.safe_account)
                                }
                                if (bitmap != null) {
                                    avatarViewerTimestamp = 0L
                                    avatarViewerBitmap = bitmap
                                    showAvatarViewer = true
                                }
                            }
                            user.publicKey == MessageRepository.SYSTEM_UPDATES_PUBLIC_KEY -> {
                                val bitmap = withContext(Dispatchers.IO) {
                                    BitmapFactory.decodeResource(context.resources, R.drawable.updates_account)
                                }
                                if (bitmap != null) {
                                    avatarViewerTimestamp = 0L
                                    avatarViewerBitmap = bitmap
                                    showAvatarViewer = true
                                }
                            }
                        }
                    }
                },
                backgroundBlurColorId = backgroundBlurColorId
        )

        if (showImageViewer && mediaViewerImages.isNotEmpty() && activeAccountPrivateKey.isNotBlank()) {
            ImageViewerScreen(
                    images = mediaViewerImages,
                    initialIndex = imageViewerInitialIndex.coerceIn(0, mediaViewerImages.lastIndex),
                    privateKey = activeAccountPrivateKey,
                    onDismiss = { showImageViewer = false },
                    isDarkTheme = isDarkTheme
            )
        }

        FullScreenAvatarViewer(
                isVisible = showAvatarViewer,
                onDismiss = { showAvatarViewer = false },
                displayName = user.title.ifBlank { user.publicKey.take(10) },
                avatarTimestamp = avatarViewerTimestamp,
                avatarBitmap = avatarViewerBitmap,
                publicKey = user.publicKey,
                isDarkTheme = isDarkTheme,
                avatarRepository = avatarRepository
        )
    }
}

private enum class OtherProfileTab(val title: String) {
    MEDIA("Media"),
    FILES("Files"),
    LINKS("Links")
}

private data class SharedPhotoItem(
        val key: String,
        val attachmentId: String,
        val blob: String,
        val localUri: String,
        val cachePublicKeys: List<String>,
        val preview: String,
        val chachaKey: String,
        val width: Int,
        val height: Int,
        val caption: String,
        val timestamp: Long,
        val transportTag: String = "",
        val transportServer: String = ""
)

private data class SharedFileItem(
        val key: String,
        val fileName: String,
        val sizeBytes: Long?,
        val localUri: String,
        val blob: String,
        val timestamp: Long
)

private data class SharedLinkItem(val key: String, val url: String, val timestamp: Long)

private data class OtherProfileSharedContent(
        val mediaPhotos: List<SharedPhotoItem> = emptyList(),
        val files: List<SharedFileItem> = emptyList(),
        val links: List<SharedLinkItem> = emptyList()
)

private suspend fun buildOtherProfileSharedContent(
        messages: List<MessageEntity>,
        accountPublicKey: String,
        accountPrivateKey: String,
        defaultFallbackPublicKey: String
): OtherProfileSharedContent {
    if (messages.isEmpty() || accountPublicKey.isBlank()) {
        return OtherProfileSharedContent()
    }

    val mediaPhotos = mutableListOf<SharedPhotoItem>()
    val files = mutableListOf<SharedFileItem>()
    val links = mutableListOf<SharedLinkItem>()

    val mediaIds = hashSetOf<String>()
    val fileIds = hashSetOf<String>()
    val linkIds = hashSetOf<String>()

    for (message in messages.asReversed()) {
        val decryptedText = decryptStoredMessageText(message.plainMessage, accountPrivateKey).trim()
        if (decryptedText.isNotEmpty()) {
            extractUrls(decryptedText).forEach { link ->
                val linkKey = "${message.messageId}::$link"
                if (linkIds.add(linkKey)) {
                    links.add(SharedLinkItem(key = linkKey, url = link, timestamp = message.timestamp))
                }
            }
        }

        val parsedAttachments =
                parseAttachmentsForOtherProfile(
                        attachmentsJson = message.attachments
                )

        parsedAttachments.forEach { attachment ->
            when (attachment.type) {
                AttachmentType.IMAGE -> {
                    val mediaKey = "${message.messageId}:${attachment.id}"
                    if (!mediaIds.add(mediaKey)) return@forEach

                    val mediaItem =
                            SharedPhotoItem(
                                    key = mediaKey,
                                    attachmentId = attachment.id,
                                    blob = attachment.blob,
                                    localUri = attachment.localUri,
                                    cachePublicKeys =
                                            listOf(
                                                            accountPublicKey,
                                                            defaultFallbackPublicKey,
                                                            message.fromPublicKey,
                                                            message.toPublicKey
                                                    )
                                                    .map { it.trim() }
                                                    .filter { it.isNotBlank() }
                                                    .distinct(),
                                    preview = attachment.preview,
                                    chachaKey = message.chachaKey,
                                    width = attachment.width,
                                    height = attachment.height,
                                    caption = decryptedText,
                                    timestamp = message.timestamp,
                                    transportTag = attachment.transportTag,
                                    transportServer = attachment.transportServer
                            )

                    mediaPhotos.add(mediaItem)
                }
                AttachmentType.FILE -> {
                    val meta = parseFileMetadata(attachment.preview)
                    val fileName = meta.fileName.ifBlank { "File" }
                    val fileKey = "${message.messageId}:${attachment.id}:$fileName"
                    if (!fileIds.add(fileKey)) return@forEach

                    val fileItem =
                            SharedFileItem(
                                    key = fileKey,
                                    fileName = fileName,
                                    sizeBytes = meta.sizeBytes,
                                    localUri = attachment.localUri,
                                    blob = attachment.blob,
                                    timestamp = message.timestamp
                            )

                    files.add(fileItem)
                }
                else -> Unit
            }
        }
    }

    return OtherProfileSharedContent(
            mediaPhotos = mediaPhotos.sortedByDescending { it.timestamp },
            files = files.sortedByDescending { it.timestamp },
            links = links.sortedByDescending { it.timestamp }
    )
}

private fun parseAttachmentsForOtherProfile(attachmentsJson: String): List<MessageAttachment> {
    if (attachmentsJson.isBlank() || attachmentsJson == "[]") return emptyList()

    return try {
        val attachments = JSONArray(attachmentsJson)
        buildList {
            for (i in 0 until attachments.length()) {
                val attachment = attachments.getJSONObject(i)
                val type = AttachmentType.fromInt(attachment.optInt("type", 0))
                if (type == AttachmentType.MESSAGES) continue

                val id = attachment.optString("id", "")
                val blob = attachment.optString("blob", "")
                val transportObj = attachment.optJSONObject("transport")
                val transportTag =
                        attachment.optString(
                                "transportTag",
                                attachment.optString(
                                        "transport_tag",
                                        transportObj?.optString("transport_tag", "") ?: ""
                                )
                        )
                val transportServer =
                        attachment.optString(
                                "transportServer",
                                attachment.optString(
                                        "transport_server",
                                        transportObj?.optString("transport_server", "") ?: ""
                                )
                        )

                add(
                        MessageAttachment(
                                id = id,
                                blob = blob,
                                type = type,
                                preview = attachment.optString("preview", ""),
                                width = attachment.optInt("width", 0),
                                height = attachment.optInt("height", 0),
                                localUri = attachment.optString("localUri", ""),
                                transportTag = transportTag,
                                transportServer = transportServer
                        )
                )
            }
        }
    } catch (_: Exception) {
        emptyList()
    }
}

private fun extractDownloadTagFromPreview(preview: String): String {
    if (preview.isBlank() || !preview.contains("::")) return ""
    return preview.substringBefore("::").trim()
}

private fun decryptStoredMessageText(encryptedText: String, privateKey: String): String {
    if (encryptedText.isBlank()) return ""
    if (privateKey.isBlank()) return ""
    return try {
        CryptoManager.decryptWithPassword(encryptedText, privateKey) ?: ""
    } catch (_: Exception) {
        ""
    }
}

private val URL_REGEX = Regex("(?i)\\b((?:https?://|www\\.)[^\\s<>()]+)")

private fun extractUrls(text: String): List<String> {
    return URL_REGEX.findAll(text).map { match ->
        val rawUrl = match.groupValues[1]
                .trim()
                .trimEnd('.', ',', ';', ':', '!', '?', ')', ']', '}')
        if (rawUrl.startsWith("www.", ignoreCase = true)) {
            "https://$rawUrl"
        } else {
            rawUrl
        }
    }.distinct().toList()
}

private data class ParsedFileMetadata(val fileName: String, val sizeBytes: Long?)

private fun parseFileMetadata(preview: String): ParsedFileMetadata {
    if (preview.isBlank()) return ParsedFileMetadata(fileName = "File", sizeBytes = null)
    val parts = preview.split("::").filter { it.isNotBlank() }
    return if (parts.size >= 2) {
        ParsedFileMetadata(
                fileName = parts.last(),
                sizeBytes = parts[parts.size - 2].toLongOrNull()
        )
    } else {
        ParsedFileMetadata(fileName = preview, sizeBytes = null)
    }
}

private fun inferMimeType(fileName: String): String {
    val ext = fileName.substringAfterLast('.', "").lowercase(Locale.getDefault())
    if (ext.isBlank()) return "*/*"
    return MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext) ?: "*/*"
}

private fun decodeBlobPayload(blob: String): ByteArray? {
    if (blob.isBlank()) return null
    val payload =
            if (blob.startsWith("data:", ignoreCase = true) && blob.contains("base64,")) {
                blob.substringAfter("base64,", "")
            } else {
                blob
            }
    return runCatching { Base64.decode(payload, Base64.DEFAULT) }.getOrNull()
}

private fun resolveSharedMediaModel(localUri: String, blob: String): Any? {
    if (blob.startsWith("data:", ignoreCase = true)) return blob
    if (blob.startsWith("http://", ignoreCase = true) || blob.startsWith("https://", ignoreCase = true)) {
        return blob
    }
    decodeBlobPayload(blob)?.let { return it }
    if (blob.isNotBlank()) return blob
    if (localUri.isNotBlank()) {
        val uri = runCatching { Uri.parse(localUri) }.getOrNull()
        if (uri != null) return uri
    }
    return null
}

private fun decodeBase64ToBitmap(base64: String): android.graphics.Bitmap? {
    val bytes = decodeBlobPayload(base64) ?: return null
    return decodeSampledBitmapFromBytes(bytes, MEDIA_THUMB_EDGE_PX, MEDIA_THUMB_EDGE_PX)
}

private fun calculateInSampleSize(
        options: BitmapFactory.Options,
        reqWidth: Int,
        reqHeight: Int
): Int {
    val height = options.outHeight
    val width = options.outWidth
    var inSampleSize = 1
    if (height > reqHeight || width > reqWidth) {
        var halfHeight = height / 2
        var halfWidth = width / 2
        while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
            inSampleSize *= 2
        }
    }
    return inSampleSize.coerceAtLeast(1)
}

private fun decodeSampledBitmapFromBytes(
        bytes: ByteArray,
        reqWidth: Int,
        reqHeight: Int
): android.graphics.Bitmap? {
    return runCatching {
                val bounds =
                        BitmapFactory.Options().apply {
                            inJustDecodeBounds = true
                        }
                BitmapFactory.decodeByteArray(bytes, 0, bytes.size, bounds)
                val options =
                        BitmapFactory.Options().apply {
                            inSampleSize = calculateInSampleSize(bounds, reqWidth, reqHeight)
                            inPreferredConfig = android.graphics.Bitmap.Config.RGB_565
                        }
                BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)
            }
            .getOrNull()
}

private fun decodeUriToBitmap(
        context: android.content.Context,
        localUri: String
): android.graphics.Bitmap? {
    if (localUri.isBlank()) return null
    val uri = runCatching { Uri.parse(localUri) }.getOrNull() ?: return null
    return runCatching {
                val bounds =
                        BitmapFactory.Options().apply {
                            inJustDecodeBounds = true
                        }
                context.contentResolver.openInputStream(uri)?.use { input ->
                    BitmapFactory.decodeStream(input, null, bounds)
                }
                val options =
                        BitmapFactory.Options().apply {
                            inSampleSize = calculateInSampleSize(bounds, MEDIA_THUMB_EDGE_PX, MEDIA_THUMB_EDGE_PX)
                            inPreferredConfig = android.graphics.Bitmap.Config.RGB_565
                        }
                context.contentResolver.openInputStream(uri)?.use { input ->
                    BitmapFactory.decodeStream(input, null, options)
                }
            }
            .getOrNull()
}

private suspend fun resolveSharedPhotoBitmap(
        context: android.content.Context,
        media: SharedPhotoItem,
        accountPublicKey: String,
        accountPrivateKey: String
): android.graphics.Bitmap? {
    SharedMediaBitmapCache.get(media.key)?.let { return it }

    decodeUriToBitmap(context, media.localUri)?.let {
        SharedMediaBitmapCache.put(media.key, it)
        return it
    }
    decodeBase64ToBitmap(media.blob)?.let {
        SharedMediaBitmapCache.put(media.key, it)
        return it
    }

    if (media.attachmentId.isBlank() || accountPrivateKey.isBlank()) {
        return null
    }

    for (cacheKey in media.cachePublicKeys) {
        val localBlob =
                AttachmentFileManager.readAttachment(
                        context = context,
                        attachmentId = media.attachmentId,
                        publicKey = cacheKey,
                        privateKey = accountPrivateKey
                )
        decodeBase64ToBitmap(localBlob.orEmpty())?.let {
            SharedMediaBitmapCache.put(media.key, it)
            return it
        }
    }

    val downloadTag = extractDownloadTagFromPreview(media.preview)
    if (downloadTag.isBlank() || media.chachaKey.isBlank() || accountPublicKey.isBlank()) return null

    val encryptedBlob =
            runCatching { TransportManager.downloadFile(media.attachmentId, downloadTag) }.getOrNull()
                    ?: return null
    val plainKeyAndNonce =
            runCatching {
                        MessageCrypto.decryptKeyFromSender(media.chachaKey, accountPrivateKey)
                    }
                    .getOrNull()
                    ?: return null
    val decryptedBlob =
            runCatching {
                        MessageCrypto.decryptAttachmentBlobWithPlainKey(
                                encryptedBlob,
                                plainKeyAndNonce
                        )
                    }
                    .getOrNull()
                    ?: return null

    runCatching {
        AttachmentFileManager.saveAttachment(
                context = context,
                blob = decryptedBlob,
                attachmentId = media.attachmentId,
                publicKey = accountPublicKey,
                privateKey = accountPrivateKey
        )
    }
    return decodeBase64ToBitmap(decryptedBlob)?.also {
        SharedMediaBitmapCache.put(media.key, it)
    }
}

private fun openUriInExternalApp(context: android.content.Context, uri: Uri, mimeType: String): Boolean {
    val intent =
            Intent(Intent.ACTION_VIEW).apply {
                setDataAndType(uri, mimeType)
                addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            }
    val chooser =
            Intent.createChooser(intent, "Open file").apply {
                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            }
    val openedWithType = runCatching { context.startActivity(chooser) }.isSuccess
    if (openedWithType) return true

    val fallbackIntent =
            Intent(Intent.ACTION_VIEW).apply {
                data = uri
                addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            }
    val fallbackChooser =
            Intent.createChooser(fallbackIntent, "Open file").apply {
                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            }
    return runCatching { context.startActivity(fallbackChooser) }.isSuccess
}

private fun openSharedFile(context: android.content.Context, file: SharedFileItem): Boolean {
    val mimeType = inferMimeType(file.fileName)

    // 1. Попробовать localUri
    if (file.localUri.isNotBlank()) {
        val local = runCatching { Uri.parse(file.localUri) }.getOrNull()
        if (local != null && openUriInExternalApp(context, local, mimeType)) return true
    }

    // 2. Проверить rosetta_downloads (файлы скачанные через CDN в чате)
    val downloadsDir = File(context.filesDir, "rosetta_downloads")
    val downloadedFile = File(downloadsDir, file.fileName)
    if (downloadedFile.exists() && downloadedFile.length() > 0) {
        val uri = runCatching {
            FileProvider.getUriForFile(context, "${context.packageName}.provider", downloadedFile)
        }.getOrNull()
        if (uri != null && openUriInExternalApp(context, uri, mimeType)) return true
    }

    // 3. Попробовать blob (inline-файлы)
    val bytes = decodeBlobPayload(file.blob) ?: return false
    val extension = file.fileName.substringAfterLast('.', "").lowercase(Locale.getDefault())
    val suffix = if (extension.isBlank()) ".bin" else ".$extension"
    val tempFile =
            runCatching {
                        File.createTempFile("shared_${System.currentTimeMillis()}_", suffix, context.cacheDir)
                    }
                    .getOrNull()
                    ?: return false
    val wroteFile = runCatching { tempFile.writeBytes(bytes) }.isSuccess
    if (!wroteFile) return false

    val uri =
            runCatching {
                        FileProvider.getUriForFile(
                                context,
                                "${context.packageName}.provider",
                                tempFile
                        )
                    }
                    .getOrNull()
                    ?: return false
    return openUriInExternalApp(context, uri, mimeType)
}

private fun formatFileSize(sizeBytes: Long?): String {
    if (sizeBytes == null || sizeBytes <= 0) return "Unknown size"
    val kb = 1024.0
    val mb = kb * kb
    return if (sizeBytes >= mb) {
        String.format(Locale.getDefault(), "%.1f MB", sizeBytes / mb)
    } else {
        String.format(Locale.getDefault(), "%.0f KB", sizeBytes / kb)
    }
}

private fun formatMediaMonthLabel(timestamp: Long): String {
    return runCatching {
        SimpleDateFormat("MMMM yyyy", Locale.getDefault()).format(Date(timestamp))
    }.getOrElse { "" }
}

private fun formatTimestamp(timestamp: Long): String {
    return SimpleDateFormat("dd MMM, HH:mm", Locale.getDefault()).format(Date(timestamp))
}

private fun formatOnlineStatus(isOnline: Boolean): String {
    return if (isOnline) "online" else "offline"
}

@Composable
private fun OtherProfileActionButtons(
        isDarkTheme: Boolean,
        onMessageClick: () -> Unit,
        isMuted: Boolean,
        onMuteClick: () -> Unit
) {
    Row(
            modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
            horizontalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        OtherProfileActionButton(
                icon = TelegramIcons.Message,
                title = "Message",
                subtitle = "Open chat",
                accent = PrimaryBlue,
                isDarkTheme = isDarkTheme,
                modifier = Modifier.weight(1f),
                onClick = onMessageClick
        )
        OtherProfileActionButton(
                icon = if (isMuted) TelegramIcons.Mute else TelegramIcons.Notifications,
                title = if (isMuted) "Muted" else "Mute",
                subtitle = if (isMuted) "Tap to unmute" else "Silence notifications",
                accent =
                        if (isMuted) {
                            if (isDarkTheme) Color(0xFFFF6B6B) else Color(0xFFE35D5D)
                        } else {
                            PrimaryBlue
                        },
                isDarkTheme = isDarkTheme,
                modifier = Modifier.weight(1f),
                onClick = onMuteClick
        )
    }
}

@Composable
private fun OtherProfileActionButton(
        icon: Painter,
        title: String,
        subtitle: String,
        accent: Color,
        isDarkTheme: Boolean,
        modifier: Modifier = Modifier,
        onClick: () -> Unit
) {
    val containerColor = if (isDarkTheme) Color(0xFF1F2733) else Color(0xFFF5F8FC)
    val textColor = if (isDarkTheme) Color.White else Color(0xFF111827)
    val secondaryTextColor = if (isDarkTheme) Color(0xFF9CA3AF) else Color(0xFF6B7280)
    Card(
            modifier = modifier.height(72.dp).clickable(onClick = onClick),
            shape = RoundedCornerShape(18.dp),
            colors = CardDefaults.cardColors(containerColor = containerColor),
            border =
                    BorderStroke(
                            0.8.dp,
                            if (isDarkTheme) accent.copy(alpha = 0.35f) else Color(0x1A111827)
                    )
    ) {
        Row(
                modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Center
        ) {
            Box(
                    modifier = Modifier.size(34.dp).clip(CircleShape).background(accent.copy(alpha = 0.16f)),
                    contentAlignment = Alignment.Center
            ) {
                Icon(
                        painter = icon,
                        contentDescription = title,
                        tint = accent,
                        modifier = Modifier.size(19.dp)
                )
            }
            Spacer(modifier = Modifier.width(12.dp))
            Column {
                Text(
                        text = title,
                        fontSize = 15.sp,
                        color = textColor,
                        fontWeight = FontWeight.SemiBold
                )
                Text(
                        text = subtitle,
                        fontSize = 12.sp,
                        color = secondaryTextColor
                )
            }
        }
    }
}

@Composable
private fun OtherProfileSharedTabs(
        selectedTab: OtherProfileTab,
        onTabSelected: (OtherProfileTab) -> Unit,
        isDarkTheme: Boolean
) {
    val tabs = OtherProfileTab.entries
    val textColor = if (isDarkTheme) Color.White else Color.Black
    val unselectedColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
    TabRow(
            selectedTabIndex = tabs.indexOf(selectedTab),
            modifier = Modifier.fillMaxWidth(),
            containerColor = Color.Transparent,
            contentColor = PrimaryBlue,
            divider = {
                Divider(
                        color = if (isDarkTheme) Color(0xFF2F3440) else Color(0xFFD9DDE5),
                        thickness = 0.5.dp
                )
            }
    ) {
        tabs.forEach { tab ->
            val selected = tab == selectedTab
            Tab(
                    selected = selected,
                    onClick = { onTabSelected(tab) },
                    text = {
                        Text(
                                text = tab.title,
                                fontSize = 16.sp,
                                fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Medium,
                                color = if (selected) textColor else unselectedColor
                        )
                    }
            )
        }
    }
}

@Composable
private fun OtherProfileEmptyState(
        animationAssetPath: String,
        title: String,
        subtitle: String,
        isDarkTheme: Boolean
) {
    val composition by rememberLottieComposition(LottieCompositionSpec.Asset(animationAssetPath))
    val progress by
            animateLottieCompositionAsState(
                    composition = composition,
                    iterations = LottieConstants.IterateForever,
                    speed = 0.9f,
                    restartOnPlay = false,
                    isPlaying = composition != null
            )
    val titleColor = if (isDarkTheme) Color(0xFFE5E5EA) else Color(0xFF1F2937)
    val subtitleColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF6B7280)

    Column(
            modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 24.dp),
            horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Box(modifier = Modifier.size(64.dp), contentAlignment = Alignment.Center) {
            if (composition != null) {
                LottieAnimation(
                        composition = composition,
                        progress = { progress },
                        modifier = Modifier.fillMaxSize()
                )
            }
        }
        Spacer(modifier = Modifier.height(10.dp))
        Text(text = title, color = titleColor, fontWeight = FontWeight.SemiBold, fontSize = 16.sp)
        Spacer(modifier = Modifier.height(4.dp))
        Text(
                text = subtitle,
                color = subtitleColor,
                fontSize = 13.sp,
                textAlign = TextAlign.Center
        )
    }
}

// ═══════════════════════════════════════════════════════════
// 🎯 COLLAPSING HEADER FOR OTHER PROFILE with METABALL EFFECT
// ═══════════════════════════════════════════════════════════
@Composable
private fun CollapsingOtherProfileHeader(
        name: String,
        username: String,
        publicKey: String,
        verified: Int,
        isOnline: Boolean,
        avatarColors: AvatarColors,
        collapseProgress: Float,
        expansionProgress: Float,
        hasAvatar: Boolean,
        onBack: () -> Unit,
        isDarkTheme: Boolean,
        showAvatarMenu: Boolean,
        onAvatarMenuChange: (Boolean) -> Unit,
        isBlocked: Boolean,
        isSystemAccount: Boolean = false,
        onBlockToggle: () -> Unit,
        avatarRepository: AvatarRepository? = null,
        onClearChat: () -> Unit,
        onAvatarLongPress: () -> Unit = {},
        backgroundBlurColorId: String = "avatar"
) {
    val density = LocalDensity.current

    val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()

    val expandedHeight = EXPANDED_HEADER_HEIGHT_OTHER + statusBarHeight
    val collapsedHeight = COLLAPSED_HEADER_HEIGHT_OTHER + statusBarHeight

    // Header height меняется только при collapse, НЕ при overscroll
    val headerHeight =
            androidx.compose.ui.unit.lerp(expandedHeight, collapsedHeight, collapseProgress)

    // Avatar font size for placeholder
    val avatarFontSize = androidx.compose.ui.unit.lerp(40.sp, 12.sp, collapseProgress)

    // Text animation - always centered
    val textDefaultY = expandedHeight - 70.dp
    // Collapsed: center text block vertically in collapsed header
    // Text block is ~42dp tall (name 18sp ~22dp + 2dp spacer + online 13sp ~18dp)
    val textBlockHeight = 42.dp
    val textCollapsedY = statusBarHeight + (COLLAPSED_HEADER_HEIGHT_OTHER - textBlockHeight) / 2
    val textY = androidx.compose.ui.unit.lerp(textDefaultY, textCollapsedY, collapseProgress)

    val nameFontSize = androidx.compose.ui.unit.lerp(24.sp, 18.sp, collapseProgress)
    val onlineFontSize = androidx.compose.ui.unit.lerp(14.sp, 13.sp, collapseProgress)

    // ═══════════════════════════════════════════════════════════
    // 🎨 TEXT COLOR - просто по теме: белый в тёмной, чёрный в светлой
    // ═══════════════════════════════════════════════════════════
    val textColor = Color.White

    // ═══════════════════════════════════════════════════════════
    // 🔙 BUTTONS Y - At top when expanded, centered when collapsed
    // ═══════════════════════════════════════════════════════════
    val buttonsExpandedY = statusBarHeight + 8.dp
    val buttonsCollapsedY = statusBarHeight + (COLLAPSED_HEADER_HEIGHT_OTHER - 48.dp) / 2
    val buttonsY = androidx.compose.ui.unit.lerp(buttonsExpandedY, buttonsCollapsedY, collapseProgress)

    Box(modifier = Modifier.fillMaxWidth().height(headerHeight).clipToBounds()) {
        // Solid opaque background floor — prevents content from bleeding through
        Box(modifier = Modifier.matchParentSize().background(
                if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFEFEFF4)
        ))

        // Expansion fraction — computed early so blur can fade during expansion
        val expandFractionEarly = expansionProgress.coerceIn(0f, 1f)
        val blurAlpha = (1f - expandFractionEarly * 2.5f).coerceIn(0f, 1f)

        // ═══════════════════════════════════════════════════════════
        // 🎨 BLURRED AVATAR BACKGROUND - fades out during expansion
        // ═══════════════════════════════════════════════════════════
        if (blurAlpha > 0.01f) {
            Box(modifier = Modifier.matchParentSize().graphicsLayer { alpha = blurAlpha }) {
                BlurredAvatarBackground(
                        publicKey = publicKey,
                        avatarRepository = avatarRepository,
                        fallbackColor = avatarColors.backgroundColor,
                        blurRadius = 20f,
                        alpha = 0.9f,
                        overlayColors = com.rosetta.messenger.ui.settings.BackgroundBlurPresets.getOverlayColors(backgroundBlurColorId),
                        isDarkTheme = isDarkTheme
                )
                // Slightly deepen avatar blur in other profile so text/icons stay readable.
                Box(
                        modifier =
                                Modifier.matchParentSize().background(
                                        Color.Black.copy(alpha = if (isDarkTheme) 0.12f else 0.04f)
                                )
                )
            }
        }

        // ═══════════════════════════════════════════════════════════
        // 🌅 BOTTOM GRADIENT — плавно исчезает когда аватарка раскрывается
        // ═══════════════════════════════════════════════════════════
        val gradientAlpha = (1f - expandFractionEarly * 1.2f).coerceIn(0f, 1f)
        if (gradientAlpha > 0.01f) {
            Box(
                    modifier = Modifier
                            .fillMaxWidth()
                            .height(80.dp)
                            .align(Alignment.BottomCenter)
                            .graphicsLayer { alpha = gradientAlpha }
                            .background(
                                    Brush.verticalGradient(
                                            colors = listOf(
                                                    Color.Transparent,
                                                    Color.Black.copy(alpha = 0.35f)
                                            )
                                    )
                            )
            )
        }

        // ═══════════════════════════════════════════════════════════
        // 👤 AVATAR — Telegram-style expansion on pull-down
        // При свайпе вниз: аватарка раскрывается на весь блок (circle → rect)
        // ═══════════════════════════════════════════════════════════
        val avatarSize = androidx.compose.ui.unit.lerp(
                AVATAR_SIZE_EXPANDED_OTHER, AVATAR_SIZE_COLLAPSED_OTHER, collapseProgress
        )
        val avatarAlpha = (1f - collapseProgress * 1.8f).coerceIn(0f, 1f)
        val contentAreaHeight = EXPANDED_HEADER_HEIGHT_OTHER - 70.dp
        val avatarExpandedY = statusBarHeight + (contentAreaHeight - AVATAR_SIZE_EXPANDED_OTHER) / 2
        val avatarCollapsedY = statusBarHeight + (COLLAPSED_HEADER_HEIGHT_OTHER - AVATAR_SIZE_COLLAPSED_OTHER) / 2
        val avatarY = androidx.compose.ui.unit.lerp(avatarExpandedY, avatarCollapsedY, collapseProgress)

        // Telegram-style expansion: circle → full header rect
        val screenWidth = LocalConfiguration.current.screenWidthDp.dp
        val expandFraction = expansionProgress.coerceIn(0f, 1f)
        val expandedAvatarWidth = androidx.compose.ui.unit.lerp(avatarSize, screenWidth, expandFraction)
        val expandedAvatarHeight = androidx.compose.ui.unit.lerp(avatarSize, headerHeight, expandFraction)
        val cornerRadius = androidx.compose.ui.unit.lerp(avatarSize / 2, 0.dp, expandFraction)
        val avatarCenterX = (screenWidth - avatarSize) / 2
        val expandedAvatarX = androidx.compose.ui.unit.lerp(avatarCenterX, 0.dp, expandFraction)
        val expandedAvatarY = androidx.compose.ui.unit.lerp(avatarY, 0.dp, expandFraction)

        // Pre-compute pixel values for graphicsLayer (avoids layout-phase recomposition)
        val expandedAvatarXPx = with(density) { expandedAvatarX.toPx() }
        val expandedAvatarYPx = with(density) { expandedAvatarY.toPx() }

        Box(
                modifier = Modifier
                        .size(width = expandedAvatarWidth, height = expandedAvatarHeight)
                        .graphicsLayer {
                            translationX = expandedAvatarXPx
                            translationY = expandedAvatarYPx
                            alpha = avatarAlpha
                            shape = RoundedCornerShape(cornerRadius)
                            clip = true
                        }
                        .background(avatarColors.backgroundColor)
                        .pointerInput(Unit) {
                            detectTapGestures(
                                    onLongPress = { onAvatarLongPress() }
                            )
                        },
                contentAlignment = Alignment.Center
        ) {
            if (publicKey == MessageRepository.SYSTEM_SAFE_PUBLIC_KEY) {
                Image(
                        painter = painterResource(id = R.drawable.safe_account),
                        contentDescription = "Safe avatar",
                        modifier = Modifier.fillMaxSize(),
                        contentScale = ContentScale.Crop
                )
            } else if (publicKey == MessageRepository.SYSTEM_UPDATES_PUBLIC_KEY) {
                Image(
                        painter = painterResource(id = R.drawable.updates_account),
                        contentDescription = "Rosetta Updates avatar",
                        modifier = Modifier.fillMaxSize(),
                        contentScale = ContentScale.Crop
                )
            } else if (hasAvatar && avatarRepository != null) {
                OtherProfileFullSizeAvatar(
                        publicKey = publicKey,
                        avatarRepository = avatarRepository,
                        isDarkTheme = isDarkTheme
                )
            } else {
                Text(
                        text = getInitials(name),
                        fontSize = avatarFontSize,
                        fontWeight = FontWeight.Bold,
                        color = avatarColors.textColor
                )
            }
        }

        // Gradient overlays when avatar is expanded
        if (expandFraction > 0.01f) {
            // Top gradient
            Box(
                    modifier = Modifier
                            .fillMaxWidth()
                            .height(120.dp)
                            .align(Alignment.TopCenter)
                            .graphicsLayer { alpha = expandFraction.coerceIn(0f, 1f) }
                            .background(
                                    Brush.verticalGradient(
                                            colors = listOf(
                                                    Color.Black.copy(alpha = 0.6f),
                                                    Color.Transparent
                                            )
                                    )
                            )
            )
            // Bottom gradient
            Box(
                    modifier = Modifier
                            .fillMaxWidth()
                            .height(120.dp)
                            .align(Alignment.BottomCenter)
                            .graphicsLayer { alpha = expandFraction.coerceIn(0f, 1f) }
                            .background(
                                    Brush.verticalGradient(
                                            colors = listOf(
                                                    Color.Transparent,
                                                    Color.Black.copy(alpha = 0.6f)
                                            )
                                    )
                            )
            )
        }

        // ═══════════════════════════════════════════════════════════
        // 🔙 BACK BUTTON - At top when expanded, centered when collapsed
        // ═══════════════════════════════════════════════════════════
        Box(
                modifier =
                        Modifier
                                .align(Alignment.TopStart)
                                .offset(x = 4.dp, y = buttonsY)
                                .size(48.dp),
                contentAlignment = Alignment.Center
        ) {
            IconButton(onClick = onBack, modifier = Modifier.size(48.dp)) {
                Icon(
                        imageVector = TablerIcons.ChevronLeft,
                        contentDescription = "Back",
                        tint = Color.White,
                        modifier = Modifier.size(24.dp)
                )
            }
        }

        // ═══════════════════════════════════════════════════════════
        // ⋮ MENU BUTTON - At top when expanded, centered when collapsed
        // ═══════════════════════════════════════════════════════════
        Box(
                modifier =
                        Modifier.align(Alignment.TopEnd)
                                .offset(x = -4.dp, y = buttonsY),
                contentAlignment = Alignment.Center
        ) {
            IconButton(onClick = { onAvatarMenuChange(true) }, modifier = Modifier.size(48.dp)) {
                Icon(
                        painter = TelegramIcons.More,
                        contentDescription = "Profile menu",
                        tint = Color.White,
                        modifier = Modifier.size(24.dp)
                )
            }

            // Меню с блокировкой пользователя
            com.rosetta.messenger.ui.chats.components.OtherProfileMenu(
                    expanded = showAvatarMenu,
                    onDismiss = { onAvatarMenuChange(false) },
                    isDarkTheme = isDarkTheme,
                    isBlocked = isBlocked,
                    isSystemAccount = isSystemAccount,
                    onBlockClick = {
                        onAvatarMenuChange(false)
                        onBlockToggle()
                    },
                    onClearChatClick = {
                        onAvatarMenuChange(false)
                        onClearChat()
                    }
            )
        }

        // ═══════════════════════════════════════════════════════════
        // 📝 TEXT BLOCK - Name + Verified + Online
        // ═══════════════════════════════════════════════════════════
        Column(
                modifier =
                        Modifier.align(Alignment.TopCenter).offset(y = textY),
                horizontalAlignment = Alignment.CenterHorizontally
        ) {
            val verifiedBadgeSize = (nameFontSize.value * 0.8f).toInt()
            // Name + Verified Badge
            Row(
                    horizontalArrangement = Arrangement.Center
            ) {
                Text(
                        text = name,
                        fontSize = nameFontSize,
                        fontWeight = FontWeight.SemiBold,
                        color = textColor,
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis,
                        modifier = Modifier.widthIn(max = 220.dp).alignByBaseline(),
                        textAlign = TextAlign.Center
                )

                if (verified > 0 || isSystemAccount) {
                    Box(
                            modifier =
                                    Modifier.padding(start = 4.dp)
                                            .alignBy { (it.measuredHeight * 0.78f).toInt() }
                    ) {
                        VerifiedBadge(
                                verified = if (verified > 0) verified else 1,
                                size = verifiedBadgeSize,
                                isDarkTheme = isDarkTheme,
                                badgeTint = Color.White
                        )
                    }
                }
            }

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

            // Online/Offline status
            val statusText = formatOnlineStatus(isOnline)
            Text(
                    text = statusText,
                    fontSize = onlineFontSize,
                    color = Color.White
            )
        }
    }
}

// ═════════════════════════════════════════════════════════════
// 🖼 FULL SIZE AVATAR FOR OTHER PROFILE - Fills entire container
// ═════════════════════════════════════════════════════════════
@Composable
private fun OtherProfileFullSizeAvatar(
        publicKey: String,
        avatarRepository: AvatarRepository?,
        isDarkTheme: Boolean = false
) {
    val avatars by
            avatarRepository?.getAvatars(publicKey, allDecode = false)?.collectAsState()
                    ?: remember { mutableStateOf(emptyList()) }

    // Сохраняем bitmap в remember чтобы не мигал при recomposition
    var bitmap by remember { mutableStateOf<android.graphics.Bitmap?>(null) }
    var isLoading by remember { mutableStateOf(true) }

    LaunchedEffect(avatars) {
        if (avatars.isNotEmpty()) {
            val newBitmap = withContext(Dispatchers.IO) {
                AvatarFileManager.base64ToBitmap(avatars.first().base64Data)
            }
            bitmap = newBitmap
            isLoading = false
        } else {
            isLoading = false
        }
    }

    when {
        bitmap != null -> {
            Image(
                    bitmap = bitmap!!.asImageBitmap(),
                    contentDescription = "Avatar",
                    modifier = Modifier.fillMaxSize(),
                    contentScale = ContentScale.Crop
            )
        }
        !isLoading -> {
            // Placeholder только когда точно нет аватарки
            val avatarColors = getAvatarColor(publicKey, isDarkTheme)
            Box(
                    modifier = Modifier.fillMaxSize().background(avatarColors.backgroundColor),
                    contentAlignment = Alignment.Center
            ) {
                Text(
                        text = publicKey.take(2).uppercase(),
                        color = avatarColors.textColor,
                        fontSize = 80.sp,
                        fontWeight = FontWeight.Bold
                )
            }
        }
        // Пока isLoading=true - ничего не показываем (прозрачно)
    }
}

// ═══════════════════════════════════════════════════════════
// 🚫 BLOCK/UNBLOCK ITEM
// ═══════════════════════════════════════════════════════════
@Composable
private fun TelegramBlockItem(isBlocked: Boolean, onToggle: () -> Unit, isDarkTheme: Boolean) {
    val iconColor = PrimaryBlue

    Row(
            modifier =
                    Modifier.fillMaxWidth()
                            .clickable(onClick = onToggle)
                            .padding(horizontal = 16.dp, vertical = 14.dp),
            verticalAlignment = Alignment.CenterVertically
    ) {
        Icon(
                imageVector = Icons.Outlined.Block,
                contentDescription = null,
                tint = iconColor,
                modifier = Modifier.size(24.dp)
        )

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

        Column(modifier = Modifier.weight(1f)) {
            Text(
                    text = if (isBlocked) "Unblock User" else "Block User",
                    fontSize = 16.sp,
                    color = iconColor,
                    fontWeight = FontWeight.Medium
            )
            Spacer(modifier = Modifier.height(2.dp))
            Text(
                    text =
                            if (isBlocked) "Allow this user to message you"
                            else "Prevent this user from messaging you",
                    fontSize = 13.sp,
                    color = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666),
                    lineHeight = 16.sp
            )
        }
    }
}
