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

import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.text.Layout
import android.view.HapticFeedbackConstants
import android.view.View
import android.widget.Toast
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Popup
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import kotlinx.coroutines.delay

data class LayoutInfo(
    val layout: Layout,
    val windowX: Int,
    val windowY: Int,
    val text: CharSequence
)

class TextSelectionHelper {

    var selectionStart by mutableIntStateOf(-1)
        private set
    var selectionEnd by mutableIntStateOf(-1)
        private set
    var selectedMessageId by mutableStateOf<String?>(null)
        private set
    // True when the selected message is the user's own (blue bubble) — used to pick
    // white handles against the blue background instead of the default blue handles.
    var isOwnMessage by mutableStateOf(false)
        private set
    var layoutInfo by mutableStateOf<LayoutInfo?>(null)
        private set
    var isActive by mutableStateOf(false)
        private set
    var handleViewProgress by mutableFloatStateOf(0f)
        private set
    var movingHandle by mutableStateOf(false)
        private set
    var movingHandleStart by mutableStateOf(false)
        private set
    // Telegram: isOneTouch = true during initial long-press drag (before finger lifts)
    var isOneTouch by mutableStateOf(false)
        private set
    // Telegram: direction not determined yet — first drag decides start or end handle
    private var movingDirectionSettling = false
    private var movingOffsetX = 0f
    private var movingOffsetY = 0f

    var startHandleX by mutableFloatStateOf(0f)
    var startHandleY by mutableFloatStateOf(0f)
    var endHandleX by mutableFloatStateOf(0f)
    var endHandleY by mutableFloatStateOf(0f)

    // Back gesture callback — registered/unregistered by overlay
    var backCallback: Any? = null

    // Overlay position in window — set by TextSelectionOverlay
    var overlayWindowX = 0f
    var overlayWindowY = 0f

    val isInSelectionMode: Boolean get() = isActive && selectionStart >= 0 && selectionEnd > selectionStart

    fun startSelection(
        messageId: String,
        info: LayoutInfo,
        touchX: Int,
        touchY: Int,
        view: View?,
        isOwnMessage: Boolean = false
    ) {
        this.isOwnMessage = isOwnMessage
        val layout = info.layout
        val localX = touchX - info.windowX
        val localY = touchY - info.windowY

        val line = layout.getLineForVertical(localY)
        val hx = localX.toFloat().coerceIn(layout.getLineLeft(line), layout.getLineRight(line))
        val offset = layout.getOffsetForHorizontal(line, hx)

        val text = info.text
        var start = offset
        var end = offset

        while (start > 0 && Character.isLetterOrDigit(text[start - 1])) start--
        while (end < text.length && Character.isLetterOrDigit(text[end])) end++

        if (start == end && end < text.length) end++

        selectedMessageId = messageId
        layoutInfo = info
        selectionStart = start
        selectionEnd = end
        isActive = true
        handleViewProgress = 1f

        // Telegram: immediately enter drag mode — user can drag without lifting finger
        movingHandle = true
        movingDirectionSettling = true
        isOneTouch = true
        movingOffsetX = 0f
        movingOffsetY = 0f

        view?.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
        showToolbar = false
    }

    fun updateSelectionStart(charOffset: Int) {
        if (!isActive) return
        val text = layoutInfo?.text ?: return
        val newStart = charOffset.coerceIn(0, text.length)
        if (newStart >= selectionEnd) return
        val changed = newStart != selectionStart
        selectionStart = newStart
        if (changed) hapticOnSelectionChange()
    }

    fun updateSelectionEnd(charOffset: Int) {
        if (!isActive) return
        val text = layoutInfo?.text ?: return
        val newEnd = charOffset.coerceIn(0, text.length)
        if (newEnd <= selectionStart) return
        val changed = newEnd != selectionEnd
        selectionEnd = newEnd
        if (changed) hapticOnSelectionChange()
    }

