package com.rosetta.messenger.ui.components

import android.content.Context
import android.content.Intent
import android.icu.text.BreakIterator
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Paint
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.text.Editable
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.TextPaint
import android.text.TextWatcher
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.text.style.DynamicDrawableSpan
import android.text.style.ForegroundColorSpan
import android.text.style.ImageSpan
import android.util.AttributeSet
import android.util.LruCache
import android.util.Patterns
import android.view.GestureDetector
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import android.widget.FrameLayout
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.foundation.background
import androidx.compose.ui.viewinterop.AndroidView
import java.util.Locale
import java.util.concurrent.ConcurrentHashMap
import java.util.regex.Pattern

private data class EmojiRenderMatch(
    val start: Int,
    val end: Int,
    val unified: String
)

private object AppleEmojiAssetResolver {
    @Volatile
    private var availableEmojiAssets: Set<String>? = null
    private val unifiedResolveCache = ConcurrentHashMap<String, String>()
    private val unresolvedUnifiedCache = ConcurrentHashMap.newKeySet<String>()

    fun normalizeUnifiedCode(code: String): String =
        code.trim().lowercase(Locale.ROOT).replace('_', '-')

    fun resolveUnifiedFromCode(context: Context, code: String): String? {
        val normalized = normalizeUnifiedCode(code)
        if (normalized.isBlank()) return null
        return resolveUnified(context, normalized)
    }

    fun collectUnicodeMatches(
        context: Context,
        text: String,
        occupiedRanges: List<IntRange> = emptyList()
    ): List<EmojiRenderMatch> {
        if (text.isEmpty()) return emptyList()

        val iterator = BreakIterator.getCharacterInstance(Locale.ROOT)
        iterator.setText(text)

        val matches = mutableListOf<EmojiRenderMatch>()
        var start = iterator.first()
        var end = iterator.next()

        while (end != BreakIterator.DONE) {
            if (start < end && !isOverlapping(start, end, occupiedRanges)) {
                val cluster = text.substring(start, end)
                val unified = resolveUnifiedFromCluster(context, cluster)
                if (!unified.isNullOrEmpty()) {
                    matches.add(
                        EmojiRenderMatch(
                            start = start,
                            end = end,
                            unified = unified
                        )
                    )
                }
            }
            start = end
            end = iterator.next()
        }

        return matches
    }

    private fun resolveUnifiedFromCluster(context: Context, cluster: String): String? {
        if (cluster.isBlank()) return null
        val codePoints = cluster.codePoints().toArray()
        if (codePoints.isEmpty()) return null

        val rawUnified = codePoints.joinToString("-") { codePoint ->
            String.format(Locale.ROOT, "%04x", codePoint)
        }

        return resolveUnified(context, rawUnified)
    }

    private fun resolveUnified(context: Context, rawUnified: String): String? {
        val key = rawUnified.lowercase(Locale.ROOT)
        unifiedResolveCache[key]?.let { return it }
        if (unresolvedUnifiedCache.contains(key)) return null

        val resolved = buildUnifiedCandidates(key).firstOrNull { candidate ->
            hasEmojiAsset(context, candidate)
        }
        if (resolved != null) {
            unifiedResolveCache[key] = resolved
        } else {
            unresolvedUnifiedCache.add(key)
        }
        return resolved
    }

    private fun hasEmojiAsset(context: Context, unified: String): Boolean =
        getAvailableEmojiAssets(context).contains(unified.lowercase(Locale.ROOT))

    private fun getAvailableEmojiAssets(context: Context): Set<String> {
        availableEmojiAssets?.let { return it }
        synchronized(this) {
            availableEmojiAssets?.let { return it }
            val loaded =
                context.assets
                    .list("emoji")
                    ?.asSequence()
                    ?.filter { it.endsWith(".png", ignoreCase = true) }
                    ?.map { it.removeSuffix(".png").lowercase(Locale.ROOT) }
                    ?.toSet()
                    ?: emptySet()
            availableEmojiAssets = loaded
            return loaded
        }
    }

    private fun buildUnifiedCandidates(rawUnified: String): List<String> {
        val parts = rawUnified.split('-').map { it.trim() }.filter { it.isNotEmpty() }
        if (parts.isEmpty()) return emptyList()

        val candidates = LinkedHashSet<String>()
        fun addCandidate(partList: List<String>) {
            if (partList.isNotEmpty()) {
                candidates.add(partList.joinToString("-"))
            }
        }

        addCandidate(parts)

        val withoutTextVs = parts.filterNot { it == "fe0e" }
        addCandidate(withoutTextVs)

        val withoutAnyVs = withoutTextVs.filterNot { it == "fe0f" }
        addCandidate(withoutAnyVs)

        if (parts.any { it == "fe0e" }) {
            addCandidate(parts.map { if (it == "fe0e") "fe0f" else it })
        }

        val keycapNoVs = withoutAnyVs.toMutableList()
        val keycapIndex = keycapNoVs.indexOf("20e3")
        if (keycapIndex > 0 && keycapNoVs.getOrNull(keycapIndex - 1) != "fe0f") {
            keycapNoVs.add(keycapIndex, "fe0f")
            addCandidate(keycapNoVs)
        }

        if (!parts.contains("fe0f")) {
            if (withoutAnyVs.size == 1) {
                addCandidate(listOf(withoutAnyVs.first(), "fe0f"))
            }
            if (withoutAnyVs.size > 1 && withoutAnyVs[0] != "200d" && withoutAnyVs[1] != "200d") {
                val withVsAfterFirst = withoutAnyVs.toMutableList().apply { add(1, "fe0f") }
                addCandidate(withVsAfterFirst)
            }
        }

        return candidates.toList()
    }

