package ca.ksamad.encore.ui

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.VolumeUp
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.DeleteSweep
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.SwipeToDismissBox
import androidx.compose.material3.SwipeToDismissBoxValue
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberSwipeToDismissBoxState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
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.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ca.ksamad.encore.mpd.model.MpdSong
import ca.ksamad.encore.mpd.model.MpdStatus
import ca.ksamad.encore.playback.AlbumArt
import ca.ksamad.encore.playback.MpdArtData
import ca.ksamad.encore.playback.SongArt
import kotlinx.coroutines.launch

/**
 * "Up next": the tracks that will auto-play from the current one to the end of the queue. We
 * deliberately drop the already-played prefix (MPD keeps played tracks in the queue unless consume
 * mode is on) so the list reflects what will actually play before MPD stops — and it shrinks as
 * playback advances even without consume. Repeat/single change that, so we caption those cases.
 *
 * The full queue is re-fetched when its version changes; the upcoming slice is derived from the
 * live current-song position, so it updates as playback moves.
 */
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun QueueScreen(
    vm: PlayerViewModel,
    onBack: () -> Unit,
) {
    val status by vm.status.collectAsStateWithLifecycle()

    // Reload the full queue when the queue version bumps (edits/consume).
    val fullQueue by
        produceState<List<MpdSong>?>(initialValue = null, status?.playlistVersion) {
            value = vm.loadQueue()
        }

    // Ids swiped away locally, hidden immediately so the row leaves without waiting
    // for the server round-trip. Reset on every reload — by then the fresh queue
    // already reflects the change (or, if the delete failed, restores the track).
    var pendingRemoval by remember(fullQueue) { mutableStateOf(emptySet<Int>()) }

    // Slice from the current song to the end. `song` is the current queue index.
    val currentPos = status?.song
    val upcoming: List<MpdSong>? = fullQueue?.let { q ->
        val slice =
            if (currentPos != null && currentPos in q.indices) q.subList(currentPos, q.size) else q
        if (pendingRemoval.isEmpty()) slice else slice.filter { it.id !in pendingRemoval }
    }
    val hasCurrent = currentPos != null && (fullQueue?.indices?.contains(currentPos) == true)

    // Guards the destructive "clear queue" action behind a confirmation dialog.
    var confirmClear by remember { mutableStateOf(false) }
    val queueNotEmpty = fullQueue?.isNotEmpty() == true

    // Undo affordance for swipe-to-remove.
    val snackbarHostState = remember { SnackbarHostState() }
    val scope = rememberCoroutineScope()

    fun removeSong(song: MpdSong) {
        val id = song.id ?: return
        pendingRemoval = pendingRemoval + id
        vm.removeQueueItem(id)
        snackbarHostState.currentSnackbarData?.dismiss()
        scope.launch {
            val result =
                snackbarHostState.showSnackbar(
                    message = "Removed “${song.title ?: song.uri}”",
                    actionLabel = "Undo",
                    duration = SnackbarDuration.Long,
                )
            if (result == SnackbarResult.ActionPerformed) {
                // Re-insert at its original index; fall back to append if unknown.
                song.pos?.let { vm.addTrackAt(song.uri, it) } ?: vm.queueTrack(song.uri)
                pendingRemoval = pendingRemoval - id
            }
        }
    }

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(upcoming?.let { "Up Next (${it.size})" } ?: "Up Next") },
                navigationIcon = {
                    IconButton(onClick = onBack) {
                        Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
                    }
                },
                actions = {
                    if (queueNotEmpty) {
                        IconButton(onClick = { confirmClear = true }) {
                            Icon(Icons.Filled.DeleteSweep, contentDescription = "Clear queue")
                        }
                    }
                },
            )
        },
        snackbarHost = { SnackbarHost(snackbarHostState) },
    ) { innerPadding ->
        when {
            upcoming == null -> {
                Box(
                    Modifier.fillMaxSize().padding(innerPadding),
                    contentAlignment = Alignment.Center,
                ) {
                    CircularProgressIndicator()
                }
            }

            upcoming.isEmpty() -> {
                Box(
                    Modifier.fillMaxSize().padding(innerPadding),
                    contentAlignment = Alignment.Center,
                ) {
                    Text("Nothing queued", color = MaterialTheme.colorScheme.onSurfaceVariant)
                }
            }

            else -> {
                LazyColumn(modifier = Modifier.padding(innerPadding)) {
                    playbackModeCaption(status)?.let { caption ->
                        item {
                            Text(
                                caption,
                                style = MaterialTheme.typography.bodySmall,
                                color = MaterialTheme.colorScheme.onSurfaceVariant,
                                modifier =
                                    Modifier.fillMaxWidth()
                                        .padding(horizontal = 16.dp, vertical = 8.dp),
                            )
                        }
                    }
                    items(upcoming, key = { it.id ?: it.uri }) { song ->
                        // First item is the currently-playing track (when there is one).
                        val isCurrent = hasCurrent && song === upcoming.first()
                        val id = song.id
                        // The current track and id-less entries aren't swipe-removable —
                        // removing the current one would disrupt playback.
                        if (isCurrent || id == null) {
                            QueueRow(
                                song,
                                isCurrent,
                                onClick = { id?.let { vm.playQueueItem(it) } },
                            )
                        } else {
                            SwipeToRemoveRow(onRemove = { removeSong(song) }) {
                                QueueRow(
                                    song,
                                    isCurrent = false,
                                    onClick = { vm.playQueueItem(id) },
                                )
                            }
                        }
                    }
                }
            }
        }
    }

    if (confirmClear) {
        AlertDialog(
            onDismissRequest = { confirmClear = false },
            title = { Text("Clear the queue?") },
            text = { Text("This removes every track from the queue and stops playback.") },
            confirmButton = {
                TextButton(
                    onClick = {
                        vm.clearQueue()
                        confirmClear = false
                    }
                ) {
                    Text("Clear")
                }
            },
            dismissButton = {
                TextButton(onClick = { confirmClear = false }) { Text("Cancel") }
            },
        )
    }
}

