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

import android.content.Context
import android.content.ContentValues
import android.graphics.Bitmap
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerDefaults
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.input.pointer.*
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.toSize
import com.rosetta.messenger.crypto.CryptoManager
import com.rosetta.messenger.crypto.MessageCrypto
import com.rosetta.messenger.network.AttachmentType
import com.rosetta.messenger.network.TransportManager
import com.rosetta.messenger.ui.chats.models.ChatMessage
import com.rosetta.messenger.ui.components.AppleEmojiText
import com.rosetta.messenger.utils.AttachmentFileManager
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.abs
import kotlin.math.absoluteValue
import kotlin.math.roundToInt

private const val TAG = "ImageViewerScreen"
private const val EDGE_TAP_FADE_DURATION_MS = 120
private const val EDGE_TAP_FADE_START_ALPHA = 0.6f
private const val IMAGE_VIEWER_CACHE_SIZE = 8

/**
 * Telegram-style CubicBezierInterpolator (0.25, 0.1, 0.25, 1.0)
 * Это основной интерполятор используемый в Telegram для открытия фото
 */
private val TelegramEasing = CubicBezierEasing(0.25f, 0.1f, 0.25f, 1.0f)

/**
 * Linear interpolation helper
 */
private fun lerp(start: Float, stop: Float, fraction: Float): Float {
    return start + (stop - start) * fraction
}

/**
 * Transform data for shared element animation
 */
private data class ImageTransform(
    val scaleX: Float,
    val scaleY: Float,
    val translationX: Float,
    val translationY: Float,
    val cornerRadius: Float
)

/**
 * Данные об источнике изображения для shared element transition
 */
data class ImageSourceBounds(
    val left: Float,
    val top: Float,
    val width: Float,
    val height: Float,
    val cornerRadius: Float = 16f,
    val thumbnailBitmap: Bitmap? = null
)

/**
 * Данные для просмотра изображения
 */
data class ViewableImage(
    val attachmentId: String,
    val preview: String,
    val blob: String,
    val chachaKey: String,
    val senderPublicKey: String,
    val senderName: String,
    val timestamp: Date,
    val width: Int = 0,
    val height: Int = 0,
    val caption: String = "",
    val chachaKeyPlainHex: String = "",
    val transportTag: String = "",
    val transportServer: String = ""
)

