package com.fabledsword.minstrel.discover.ui

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import com.composables.icons.lucide.Lucide
import com.composables.icons.lucide.Search as LucideSearch
import com.fabledsword.minstrel.discover.data.RequestOutcome
import com.fabledsword.minstrel.models.ArtistSuggestionRef
import com.fabledsword.minstrel.models.LidarrRequestKind
import com.fabledsword.minstrel.models.LidarrSearchResultRef
import com.fabledsword.minstrel.models.SuggestionSnoozeRef
import com.fabledsword.minstrel.nav.Discover
import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets
import com.fabledsword.minstrel.shared.widgets.ErrorRetry
import com.fabledsword.minstrel.shared.widgets.LoadingCentered
import com.fabledsword.minstrel.shared.widgets.MinstrelTopAppBar
import com.fabledsword.minstrel.shared.widgets.PullToRefreshScaffold
import kotlinx.coroutines.launch

// ─── Screen ──────────────────────────────────────────────────────────

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DiscoverScreen(
    navController: NavHostController,
    viewModel: DiscoverViewModel = hiltViewModel(),
) {
    val state by viewModel.state.collectAsStateWithLifecycle()
    val snackbar = remember { SnackbarHostState() }
    val scope = rememberCoroutineScope()

    Scaffold(
        contentWindowInsets = ShellContentWindowInsets,
        modifier = Modifier.fillMaxSize(),
        topBar = {
            MinstrelTopAppBar(
                title = "Discover",
                navController = navController,
                currentRouteName = Discover::class.qualifiedName,
                onBack = { navController.popBackStack() },
            )
        },
        snackbarHost = { SnackbarHost(snackbar) },
    ) { inner ->
        DiscoverBody(
            inner = inner,
            state = state,
            viewModel = viewModel,
            snackbar = snackbar,
            scope = scope,
        )
    }
}

@Composable
private fun DiscoverBody(
    inner: androidx.compose.foundation.layout.PaddingValues,
    state: DiscoverState,
    viewModel: DiscoverViewModel,
    snackbar: SnackbarHostState,
    scope: kotlinx.coroutines.CoroutineScope,
) {
    Column(modifier = Modifier.fillMaxSize().padding(inner)) {
        SearchBar(
            query = state.query,
            onQueryChange = viewModel::setQuery,
            onSubmit = viewModel::runSearch,
        )
        KindChips(kind = state.kind, onChange = viewModel::setKind)
        PullToRefreshScaffold(
            onRefresh = { viewModel.loadSuggestions().join() },
            modifier = Modifier.fillMaxSize(),
        ) {
            when (val r = state.results) {
                ResultsState.Idle -> SuggestionsPane(
                    state = state.suggestions,
                    locallyRequestedMbids = state.locallyRequestedMbids,
                    snoozeUi = SnoozeUi(
                        locallySnoozedMbids = state.locallySnoozedMbids,
                        snoozes = state.snoozes,
                        // No snackbar on snooze: the row itself flips to "Not
                        // right now" with an Undo, so a snackbar would only
                        // repeat what the user can already see — and cover the
                        // next row while doing it.
                        onSnooze = { s -> scope.launch { viewModel.snoozeSuggestion(s) } },
                        onUnsnooze = { mbid, name ->
                            scope.launch { viewModel.unsnoozeSuggestion(mbid, name) }
                        },
                    ),
                    onRequest = { s ->
                        scope.launch {
                            val outcome = viewModel.requestSuggestion(s)
                            snackbar.showSnackbar(snackbarFor(outcome, s.name))
                        }
                    },
                    onRetry = { viewModel.loadSuggestions() },
                )
                ResultsState.Loading -> LoadingCentered()
                is ResultsState.Loaded -> ResultsList(
                    rows = r.items,
                    locallyRequestedMbids = state.locallyRequestedMbids,
                    onRequest = { row ->
                        scope.launch {
                            val outcome = viewModel.requestRow(row)
                            snackbar.showSnackbar(snackbarFor(outcome, row.name))
                        }
                    },
                )
                is ResultsState.Error -> ErrorRetry(
                    title = "Search failed",
                    message = r.message,
                    onRetry = viewModel::runSearch,
                )
            }
        }
    }
}

@Composable
private fun SearchBar(
    query: String,
    onQueryChange: (String) -> Unit,
    onSubmit: () -> Unit,
) {
    OutlinedTextField(
        value = query,
        onValueChange = onQueryChange,
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 16.dp, vertical = 8.dp),
        singleLine = true,
        placeholder = { Text("Search Lidarr for new music") },
        keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
        keyboardActions = KeyboardActions(onSearch = { onSubmit() }),
        trailingIcon = {
            IconButton(onClick = onSubmit) {
                Icon(Lucide.LucideSearch, contentDescription = "Search")
            }
        },
    )
}

@Composable
private fun KindChips(kind: LidarrRequestKind, onChange: (LidarrRequestKind) -> Unit) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 16.dp, vertical = 4.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        FilterChip(
            selected = kind == LidarrRequestKind.ARTIST,
            onClick = { onChange(LidarrRequestKind.ARTIST) },
            label = { Text("Artists") },
        )
        FilterChip(
            selected = kind == LidarrRequestKind.ALBUM,
            onClick = { onChange(LidarrRequestKind.ALBUM) },
            label = { Text("Albums") },
        )
    }
}

