package ca.ksamad.encore.ui

import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.util.VelocityTracker
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import kotlin.math.abs
import kotlin.math.roundToInt
import kotlinx.coroutines.launch

/**
 * A swipe only fires when the finger is still moving horizontally faster than this on release — a
 * deliberate flick. A slow drag (e.g. the small sideways drift while scrolling the list up and down)
 * settles back without triggering, which is the whole point.
 */
private val SWIPE_VELOCITY_THRESHOLD = 450.dp // per second

/** How far a row can be dragged sideways (enough to show the reveal, then it clamps). */
private val SWIPE_MAX_REVEAL = 96.dp

/** Minimum sideways travel before a flick counts, so a fast stationary twitch can't trigger it. */
private val SWIPE_MIN_DISTANCE = 24.dp

/**
 * Wraps [content] in the app's shared "queue swipe" affordance: flick right to [onAddToQueue]
 * (append), flick left to [onPlayNext] (insert after the current track). Used for both album rows
 * and individual tracks so the gesture and its meaning stay identical wherever it appears.
 *
 * Unlike a dismiss-style swipe, committing is gated on **release velocity**, not distance — so
 * accidental sideways movement while scrolling vertically slides a little and springs back instead
 * of firing. [content] should be opaque (e.g. a `ListItem`) so it hides the coloured reveal once
 * settled at rest.
 */
@Composable
fun QueueSwipeRow(
    onAddToQueue: () -> Unit,
    onPlayNext: () -> Unit,
    content: @Composable () -> Unit,
) {
    val scope = rememberCoroutineScope()
    val offsetX = remember { Animatable(0f) }

    val density = LocalDensity.current
    val velocityThresholdPx = with(density) { SWIPE_VELOCITY_THRESHOLD.toPx() }
    val maxRevealPx = with(density) { SWIPE_MAX_REVEAL.toPx() }
    val minDistancePx = with(density) { SWIPE_MIN_DISTANCE.toPx() }

    Box(
        modifier =
            Modifier.pointerInput(Unit) {
                val tracker = VelocityTracker()
                detectHorizontalDragGestures(
                    onDragStart = { tracker.resetTracking() },
                    onHorizontalDrag = { change, dragAmount ->
                        tracker.addPosition(change.uptimeMillis, change.position)
                        val target = (offsetX.value + dragAmount).coerceIn(-maxRevealPx, maxRevealPx)
                        scope.launch { offsetX.snapTo(target) }
                        change.consume()
                    },
                    onDragEnd = {
                        val velocity = tracker.calculateVelocity().x
                        val offset = offsetX.value
                        // A deliberate flick: fast enough, far enough, and the flick and the
                        // drag point the same way (so a bounce-back release doesn't count).
                        val committed =
                            abs(velocity) >= velocityThresholdPx &&
                                abs(offset) >= minDistancePx &&
                                (velocity > 0f) == (offset > 0f)
                        if (committed) {
                            if (offset > 0f) onAddToQueue() else onPlayNext()
                        }
                        scope.launch { offsetX.animateTo(0f) }
                    },
                    onDragCancel = { scope.launch { offsetX.animateTo(0f) } },
                )
            }
    ) {
        SwipeActionBackground(offset = offsetX.value, modifier = Modifier.matchParentSize())
        Box(modifier = Modifier.offset { IntOffset(offsetX.value.roundToInt(), 0) }) { content() }
    }
}

/**
 * The coloured reveal shown behind a swiping row: an "Add to queue" hint on the leading edge (drag
 * right, [offset] > 0) and a "Play next" hint on the trailing edge (drag left, [offset] < 0).
 * Renders empty while the row is settled at rest.
 */
@Composable
private fun SwipeActionBackground(
    offset: Float,
    modifier: Modifier = Modifier,
) {
    if (offset == 0f) {
        Box(modifier)
        return
    }

    val queueing = offset > 0f
    val container =
        if (queueing) {
            MaterialTheme.colorScheme.secondaryContainer
        } else {
            MaterialTheme.colorScheme.tertiaryContainer
        }
    val onContainer =
        if (queueing) {
            MaterialTheme.colorScheme.onSecondaryContainer
        } else {
            MaterialTheme.colorScheme.onTertiaryContainer
        }
    val icon = if (queueing) Icons.Filled.Add else Icons.Filled.PlayArrow
    val label = if (queueing) "Add to queue" else "Play next"

    Box(
        modifier.background(container).padding(horizontal = 24.dp),
        contentAlignment = if (queueing) Alignment.CenterStart else Alignment.CenterEnd,
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) {
            if (queueing) {
                Icon(icon, contentDescription = null, tint = onContainer)
                Spacer(Modifier.width(8.dp))
                Text(label, color = onContainer)
            } else {
                Text(label, color = onContainer)
                Spacer(Modifier.width(8.dp))
                Icon(icon, contentDescription = null, tint = onContainer)
            }
        }
    }
}