/**
 * 📸 Полноэкранный просмотрщик фото в стиле Telegram
 *
 * Функции:
 * - Shared element transition анимация открытия/закрытия (как в Telegram)
 * - Свайп влево/вправо для листания фото
 * - Pinch-to-zoom
 * - Свайп вниз для закрытия
 * - Плавные анимации 200ms с CubicBezier easing
 * - Индикатор позиции
 */
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ImageViewerScreen(
    images: List<ViewableImage>,
    initialIndex: Int,
    privateKey: String,
    onDismiss: () -> Unit,
    onClosingStart: () -> Unit = {}, // Вызывается сразу при начале закрытия (для status bar)
    isDarkTheme: Boolean = true,
    sourceBounds: ImageSourceBounds? = null
) {
    if (images.isEmpty()) {
        onDismiss()
        return
    }

    val scope = rememberCoroutineScope()
    val context = LocalContext.current
    val view = LocalView.current
    val focusManager = LocalFocusManager.current
    val density = LocalDensity.current

    // ═══════════════════════════════════════════════════════════
    // 🎬 TELEGRAM-STYLE SHARED ELEMENT ANIMATION
    // ═══════════════════════════════════════════════════════════

    // Animation state: 0 = animating in, 1 = fully open, 2 = animating out
    var animationState by remember { mutableStateOf(if (sourceBounds != null) 0 else 1) }
    var isClosing by remember { mutableStateOf(false) }

    // Animation progress (0 = at source, 1 = fullscreen)
    val animationProgress = remember { Animatable(if (sourceBounds != null) 0f else 1f) }

    // Screen size
    var screenSize by remember { mutableStateOf(IntSize.Zero) }

    // Start enter animation
    LaunchedEffect(Unit) {
        if (sourceBounds != null && animationState == 0) {
            animationProgress.animateTo(
                targetValue = 1f,
                animationSpec = tween(
                    durationMillis = 200,
                    easing = TelegramEasing
                )
            )
            animationState = 1
        }
    }

    // Close with animation - красиво возвращаемся к пузырьку
    fun closeWithAnimation() {
        if (isClosing) return
        isClosing = true
        onClosingStart() // Сразу сбрасываем status bar

        scope.launch {
            if (sourceBounds != null) {
                // Плавно возвращаемся к исходному положению пузырька
                animationProgress.animateTo(
                    targetValue = 0f,
                    animationSpec = tween(
                        durationMillis = 280,
                        easing = FastOutSlowInEasing
                    )
                )
            }
            onDismiss()
        }
    }

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

    // Pager state
    val pagerState = rememberPagerState(
        initialPage = initialIndex.coerceIn(0, images.size - 1),
        pageCount = { images.size }
    )

    // UI visibility state
    var showControls by remember { mutableStateOf(true) }
    var showKebabMenu by remember { mutableStateOf(false) }
    var isSavingToGallery by remember { mutableStateOf(false) }
    var isTapNavigationInProgress by remember { mutableStateOf(false) }
    val edgeTapFadeAlpha = remember { Animatable(1f) }
    val imageBitmapCache =
        remember {
            object : LinkedHashMap<String, Bitmap>(IMAGE_VIEWER_CACHE_SIZE, 0.75f, true) {
                override fun removeEldestEntry(
                    eldest: MutableMap.MutableEntry<String, Bitmap>?
                ): Boolean = size > IMAGE_VIEWER_CACHE_SIZE
            }
        }

    fun getCachedBitmap(attachmentId: String): Bitmap? =
        synchronized(imageBitmapCache) { imageBitmapCache[attachmentId] }
            ?: ImageBitmapCache.get("img_$attachmentId")

    fun cacheBitmap(attachmentId: String, bitmap: Bitmap) {
        synchronized(imageBitmapCache) { imageBitmapCache[attachmentId] = bitmap }
    }

    // ═══════════════════════════════════════════════════════════
    // 🎬 SHARED ELEMENT TRANSFORM - вычисляем трансформацию
    // ═══════════════════════════════════════════════════════════
    val imageTransform by remember(sourceBounds, screenSize, animationProgress.value) {
        derivedStateOf {
            if (sourceBounds != null && screenSize != IntSize.Zero) {
                val progress = animationProgress.value
                
                // Вычисляем scale для перехода от источника к fullscreen
                val sourceScaleX = sourceBounds.width / screenSize.width.toFloat()
                val sourceScaleY = sourceBounds.height / screenSize.height.toFloat()
                
                val currentScaleX = lerp(sourceScaleX, 1f, progress)
                val currentScaleY = lerp(sourceScaleY, 1f, progress)
                
                // Вычисляем translation для центрирования
                val targetCenterX = screenSize.width / 2f
                val targetCenterY = screenSize.height / 2f
                val sourceCenterX = sourceBounds.left + sourceBounds.width / 2f
                val sourceCenterY = sourceBounds.top + sourceBounds.height / 2f
                
                val currentTranslationX = lerp(sourceCenterX - targetCenterX, 0f, progress)
                val currentTranslationY = lerp(sourceCenterY - targetCenterY, 0f, progress)
                
                // Скругление углов: от sourceBounds.cornerRadius до 0
                val currentCornerRadius = lerp(sourceBounds.cornerRadius, 0f, progress)
                
                ImageTransform(
                    scaleX = currentScaleX,
                    scaleY = currentScaleY,
                    translationX = currentTranslationX,
                    translationY = currentTranslationY,
                    cornerRadius = currentCornerRadius
                )
            } else {
                ImageTransform(1f, 1f, 0f, 0f, 0f)
            }
        }
    }

    // ═══════════════════════════════════════════════════════════
    // 🎬 SWIPE TO DISMISS
    // ═══════════════════════════════════════════════════════════
    var dragOffsetY by remember { mutableFloatStateOf(0f) }
    var isDragging by remember { mutableStateOf(false) }
    var dragVelocity by remember { mutableFloatStateOf(0f) }
    val dismissThreshold = 100f
    val velocityThreshold = 500f
    
    // Анимированные значения
    val animatedOffsetY = remember { Animatable(0f) }
    val dismissAlpha = remember { Animatable(1f) }
    
    // Синхронизируем drag с анимацией
    LaunchedEffect(isDragging, dragOffsetY) {
        if (isDragging) {
            animatedOffsetY.snapTo(dragOffsetY)
        }
    }

    // 🎬 Swipe dismiss - плавный fade out на месте
    fun smoothDismiss() {
        if (isClosing) return
        isClosing = true
        onClosingStart()

        scope.launch {
            // Простой быстрый fade out — как в Telegram
            dismissAlpha.animateTo(
                targetValue = 0f,
                animationSpec = tween(
                    durationMillis = 200,
                    easing = FastOutSlowInEasing
                )
            )
            onDismiss()
        }
    }
    
    // Плавный возврат на место
    fun snapBack() {
        scope.launch {
            animatedOffsetY.animateTo(
                targetValue = 0f,
                animationSpec = tween(
                    durationMillis = 250,
                    easing = FastOutSlowInEasing
                )
            )
        }
    }

    // Alpha на основе drag прогресса
    val dismissProgress = (animatedOffsetY.value.absoluteValue / 300f).coerceIn(0f, 1f)
    val backgroundAlpha = animationProgress.value * dismissAlpha.value * (1f - dismissProgress * 0.7f)

    // Current image info
    val currentImage = images.getOrNull(pagerState.currentPage)
    val dateFormat = remember { SimpleDateFormat("d MMMM, HH:mm", Locale.getDefault()) }

    // Prefetch ближайших изображений, чтобы при свайпе не было спиннера.
    LaunchedEffect(pagerState.currentPage, images, privateKey) {
        val current = pagerState.currentPage
        val prefetchIndexes =
            listOf(current - 2, current - 1, current + 1, current + 2).filter { it in images.indices }

        prefetchIndexes.forEach { index ->
            val prefetchImage = images[index]
            if (getCachedBitmap(prefetchImage.attachmentId) != null) return@forEach

            launch(Dispatchers.IO) {
                val bitmap = loadBitmapForViewerImage(context, prefetchImage, privateKey)
                if (bitmap != null) {
                    cacheBitmap(prefetchImage.attachmentId, bitmap)
                }
            }
        }
    }

    BackHandler {
        closeWithAnimation()
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .onSizeChanged { screenSize = it }
            .background(Color.Black.copy(alpha = backgroundAlpha))
    ) {
        // ═══════════════════════════════════════════════════════════
        // 📸 HORIZONTAL PAGER с Telegram-style эффектом наслаивания
        // ═══════════════════════════════════════════════════════════
        
        // 🔥 Применяем shared element transform только для текущей страницы при анимации
        val isAnimating = animationProgress.value < 1f
        val shouldApplyTransform = isAnimating && pagerState.currentPage == initialIndex
        
        HorizontalPager(
            state = pagerState,
            modifier = Modifier
                .fillMaxSize()
                .graphicsLayer {
                    alpha = dismissAlpha.value * edgeTapFadeAlpha.value
                    translationY = animatedOffsetY.value
                    
                    // 🔥 Shared element transform при входе/выходе
                    if (shouldApplyTransform) {
                        scaleX = imageTransform.scaleX
                        scaleY = imageTransform.scaleY
                        translationX = imageTransform.translationX
                        translationY = imageTransform.translationY
                    }
                }
                .then(
                    if (shouldApplyTransform && imageTransform.cornerRadius > 0f) {
                        Modifier.clip(RoundedCornerShape(imageTransform.cornerRadius.dp))
                    } else {
                        Modifier
                    }
                ),
            pageSpacing = 30.dp, // Telegram: dp(30) между фото
            key = { page -> "${page}_${images.getOrNull(page)?.attachmentId ?: "empty"}" },
            userScrollEnabled = !isAnimating, // Отключаем скролл во время анимации
            beyondBoundsPageCount = 1,
            flingBehavior = PagerDefaults.flingBehavior(
                state = pagerState,
                snapPositionalThreshold = 0.5f
            )
        ) { page ->
            val image = images.getOrNull(page) ?: return@HorizontalPager
            
            // 🎬 Telegram-style наслаивание: правая страница уменьшается и затемняется
            val pageOffset = (pagerState.currentPage - page) + pagerState.currentPageOffsetFraction
            // Эффект только для соседней правой страницы, не для текущей на границе
            val isRightPage = pageOffset < 0 && page != pagerState.currentPage
            
            // Scale эффект только для правой страницы (1.0 → 0.7)
            val scaleFactor = if (isRightPage) {
                val progress = (-pageOffset).coerceIn(0f, 1f)
                1f - (progress * 0.3f)
            } else 1f
            
            // Alpha для правой страницы
            val pageAlpha = if (isRightPage) {
                1f - ((-pageOffset).coerceIn(0f, 1f) * 0.3f)
            } else 1f

            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .graphicsLayer {
                        scaleX = scaleFactor
                        scaleY = scaleFactor
                        alpha = pageAlpha
                    }
            ) {
                ZoomableImage(
                image = image,
                privateKey = privateKey,
                cachedBitmap = getCachedBitmap(image.attachmentId),
                onBitmapLoaded = { id, bitmap -> cacheBitmap(id, bitmap) },
                onTap = { tapOffset ->
                    // 👆 Tap on left/right edge (20% zone) to navigate
                    val edgeZone = screenSize.width * 0.20f
                    val tapX = tapOffset.x
                    val screenW = screenSize.width.toFloat()

                    fun animateToPage(targetPage: Int) {
                        if (targetPage == pagerState.currentPage) return
                        if (isTapNavigationInProgress || pagerState.isScrollInProgress) return

                        scope.launch {
                            isTapNavigationInProgress = true
                            try {
                                // Мгновенный отклик: сразу переключаем страницу и даём короткий fade-in.
                                edgeTapFadeAlpha.stop()
                                edgeTapFadeAlpha.snapTo(EDGE_TAP_FADE_START_ALPHA)
                                pagerState.scrollToPage(targetPage)
                                edgeTapFadeAlpha.animateTo(
                                    targetValue = 1f,
                                    animationSpec = tween(
                                        durationMillis = EDGE_TAP_FADE_DURATION_MS,
                                        easing = FastOutSlowInEasing
                                    )
                                )
                            } finally {
                                isTapNavigationInProgress = false
                            }
                        }
                    }

                    when {
                        tapX < edgeZone && pagerState.currentPage > 0 -> {
                            animateToPage(pagerState.currentPage - 1)
                        }
                        tapX > screenW - edgeZone && pagerState.currentPage < images.size - 1 -> {
                            animateToPage(pagerState.currentPage + 1)
                        }
                        else -> showControls = !showControls
                    }
                },
                onVerticalDrag = { dragAmount, velocity ->
                    dragOffsetY += dragAmount
                    dragVelocity = velocity
                    isDragging = true
                },
                onDragEnd = {
                    isDragging = false
                    val shouldDismiss = dragOffsetY.absoluteValue > dismissThreshold || 
                                        dragVelocity.absoluteValue > velocityThreshold
                    if (shouldDismiss) {
                        smoothDismiss()
                    } else {
                        dragOffsetY = 0f
                        dragVelocity = 0f
                        snapBack()
                    }
                }
                )
            } // Закрытие Box wrapper
        }
        
        // ═══════════════════════════════════════════════════════════
        // 🎛️ TOP BAR - Telegram-style (200ms, 24dp slide)
        // ═══════════════════════════════════════════════════════════
        AnimatedVisibility(
            visible = showControls && animationState == 1 && !isClosing,
            enter = fadeIn(tween(200, easing = FastOutSlowInEasing)) + 
                    slideInVertically(tween(200, easing = FastOutSlowInEasing)) { -96 }, // ~24dp
            exit = fadeOut(tween(200, easing = FastOutSlowInEasing)) + 
                   slideOutVertically(tween(200, easing = FastOutSlowInEasing)) { -96 },
            modifier = Modifier.align(Alignment.TopCenter)
        ) {
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .background(
                        Color.Black.copy(alpha = 0.5f)
                    )
                    .statusBarsPadding()
                    .padding(horizontal = 4.dp, vertical = 8.dp)
            ) {
                // Back button
                IconButton(
                    onClick = { closeWithAnimation() },
                    modifier = Modifier.align(Alignment.CenterStart)
                ) {
                    Icon(
                        imageVector = TablerIcons.ChevronLeft,
                        contentDescription = "Close",
                        tint = Color.White
                    )
                }

                // Kebab menu (save image)
                Box(modifier = Modifier.align(Alignment.CenterEnd)) {
                    IconButton(
                        onClick = { showKebabMenu = true },
                        enabled = currentImage != null && !isSavingToGallery
                    ) {
                        Icon(
                            imageVector = Icons.Default.MoreVert,
                            contentDescription = "More",
                            tint = Color.White
                        )
                    }

                    DropdownMenu(
                        expanded = showKebabMenu,
                        onDismissRequest = { showKebabMenu = false }
                    ) {
                        DropdownMenuItem(
                            text = {
                                Text(
                                    text = if (isSavingToGallery) "Saving..." else "Save to Gallery"
                                )
                            },
                            enabled = !isSavingToGallery,
                            onClick = {
                                val imageToSave = currentImage ?: return@DropdownMenuItem
                                showKebabMenu = false
                                if (isSavingToGallery) return@DropdownMenuItem

                                scope.launch {
                                    isSavingToGallery = true
                                    try {
                                        val cachedBitmap = getCachedBitmap(imageToSave.attachmentId)
                                        val bitmapToSave =
                                            cachedBitmap ?: withContext(Dispatchers.IO) {
                                                loadBitmapForViewerImage(context, imageToSave, privateKey)
                                            }

                                        if (bitmapToSave != null && cachedBitmap == null) {
                                            cacheBitmap(imageToSave.attachmentId, bitmapToSave)
                                        }

                                        val saved =
                                            if (bitmapToSave != null) {
                                                withContext(Dispatchers.IO) {
                                                    saveBitmapToGallery(context, bitmapToSave, imageToSave)
                                                }
                                            } else {
                                                false
                                            }

                                        Toast.makeText(
                                            context,
                                            if (saved) "Saved to gallery" else "Failed to save image",
                                            Toast.LENGTH_SHORT
                                        ).show()
                                    } catch (_: Exception) {
                                        Toast.makeText(
                                            context,
                                            "Failed to save image",
                                            Toast.LENGTH_SHORT
                                        ).show()
                                    } finally {
                                        isSavingToGallery = false
                                    }
                                }
                            }
                        )
                    }
                }
                
                // Title and date
                Column(
                    modifier = Modifier
                        .align(Alignment.Center)
                        .padding(horizontal = 56.dp),
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    Text(
                        text = currentImage?.senderName ?: "",
                        color = Color.White,
                        fontSize = 16.sp,
                        fontWeight = FontWeight.SemiBold,
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis
                    )
                    Text(
                        text = currentImage?.timestamp?.let { dateFormat.format(it) } ?: "",
                        color = Color.White.copy(alpha = 0.7f),
                        fontSize = 13.sp
                    )
                }
            }
        }

        // ═══════════════════════════════════════════════════════════
        // 📝 CAPTION BAR - Telegram-style снизу с анимацией
        // ═══════════════════════════════════════════════════════════
        val rawCaption = currentImage?.caption ?: ""
        // Filter out encrypted/garbled captions (base64 strings without spaces)
        val currentCaption = if (rawCaption.isNotBlank() && !rawCaption.contains(' ') && rawCaption.length > 40 && rawCaption.matches(Regex("^[A-Za-z0-9+/=:]+$"))) "" else rawCaption
        if (currentCaption.isNotEmpty()) {
            AnimatedVisibility(
                visible = showControls && animationState == 1 && !isClosing,
                enter = fadeIn(tween(200, easing = FastOutSlowInEasing)) +
                        slideInVertically(tween(200, easing = FastOutSlowInEasing)) { 96 },
                exit = fadeOut(tween(200, easing = FastOutSlowInEasing)) +
                       slideOutVertically(tween(200, easing = FastOutSlowInEasing)) { 96 },
                modifier = Modifier
                    .align(Alignment.BottomCenter)
                    .navigationBarsPadding()
            ) {
                Box(
                    modifier = Modifier
                        .fillMaxWidth()
                        .background(Color.Black.copy(alpha = 0.5f))
                        .padding(horizontal = 16.dp, vertical = 16.dp)
                ) {
                    AppleEmojiText(
                        text = currentCaption,
                        color = Color.White,
                        fontSize = 15.sp,
                        maxLines = 4,
                        overflow = android.text.TextUtils.TruncateAt.END
                    )
                }
            }
        }
    }
}