/**
 * The snooze surface's data and callbacks, bundled rather than threaded
 * through as four more parameters — the pane grew from one action to three
 * with slice 4 and the signatures stopped being readable.
 */
private data class SnoozeUi(
    val locallySnoozedMbids: Set<String>,
    val snoozes: List<SuggestionSnoozeRef>,
    val onSnooze: (ArtistSuggestionRef) -> Unit,
    val onUnsnooze: (String, String) -> Unit,
)

@Composable
private fun SuggestionsPane(
    state: SuggestionState,
    locallyRequestedMbids: Set<String>,
    snoozeUi: SnoozeUi,
    onRequest: (ArtistSuggestionRef) -> Unit,
    onRetry: () -> Unit,
) {
    when (state) {
        SuggestionState.Loading -> LoadingCentered()
        is SuggestionState.Error -> ErrorRetry(
            title = "Couldn't load suggestions",
            message = state.message,
            onRetry = onRetry,
        )
        is SuggestionState.Loaded -> SuggestionsList(
            items = state.items.filter { it.mbid !in locallyRequestedMbids },
            snoozeUi = snoozeUi,
            onRequest = onRequest,
        )
    }
}

@Composable
private fun SuggestionsList(
    items: List<ArtistSuggestionRef>,
    snoozeUi: SnoozeUi,
    onRequest: (ArtistSuggestionRef) -> Unit,
) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(bottom = 16.dp),
    ) {
        item { SuggestionsHeader() }
        if (items.isEmpty()) {
            // An empty deck used to mean one thing — no listening signal yet.
            // With snoozing it can also mean "you parked them all", and telling
            // that user to go listen to something would be wrong advice.
            item {
                CenteredMessage(
                    if (snoozeUi.snoozes.isEmpty()) {
                        "Listen to or like an artist to fill this in."
                    } else {
                        "Nothing new right now — the artists you've parked are below."
                    },
                )
            }
        } else {
            items(items = items, key = { it.mbid }) { s ->
                SuggestionTile(
                    s = s,
                    snoozed = s.mbid in snoozeUi.locallySnoozedMbids,
                    onRequest = { onRequest(s) },
                    onSnooze = { snoozeUi.onSnooze(s) },
                    onUnsnooze = { snoozeUi.onUnsnooze(s.mbid, s.name) },
                )
                HorizontalDivider()
            }
        }
        // Parked candidates live at the bottom of the same scroll, not behind a
        // separate screen: it's a short list the user rarely needs, but it must
        // be reachable — a snoozed candidate is gone from the deck above, so
        // this is the only way back to it.
        if (snoozeUi.snoozes.isNotEmpty()) {
            item { SnoozedHeader() }
            items(items = snoozeUi.snoozes, key = { "snoozed-${it.mbid}" }) { row ->
                SnoozedTile(
                    row = row,
                    onUnsnooze = { snoozeUi.onUnsnooze(row.mbid, row.name) },
                )
                HorizontalDivider()
            }
        }
    }
}

@Composable
private fun SnoozedHeader() {
    Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)) {
        HorizontalDivider(modifier = Modifier.padding(bottom = 12.dp))
        Text(
            text = "Not right now",
            style = MaterialTheme.typography.titleMedium,
            color = MaterialTheme.colorScheme.onBackground,
        )
        Text(
            text = "These come back on their own. Nothing here counts against your taste profile.",
            style = MaterialTheme.typography.bodySmall,
            color = MaterialTheme.colorScheme.onSurfaceVariant,
        )
    }
}

@Composable
private fun SuggestionsHeader() {
    Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)) {
        Text(
            text = "Suggested for you",
            style = MaterialTheme.typography.titleLarge,
            color = MaterialTheme.colorScheme.onBackground,
        )
        Text(
            text = "Out-of-library artists drawn from what you've liked and played.",
            style = MaterialTheme.typography.bodySmall,
            color = MaterialTheme.colorScheme.onSurfaceVariant,
        )
    }
}

@Composable
private fun ResultsList(
    rows: List<LidarrSearchResultRef>,
    locallyRequestedMbids: Set<String>,
    onRequest: (LidarrSearchResultRef) -> Unit,
) {
    if (rows.isEmpty()) {
        CenteredMessage("Nothing to add for that search yet.")
        return
    }
    LazyColumn(modifier = Modifier.fillMaxSize()) {
        items(items = rows, key = { it.mbid }) { row ->
            ResultTile(
                row = row,
                locallyRequested = row.mbid in locallyRequestedMbids,
                onRequest = { onRequest(row) },
            )
            HorizontalDivider()
        }
    }
}

@Composable
private fun CenteredMessage(text: String) {
    Box(modifier = Modifier.fillMaxSize().padding(24.dp), contentAlignment = Alignment.Center) {
        Text(
            text = text,
            style = MaterialTheme.typography.bodyMedium,
            color = MaterialTheme.colorScheme.onSurfaceVariant,
        )
    }
}

private fun snackbarFor(outcome: RequestOutcome, name: String): String = when (outcome) {
    RequestOutcome.ACCEPTED -> "Requested: $name"
    RequestOutcome.QUEUED -> "Request queued: $name"
}