    private fun isOverlapping(start: Int, end: Int, occupiedRanges: List<IntRange>): Boolean =
        occupiedRanges.any { range ->
            start < (range.last + 1) && end > range.first
        }
}

private class TelegramLikeEmojiSpan(
    emojiDrawable: Drawable,
    private var sourceFontMetrics: Paint.FontMetricsInt?
) : ImageSpan(emojiDrawable, DynamicDrawableSpan.ALIGN_BOTTOM) {

    private var sizePx: Int = resolveSize(sourceFontMetrics)

    private fun resolveSize(metrics: Paint.FontMetricsInt?): Int {
        val metricsSize = metrics?.let { kotlin.math.abs(it.descent) + kotlin.math.abs(it.ascent) } ?: 0
        if (metricsSize > 0) return metricsSize
        val intrinsic = drawable.intrinsicHeight
        return if (intrinsic > 0) intrinsic else 20
    }

    override fun getSize(
        paint: Paint,
        text: CharSequence,
        start: Int,
        end: Int,
        fm: Paint.FontMetricsInt?
    ): Int {
        val metrics = sourceFontMetrics ?: paint.fontMetricsInt
        sourceFontMetrics = metrics
        sizePx = resolveSize(metrics)
        val scaledSize = sizePx.coerceAtLeast(1)
        drawable.setBounds(0, 0, scaledSize, scaledSize)
        fm?.let {
            it.ascent = metrics.ascent
            it.descent = metrics.descent
            it.top = metrics.top
            it.bottom = metrics.bottom
            it.leading = metrics.leading
        }
        return scaledSize
    }
}

/**
 * Apple Emoji EditText - кастомный EditText с PNG эмодзи
 * Заменяет системные эмодзи на Apple PNG изображения из assets
 */