    private fun hapticOnSelectionChange() {
        magnifierView?.performHapticFeedback(
            HapticFeedbackConstants.TEXT_HANDLE_MOVE,
            HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING
        )
    }

    fun beginHandleDrag(isStart: Boolean, touchX: Float, touchY: Float) {
        movingHandle = true
        movingHandleStart = isStart
        movingOffsetX = if (isStart) startHandleX - touchX else endHandleX - touchX
        movingOffsetY = if (isStart) startHandleY - touchY else endHandleY - touchY
    }

    fun moveHandle(touchX: Float, touchY: Float) {
        if (!movingHandle) return
        val x = (touchX + movingOffsetX).toInt()
        val y = (touchY + movingOffsetY).toInt()
        val offset = getCharOffsetFromCoords(x, y)
        if (offset < 0) return

        // Telegram: first drag determines which handle to move
        if (movingDirectionSettling) {
            if (offset < selectionStart) {
                movingDirectionSettling = false
                movingHandleStart = true
            } else if (offset > selectionEnd) {
                movingDirectionSettling = false
                movingHandleStart = false
            } else {
                return // still within selected word, wait for more movement
            }
        }

        if (movingHandleStart) updateSelectionStart(offset) else updateSelectionEnd(offset)
    }

    fun endHandleDrag() {
        movingHandle = false
        movingDirectionSettling = false
        isOneTouch = false
        showFloatingToolbar()
    }

    var showToolbar by mutableStateOf(false)
        private set

    fun showFloatingToolbar() {
        if (isInSelectionMode && !movingHandle) {
            showToolbar = true
        }
    }

    fun hideFloatingToolbar() {
        showToolbar = false
    }

    private var magnifier: android.widget.Magnifier? = null
    private var magnifierView: View? = null

    fun setMagnifierView(view: View?) {
        magnifierView = view
    }

    fun showMagnifier(overlayLocalX: Float, overlayLocalY: Float) {
        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.P) return
        val view = magnifierView ?: return
        if (!movingHandle) return
        if (magnifier == null) {
            magnifier = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
                android.widget.Magnifier.Builder(view)
                    .setSize(240, 64)
                    .setCornerRadius(12f)
                    .setElevation(4f)
                    .setDefaultSourceToMagnifierOffset(0, -96)
                    .build()
            } else {
                @Suppress("DEPRECATION")
                android.widget.Magnifier(view)
            }
        }
        val info = layoutInfo ?: return

        // Magnifier should show at the HANDLE position (current char), not finger
        // Use handle X for horizontal, and line center for vertical
        val handleX = if (movingHandleStart) startHandleX else endHandleX
        val handleY = if (movingHandleStart) startHandleY else endHandleY
        val activeOffset = if (movingHandleStart) selectionStart else selectionEnd
        val layout = info.layout
        val line = layout.getLineForOffset(activeOffset.coerceIn(0, info.text.length))
        val lineCenter = (layout.getLineTop(line) + layout.getLineBottom(line)) / 2f

        // Convert to view-local coordinates
        val viewLoc = IntArray(2)
        view.getLocationInWindow(viewLoc)
        val sourceX = (handleX + overlayWindowX - viewLoc[0]).coerceIn(0f, view.width.toFloat())
        val sourceY = (lineCenter + info.windowY - viewLoc[1]).coerceIn(0f, view.height.toFloat())
        magnifier?.show(sourceX, sourceY)
    }

    fun hideMagnifier() {
        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.P) return
        magnifier?.dismiss()
        magnifier = null
    }

    fun getCharOffsetFromCoords(overlayLocalX: Int, overlayLocalY: Int): Int {
        val info = layoutInfo ?: return -1
        // overlay-local → text-local: subtract text position relative to overlay
        val textLocalX = overlayLocalX - (info.windowX - overlayWindowX)
        val textLocalY = overlayLocalY - (info.windowY - overlayWindowY)
        val layout = info.layout
        val line = layout.getLineForVertical(textLocalY.toInt().coerceIn(0, layout.height))
        val hx = textLocalX.toFloat().coerceIn(layout.getLineLeft(line), layout.getLineRight(line))
        return layout.getOffsetForHorizontal(line, hx)
    }

    fun getSelectedText(): CharSequence? {
        if (!isInSelectionMode) return null
        val text = layoutInfo?.text ?: return null
        val start = selectionStart.coerceIn(0, text.length)
        val end = selectionEnd.coerceIn(start, text.length)
        return text.subSequence(start, end)
    }

    fun copySelectedText(context: Context) {
        val selectedText = getSelectedText() ?: return
        val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
        clipboard.setPrimaryClip(ClipData.newPlainText("selected_text", selectedText))
        Toast.makeText(context, "Copied", Toast.LENGTH_SHORT).show()
        clear()
    }

    fun selectAll() {
        val text = layoutInfo?.text ?: return
        selectionStart = 0
        selectionEnd = text.length
    }

    fun clear() {
        selectionStart = -1
        selectionEnd = -1
        selectedMessageId = null
        layoutInfo = null
        isActive = false
        handleViewProgress = 0f
        movingHandle = false
        movingDirectionSettling = false
        isOneTouch = false
        showToolbar = false
        hideMagnifier()
    }
}