/**
 * 🔍 Zoomable Image - Telegram style
 * - Double tap to zoom 2.5x / reset
 * - Pinch to zoom 1x-5x
 * - Pan when zoomed
 * - Vertical drag to dismiss when not zoomed (with velocity tracking)
 */
@Composable
private fun ZoomableImage(
    image: ViewableImage,
    privateKey: String,
    cachedBitmap: Bitmap? = null,
    onBitmapLoaded: (String, Bitmap) -> Unit = { _, _ -> },
    onTap: (Offset) -> Unit,
    onVerticalDrag: (Float, Float) -> Unit = { _, _ -> }, // dragAmount, velocity
    onDragEnd: () -> Unit = {}
) {
    val context = LocalContext.current
    val scope = rememberCoroutineScope()
    
    var bitmap by remember(image.attachmentId, cachedBitmap) { mutableStateOf(cachedBitmap) }
    var previewBitmap by remember { mutableStateOf<Bitmap?>(null) }
    var isLoading by remember(image.attachmentId, cachedBitmap) { mutableStateOf(cachedBitmap == null) }
    var loadError by remember { mutableStateOf<String?>(null) }
    
    // Zoom and pan state with animation
    var scale by remember { mutableFloatStateOf(1f) }
    var offsetX by remember { mutableFloatStateOf(0f) }
    var offsetY by remember { mutableFloatStateOf(0f) }
    
    // 🔥 Telegram-style smooth animations
    val animatedScale by animateFloatAsState(
        targetValue = scale,
        animationSpec = spring(dampingRatio = 0.9f, stiffness = 400f),
        label = "scale"
    )
    val animatedOffsetX by animateFloatAsState(
        targetValue = offsetX,
        animationSpec = spring(dampingRatio = 0.9f, stiffness = 400f),
        label = "offsetX"
    )
    val animatedOffsetY by animateFloatAsState(
        targetValue = offsetY,
        animationSpec = spring(dampingRatio = 0.9f, stiffness = 400f),
        label = "offsetY"
    )
    
    // Container size
    var containerSize by remember { mutableStateOf(IntSize.Zero) }
    
    val minScale = 1f
    val maxScale = 5f
    
    // Load preview first
    LaunchedEffect(image.preview) {
        withContext(Dispatchers.IO) {
            try {
                // Декодируем blur preview (после ::)
                if (image.preview.contains("::")) {
                    val blurPart = image.preview.substringAfter("::")
                    previewBitmap = base64ToBitmapSafe(blurPart)
                }
            } catch (e: Exception) {
            }
        }
    }
    
    // Load image
    LaunchedEffect(image.attachmentId, cachedBitmap) {
        if (cachedBitmap != null) {
            bitmap = cachedBitmap
            loadError = null
            isLoading = false
            return@LaunchedEffect
        }

        isLoading = true
        loadError = null
        
        val loadedBitmap = withContext(Dispatchers.IO) { loadBitmapForViewerImage(context, image, privateKey) }
        if (loadedBitmap != null) {
            bitmap = loadedBitmap
            onBitmapLoaded(image.attachmentId, loadedBitmap)
            loadError = null
        } else {
            loadError = "Failed to load image"
        }
        isLoading = false
    }
    
    // Reset zoom when image changes
    LaunchedEffect(image.attachmentId) {
        scale = 1f
        offsetX = 0f
        offsetY = 0f
    }
    
    Box(
        modifier = Modifier
            .fillMaxSize()
            .onSizeChanged { containerSize = it }
            .pointerInput(Unit) {
                detectTapGestures(
                    onTap = { offset -> onTap(offset) },
                    onDoubleTap = { tapOffset ->
                        if (scale > 1.1f) {
                            // Zoom out - плавно возвращаемся
                            scale = 1f
                            offsetX = 0f
                            offsetY = 0f
                        } else {
                            // 🔥 Telegram-style: zoom in к точке тапа
                            val targetScale = 2.5f
                            val centerX = containerSize.width / 2f
                            val centerY = containerSize.height / 2f

                            // Вычисляем offset так, чтобы точка тапа осталась на месте
                            val tapX = tapOffset.x - centerX
                            val tapY = tapOffset.y - centerY

                            scale = targetScale
                            offsetX = tapX - tapX * targetScale
                            offsetY = tapY - tapY * targetScale

                            // Ограничиваем offset
                            val maxX = (containerSize.width * (targetScale - 1) / 2f)
                            val maxY = (containerSize.height * (targetScale - 1) / 2f)
                            offsetX = offsetX.coerceIn(-maxX, maxX)
                            offsetY = offsetY.coerceIn(-maxY, maxY)
                        }
                    }
                )
            }
            .pointerInput(Unit) {
                var isVerticalDragging = false
                var lastDragTime = 0L
                var lastDragY = 0f
                var currentVelocity = 0f
                val touchSlopValue = 20f

                awaitEachGesture {
                    val down = awaitFirstDown(requireUnconsumed = false)
                    lastDragTime = System.currentTimeMillis()
                    lastDragY = down.position.y
                    currentVelocity = 0f

                    var zoom = 1f
                    var pastTouchSlop = false
                    var lockedToDismiss = false

                    do {
                        val event = awaitPointerEvent()
                        val canceled = event.changes.any { it.isConsumed }

                        if (!canceled) {
                            val zoomChange = event.calculateZoom()
                            val panChange = event.calculatePan()
                            // 🔥 Telegram-style: получаем центр жеста для zoom к точке
                            val centroid = event.calculateCentroid(useCurrent = false)

                            if (!pastTouchSlop) {
                                zoom *= zoomChange
                                val centroidSize = event.calculateCentroidSize(useCurrent = false)
                                val touchMoved = abs(panChange.x) > touchSlopValue || abs(panChange.y) > touchSlopValue
                                val zoomMotion = abs(1 - zoom) * centroidSize > touchSlopValue

                                if (touchMoved || zoomMotion) {
                                    pastTouchSlop = true

                                    // Vertical dismiss only when not zoomed
                                    if (scale <= 1.05f && zoomChange == 1f &&
                                        abs(panChange.y) > abs(panChange.x) * 1.5f) {
                                        lockedToDismiss = true
                                        isVerticalDragging = true
                                    }
                                }
                            }

                            if (pastTouchSlop) {
                                if (lockedToDismiss) {
                                    val currentTime = System.currentTimeMillis()
                                    val currentY = event.changes.firstOrNull()?.position?.y ?: lastDragY
                                    val timeDelta = (currentTime - lastDragTime).coerceAtLeast(1L)
                                    val positionDelta = currentY - lastDragY
                                    currentVelocity = (positionDelta / timeDelta) * 1000f
                                    lastDragTime = currentTime
                                    lastDragY = currentY

                                    onVerticalDrag(panChange.y, currentVelocity)
                                    event.changes.forEach { it.consume() }
                                } else {
                                    // 🔥 Telegram-style pinch zoom к центру жеста
                                    val oldScale = scale
                                    val newScale = (scale * zoomChange).coerceIn(minScale, maxScale)

                                    // Центр контейнера
                                    val centerX = containerSize.width / 2f
                                    val centerY = containerSize.height / 2f

                                    // Вычисляем новый offset так, чтобы точка под пальцами оставалась на месте
                                    var newOffsetX = offsetX
                                    var newOffsetY = offsetY

                                    if (zoomChange != 1f && centroid != Offset.Unspecified) {
                                        // Точка жеста относительно центра
                                        val gestureX = centroid.x - centerX
                                        val gestureY = centroid.y - centerY

                                        // Корректируем offset чтобы точка под пальцами не двигалась
                                        val scaleRatio = newScale / oldScale
                                        newOffsetX = gestureX - (gestureX - offsetX) * scaleRatio
                                        newOffsetY = gestureY - (gestureY - offsetY) * scaleRatio
                                    }

                                    // Добавляем pan
                                    newOffsetX += panChange.x
                                    newOffsetY += panChange.y

                                    // Ограничиваем offset
                                    val maxX = (containerSize.width * (newScale - 1) / 2f).coerceAtLeast(0f)
                                    val maxY = (containerSize.height * (newScale - 1) / 2f).coerceAtLeast(0f)

                                    scale = newScale
                                    offsetX = newOffsetX.coerceIn(-maxX, maxX)
                                    offsetY = newOffsetY.coerceIn(-maxY, maxY)

                                    if (scale > 1.05f) {
                                        event.changes.forEach { it.consume() }
                                    }
                                }
                            }
                        }
                    } while (event.changes.any { it.pressed })

                    if (isVerticalDragging) {
                        isVerticalDragging = false
                        onDragEnd()
                    }

                    // Snap back if scale is close to 1
                    if (scale < 1.05f) {
                        scale = 1f
                        offsetX = 0f
                        offsetY = 0f
                    }
                }
            },
        contentAlignment = Alignment.Center
    ) {
        // Blur preview в фоне
        if (previewBitmap != null && bitmap == null) {
            Image(
                bitmap = previewBitmap!!.asImageBitmap(),
                contentDescription = "Preview",
                modifier = Modifier.fillMaxSize(),
                contentScale = ContentScale.Fit
            )
        }
        
        when {
            isLoading -> {
                CircularProgressIndicator(
                    color = Color.White,
                    modifier = Modifier.size(48.dp)
                )
            }
            loadError != null -> {
                Column(
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.spacedBy(8.dp)
                ) {
                    Icon(
                        imageVector = TablerIcons.PhotoOff,
                        contentDescription = null,
                        tint = Color.White.copy(alpha = 0.5f),
                        modifier = Modifier.size(48.dp)
                    )
                    Text(
                        text = "Failed to load image",
                        color = Color.White.copy(alpha = 0.5f),
                        fontSize = 14.sp
                    )
                }
            }
            bitmap != null -> {
                Image(
                    bitmap = bitmap!!.asImageBitmap(),
                    contentDescription = "Photo",
                    modifier = Modifier
                        .fillMaxSize()
                        .graphicsLayer {
                            scaleX = animatedScale
                            scaleY = animatedScale
                            translationX = animatedOffsetX
                            translationY = animatedOffsetY
                        },
                    contentScale = ContentScale.Fit
                )
            }
        }
    }
}