class AppleEmojiEditTextView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = android.R.attr.editTextStyle
) : EditText(context, attrs, defStyleAttr) {

    var onTextChange: ((String) -> Unit)? = null
    var onSelectionRangeChange: ((Int, Int) -> Unit)? = null
    /** Called when the view's measured height changes (for multiline expansion animation). */
    var onHeightChanged: ((oldHeight: Int, newHeight: Int) -> Unit)? = null
    private var isUpdating = false
    private var lastMeasuredHeight: Int = 0

    companion object {
        // Regex для эмодзи - public для доступа из других компонентов
        val EMOJI_PATTERN = Pattern.compile(
            "[\\x{1F600}-\\x{1F64F}]|" +  // Emoticons
            "[\\x{1F300}-\\x{1F5FF}]|" +  // Misc Symbols and Pictographs
            "[\\x{1F680}-\\x{1F6FF}]|" +  // Transport and Map
            "[\\x{1F1E0}-\\x{1F1FF}]|" +  // Flags
            "[\\x{2600}-\\x{26FF}]|" +    // Misc symbols
            "[\\x{2700}-\\x{27BF}]|" +    // Dingbats
            "[\\x{FE00}-\\x{FE0F}]|" +    // Variation Selectors
            "[\\x{1F900}-\\x{1F9FF}]|" +  // Supplemental Symbols
            "[\\x{1FA00}-\\x{1FA6F}]|" +  // Chess Symbols
            "[\\x{1FA70}-\\x{1FAFF}]|" +  // Symbols Extended-A
            "[\\x{231A}-\\x{231B}]|" +    // Watch, Hourglass
            "[\\x{23E9}-\\x{23F3}]|" +    // Media controls
            "[\\x{23F8}-\\x{23FA}]|" +    // Media controls
            "[\\x{25AA}-\\x{25AB}]|" +    // Squares
            "[\\x{25B6}]|[\\x{25C0}]|" +  // Play buttons
            "[\\x{25FB}-\\x{25FE}]|" +    // Squares
            "[\\x{2614}-\\x{2615}]|" +    // Umbrella, Hot beverage
            "[\\x{2648}-\\x{2653}]|" +    // Zodiac
            "[\\x{267F}]|[\\x{2693}]|" +  // Wheelchair, Anchor
            "[\\x{26A1}]|[\\x{26AA}-\\x{26AB}]|" +
            "[\\x{26BD}-\\x{26BE}]|" +    // Sports
            "[\\x{26C4}-\\x{26C5}]|" +    // Weather
            "[\\x{26CE}]|[\\x{26D4}]|" +
            "[\\x{26EA}]|[\\x{26F2}-\\x{26F3}]|" +
            "[\\x{26F5}]|[\\x{26FA}]|[\\x{26FD}]|" +
            "[\\x{2702}]|[\\x{2705}]|" +
            "[\\x{2708}-\\x{270D}]|[\\x{270F}]|" +
            "[\\x{2712}]|[\\x{2714}]|[\\x{2716}]|" +
            "[\\x{271D}]|[\\x{2721}]|" +
            "[\\x{2728}]|[\\x{2733}-\\x{2734}]|" +
            "[\\x{2744}]|[\\x{2747}]|" +
            "[\\x{274C}]|[\\x{274E}]|" +
            "[\\x{2753}-\\x{2755}]|[\\x{2757}]|" +
            "[\\x{2763}-\\x{2764}]|" +
            "[\\x{2795}-\\x{2797}]|[\\x{27A1}]|" +
            "[\\x{27B0}]|[\\x{27BF}]|" +
            "[\\x{2934}-\\x{2935}]|" +
            "[\\x{2B05}-\\x{2B07}]|" +
            "[\\x{2B1B}-\\x{2B1C}]|" +
            "[\\x{2B50}]|[\\x{2B55}]|" +
            "[\\x{3030}]|[\\x{303D}]|" +
            "[\\x{3297}]|[\\x{3299}]"
        )
        
        // 🔥 Паттерн для :emoji_XXXX: формата (как в десктопе)
        val EMOJI_CODE_PATTERN: Pattern = Pattern.compile(":emoji_([a-fA-F0-9_-]+):")

        // Кэш bitmap
        private val bitmapCache = LruCache<String, Bitmap>(500)
    }

    init {
        // Настраиваем EditText
        background = null
        setBackgroundColor(android.graphics.Color.TRANSPARENT)
        setPadding(0, 0, 0, 0)
        gravity = Gravity.CENTER_VERTICAL or Gravity.START
        isSingleLine = false
        maxLines = 5
        imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
        
        addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
            override fun afterTextChanged(s: Editable?) {
                if (!isUpdating && s != null) {
                    replaceEmojisWithImages(s)
                    // Отправляем plain text без spans
                    onTextChange?.invoke(s.toString())
                }
            }
        })
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        if (oldh != 0 && h != oldh) {
            onHeightChanged?.invoke(oldh, h)
        }
        lastMeasuredHeight = h
    }

    override fun onSelectionChanged(selStart: Int, selEnd: Int) {
        super.onSelectionChanged(selStart, selEnd)
        onSelectionRangeChange?.invoke(selStart, selEnd)
    }

    fun setTextWithEmojis(newText: String) {
        if (newText == text.toString()) return
        isUpdating = true
        try {
            val cursorPos = selectionStart
            setText(newText)
            // Восстанавливаем позицию курсора в конец текста
            val newCursorPos = if (cursorPos >= 0) newText.length else cursorPos
            setSelection(newCursorPos.coerceIn(0, newText.length))
        } finally {
            isUpdating = false
        }
        replaceEmojisWithImages(editableText)
    }

    private fun replaceEmojisWithImages(editable: Editable) {
        if (isUpdating) return
        isUpdating = true

        try {
            val textStr = editable.toString()
            val hasEmojiHints = containsEmojiHints(textStr)
            if (!hasEmojiHints) {
                // Fast path for plain text: skip heavy grapheme/asset pipeline.
                // Also drop stale spans if user removed emoji content.
                editable.getSpans(0, editable.length, ImageSpan::class.java).forEach {
                    editable.removeSpan(it)
                }
                return
            }
            val cursorPosition = selectionStart
            
            // 🔥 Собираем все позиции эмодзи (и Unicode, и :emoji_code:)
            data class EmojiMatch(val start: Int, val end: Int, val unified: String)
            val emojiMatches = mutableListOf<EmojiMatch>()
            
            // 1. Ищем :emoji_XXXX: формат
            val codeMatcher = EMOJI_CODE_PATTERN.matcher(textStr)
            while (codeMatcher.find()) {
                val rawCode = codeMatcher.group(1) ?: continue
                val unified =
                    AppleEmojiAssetResolver.resolveUnifiedFromCode(context, rawCode)
                        ?: AppleEmojiAssetResolver.normalizeUnifiedCode(rawCode)
                emojiMatches.add(EmojiMatch(codeMatcher.start(), codeMatcher.end(), unified))
            }
            
            // 2. Ищем реальные Unicode эмодзи (графемные кластеры, включая ZWJ/flags/skin tones)
            val occupiedRanges = emojiMatches.map { it.start until it.end }
            val unicodeMatches =
                AppleEmojiAssetResolver.collectUnicodeMatches(
                    context = context,
                    text = textStr,
                    occupiedRanges = occupiedRanges
                )
            unicodeMatches.forEach { match ->
                emojiMatches.add(EmojiMatch(match.start, match.end, match.unified))
            }
            
            // 3. Обрабатываем все найденные эмодзи
            for (match in emojiMatches) {
                val start = match.start
                val end = match.end

                // Проверяем, есть ли уже ImageSpan
                val existingSpans = editable.getSpans(start, end, ImageSpan::class.java)
                if (existingSpans.isNotEmpty()) continue

                val unified = match.unified
                var bitmap = bitmapCache.get(unified)
                if (bitmap == null) {
                    bitmap = loadFromAssets(unified)
                    if (bitmap != null) {
                        bitmapCache.put(unified, bitmap)
                    }
                }

                if (bitmap != null && start < editable.length && end <= editable.length) {
                    val drawable = BitmapDrawable(resources, bitmap)
                    val imageSpan = TelegramLikeEmojiSpan(drawable, paint.fontMetricsInt)
                    editable.setSpan(imageSpan, start, end, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE)
                }
            }

            // Восстанавливаем курсор, убедившись что он в допустимых пределах
            // 🔥 Захватываем длину ДО post, т.к. text может измениться
            val safePosition = cursorPosition.coerceIn(0, editable.length)
            if (safePosition >= 0) {
                post { 
                    try {
                        val currentLength = text?.length ?: 0
                        if (safePosition <= currentLength) {
                            setSelection(safePosition.coerceIn(0, currentLength))
                        }
                    } catch (e: Exception) {
                        // Игнорируем - текст мог измениться
                    }
                }
            }
        } finally {
            isUpdating = false
        }
    }

    private fun containsEmojiHints(text: String): Boolean {
        if (text.isEmpty()) return false
        if (text.indexOf(":emoji_") >= 0) return true
        for (ch in text) {
            if (Character.isSurrogate(ch) || ch == '\u200D' || ch == '\uFE0F') return true
        }
        return false
    }

    private fun loadFromAssets(unified: String): Bitmap? {
        return try {
            val inputStream = getContext().assets.open("emoji/$unified.png")
            val bitmap = BitmapFactory.decodeStream(inputStream)
            inputStream.close()
            bitmap
        } catch (e: Exception) {
            null
        }
    }

}