private val HandleSize = 22.dp
private val HandleInset = 8.dp
private val HighlightCorner = 6.dp
private val HighlightColor = PrimaryBlue.copy(alpha = 0.3f)
private val HandleColor = PrimaryBlue

@Composable
private fun FloatingToolbarPopup(
    helper: TextSelectionHelper
) {
    val context = LocalContext.current

    LaunchedEffect(helper.isActive, helper.movingHandle) {
        if (helper.isActive && !helper.movingHandle && !helper.showToolbar) {
            delay(200)
            helper.showFloatingToolbar()
        }
    }

    if (!helper.showToolbar || !helper.isInSelectionMode) return

    val info = helper.layoutInfo ?: return
    val layout = info.layout
    val density = LocalDensity.current
    val startLine = layout.getLineForOffset(helper.selectionStart.coerceIn(0, info.text.length))

    // Toolbar positioned ABOVE selection top, in overlay-local coordinates
    val selectionCenterX = (helper.startHandleX + helper.endHandleX) / 2f
    val selectionTopY = layout.getLineTop(startLine).toFloat() +
        (info.windowY - helper.overlayWindowY)
    // Toolbar is ~48dp tall + 8dp gap above selection
    val toolbarOffsetPx = with(density) { 56.dp.toPx() }
    val toolbarWidthPx = with(density) { 200.dp.toPx() }
    val toolbarX = (selectionCenterX - toolbarWidthPx / 2f).coerceAtLeast(with(density) { 8.dp.toPx() })
    val toolbarY = (selectionTopY - toolbarOffsetPx).coerceAtLeast(0f)

    Popup(
        alignment = Alignment.TopStart,
        offset = IntOffset(toolbarX.toInt(), toolbarY.toInt())
    ) {
        Row(
            modifier = Modifier
                .shadow(4.dp, RoundedCornerShape(8.dp))
                .clip(RoundedCornerShape(8.dp))
                .background(Color(0xFF333333))
                .padding(horizontal = 4.dp, vertical = 2.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = "Copy",
                color = Color.White,
                fontSize = 14.sp,
                fontWeight = FontWeight.Medium,
                modifier = Modifier
                    .clickable { helper.copySelectedText(context) }
                    .padding(horizontal = 12.dp, vertical = 8.dp)
            )
            val allSelected = helper.selectionStart <= 0 &&
                helper.selectionEnd >= info.text.length
            if (!allSelected) {
                Text(
                    text = "Select All",
                    color = Color.White,
                    fontSize = 14.sp,
                    fontWeight = FontWeight.Medium,
                    modifier = Modifier
                        .clickable {
                            helper.selectAll()
                            helper.hideFloatingToolbar()
                        }
                        .padding(horizontal = 12.dp, vertical = 8.dp)
                )
            }
        }
    }
}