/**
 * Загружает Bitmap для ImageViewer:
 * 1) из in-message blob
 * 2) из локального encrypted attachment файла
 * 3) с transport (с последующим сохранением в локальный файл)
 */
private fun viewerLog(context: Context, msg: String) {
    try {
        val ts = java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.getDefault()).format(java.util.Date())
        val dir = java.io.File(context.filesDir, "crash_reports")
        if (!dir.exists()) dir.mkdirs()
        java.io.File(dir, "image_logs.txt").appendText("$ts [Viewer] $msg\n")
    } catch (_: Exception) {}
}

private suspend fun loadBitmapForViewerImage(
    context: Context,
    image: ViewableImage,
    privateKey: String
): Bitmap? {
    val id = image.attachmentId.take(10)
    viewerLog(context, "=== LOAD id=$id blob=${image.blob.length} preview=${image.preview.length} chacha=${image.chachaKey.length} tag=${image.transportTag.take(12)} sender=${image.senderPublicKey.take(12)} ===")
    return try {
        // 0. In-memory кэш
        val cached = ImageBitmapCache.get("img_${image.attachmentId}")
        if (cached != null) {
            viewerLog(context, "  [0] HIT cache ${cached.width}x${cached.height}")
            return cached
        }
        viewerLog(context, "  [0] MISS cache")

        // 1. Blob в сообщении
        if (image.blob.isNotEmpty()) {
            viewerLog(context, "  [1] blob ${image.blob.length} chars")
            val bmp = base64ToBitmapSafe(image.blob)
            if (bmp != null) { viewerLog(context, "  [1] OK ${bmp.width}x${bmp.height}"); return bmp }
            viewerLog(context, "  [1] decode FAIL")
        }

        // 2. Локальный encrypted cache
        viewerLog(context, "  [2] readAttachment sender=${image.senderPublicKey.take(12)}")
        val localBlob =
            AttachmentFileManager.readAttachment(context, image.attachmentId, image.senderPublicKey, privateKey)
        if (localBlob != null) {
            viewerLog(context, "  [2] local ${localBlob.length} chars")
            val bmp = base64ToBitmapSafe(localBlob)
            if (bmp != null) { viewerLog(context, "  [2] OK ${bmp.width}x${bmp.height}"); return bmp }
            viewerLog(context, "  [2] decode FAIL")
        } else { viewerLog(context, "  [2] NOT found") }

        // 2.5. Ждём bitmap из кеша
        viewerLog(context, "  [2.5] await 3s...")
        val awaitedFromCache = ImageBitmapCache.awaitCached("img_${image.attachmentId}", 3000)
        if (awaitedFromCache != null) {
            viewerLog(context, "  [2.5] OK ${awaitedFromCache.width}x${awaitedFromCache.height}")
            return awaitedFromCache
        }
        viewerLog(context, "  [2.5] timeout")

        // 3. CDN download
        var downloadTag = getDownloadTag(image.preview)
        if (downloadTag.isEmpty() && image.transportTag.isNotEmpty()) {
            downloadTag = image.transportTag
        }
        viewerLog(context, "  [3] tag=${downloadTag.take(12)} preview=${image.preview.take(20)}")
        if (downloadTag.isEmpty()) {
            viewerLog(context, "  [3] NO TAG → FAIL")
            return null
        }

        val server = TransportManager.getTransportServer() ?: "none"
        viewerLog(context, "  [3] downloading from $server")
        val encryptedContent = TransportManager.downloadFile(image.attachmentId, downloadTag)
        viewerLog(context, "  [3] downloaded ${encryptedContent.length} bytes")
        if (encryptedContent.isEmpty()) {
            viewerLog(context, "  [3] EMPTY → FAIL")
            return null
        }

        // Decrypt
        val decrypted: String? =
            if (image.chachaKeyPlainHex.isNotEmpty()) {
                val plainKey = image.chachaKeyPlainHex.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
                MessageCrypto.decryptAttachmentBlobWithPlainKey(encryptedContent, plainKey)
                    ?: MessageCrypto.decryptReplyBlob(encryptedContent, plainKey).takeIf { it.isNotEmpty() }
            } else if (image.chachaKey.startsWith("group:")) {
                val groupPassword = CryptoManager.decryptWithPassword(
                    image.chachaKey.removePrefix("group:"), privateKey
                )
                if (groupPassword != null) {
                    CryptoManager.decryptWithPassword(encryptedContent, groupPassword)
                        ?: run {
                            val hexKey = groupPassword.toByteArray(Charsets.ISO_8859_1)
                                .joinToString("") { "%02x".format(it.toInt() and 0xff) }
                            CryptoManager.decryptWithPassword(encryptedContent, hexKey)
                        }
                } else null
            } else if (image.chachaKey.isNotEmpty()) {
                val decryptedKeyAndNonce = MessageCrypto.decryptKeyFromSender(image.chachaKey, privateKey)
                MessageCrypto.decryptAttachmentBlobWithPlainKey(encryptedContent, decryptedKeyAndNonce)
            } else {
                null
            }

        viewerLog(context, "  [3] decrypt=${if (decrypted != null) "${decrypted.length} chars" else "NULL"} method=${when { image.chachaKeyPlainHex.isNotEmpty() -> "plainHex"; image.chachaKey.startsWith("group:") -> "group"; image.chachaKey.isNotEmpty() -> "chacha"; else -> "none" }}")
        if (decrypted == null) {
            viewerLog(context, "  [3] DECRYPT FAIL")
            return null
        }

        val decodedBitmap = base64ToBitmapSafe(decrypted)
        viewerLog(context, "  [3] bitmap=${if (decodedBitmap != null) "${decodedBitmap.width}x${decodedBitmap.height}" else "NULL"}")
        if (decodedBitmap == null) {
            return null
        }

        // Сохраняем локально
        AttachmentFileManager.saveAttachment(
            context = context,
            blob = decrypted,
            attachmentId = image.attachmentId,
            publicKey = image.senderPublicKey,
            privateKey = privateKey
        )
        viewerLog(context, "  DONE OK")
        decodedBitmap
    } catch (e: Exception) {
        viewerLog(context, "  EXCEPTION: ${e.javaClass.simpleName}: ${e.message}")
        null
    }
}