/**
 * Compose обёртка для AppleEmojiEditText
 */
@Composable
fun AppleEmojiTextField(
    value: String,
    onValueChange: (String) -> Unit,
    modifier: Modifier = Modifier,
    textColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.White,
    textSize: Float = 16f,
    hint: String = "Message",
    hintColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.Gray,
    onViewCreated: ((AppleEmojiEditTextView) -> Unit)? = null,
    requestFocus: Boolean = false,
    onFocusChanged: ((Boolean) -> Unit)? = null,
    onHeightChanged: ((oldHeight: Int, newHeight: Int) -> Unit)? = null,
    onSelectionChanged: ((Int, Int) -> Unit)? = null
) {
    // Храним ссылку на view для управления фокусом
    var editTextView by remember { mutableStateOf<AppleEmojiEditTextView?>(null) }
    
    // 🔥 Автоматический запрос фокуса когда requestFocus = true
    LaunchedEffect(requestFocus) {
        if (requestFocus && editTextView != null) {
            editTextView?.requestFocus()
            // Показываем клавиатуру
            val imm = editTextView?.context?.getSystemService(android.content.Context.INPUT_METHOD_SERVICE) as? android.view.inputmethod.InputMethodManager
            imm?.showSoftInput(editTextView, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT)
        }
    }
    
    AndroidView(
        factory = { ctx ->
            AppleEmojiEditTextView(ctx).apply {
                setTextColor(textColor.toArgb())
                setHintTextColor(hintColor.toArgb())
                setHint(hint)
                setTextSize(textSize)
                onTextChange = onValueChange
                onSelectionRangeChange = onSelectionChanged
                this.onHeightChanged = onHeightChanged
                // Убираем все возможные фоны у EditText
                background = null
                setBackgroundColor(android.graphics.Color.TRANSPARENT)
                // Сохраняем ссылку на view
                editTextView = this
                // Подключаем callback для изменения фокуса
                setOnFocusChangeListener { _, hasFocus ->
                    onFocusChanged?.invoke(hasFocus)
                }
                // Уведомляем о создании view
                onViewCreated?.invoke(this)
            }
        },
        update = { view ->
            // Always update the callback to prevent stale lambda references
            // after recomposition (e.g., after sending a photo)
            view.onTextChange = onValueChange
            view.onSelectionRangeChange = onSelectionChanged

            if (view.text.toString() != value) {
                view.setTextWithEmojis(value)
            }
            // Гарантируем прозрачность у EditText
            view.background = null
            // 🔥 Убираем фон у AndroidViewHolder (parent FrameLayout)
            (view.parent as? FrameLayout)?.apply {
                setBackgroundColor(android.graphics.Color.TRANSPARENT)
                background = null
            }
        },
        // 🔥 Прозрачный фон для Compose контейнера
        modifier = modifier.background(Color.Transparent)
    )
}

/**
 * TextView с Apple эмодзи (для отображения, не редактирования)
 * 🔥 Поддержка кликабельных ссылок (URL, email, телефоны)
 */