@Composable
fun TextSelectionOverlay(
    helper: TextSelectionHelper,
    modifier: Modifier = Modifier
) {
    if (!helper.isInSelectionMode) return

    val density = LocalDensity.current
    val handleSizePx = with(density) { HandleSize.toPx() }
    val handleInsetPx = with(density) { HandleInset.toPx() }
    val highlightCornerPx = with(density) { HighlightCorner.toPx() }
    // Read isOwnMessage at composition level so Canvas properly invalidates on change.
    // On own (blue) bubbles use the light-blue typing color — reads better than pure white.
    val handleColor = if (helper.isOwnMessage) Color(0xFF54A9EB) else HandleColor
    val highlightColor = if (helper.isOwnMessage) Color(0xFF54A9EB).copy(alpha = 0.45f) else HighlightColor

    // Block predictive back gesture completely during text selection.
    // BackHandler alone doesn't prevent the swipe animation on Android 13+
    // with enableOnBackInvokedCallback=true. We must register an
    // OnBackInvokedCallback at PRIORITY_OVERLAY to fully suppress it.
    val activity = LocalContext.current as? android.app.Activity
    LaunchedEffect(helper.isActive) {
        if (android.os.Build.VERSION.SDK_INT >= 33 && activity != null) {
            if (helper.isActive) {
                val cb = android.window.OnBackInvokedCallback { /* consumed, do nothing */ }
                activity.onBackInvokedDispatcher.registerOnBackInvokedCallback(
                    android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY, cb
                )
                helper.backCallback = cb
            } else {
                helper.backCallback?.let { cb ->
                    runCatching {
                        activity.onBackInvokedDispatcher.unregisterOnBackInvokedCallback(
                            cb as android.window.OnBackInvokedCallback
                        )
                    }
                }
                helper.backCallback = null
            }
        }
    }
    // Fallback for Android < 13
    androidx.activity.compose.BackHandler(enabled = helper.isActive) {
        // consumed — no navigation back while selecting
    }

    Box(
        modifier = modifier
            .fillMaxSize()
            .onGloballyPositioned { coords ->
                val pos = coords.positionInWindow()
                helper.overlayWindowX = pos.x
                helper.overlayWindowY = pos.y
            }
    ) {
        FloatingToolbarPopup(helper = helper)
        Canvas(
            modifier = Modifier
                .fillMaxSize()
                .pointerInput(helper.isActive) {
                    if (!helper.isActive) return@pointerInput
                    awaitPointerEventScope {
                        while (true) {
                            val event = awaitPointerEvent()
                            val change = event.changes.firstOrNull() ?: continue

                            when {
                                change.pressed && !helper.movingHandle -> {
                                    val x = change.position.x
                                    val y = change.position.y
                                    val startRect = Rect(
                                        helper.startHandleX - handleSizePx / 2 - handleInsetPx,
                                        helper.startHandleY - handleInsetPx,
                                        helper.startHandleX + handleSizePx / 2 + handleInsetPx,
                                        helper.startHandleY + handleSizePx + handleInsetPx
                                    )
                                    val endRect = Rect(
                                        helper.endHandleX - handleSizePx / 2 - handleInsetPx,
                                        helper.endHandleY - handleInsetPx,
                                        helper.endHandleX + handleSizePx / 2 + handleInsetPx,
                                        helper.endHandleY + handleSizePx + handleInsetPx
                                    )
                                    when {
                                        startRect.contains(Offset(x, y)) -> {
                                            helper.beginHandleDrag(isStart = true, x, y)
                                            helper.hideFloatingToolbar()
                                            change.consume()
                                        }
                                        endRect.contains(Offset(x, y)) -> {
                                            helper.beginHandleDrag(isStart = false, x, y)
                                            helper.hideFloatingToolbar()
                                            change.consume()
                                        }
                                        else -> {
                                            helper.clear()
                                        }
                                    }
                                }
                                change.pressed && helper.movingHandle -> {
                                    helper.moveHandle(change.position.x, change.position.y)
                                    helper.showMagnifier(change.position.x, change.position.y)
                                    change.consume()
                                }
                                !change.pressed && helper.movingHandle -> {
                                    helper.hideMagnifier()
                                    helper.endHandleDrag()
                                    change.consume()
                                }
                            }
                        }
                    }
                }
        ) {
            val info = helper.layoutInfo ?: return@Canvas
            val layout = info.layout
            val text = info.text

            // Convert window coords to overlay-local coords
            val offsetX = info.windowX - helper.overlayWindowX
            val offsetY = info.windowY - helper.overlayWindowY

            val startOffset = helper.selectionStart.coerceIn(0, text.length)
            val endOffset = helper.selectionEnd.coerceIn(0, text.length)
            if (startOffset >= endOffset) return@Canvas

            val startLine = layout.getLineForOffset(startOffset)
            val endLine = layout.getLineForOffset(endOffset)

            // Padding around highlight for breathing room
            val padH = 3.dp.toPx()
            val padV = 2.dp.toPx()

            // Build a single unified Path from all per-line rects, then fill once.
            // This avoids double-alpha artifacts where adjacent lines' padding overlaps.
            val highlightPath = Path()
            for (line in startLine..endLine) {
                // Only pad the outer edges (top of first line, bottom of last line).
                // Inner edges meet at lineBottom == nextLineTop so the union fills fully.
                val topPad = if (line == startLine) padV else 0f
                val bottomPad = if (line == endLine) padV else 0f
                val lineTop = layout.getLineTop(line).toFloat() + offsetY - topPad
                val lineBottom = layout.getLineBottom(line).toFloat() + offsetY + bottomPad
                val left = if (line == startLine) {
                    layout.getPrimaryHorizontal(startOffset) + offsetX - padH
                } else {
                    layout.getLineLeft(line) + offsetX - padH
                }
                val right = if (line == endLine) {
                    layout.getPrimaryHorizontal(endOffset) + offsetX + padH
                } else {
                    layout.getLineRight(line) + offsetX + padH
                }
                highlightPath.addRoundRect(
                    RoundRect(
                        rect = Rect(left, lineTop, right, lineBottom),
                        cornerRadius = CornerRadius(highlightCornerPx)
                    )
                )
            }
            drawPath(path = highlightPath, color = highlightColor)

            val startHx = layout.getPrimaryHorizontal(startOffset) + offsetX
            val startHy = layout.getLineBottom(startLine).toFloat() + offsetY
            val endHx = layout.getPrimaryHorizontal(endOffset) + offsetX
            val endHy = layout.getLineBottom(endLine).toFloat() + offsetY

            helper.startHandleX = startHx
            helper.startHandleY = startHy
            helper.endHandleX = endHx
            helper.endHandleY = endHy

            drawStartHandle(startHx, startHy, handleSizePx, handleColor)
            drawEndHandle(endHx, endHy, handleSizePx, handleColor)
        }
    }
}

private fun DrawScope.drawStartHandle(x: Float, y: Float, size: Float, color: Color) {
    val half = size / 2f
    drawCircle(
        color = color,
        radius = half,
        center = Offset(x, y + half)
    )
    drawRect(
        color = color,
        topLeft = Offset(x, y),
        size = Size(half, half)
    )
}

private fun DrawScope.drawEndHandle(x: Float, y: Float, size: Float, color: Color) {
    val half = size / 2f
    drawCircle(
        color = color,
        radius = half,
        center = Offset(x, y + half)
    )
    drawRect(
        color = color,
        topLeft = Offset(x - half, y),
        size = Size(half, half)
    )
}
