package com.rosetta.messenger.ui.components

import android.app.Activity
import android.content.Context
import android.view.animation.DecelerateInterpolator
import android.view.inputmethod.InputMethodManager
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.*
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.*
import androidx.compose.ui.input.pointer.util.VelocityTracker
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.findViewTreeLifecycleOwner
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.coroutines.coroutineContext

// Telegram's CubicBezierInterpolator(0.25, 0.1, 0.25, 1.0)
private val TelegramEasing = CubicBezierEasing(0.25f, 0.1f, 0.25f, 1.0f)
private val TelegramOpenDecelerateEasing =
    Easing { input -> DecelerateInterpolator(1.5f).getInterpolation(input) }

// Swipe-back thresholds tuned for ultra-light navigation.
private const val COMPLETION_THRESHOLD = 0.05f // 5% of screen width
private const val FLING_VELOCITY_THRESHOLD = 35f // px/s
private const val ANIMATION_DURATION_ENTER = 300
private const val ANIMATION_DURATION_ENTER_TELEGRAM_CHAT = 150
private const val ANIMATION_DURATION_EXIT = 200
private const val EDGE_ZONE_DP = 320
private const val EDGE_ZONE_WIDTH_FRACTION = 0.85f
private const val TOUCH_SLOP_FACTOR = 0.35f
private const val HORIZONTAL_DOMINANCE_RATIO = 1.05f
private const val BACKGROUND_MIN_SCALE = 0.97f
private const val BACKGROUND_PARALLAX_DP = 4

enum class SwipeBackEnterAnimation {
    Fade,
    SlideFromRight
}

private suspend fun runTelegramChatEnterAnimation(
        alphaAnimatable: Animatable<Float, AnimationVector1D>,
        enterOffsetAnimatable: Animatable<Float, AnimationVector1D>,
        startOffsetPx: Float
) {
    var progress = 0f
    var lastFrameTimeMs = withFrameNanos { it } / 1_000_000L

    while (coroutineContext.isActive && progress < 1f) {
        val nowMs = withFrameNanos { it } / 1_000_000L
        var dtMs = nowMs - lastFrameTimeMs
        if (dtMs > 40L && progress == 0f) {
            dtMs = 0L
        } else if (dtMs > 18L) {
            dtMs = 18L
        }
        lastFrameTimeMs = nowMs

        progress =
                (progress + (dtMs / ANIMATION_DURATION_ENTER_TELEGRAM_CHAT.toFloat()))
                        .coerceIn(0f, 1f)

        val interpolated = TelegramOpenDecelerateEasing.transform(progress).coerceIn(0f, 1f)
        alphaAnimatable.snapTo(interpolated)
        enterOffsetAnimatable.snapTo(startOffsetPx * (1f - interpolated))
    }

    alphaAnimatable.snapTo(1f)
    enterOffsetAnimatable.snapTo(0f)
}

/**
 * Telegram-style swipe back container (optimized)
 *
 * Wraps content and allows swiping from the left edge to go back. Features:
 * - Edge-only swipe detection (left 30dp)
 * - Direct state update during drag (no coroutine overhead)
 * - VelocityTracker for fling detection
 * - Smooth Telegram-style bezier animation
 * - Scrim (dimming) on background
 * - Shadow on left edge during swipe
 * - Low completion threshold for comfortable one-handed back swipe
 */
private object SwipeBackSharedProgress {
    var ownerId by mutableLongStateOf(Long.MIN_VALUE)
    var ownerLayer by mutableIntStateOf(Int.MIN_VALUE)
    var progress by mutableFloatStateOf(0f)
    var active by mutableStateOf(false)
}

@Composable
fun SwipeBackBackgroundEffect(
        modifier: Modifier = Modifier,
        layer: Int = 0,
        content: @Composable BoxScope.() -> Unit
) {
    val density = LocalDensity.current
    val progress = SwipeBackSharedProgress.progress.coerceIn(0f, 1f)
    val active = SwipeBackSharedProgress.active && SwipeBackSharedProgress.ownerLayer == layer + 1
    val parallaxPx = with(density) { BACKGROUND_PARALLAX_DP.dp.toPx() }
    val scale = if (active) BACKGROUND_MIN_SCALE + (1f - BACKGROUND_MIN_SCALE) * progress else 1f
    val backgroundTranslationX = if (active) parallaxPx * (1f - progress) else 0f

    Box(
            modifier =
                    modifier.graphicsLayer {
                        transformOrigin = TransformOrigin(1f, 0f)
                        scaleX = scale
                        scaleY = scale
                        translationX = backgroundTranslationX
                    },
            content = content
    )
}