/**
 * Art model for a queue row. Keying by album — not the per-track URI — means every track of the
 * same album resolves to one cache entry, so a 12-track album is fetched from the server once
 * instead of twelve times, and reuses whatever the album library already cached. Falls back to the
 * track's own art only for entries with no album tag (e.g. a loose file or a stream).
 */
private fun queueArt(song: MpdSong): MpdArtData =
    song.album
        ?.takeIf { it.isNotBlank() }
        ?.let { AlbumArt(it, song.albumArtist) } ?: SongArt(song.uri, song.lastModified)

/** A single queue entry — art, title/artist, and a duration or now-playing badge. */
@Composable
private fun QueueRow(
    song: MpdSong,
    isCurrent: Boolean,
    onClick: () -> Unit,
) {
    ListItem(
        modifier = Modifier.clickable(onClick = onClick),
        colors =
            if (isCurrent) {
                ListItemDefaults.colors(containerColor = MaterialTheme.colorScheme.primaryContainer)
            } else {
                ListItemDefaults.colors()
            },
        leadingContent = {
            ArtImage(
                model = queueArt(song),
                iconSize = 20.dp,
                modifier = Modifier.size(44.dp).clip(RoundedCornerShape(6.dp)),
            )
        },
        headlineContent = {
            Text(
                song.title ?: song.uri,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
                fontWeight = if (isCurrent) FontWeight.Bold else FontWeight.Normal,
            )
        },
        supportingContent =
            song.artist?.let {
                { Text(it, maxLines = 1, overflow = TextOverflow.Ellipsis) }
            },
        trailingContent =
            if (isCurrent) {
                { Icon(Icons.AutoMirrored.Filled.VolumeUp, contentDescription = "Now playing") }
            } else {
                song.duration?.let { { Text(formatDuration(it)) } }
            },
    )
}

/**
 * Wraps [content] in a swipe-to-remove gesture: a swipe in either direction settles the row
 * off-screen and calls [onRemove]. The reveal is a red trash background on whichever edge is being
 * swiped from.
 */
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SwipeToRemoveRow(
    onRemove: () -> Unit,
    content: @Composable () -> Unit,
) {
    val state =
        rememberSwipeToDismissBoxState(
            confirmValueChange = { target ->
                if (target != SwipeToDismissBoxValue.Settled) {
                    onRemove()
                    true // Commit the dismiss; the row is also filtered from the list.
                } else {
                    false
                }
            }
        )
    SwipeToDismissBox(
        state = state,
        backgroundContent = { RemoveBackground(state.dismissDirection) },
    ) {
        content()
    }
}

/** Red "delete" reveal behind a swiping queue row; empty while settled. */
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun RemoveBackground(direction: SwipeToDismissBoxValue) {
    if (direction == SwipeToDismissBoxValue.Settled) {
        Box(Modifier.fillMaxSize())
        return
    }
    val alignment =
        if (direction == SwipeToDismissBoxValue.StartToEnd) {
            Alignment.CenterStart
        } else {
            Alignment.CenterEnd
        }
    Box(
        Modifier.fillMaxSize()
            .background(MaterialTheme.colorScheme.errorContainer)
            .padding(horizontal = 24.dp),
        contentAlignment = alignment,
    ) {
        Icon(
            Icons.Filled.Delete,
            contentDescription = "Remove from queue",
            tint = MaterialTheme.colorScheme.onErrorContainer,
        )
    }
}

/** Explains what will actually happen at the end of the list, given the modes. */
private fun playbackModeCaption(status: MpdStatus?): String? =
    when {
        status == null -> null
        status.repeat && status.single -> "Repeating the current track."
        status.single -> "Single mode — stops after the current track."
        status.repeat -> "Repeat on — the queue loops, so playback won't stop."
        else -> null
    }

/** `m:ss` for a duration in seconds. Shared across the queue/album track lists. */
internal fun formatDuration(seconds: Double): String {
    val total = seconds.toInt().coerceAtLeast(0)
    return "%d:%02d".format(total / 60, total % 60)
}