@Composable
fun AppleEmojiText(
    text: String,
    modifier: Modifier = Modifier,
    color: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.White,
    fontSize: androidx.compose.ui.unit.TextUnit = androidx.compose.ui.unit.TextUnit.Unspecified,
    fontWeight: androidx.compose.ui.text.font.FontWeight? = null,
    maxLines: Int = Int.MAX_VALUE,
    overflow: android.text.TextUtils.TruncateAt? = null,
    linkColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color(0xFF54A9EB), // Telegram-style blue
    mentionColor: androidx.compose.ui.graphics.Color = linkColor,
    enableLinks: Boolean = true, // 🔥 Включить кликабельные ссылки
    enableMentions: Boolean = false,
    onMentionClick: ((String) -> Unit)? = null,
    onClickableSpanPressStart: (() -> Unit)? = null,
    onClick: (() -> Unit)? = null, // 🔥 Обычный tap (selection mode в MessageBubble)
    onLongClick: (() -> Unit)? = null, // 🔥 Callback для long press (selection в MessageBubble)
    onTextLongPress: ((touchX: Int, touchY: Int) -> Unit)? = null,
    onSelectionDrag: ((touchX: Int, touchY: Int) -> Unit)? = null,
    onSelectionDragEnd: (() -> Unit)? = null,
    onViewCreated: ((com.rosetta.messenger.ui.components.AppleEmojiTextView) -> Unit)? = null,
    minHeightMultiplier: Float = 1.5f
) {
    val fontSizeValue = if (fontSize == androidx.compose.ui.unit.TextUnit.Unspecified) 15f 
        else fontSize.value
    
    // Минимальная высота для корректного отображения emoji
    val minHeight = (fontSizeValue * minHeightMultiplier).toInt()
    
    // Преобразуем FontWeight в Android typeface style
    val typefaceStyle = when (fontWeight) {
        androidx.compose.ui.text.font.FontWeight.Bold,
        androidx.compose.ui.text.font.FontWeight.ExtraBold,
        androidx.compose.ui.text.font.FontWeight.Black,
        androidx.compose.ui.text.font.FontWeight.SemiBold -> android.graphics.Typeface.BOLD
        androidx.compose.ui.text.font.FontWeight.Medium -> android.graphics.Typeface.NORMAL // Medium не поддерживается напрямую
        else -> android.graphics.Typeface.NORMAL
    }
    
    AndroidView(
        factory = { ctx ->
            AppleEmojiTextView(ctx).apply {
                setTextColor(color.toArgb())
                setTextSize(fontSizeValue)
                minimumHeight = (minHeight * ctx.resources.displayMetrics.density).toInt()
                setTypeface(typeface, typefaceStyle)
                // 🔥 Поддержка maxLines и ellipsize
                setMaxLines(maxLines)
                if (overflow != null) {
                    ellipsize = overflow
                }
                // 🔥 Включаем кликабельные ссылки с поддержкой long press
                if (enableLinks) {
                    setLinkColor(linkColor.toArgb())
                    enableClickableLinks(true, onLongClick)
                } else {
                    // 🔥 ВАЖНО: в selection mode полностью отключаем LinkMovementMethod,
                    // иначе тапы по тексту могут "съедаться" и не доходить до onClick.
                    enableClickableLinks(false, onLongClick)
                }
                setMentionColor(mentionColor.toArgb())
                enableMentionHighlight(enableMentions)
                setOnMentionClickListener(onMentionClick)
                setOnClickableSpanPressStartListener(onClickableSpanPressStart)
                onTextLongPressCallback = onTextLongPress
                this.onSelectionDrag = onSelectionDrag
                this.onSelectionDragEnd = onSelectionDragEnd
                onViewCreated?.invoke(this)
                // In link/mention mode, keep span clicks exclusive to avoid parent bubble menu tap.
                val canUseTextViewClick = !enableLinks
                setOnClickListener(
                    if (canUseTextViewClick && onClick != null) {
                        View.OnClickListener { onClick.invoke() }
                    } else {
                        null
                    }
                )
                setTextWithEmojisIfNeeded(text)
            }
        },
        update = { view ->
            view.setTextColor(color.toArgb())
            view.setTypeface(view.typeface, typefaceStyle)
            // 🔥 Обновляем maxLines и ellipsize
            view.maxLines = maxLines
            if (overflow != null) {
                view.ellipsize = overflow
            }
            // 🔥 Обновляем настройки ссылок с поддержкой long press
            if (enableLinks) {
                view.setLinkColor(linkColor.toArgb())
                view.enableClickableLinks(true, onLongClick)
            } else {
                // 🔥 ВАЖНО: в selection mode полностью отключаем LinkMovementMethod,
                // иначе тапы по тексту могут "съедаться" и не доходить до onClick.
                view.enableClickableLinks(false, onLongClick)
            }
            view.setMentionColor(mentionColor.toArgb())
            view.enableMentionHighlight(enableMentions)
            view.setOnMentionClickListener(onMentionClick)
            view.setOnClickableSpanPressStartListener(onClickableSpanPressStart)
            view.onTextLongPressCallback = onTextLongPress
            view.onSelectionDrag = onSelectionDrag
            view.onSelectionDragEnd = onSelectionDragEnd
            // In link/mention mode, keep span clicks exclusive to avoid parent bubble menu tap.
            val canUseTextViewClick = !enableLinks
            view.setOnClickListener(
                if (canUseTextViewClick && onClick != null) {
                    View.OnClickListener { onClick.invoke() }
                } else {
                    null
                }
            )
            view.setTextWithEmojisIfNeeded(text)
        },
        modifier = modifier
    )
}

/**
 * Apple Emoji TextView - для отображения текста с PNG эмодзи
 * 🔥 Поддерживает:
 * - Реальные Unicode эмодзи (😀)
 * - Текстовый формат :emoji_1f600: (как в React Native версии)
 * - Кликабельные ссылки (URL, email, телефоны)
 */