@Composable
fun SwipeBackContainer(
        isVisible: Boolean,
        onBack: () -> Unit,
        isDarkTheme: Boolean,
        layer: Int = 1,
        swipeEnabled: Boolean = true,
        propagateBackgroundProgress: Boolean = true,
        deferToChildren: Boolean = false,
        enterAnimation: SwipeBackEnterAnimation = SwipeBackEnterAnimation.Fade,
        // Return true to cancel the swipe — screen bounces back and onBack is NOT called.
        onInterceptSwipeBack: () -> Boolean = { false },
        content: @Composable () -> Unit
) {
    // 🚀 Lazy composition: skip ALL setup until the screen is opened for the first time.
    // Saves ~15 remember/Animatable allocations per unused screen at startup (×12 screens).
    var wasEverVisible by remember { mutableStateOf(false) }
    if (isVisible) wasEverVisible = true
    if (!wasEverVisible) return

    val density = LocalDensity.current
    val configuration = LocalConfiguration.current
    val screenWidthPx = with(density) { configuration.screenWidthDp.dp.toPx() }
    val edgeZonePx = with(density) { EDGE_ZONE_DP.dp.toPx() }
    val effectiveEdgeZonePx = maxOf(edgeZonePx, screenWidthPx * EDGE_ZONE_WIDTH_FRACTION)
    val backgroundParallaxPx = with(density) { BACKGROUND_PARALLAX_DP.dp.toPx() }
    val chatEnterOffsetPx = with(density) { 48.dp.toPx() }
    val containerId = remember { System.identityHashCode(Any()).toLong() }

    // Animation state for swipe (used only for swipe animations, not during drag)
    val offsetAnimatable = remember { Animatable(0f) }
    val enterOffsetAnimatable = remember { Animatable(0f) }

    // Alpha animation for fade-in entry
    val alphaAnimatable = remember { Animatable(0f) }

    // Drag state
    var dragOffset by remember { mutableFloatStateOf(0f) }
    var isDragging by remember { mutableStateOf(false) }

    // Visibility state
    var shouldShow by remember { mutableStateOf(false) }
    var isAnimatingIn by remember { mutableStateOf(false) }
    var isAnimatingOut by remember { mutableStateOf(false) }

    // Coroutine scope for animations
    val scope = rememberCoroutineScope()

    // 🔥 Keyboard controller для скрытия клавиатуры при свайпе (надёжный метод через
    // InputMethodManager)
    val context = LocalContext.current
    val view = LocalView.current
    val focusManager = LocalFocusManager.current
    val lifecycleOwner = view.findViewTreeLifecycleOwner()
    val dismissKeyboard: () -> Unit = {
        val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        imm.hideSoftInputFromWindow(view.windowToken, 0)
        imm.hideSoftInputFromWindow(view.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
        (view.context as? Activity)?.window?.let { window ->
            WindowCompat.getInsetsController(window, view).hide(WindowInsetsCompat.Type.ime())
        }
        focusManager.clearFocus(force = true)
    }

    fun computeCurrentOffset(): Float {
        val base = if (isDragging) dragOffset else offsetAnimatable.value
        val enter = if (isAnimatingIn && enterAnimation == SwipeBackEnterAnimation.SlideFromRight) {
            enterOffsetAnimatable.value
        } else 0f
        return base + enter
    }

    // Current alpha: use animatable during fade animations, otherwise 1
    val currentAlpha = if (isAnimatingIn || isAnimatingOut) alphaAnimatable.value else 1f
    val sharedOwnerId = SwipeBackSharedProgress.ownerId
    val sharedOwnerLayer = SwipeBackSharedProgress.ownerLayer
    val sharedProgress = SwipeBackSharedProgress.progress
    val sharedActive = SwipeBackSharedProgress.active
    val isBackgroundForActiveSwipe =
            sharedActive && sharedOwnerId != containerId && sharedOwnerLayer == layer + 1
    val backgroundScale =
            if (isBackgroundForActiveSwipe) {
                BACKGROUND_MIN_SCALE + (1f - BACKGROUND_MIN_SCALE) * sharedProgress.coerceIn(0f, 1f)
            } else {
                1f
            }
    val backgroundTranslationX =
            if (isBackgroundForActiveSwipe) {
                backgroundParallaxPx * (1f - sharedProgress.coerceIn(0f, 1f))
            } else {
                0f
            }

    fun updateSharedSwipeProgress(progress: Float, active: Boolean) {
        if (!propagateBackgroundProgress) return
        SwipeBackSharedProgress.ownerId = containerId
        SwipeBackSharedProgress.ownerLayer = layer
        SwipeBackSharedProgress.progress = progress.coerceIn(0f, 1f)
        SwipeBackSharedProgress.active = active
    }

    fun clearSharedSwipeProgressIfOwner() {
        if (!propagateBackgroundProgress) return
        if (SwipeBackSharedProgress.ownerId == containerId) {
            SwipeBackSharedProgress.ownerId = Long.MIN_VALUE
            SwipeBackSharedProgress.ownerLayer = Int.MIN_VALUE
            SwipeBackSharedProgress.progress = 0f
            SwipeBackSharedProgress.active = false
        }
    }

    fun forceResetSwipeState() {
        isDragging = false
        dragOffset = 0f
        clearSharedSwipeProgressIfOwner()
        scope.launch {
            offsetAnimatable.snapTo(0f)
            if (enterAnimation == SwipeBackEnterAnimation.SlideFromRight) {
                enterOffsetAnimatable.snapTo(0f)
            }
            if (shouldShow && !isAnimatingOut) {
                alphaAnimatable.snapTo(1f)
            }
        }
    }

    // Handle visibility changes
    // 🔥 FIX: try/finally ensures animation flags are ALWAYS reset even if
    // LaunchedEffect is cancelled by rapid isVisible changes (fast swipes).
    // Without this, isAnimatingIn/isAnimatingOut can stay true forever,
    // leaving an invisible overlay that blocks all touches on the chat list.
    LaunchedEffect(isVisible) {
        if (isVisible && !shouldShow) {
            // Animate in: fade-in
            shouldShow = true
            isAnimatingIn = true
            try {
                offsetAnimatable.snapTo(0f) // No slide for entry
                dragOffset = 0f
                alphaAnimatable.snapTo(0f)
                if (enterAnimation == SwipeBackEnterAnimation.SlideFromRight) {
                    enterOffsetAnimatable.snapTo(chatEnterOffsetPx)
                    runTelegramChatEnterAnimation(
                            alphaAnimatable = alphaAnimatable,
                            enterOffsetAnimatable = enterOffsetAnimatable,
                            startOffsetPx = chatEnterOffsetPx
                    )
                } else {
                    enterOffsetAnimatable.snapTo(0f)
                    alphaAnimatable.animateTo(
                            targetValue = 1f,
                            animationSpec =
                                    tween(
                                            durationMillis = ANIMATION_DURATION_ENTER,
                                            easing = FastOutSlowInEasing
                                    )
                    )
                }
            } finally {
                isAnimatingIn = false
                enterOffsetAnimatable.snapTo(0f)
            }
        } else if (!isVisible && shouldShow) {
            // Animate out: fade-out (when triggered by button, not swipe)
            isAnimatingOut = true
            try {
                alphaAnimatable.animateTo(
                        targetValue = 0f,
                        animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing)
                )
            } finally {
                shouldShow = false
                isAnimatingOut = false
                dragOffset = 0f
                clearSharedSwipeProgressIfOwner()
            }
        }
    }

    DisposableEffect(lifecycleOwner) {
        if (lifecycleOwner == null) {
            onDispose { }
        } else {
            val observer = LifecycleEventObserver { _, event ->
                if (event == Lifecycle.Event.ON_PAUSE || event == Lifecycle.Event.ON_STOP) {
                    forceResetSwipeState()
                }
            }
            lifecycleOwner.lifecycle.addObserver(observer)
            onDispose {
                lifecycleOwner.lifecycle.removeObserver(observer)
            }
        }
    }

    DisposableEffect(Unit) {
        onDispose {
            forceResetSwipeState()
        }
    }

    if (!shouldShow && !isAnimatingIn && !isAnimatingOut) return

    val currentOffset = computeCurrentOffset()
    val swipeProgress = (currentOffset / screenWidthPx).coerceIn(0f, 1f)
    val scrimAlpha = if (isDragging || currentOffset > 0f) 0.14f * (1f - swipeProgress) else 0f

    Box(
            modifier =
                    Modifier.fillMaxSize().graphicsLayer {
                        if (isBackgroundForActiveSwipe) {
                            transformOrigin = TransformOrigin(1f, 0f)
                            scaleX = backgroundScale
                            scaleY = backgroundScale
                            translationX = backgroundTranslationX
                        }
                    }
    ) {
        // Scrim (dimming layer behind the screen) - only when swiping
        if (!isAnimatingIn && currentOffset > 0f) {
            Box(modifier = Modifier.fillMaxSize().background(Color.Black.copy(alpha = scrimAlpha)))
        }

        // Content with swipe gesture
        Box(
                modifier =
                        Modifier.fillMaxSize()
                                .graphicsLayer {
                                    translationX = currentOffset
                                    alpha = currentAlpha
                                }
                                .background(if (isDarkTheme) Color(0xFF1B1B1B) else Color.White)
                                .pointerInput(swipeEnabled, isAnimatingIn, isAnimatingOut, deferToChildren) {
                                    if (!swipeEnabled || isAnimatingIn || isAnimatingOut) return@pointerInput

                                                val velocityTracker = VelocityTracker()
                                                val touchSlop =
                                                        viewConfiguration.touchSlop *
                                                                TOUCH_SLOP_FACTOR

                                                awaitEachGesture {
                                                    val down =
                                                            awaitFirstDown(
                                                                    requireUnconsumed = false
                                                            )

                                                    // Edge-only detection
                                                    if (down.position.x > effectiveEdgeZonePx) {
                                                        return@awaitEachGesture
                                                    }

                                                    velocityTracker.resetTracking()
                                                    var startedSwipe = false
                                                    var totalDragX = 0f
                                                    var totalDragY = 0f
                                                    var passedSlop = false
                                                    var keyboardHiddenForGesture = false
                                                    var resetOnFinally = true

                                                    // deferToChildren=true: pre-slop uses Main pass so children
                                                    // (e.g. LazyRow) process first — if they consume, we back off.
                                                    // deferToChildren=false (default): always use Initial pass
                                                    // to intercept before children (original behavior).
                                                    // Post-claim: always Initial to block children.
                                                    try {
                                                        while (true) {
                                                        val pass =
                                                                if (startedSwipe || !deferToChildren)
                                                                        PointerEventPass.Initial
                                                                else PointerEventPass.Main
                                                        val event = awaitPointerEvent(pass)
                                                        val change =
                                                                event.changes.firstOrNull {
                                                                    it.id == down.id
                                                                }
                                                                        ?: break

                                                        if (change.changedToUpIgnoreConsumed()) {
                                                            resetOnFinally = false
                                                            break
                                                        }

                                                        val dragDelta = change.positionChange()
                                                        totalDragX += dragDelta.x
                                                        totalDragY += dragDelta.y

                                                        if (!keyboardHiddenForGesture &&
                                                                        totalDragX > 10f &&
                                                                        kotlin.math.abs(totalDragX) >
                                                                                kotlin.math.abs(totalDragY)) {
                                                            dismissKeyboard()
                                                            keyboardHiddenForGesture = true
                                                        }

                                                        if (!passedSlop) {
                                                            // Child (e.g. LazyRow) already consumed — let it handle
                                                            if (change.isConsumed) break

                                                            val totalDistance =
                                                                    kotlin.math.sqrt(
                                                                            totalDragX *
                                                                                    totalDragX +
                                                                                    totalDragY *
                                                                                            totalDragY
                                                                    )
                                                            if (totalDistance < touchSlop) continue

                                                            // Slop exceeded — only claim rightward
                                                            // + mostly horizontal
                                                            if (totalDragX > 0 &&
                                                                            kotlin.math.abs(
                                                                                    totalDragX
                                                                            ) >
                                                                                    kotlin.math.abs(
                                                                                            totalDragY
                                                                                    ) *
                                                                                            HORIZONTAL_DOMINANCE_RATIO
                                                            ) {
                                                                passedSlop = true
                                                                startedSwipe = true
                                                                isDragging = true
                                                                dragOffset = offsetAnimatable.value
                                                                updateSharedSwipeProgress(
                                                                        progress =
                                                                                dragOffset /
                                                                                        screenWidthPx,
                                                                        active = true
                                                                )
                                                                dismissKeyboard()
                                                                keyboardHiddenForGesture = true

                                                                change.consume()
                                                            } else {
                                                                // Vertical or leftward — let
                                                                // children handle
                                                                break
                                                            }
                                                        } else {
                                                            // We own the gesture — update drag
                                                            dragOffset =
                                                                    (dragOffset + dragDelta.x)
                                                                            .coerceIn(
                                                                                    0f,
                                                                                    screenWidthPx
                                                                            )
                                                            updateSharedSwipeProgress(
                                                                    progress =
                                                                            dragOffset /
                                                                                    screenWidthPx,
                                                                    active = true
                                                            )
                                                            velocityTracker.addPosition(
                                                                    change.uptimeMillis,
                                                                    change.position
                                                            )
                                                            change.consume()
                                                        }
                                                        }
                                                    } finally {
                                                        // Сбрасываем только при отмене/прерывании жеста.
                                                        // При обычном UP сброс делаем позже, чтобы не было рывка.
                                                        if (resetOnFinally && isDragging) {
                                                            forceResetSwipeState()
                                                        }
                                                    }

                                                    // Handle drag end
                                                    if (startedSwipe) {
                                                        isDragging = false
                                                        val velocity =
                                                                velocityTracker.calculateVelocity()
                                                                        .x
                                                        val currentProgress =
                                                                dragOffset / screenWidthPx

                                                        val shouldComplete =
                                                                currentProgress >=
                                                                        COMPLETION_THRESHOLD ||
                                                                velocity >
                                                                        FLING_VELOCITY_THRESHOLD || // Fast fling right
                                                                        (currentProgress >=
                                                                                COMPLETION_THRESHOLD *
                                                                                        0.5f &&
                                                                                velocity >
                                                                                        -FLING_VELOCITY_THRESHOLD * 4f) // Complete by default once swipe starts unless user strongly throws back

                                                        scope.launch {
                                                            offsetAnimatable.snapTo(dragOffset)
                                                            updateSharedSwipeProgress(
                                                                    progress =
                                                                            dragOffset /
                                                                                    screenWidthPx,
                                                                    active = true
                                                            )

                                                            if (shouldComplete) {
                                                                // Intercept: if owner handled back locally (e.g. clear
                                                                // message selection), bounce back without exiting.
                                                                if (onInterceptSwipeBack()) {
                                                                    dismissKeyboard()
                                                                    offsetAnimatable.animateTo(
                                                                            targetValue = 0f,
                                                                            animationSpec =
                                                                                    tween(
                                                                                            durationMillis =
                                                                                                    ANIMATION_DURATION_EXIT,
                                                                                            easing =
                                                                                                    TelegramEasing
                                                                                    ),
                                                                            block = {
                                                                                updateSharedSwipeProgress(
                                                                                        progress =
                                                                                                value /
                                                                                                        screenWidthPx,
                                                                                        active = true
                                                                                )
                                                                            }
                                                                    )
                                                                    dragOffset = 0f
                                                                    clearSharedSwipeProgressIfOwner()
                                                                    return@launch
                                                                }
                                                                offsetAnimatable.animateTo(
                                                                        targetValue = screenWidthPx,
                                                                        animationSpec =
                                                                                tween(
                                                                                        durationMillis =
                                                                                                ANIMATION_DURATION_EXIT,
                                                                                        easing =
                                                                                                TelegramEasing
                                                                                ),
                                                                        block = {
                                                                            updateSharedSwipeProgress(
                                                                                    progress =
                                                                                            value /
                                                                                                    screenWidthPx,
                                                                                    active = true
                                                                            )
                                                                        }
                                                                )
                                                                // 🔥 FIX: Reset state BEFORE onBack() to prevent
                                                                // redundant fade-out animation in LaunchedEffect.
                                                                // Without this, onBack() changes isVisible→false,
                                                                // triggering a 200ms fade-out during which an invisible
                                                                // overlay blocks all touches on the chat list.
                                                                shouldShow = false
                                                                dragOffset = 0f
                                                                clearSharedSwipeProgressIfOwner()
                                                                dismissKeyboard()
                                                                onBack()
                                                            } else {
                                                                offsetAnimatable.animateTo(
                                                                        targetValue = 0f,
                                                                        animationSpec =
                                                                                tween(
                                                                                        durationMillis =
                                                                                                ANIMATION_DURATION_EXIT,
                                                                                        easing =
                                                                                                TelegramEasing
                                                                                ),
                                                                        block = {
                                                                            updateSharedSwipeProgress(
                                                                                    progress =
                                                                                            value /
                                                                                                    screenWidthPx,
                                                                                    active = true
                                                                            )
                                                                        }
                                                                )
                                                                dragOffset = 0f
                                                                clearSharedSwipeProgressIfOwner()
                                                            }
                                                        }
                                                    }
                                                }
                                    }
        ) { content() }
    }
}