private fun saveBitmapToGallery(
    context: Context,
    bitmap: Bitmap,
    image: ViewableImage
): Boolean {
    val resolver = context.contentResolver
    val fileName = buildGalleryFileName(image)

    val values =
        ContentValues().apply {
            put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
            put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                put(
                    MediaStore.Images.Media.RELATIVE_PATH,
                    "${Environment.DIRECTORY_PICTURES}/Rosetta"
                )
                put(MediaStore.Images.Media.IS_PENDING, 1)
            }
        }

    val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) ?: return false

    return try {
        resolver.openOutputStream(uri)?.use { output ->
            if (!bitmap.compress(Bitmap.CompressFormat.JPEG, 95, output)) {
                throw IOException("Bitmap compression failed")
            }
        } ?: throw IOException("Cannot open output stream")

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            val finalizeValues =
                ContentValues().apply { put(MediaStore.Images.Media.IS_PENDING, 0) }
            resolver.update(uri, finalizeValues, null, null)
        }
        true
    } catch (_: Exception) {
        runCatching { resolver.delete(uri, null, null) }
        false
    }
}

private fun buildGalleryFileName(image: ViewableImage): String {
    val formatter = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US)
    val timePart = formatter.format(image.timestamp)
    val idPart = image.attachmentId.take(8)
    return "Rosetta_${timePart}_$idPart.jpg"
}