class AppleEmojiTextView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = android.R.attr.textViewStyle
) : android.widget.TextView(context, attrs, defStyleAttr) {

    companion object {
        private const val LARGE_TEXT_RENDER_THRESHOLD = 4000
        private const val HARD_TEXT_RENDER_LIMIT = 24_000
        private const val HARD_TEXT_RENDER_HEAD = 16_000
        private const val HARD_TEXT_RENDER_TAIL = 7_000
        private val EMOJI_PATTERN = AppleEmojiEditTextView.EMOJI_PATTERN
        // 🔥 Паттерн для :emoji_XXXX: формата (из React Native)
        private val EMOJI_CODE_PATTERN = Pattern.compile(":emoji_([a-fA-F0-9_-]+):")
        private val bitmapCache = LruCache<String, Bitmap>(100)
        
        // 🔥 Паттерны для ссылок
        private val URL_PATTERN = Pattern.compile(
            "(https?://[\\w\\-._~:/?#\\[\\]@!$&'()*+,;=%]+)" +
            "|(www\\.[\\w\\-._~:/?#\\[\\]@!$&'()*+,;=%]+)" +
            "|([\\w\\-]+\\.(com|org|net|ru|io|dev|app|me|co|info|biz|edu|gov|uk|de|fr|jp|cn|br|in|au|ca|it|es|nl|se|no|fi|dk|pl|ua|kz|by)[\\w\\-._~:/?#\\[\\]@!$&'()*+,;=%]*)"
        )
        private val EMAIL_PATTERN = Patterns.EMAIL_ADDRESS
        private val PHONE_PATTERN = Pattern.compile(
            "\\+?[0-9][\\s\\-()0-9]{6,}[0-9]"
        )
        private val MENTION_PATTERN = Pattern.compile("(?<![A-Za-z0-9_])@[A-Za-z0-9_]{1,32}(?![A-Za-z0-9_])")
    }

    private fun safeRenderText(rawText: String): String {
        if (rawText.length <= HARD_TEXT_RENDER_LIMIT) return rawText
        val skippedChars = rawText.length - HARD_TEXT_RENDER_HEAD - HARD_TEXT_RENDER_TAIL
        val head = rawText.take(HARD_TEXT_RENDER_HEAD).trimEnd()
        val tail = rawText.takeLast(HARD_TEXT_RENDER_TAIL).trimStart()
        return buildString(head.length + tail.length + 160) {
            append(head)
            append("\n\n--- MESSAGE TRUNCATED FOR STABILITY ---\n")
            append("Skipped ")
            append(skippedChars.coerceAtLeast(0))
            append(" chars. Open source log/file for full text.\n\n")
            append(tail)
        }
    }

    private var linkColorValue: Int = 0xFF54A9EB.toInt() // Default Telegram blue
    private var linksEnabled: Boolean = false
    private var mentionColorValue: Int = 0xFF54A9EB.toInt()
    private var mentionsEnabled: Boolean = false
    private var mentionClickCallback: ((String) -> Unit)? = null
    private var clickableSpanPressStartCallback: (() -> Unit)? = null
    private var lastRenderedText: String? = null
    private var lastRenderedLinksEnabled: Boolean = false
    private var lastRenderedMentionsEnabled: Boolean = false
    private var lastRenderedMentionClickable: Boolean = false

    // 🔥 Long press callback для selection в MessageBubble
    var onLongClickCallback: (() -> Unit)? = null
    var onTextLongPressCallback: ((touchX: Int, touchY: Int) -> Unit)? = null
    // Telegram flow: forward drag/up events after long press fires
    var onSelectionDrag: ((touchX: Int, touchY: Int) -> Unit)? = null
    var onSelectionDragEnd: (() -> Unit)? = null
    private var downOnClickableSpan: Boolean = false
    private var suppressPerformClickOnce: Boolean = false
    private var selectionDragActive: Boolean = false

    // 🔥 GestureDetector для обработки long press поверх LinkMovementMethod
    private val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
        override fun onLongPress(e: MotionEvent) {
            if (downOnClickableSpan) return
            if (onTextLongPressCallback != null) {
                onTextLongPressCallback?.invoke(e.rawX.toInt(), e.rawY.toInt())
                selectionDragActive = true
                parent?.requestDisallowInterceptTouchEvent(true) // block scroll during drag
            } else {
                onLongClickCallback?.invoke()
            }
        }
    })

    init {
        // Отключаем лишние отступы шрифта для корректного отображения emoji
        includeFontPadding = false
    }

    /**
     * 🔥 Перехватываем touch события для обработки long press
     * GestureDetector обрабатывает long press, затем передаем событие parent для ссылок
     */
    override fun dispatchTouchEvent(event: MotionEvent): Boolean {
        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                downOnClickableSpan = isTouchOnClickableSpan(event)
                suppressPerformClickOnce = downOnClickableSpan
                selectionDragActive = false
                if (downOnClickableSpan) {
                    clickableSpanPressStartCallback?.invoke()
                    parent?.requestDisallowInterceptTouchEvent(true)
                }
            }
            MotionEvent.ACTION_MOVE -> {
                if (selectionDragActive) {
                    onSelectionDrag?.invoke(event.rawX.toInt(), event.rawY.toInt())
                    return true
                }
            }
            MotionEvent.ACTION_CANCEL,
            MotionEvent.ACTION_UP -> {
                if (selectionDragActive) {
                    selectionDragActive = false
                    onSelectionDragEnd?.invoke()
                    downOnClickableSpan = false
                    parent?.requestDisallowInterceptTouchEvent(false)
                    return true
                }
                downOnClickableSpan = false
                parent?.requestDisallowInterceptTouchEvent(false)
            }
        }

        gestureDetector.onTouchEvent(event)
        return super.dispatchTouchEvent(event)
    }

    override fun performClick(): Boolean {
        if (suppressPerformClickOnce) {
            suppressPerformClickOnce = false
            return true
        }
        return super.performClick()
    }

    private fun isTouchOnClickableSpan(event: MotionEvent): Boolean {
        val currentText = text as? Spanned ?: return false
        val hasClickableAtOffset: (Int) -> Boolean = { offset ->
            if (offset < 0 || offset > currentText.length) {
                false
            } else {
                val start = (offset - 1).coerceAtLeast(0)
                val end = (offset + 1).coerceAtMost(currentText.length)
                currentText.getSpans(start, end, ClickableSpan::class.java).isNotEmpty()
            }
        }

        val directOffset = runCatching { getOffsetForPosition(event.x, event.y) }.getOrNull()
        if (directOffset != null && hasClickableAtOffset(directOffset)) {
            return true
        }

        val textLayout = layout ?: return false
        val x = (event.x - totalPaddingLeft + scrollX).toInt()
        val y = (event.y - totalPaddingTop + scrollY).toInt()
        if (x < 0 || y < 0 || x > textLayout.width || y > textLayout.height) return false

        val line = textLayout.getLineForVertical(y)
        val horizontal = x.toFloat().coerceIn(textLayout.getLineLeft(line), textLayout.getLineRight(line))
        val layoutOffset = textLayout.getOffsetForHorizontal(line, horizontal)
        return hasClickableAtOffset(layoutOffset)
    }

    /**
     * 🔥 Установить цвет для ссылок
     */
    fun setLinkColor(color: Int) {
        linkColorValue = color
    }

    fun setMentionColor(color: Int) {
        mentionColorValue = color
    }

    fun enableMentionHighlight(enable: Boolean) {
        mentionsEnabled = enable
        updateMovementMethod()
    }

    fun setOnMentionClickListener(listener: ((String) -> Unit)?) {
        mentionClickCallback = listener
        updateMovementMethod()
    }

    fun setOnClickableSpanPressStartListener(listener: (() -> Unit)?) {
        clickableSpanPressStartCallback = listener
    }

    /**
     * 🔥 Включить/выключить кликабельные ссылки
     * @param enable - включить ссылки
     * @param onLongClick - callback для long press (для selection в MessageBubble)
     */
    fun enableClickableLinks(enable: Boolean, onLongClick: (() -> Unit)? = null) {
        linksEnabled = enable
        onLongClickCallback = onLongClick
        updateMovementMethod()
    }

    private fun updateMovementMethod() {
        val hasMentionClicks = mentionsEnabled && mentionClickCallback != null
        if (linksEnabled || hasMentionClicks) {
            movementMethod = LinkMovementMethod.getInstance()
            // Убираем highlight при клике
            highlightColor = android.graphics.Color.TRANSPARENT
        } else {
            movementMethod = null
        }
    }

    fun getLayoutInfo(): com.rosetta.messenger.ui.chats.components.LayoutInfo? {
        val l = layout ?: return null
        val loc = IntArray(2)
        getLocationInWindow(loc)
        return com.rosetta.messenger.ui.chats.components.LayoutInfo(
            layout = l,
            windowX = loc[0] + totalPaddingLeft,
            windowY = loc[1] + totalPaddingTop,
            text = text ?: return null
        )
    }

    fun setTextWithEmojis(text: String) {
        val renderText = safeRenderText(text)
        val isLargeText = renderText.length > LARGE_TEXT_RENDER_THRESHOLD
        val processMentions = mentionsEnabled && !isLargeText
        val processLinks = linksEnabled && !isLargeText
        val processEmoji = !isLargeText || containsEmojiHints(renderText)

        // Для длинных логов (без emoji/links/mentions) не запускаем дорогой regex/span пайплайн.
        if (!processEmoji && !processMentions && !processLinks) {
            setText(renderText)
            return
        }

        // 🔥 Сначала заменяем :emoji_XXXX: на PNG изображения
        val spannable = SpannableStringBuilder(renderText)
        
        // Собираем все замены (чтобы не сбить индексы)
        data class EmojiMatch(
            val start: Int,
            val end: Int,
            val unified: String,
            val isCodeFormat: Boolean
        )
        val emojiMatches = mutableListOf<EmojiMatch>()
        
        // 1. Ищем :emoji_XXXX: формат
        val codeMatcher = EMOJI_CODE_PATTERN.matcher(renderText)
        while (codeMatcher.find()) {
            val rawCode = codeMatcher.group(1) ?: continue
            val unified =
                AppleEmojiAssetResolver.resolveUnifiedFromCode(context, rawCode)
                    ?: AppleEmojiAssetResolver.normalizeUnifiedCode(rawCode)
            emojiMatches.add(
                EmojiMatch(
                    start = codeMatcher.start(),
                    end = codeMatcher.end(),
                    unified = unified,
                    isCodeFormat = true
                )
            )
        }
        
        // 2. Ищем реальные Unicode эмодзи (включая составные iOS-кластеры)
        val occupiedRanges = emojiMatches.map { it.start until it.end }
        val unicodeMatches =
            AppleEmojiAssetResolver.collectUnicodeMatches(
                context = context,
                text = renderText,
                occupiedRanges = occupiedRanges
            )
        unicodeMatches.forEach { match ->
            emojiMatches.add(
                EmojiMatch(
                    start = match.start,
                    end = match.end,
                    unified = match.unified,
                    isCodeFormat = false
                )
            )
        }
        
        // 3. Сортируем по позиции в обратном порядке (чтобы не сбить индексы при замене)
        emojiMatches.sortByDescending { it.start }
        
        // 4. Применяем все замены
        for (match in emojiMatches) {
            val bitmap = loadEmojiBitmap(match.unified)
            if (bitmap != null) {
                val drawable = BitmapDrawable(resources, bitmap)
                val span = TelegramLikeEmojiSpan(drawable, paint.fontMetricsInt)
                
                // Для :emoji_XXXX: заменяем весь текст на пробел + span
                // Для Unicode эмодзи оставляем символ как есть
                if (match.isCodeFormat) {
                    spannable.replace(match.start, match.end, "\u200B") // Zero-width space
                    spannable.setSpan(span, match.start, match.start + 1, 
                        android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                } else {
                    // Это Unicode эмодзи
                    spannable.setSpan(span, match.start, match.end, 
                        android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                }
            } else if (match.isCodeFormat) {
                // 🔥 Fallback: если PNG не найден, конвертируем :emoji_XXXX: в Unicode эмодзи
                val unicodeEmoji = unifiedToEmoji(match.unified)
                if (unicodeEmoji != null) {
                    spannable.replace(match.start, match.end, unicodeEmoji)
                }
            }
        }
        
        if (processMentions) {
            addMentionHighlights(spannable)
        }

        // 🔥 5. Добавляем кликабельные ссылки после обработки эмодзи
        if (processLinks) {
            addClickableLinks(spannable)
        }

        setText(spannable)
    }

    fun setTextWithEmojisIfNeeded(text: String) {
        val mentionClickable = mentionsEnabled && mentionClickCallback != null
        if (lastRenderedText == text &&
            lastRenderedLinksEnabled == linksEnabled &&
            lastRenderedMentionsEnabled == mentionsEnabled &&
            lastRenderedMentionClickable == mentionClickable
        ) {
            return
        }
        setTextWithEmojis(text)
        lastRenderedText = text
        lastRenderedLinksEnabled = linksEnabled
        lastRenderedMentionsEnabled = mentionsEnabled
        lastRenderedMentionClickable = mentionClickable
    }
    
    /**
     * 🔥 Добавляет кликабельные ссылки (URL, email, телефоны) в spannable
     */
    private enum class LinkType { URL, EMAIL, PHONE }

    private fun addMentionHighlights(spannable: SpannableStringBuilder) {
        val textStr = spannable.toString()
        val mentionMatcher = MENTION_PATTERN.matcher(textStr)
        while (mentionMatcher.find()) {
            val start = mentionMatcher.start()
            val end = mentionMatcher.end()
            val overlapsClickable = spannable.getSpans(start, end, ClickableSpan::class.java).isNotEmpty()
            if (overlapsClickable) continue
            val mentionText = textStr.substring(start, end).trim().trimStart('@')
            val callback = mentionClickCallback
            if (callback != null) {
                val clickableSpan = object : ClickableSpan() {
                    override fun onClick(widget: View) {
                        if (mentionText.isNotBlank()) {
                            callback.invoke(mentionText)
                        }
                    }

                    override fun updateDrawState(ds: TextPaint) {
                        super.updateDrawState(ds)
                        ds.color = mentionColorValue
                        ds.isUnderlineText = false
                    }
                }
                spannable.setSpan(
                    clickableSpan,
                    start,
                    end,
                    android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
                )
            } else {
                spannable.setSpan(
                    ForegroundColorSpan(mentionColorValue),
                    start,
                    end,
                    android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
                )
            }
        }
    }
    
    private fun addClickableLinks(spannable: SpannableStringBuilder) {
        val textStr = spannable.toString()
        
        // Собираем все найденные ссылки
        data class LinkMatch(val start: Int, val end: Int, val url: String, val type: LinkType)
        val linkMatches = mutableListOf<LinkMatch>()
        
        // 1. Ищем URL
        val urlMatcher = URL_PATTERN.matcher(textStr)
        while (urlMatcher.find()) {
            var url = urlMatcher.group()
            // Добавляем https:// если нет протокола
            if (!url.startsWith("http://") && !url.startsWith("https://")) {
                url = "https://$url"
            }
            linkMatches.add(LinkMatch(urlMatcher.start(), urlMatcher.end(), url, LinkType.URL))
        }
        
        // 2. Ищем Email
        val emailMatcher = EMAIL_PATTERN.matcher(textStr)
        while (emailMatcher.find()) {
            val email = emailMatcher.group()
            // Проверяем что не перекрывается с URL
            val overlaps = linkMatches.any { 
                (emailMatcher.start() >= it.start && emailMatcher.start() < it.end) ||
                (emailMatcher.end() > it.start && emailMatcher.end() <= it.end)
            }
            if (!overlaps) {
                linkMatches.add(LinkMatch(emailMatcher.start(), emailMatcher.end(), "mailto:$email", LinkType.EMAIL))
            }
        }
        
        // 3. Ищем телефоны
        val phoneMatcher = PHONE_PATTERN.matcher(textStr)
        while (phoneMatcher.find()) {
            val phone = phoneMatcher.group().replace("[\\s\\-()]".toRegex(), "")
            // Проверяем что не перекрывается с другими ссылками
            val overlaps = linkMatches.any { 
                (phoneMatcher.start() >= it.start && phoneMatcher.start() < it.end) ||
                (phoneMatcher.end() > it.start && phoneMatcher.end() <= it.end)
            }
            if (!overlaps && phone.length >= 7) {
                linkMatches.add(LinkMatch(phoneMatcher.start(), phoneMatcher.end(), "tel:$phone", LinkType.PHONE))
            }
        }
        
        // 4. Применяем ClickableSpan для каждой ссылки
        for (link in linkMatches) {
            val clickableSpan = object : ClickableSpan() {
                override fun onClick(widget: View) {
                    try {
                        val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link.url))
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                        context.startActivity(intent)
                    } catch (e: Exception) {
                        // Если не удалось открыть ссылку, игнорируем
                        e.printStackTrace()
                    }
                }
                
                override fun updateDrawState(ds: TextPaint) {
                    super.updateDrawState(ds)
                    ds.color = linkColorValue
                    ds.isUnderlineText = false // Убираем подчёркивание (Telegram-style)
                }
            }
            
            spannable.setSpan(
                clickableSpan,
                link.start,
                link.end,
                android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
            )
        }
    }

    private fun loadEmojiBitmap(unified: String): Bitmap? {
        bitmapCache.get(unified)?.let { return it }
        
        return try {
            val path = "emoji/$unified.png"
            val inputStream = context.assets.open(path)
            val bitmap = BitmapFactory.decodeStream(inputStream)
            inputStream.close()
            bitmap?.let { bitmapCache.put(unified, it) }
            bitmap
        } catch (e: Exception) {
            null
        }
    }

    /**
     * 🔥 Конвертирует unified код (1f600) в Unicode эмодзи (😀)
     */
    private fun unifiedToEmoji(unified: String): String? {
        return try {
            val codePoints = unified.split("-").map { it.toInt(16) }
            String(codePoints.toIntArray(), 0, codePoints.size)
        } catch (e: Exception) {
            null
        }
    }

    private fun containsEmojiHints(text: String): Boolean {
        if (text.indexOf(":emoji_") >= 0) return true
        for (ch in text) {
            if (Character.isSurrogate(ch) || ch == '\u200D' || ch == '\uFE0F') {
                return true
            }
        }
        return false
    }
}
