package ca.ksamad.encore.ui

import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import ca.ksamad.encore.data.ConnectionSettings
import ca.ksamad.encore.mpd.model.MpdAlbum

/**
 * App root. Renders whichever [AppScreen] the [PlayerViewModel] decides on: a brief loading splash
 * while it reads settings / auto-connects, the connect form (first run, after disconnect, or on
 * error), or the now-playing screen.
 */

/** Sub-screens layered over the player (no nav library needed for this few). */
private enum class PlayerOverlay {
    None,
    Settings,
    Albums,
    Queue,
}

/** Saves the drilled-into album (name + artist) across config change/process death. */
private val AlbumSaver =
    listSaver<MpdAlbum?, String?>(
        save = { listOf(it?.name, it?.albumArtist) },
        restore = { saved -> saved[0]?.let { name -> MpdAlbum(name, saved[1]) } },
    )

@Composable
fun EncoreApp(vm: PlayerViewModel = viewModel()) {
    val screen by vm.screen.collectAsStateWithLifecycle()
    var overlay by rememberSaveable { mutableStateOf(PlayerOverlay.None) }
    // When set (within the Albums overlay), the album detail screen is shown.
    var detailAlbum by rememberSaveable(stateSaver = AlbumSaver) { mutableStateOf<MpdAlbum?>(null) }
    // Owned here (not in AlbumsScreen) so the search text survives drilling into an
    // album and back — AlbumsScreen leaves composition while the detail is shown.
    var albumSearch by rememberSaveable { mutableStateOf("") }
    // Same reasoning for the library's scroll position (one per layout), so backing
    // out of an album returns you to where you were. rememberLazy*State is itself
    // saveable, so these also survive rotation/process death.
    val albumListState = rememberLazyListState()
    val albumGridState = rememberLazyGridState()

    // Overlays only make sense over the player; leaving it (e.g. after a
    // reset/disconnect) drops us back to the normal screen flow.
    LaunchedEffect(screen) {
        if (screen !is AppScreen.Player) {
            overlay = PlayerOverlay.None
            detailAlbum = null
        }
    }

    // Forget the album search once we leave the library entirely (to the player or
    // another overlay). The detail screen keeps overlay == Albums, so browsing into
    // an album and back preserves it.
    LaunchedEffect(overlay) {
        if (overlay != PlayerOverlay.Albums) albumSearch = ""
    }

    when (val s = screen) {
        is AppScreen.Loading -> {
            LoadingScreen()
        }

        is AppScreen.Connect -> {
            ConnectScreen(vm, s.settings, s.error)
        }

        is AppScreen.Player -> {
            // The overlays form a back stack the OS knows nothing about (we don't use a
            // nav library). Intercept the system/gesture back button and pop one level so
            // it mirrors the on-screen back arrow, instead of falling through and closing
            // the app. Only enabled when there's somewhere to pop back to.
            BackHandler(enabled = overlay != PlayerOverlay.None) {
                if (detailAlbum != null) {
                    detailAlbum = null
                } else {
                    overlay = PlayerOverlay.None
                }
            }

            when (overlay) {
                PlayerOverlay.Settings -> {
                    SettingsScreen(vm, onBack = { overlay = PlayerOverlay.None })
                }

                PlayerOverlay.Albums -> {
                    val selected = detailAlbum
                    if (selected != null) {
                        AlbumDetailScreen(
                            vm,
                            album = selected,
                            onBack = { detailAlbum = null },
                            onPlay = {
                                detailAlbum = null
                                overlay = PlayerOverlay.None
                            },
                        )
                    } else {
                        AlbumsScreen(
                            vm,
                            query = albumSearch,
                            onQueryChange = { albumSearch = it },
                            listState = albumListState,
                            gridState = albumGridState,
                            onBack = { overlay = PlayerOverlay.None },
                            onOpenAlbum = { detailAlbum = it },
                        )
                    }
                }

                PlayerOverlay.Queue -> {
                    QueueScreen(vm, onBack = { overlay = PlayerOverlay.None })
                }

                PlayerOverlay.None -> {
                    NowPlayingScreen(
                        vm,
                        onOpenSettings = { overlay = PlayerOverlay.Settings },
                        onOpenLibrary = { overlay = PlayerOverlay.Albums },
                        onOpenQueue = { overlay = PlayerOverlay.Queue },
                    )
                }
            }
        }
    }
}

@Composable
private fun LoadingScreen() {
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
    ) {
        CircularProgressIndicator()
        Text(
            "Connecting…",
            style = MaterialTheme.typography.bodyMedium,
            modifier = Modifier.padding(top = 16.dp),
        )
    }
}

@Composable
private fun ConnectScreen(
    vm: PlayerViewModel,
    saved: ConnectionSettings,
    error: String?,
) {
    // Seed the fields from the persisted settings. Keyed on the loaded values so
    // the form re-seeds once DataStore delivers them, but a user's edits after
    // that stick (rememberSaveable also survives rotation/process death).
    var host by rememberSaveable(saved.host) { mutableStateOf(saved.host) }
    var port by rememberSaveable(saved.port) { mutableStateOf(saved.port.toString()) }
    var password by rememberSaveable(saved.password) { mutableStateOf(saved.password ?: "") }
    var showPassword by rememberSaveable { mutableStateOf(false) }

    Column(
        modifier = Modifier.fillMaxSize().padding(24.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
    ) {
        Text("Encore", style = MaterialTheme.typography.headlineMedium)
        Text(
            "Connect to your MPD server",
            style = MaterialTheme.typography.bodyMedium,
            modifier = Modifier.padding(top = 4.dp, bottom = 24.dp),
        )

        OutlinedTextField(
            value = host,
            onValueChange = { host = it },
            label = { Text("Host") },
            singleLine = true,
            modifier = Modifier.fillMaxWidth(),
        )
        Spacer(Modifier.height(12.dp))
        OutlinedTextField(
            value = port,
            onValueChange = { port = it.filter(Char::isDigit) },
            label = { Text("Port") },
            singleLine = true,
            keyboardOptions =
                androidx.compose.foundation.text.KeyboardOptions(
                    keyboardType = KeyboardType.Number
                ),
            modifier = Modifier.fillMaxWidth(),
        )
        Spacer(Modifier.height(12.dp))
        OutlinedTextField(
            value = password,
            onValueChange = { password = it },
            label = { Text("Password (optional)") },
            singleLine = true,
            visualTransformation =
                if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
            keyboardOptions =
                androidx.compose.foundation.text.KeyboardOptions(
                    keyboardType = KeyboardType.Password
                ),
            trailingIcon = {
                IconButton(onClick = { showPassword = !showPassword }) {
                    Icon(
                        if (showPassword) Icons.Filled.VisibilityOff else Icons.Filled.Visibility,
                        contentDescription = if (showPassword) "Hide password" else "Show password",
                    )
                }
            },
            modifier = Modifier.fillMaxWidth(),
        )
        Spacer(Modifier.height(24.dp))

        Button(
            onClick = { vm.connect(host, port.toIntOrNull() ?: 6600, password) },
            enabled = host.isNotBlank(),
            modifier = Modifier.fillMaxWidth(),
        ) {
            Text("Connect")
        }

        if (error != null) {
            Text(
                text = error,
                style = MaterialTheme.typography.bodySmall,
                color = MaterialTheme.colorScheme.error,
                modifier = Modifier.padding(top = 16.dp),
            )
        }
    }
}