/**
 * Безопасное декодирование base64 в Bitmap
 */
private fun base64ToBitmapSafe(base64String: String): Bitmap? {
    return base64ToBitmap(base64String)
}

/**
 * Проверка является ли строка UUID
 */
private fun isUUID(str: String): Boolean {
    return str.matches(Regex("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"))
}

/**
 * 🔧 Helper: Извлечь все изображения из списка ChatMessage
 */
fun extractImagesFromMessages(
    messages: List<ChatMessage>,
    currentPublicKey: String,
    opponentPublicKey: String,
    opponentName: String
): List<ViewableImage> {
    val seenIds = mutableSetOf<String>()
    return messages
        .flatMap { message ->
            val mainImages = message.attachments
                .filter { it.type == AttachmentType.IMAGE }
                .map { attachment ->
                    ViewableImage(
                        attachmentId = attachment.id,
                        preview = attachment.preview,
                        blob = attachment.blob,
                        chachaKey = message.chachaKey,
                        senderPublicKey = if (message.isOutgoing) currentPublicKey else opponentPublicKey,
                        senderName = if (message.isOutgoing) "You" else opponentName,
                        timestamp = message.timestamp,
                        width = attachment.width,
                        height = attachment.height,
                        caption = message.text,
                        transportTag = attachment.transportTag,
                        transportServer = attachment.transportServer
                    )
                }

            // Also include images from reply/forward bubbles
            val replyImages = message.replyData?.attachments
                ?.filter { it.type == AttachmentType.IMAGE }
                ?.map { attachment ->
                    val replySenderKey = message.replyData.senderPublicKey.ifEmpty {
                        if (message.replyData.isFromMe) currentPublicKey else opponentPublicKey
                    }
                    val replyChachaKey = message.replyData.chachaKey.ifEmpty { message.chachaKey }
                    ViewableImage(
                        attachmentId = attachment.id,
                        preview = attachment.preview,
                        blob = attachment.blob,
                        chachaKey = replyChachaKey,
                        senderPublicKey = replySenderKey,
                        senderName = message.replyData.senderName,
                        timestamp = message.timestamp,
                        width = attachment.width,
                        height = attachment.height,
                        caption = message.replyData.text,
                        chachaKeyPlainHex = message.replyData.chachaKeyPlainHex,
                        transportTag = attachment.transportTag,
                        transportServer = attachment.transportServer
                    )
                } ?: emptyList()

            // Also include images from forwarded messages
            val forwardedImages = message.forwardedMessages.flatMap { fwd ->
                fwd.attachments
                    .filter { it.type == AttachmentType.IMAGE }
                    .map { attachment ->
                        val fwdSenderKey = fwd.senderPublicKey.ifEmpty {
                            if (fwd.isFromMe) currentPublicKey else opponentPublicKey
                        }
                        val fwdChachaKey = fwd.chachaKey.ifEmpty { message.chachaKey }
                        ViewableImage(
                            attachmentId = attachment.id,
                            preview = attachment.preview,
                            blob = attachment.blob,
                            chachaKey = fwdChachaKey,
                            senderPublicKey = fwdSenderKey,
                            senderName = fwd.senderName,
                            timestamp = message.timestamp,
                            width = attachment.width,
                            height = attachment.height,
                            caption = fwd.text,
                            chachaKeyPlainHex = fwd.chachaKeyPlainHex,
                            transportTag = attachment.transportTag,
                            transportServer = attachment.transportServer
                        )
                    }
            }

            mainImages + replyImages + forwardedImages
        }
        .filter { seenIds.add(it.attachmentId) }
        .sortedBy { it.timestamp }
}

/**
 * 🔧 Helper: Найти индекс изображения по attachmentId
 */
fun findImageIndex(images: List<ViewableImage>, attachmentId: String): Int {
    return images.indexOfFirst { it.attachmentId == attachmentId }.coerceAtLeast(0)
}
