package net.sergeych.toread

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
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.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.BookmarkAdd
import androidx.compose.material.icons.filled.BookmarkRemove
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.DoneAll
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.material.icons.filled.FolderOpen
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.Language
import androidx.compose.material.icons.filled.MenuBook
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.RemoveDone
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.Card
import androidx.compose.material3.Checkbox
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
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.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import net.sergeych.toread.storage.BookReadingStatus
import kotlin.math.roundToInt
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@Composable
@OptIn(ExperimentalMaterial3Api::class)
internal fun LibraryScreen(
    state: AppState.Library,
    activeScan: LibraryScanProgress?,
    itemRefreshRequest: LibraryItemRefreshRequest?,
    hiddenFileIds: Set<String>,
    onStateChange: (AppState) -> Unit,
    onLibraryFilterChange: (LibraryFilter) -> Unit,
    onNavigateToScan: () -> Unit,
    onDeleteRequested: (
        request: LibraryDeleteRequest,
        remove: () -> Unit,
        restore: () -> Unit,
    ) -> Unit,
) {
    val scope = rememberCoroutineScope()
    val focusManager = LocalFocusManager.current
    var busy by remember { mutableStateOf(false) }
    var openingBook by remember { mutableStateOf(false) }
    var message by remember(state.message) { mutableStateOf(state.message) }
    var items by remember(state.items) { mutableStateOf(state.items) }
    var loadingLibrary by remember(state.items) { mutableStateOf(false) }
    var recentlyAddedItems by remember(state.items) { mutableStateOf<List<LibraryItem>>(emptyList()) }
    var wasScanning by remember { mutableStateOf(false) }
    var settingsMenuOpen by remember { mutableStateOf(false) }
    var localeMenuOpen by remember { mutableStateOf(false) }
    var autoScanDownloads by remember { mutableStateOf(true) }
    var searchText by remember { mutableStateOf("") }
    var searchFocused by remember { mutableStateOf(false) }
    var searchResults by remember { mutableStateOf<List<LibraryItem>>(emptyList()) }
    var searching by remember { mutableStateOf(false) }
    var selectedFilter by remember(state.scanPath, state.selectedFilter) { mutableStateOf(state.selectedFilter) }
    val coverCache = remember { mutableStateMapOf<String, LibraryCover?>() }
    val searchActive = searchText.isNotBlank()
    val libraryItems = items.filterNot { it.fileId in hiddenFileIds }
    val visibleSearchResults = searchResults.filterNot { it.fileId in hiddenFileIds }
    val sourceItems = if (searchActive) visibleSearchResults else libraryItems
    val recentlyAdded = recentlyAddedItems.filterNot { it.fileId in hiddenFileIds }
    val visibleItems = selectedFilter.apply(sourceItems, recentlyAdded, searchActive)
        .withoutDuplicateFileIds()
    val showShareAction = canShareLibraryBookFile()
    val showViewFileAction = canViewLibraryBookFile()

    suspend fun loadLibrary() {
        if (loadingLibrary) return
        loadingLibrary = true
        val previousItems = items
        try {
            val loadedItems = loadLibraryItems()
            if (loadedItems != previousItems) {
                items = loadedItems
            }
            val visibleFileIds = loadedItems.mapTo(mutableSetOf()) { it.fileId }
            recentlyAddedItems.mapTo(visibleFileIds) { it.fileId }
            searchResults.mapTo(visibleFileIds) { it.fileId }
            coverCache.keys.toList().forEach { fileId ->
                if (fileId !in visibleFileIds) coverCache.remove(fileId)
            }
        } catch (t: Throwable) {
            message = t.message ?: strings.couldNotLoadLibrary
        } finally {
            loadingLibrary = false
        }
    }

    suspend fun loadRecentlyAdded() {
        val since = currentLibraryTimeMillis() - RecentlyAddedWindowMillis
        recentlyAddedItems = loadRecentlyAddedLibraryItems(since, RecentlyAddedLimit)
    }

    suspend fun refreshLibraryItem(fileId: String) {
        val updatedItem = loadLibraryItem(fileId) ?: return
        items = items.replaceLibraryItem(updatedItem)
        searchResults = searchResults.replaceLibraryItem(updatedItem)
        recentlyAddedItems = recentlyAddedItems.replaceLibraryItem(updatedItem)
    }

    fun refresh(nextMessage: String? = message) {
        message = nextMessage
        scope.launch {
            if (searchActive) {
                searching = true
                searchResults = searchLibraryItems(searchText, SearchResultLimit)
                searching = false
            } else {
                loadLibrary()
                loadRecentlyAdded()
            }
        }
    }

    suspend fun applyReadingStatus(
        item: LibraryItem,
        status: BookReadingStatus,
        successMessage: String,
    ) {
        if (markLibraryReadingStatus(item.fileId, status)) {
            val updatedItem = loadLibraryItem(item.fileId) ?: item.copy(readingStatus = status)
            items = items.replaceLibraryItem(updatedItem)
            searchResults = searchResults.replaceLibraryItem(updatedItem)
            recentlyAddedItems = recentlyAddedItems.replaceLibraryItem(updatedItem)
            message = successMessage
            if (searchActive) {
                searching = true
                searchResults = searchLibraryItems(searchText, SearchResultLimit)
                searching = false
            } else {
                loadLibrary()
                loadRecentlyAdded()
            }
        } else {
            message = strings.couldNotUpdate(item.title)
        }
    }

    suspend fun applyFavorite(
        item: LibraryItem,
        favorite: Boolean,
        successMessage: String,
    ) {
        if (markLibraryFavorite(item.fileId, favorite)) {
            val updatedItem = loadLibraryItem(item.fileId) ?: item.copy(favorite = favorite)
            items = items.replaceLibraryItem(updatedItem)
            searchResults = searchResults.replaceLibraryItem(updatedItem)
            recentlyAddedItems = recentlyAddedItems.replaceLibraryItem(updatedItem)
            message = successMessage
            if (searchActive) {
                searching = true
                searchResults = searchLibraryItems(searchText, SearchResultLimit)
                searching = false
            } else {
                loadLibrary()
                loadRecentlyAdded()
            }
        } else {
            message = strings.couldNotUpdate(item.title)
        }
    }

    fun rescanAllLibrary() {
        settingsMenuOpen = false
        localeMenuOpen = false
        scope.launch {
            busy = true
            message = strings.rescanningLibrary
            try {
                val report = rescanAllLibraryBooks()
                refresh(
                    strings.rescanReport(report.scannedFiles, report.updatedFiles, report.failedFiles),
                )
            } catch (t: Throwable) {
                message = t.message ?: strings.libraryRescanFailed
            } finally {
                busy = false
            }
        }
    }

    fun clearSearch() {
        searchText = ""
        searchResults = emptyList()
        searching = false
    }

    fun closeSearch() {
        clearSearch()
        focusManager.clearFocus()
        searchFocused = false
    }

    LaunchedEffect(state.scanPath, state.message) {
        if (items.isEmpty()) {
            loadLibrary()
            loadRecentlyAdded()
        }
    }

    LaunchedEffect(itemRefreshRequest) {
        itemRefreshRequest?.let { refreshLibraryItem(it.fileId) }
    }

    LaunchedEffect(searchText) {
        val query = searchText
        if (query.isBlank()) {
            searchResults = emptyList()
            searching = false
            return@LaunchedEffect
        }
        searching = true
        delay(1_000)
        runCatching { searchLibraryItems(query, SearchResultLimit) }
            .onSuccess {
                if (searchText == query) {
                    searchResults = it
                    message = null
                }
            }
            .onFailure {
                if (searchText == query) {
                    searchResults = emptyList()
                    message = it.message ?: strings.searchFailed
                }
            }
        if (searchText == query) searching = false
    }

    LaunchedEffect(searchFocused) {
        if (searchFocused) {
            settingsMenuOpen = false
            localeMenuOpen = false
        }
    }

    LaunchedEffect(Unit) {
        autoScanDownloads = loadScanDownloadsAutomatically()
    }

    LaunchedEffect(activeScan != null) {
        if (activeScan != null) {
            wasScanning = true
            while (true) {
                delay(2_000)
                loadLibrary()
                loadRecentlyAdded()
            }
        } else if (wasScanning) {
            wasScanning = false
            loadLibrary()
            loadRecentlyAdded()
        }
    }

    Box(Modifier.fillMaxSize()) {
        Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Row(
                        modifier = Modifier.fillMaxWidth().animateContentSize(),
                        verticalAlignment = Alignment.CenterVertically,
                        horizontalArrangement = Arrangement.spacedBy(8.dp),
                    ) {
                        AnimatedVisibility(visible = !searchFocused) {
                            LibraryFilterSelect(
                                selected = selectedFilter,
                                onSelected = {
                                    selectedFilter = it
                                    onLibraryFilterChange(it)
                                    scope.launch {
                                        saveLibraryFilter(it)
                                    }
                                },
                            )
                        }
                        LibrarySearchField(
                            value = searchText,
                            focused = searchFocused,
                            onValueChange = { searchText = it },
                            onClear = ::clearSearch,
                            onClose = ::closeSearch,
                            onFocusChanged = { searchFocused = it },
                            modifier = Modifier.weight(1f),
                        )
                    }
                },
                colors = themedTopAppBarColors(),
                actions = {
                    AnimatedVisibility(visible = !searchFocused) {
                        Box {
                            IconButton(onClick = { settingsMenuOpen = true }) {
                                Icon(Icons.Filled.MoreVert, contentDescription = strings.libraryOptions)
                            }
                            DropdownMenu(
                                expanded = settingsMenuOpen,
                                onDismissRequest = {
                                    settingsMenuOpen = false
                                    localeMenuOpen = false
                                },
                            ) {
//                            DropdownMenuItem(
//                                text = { Text("Rescan all library") },
//                                enabled = !busy && activeScan == null,
//                                onClick = ::rescanAllLibrary,
//                            )
//                            HorizontalDivider()
                            DropdownMenuItem(
                                leadingIcon = {
                                    Checkbox(checked = autoScanDownloads, onCheckedChange = null)
                                },
                                text = { Text(strings.scanDownloadsAutomatically) },
                                onClick = {
                                    val next = !autoScanDownloads
                                    autoScanDownloads = next
                                    settingsMenuOpen = false
                                    scope.launch { saveScanDownloadsAutomatically(next) }
                                },
                            )
                            HorizontalDivider()
                            Box {
                                DropdownMenuItem(
                                    leadingIcon = {
                                        Icon(Icons.Filled.Language, contentDescription = null)
                                    },
                                    text = { Text(strings.locale) },
                                    trailingIcon = {
                                        Icon(Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = null)
                                    },
                                    onClick = { localeMenuOpen = true },
                                )
                                DropdownMenu(expanded = localeMenuOpen, onDismissRequest = { localeMenuOpen = false }) {
                                    availableAppLanguages.forEach { language ->
                                        val checked = selectedAppLocaleTag == language.localeTag
                                        DropdownMenuItem(
                                            leadingIcon = {
                                                if (checked) {
                                                    Icon(Icons.Filled.Check, contentDescription = null)
                                                }
                                            },
                                            text = { Text(strings.appLanguageName(language)) },
                                            onClick = {
                                                selectedAppLocaleTag = language.localeTag
                                                localeMenuOpen = false
                                                settingsMenuOpen = false
                                                scope.launch { saveAppLocaleTag(language.localeTag) }
                                            },
                                        )
                                    }
                                    HorizontalDivider()
                                    DropdownMenuItem(
                                        leadingIcon = {
                                            if (selectedAppLocaleTag == null) {
                                                Icon(Icons.Filled.Check, contentDescription = null)
                                            }
                                        },
                                        text = { Text(strings.systemLocale) },
                                        onClick = {
                                            selectedAppLocaleTag = null
                                            localeMenuOpen = false
                                            settingsMenuOpen = false
                                            scope.launch { saveAppLocaleTag(null) }
                                        },
                                    )
                                }
                            }
                            HorizontalDivider()
                            DropdownMenuItem(
                                leadingIcon = {
                                    Icon(Icons.Filled.Info, contentDescription = null)
                                },
                                text = { Text(strings.appVersion(AppVersionDisplay)) },
                                enabled = false,
                                onClick = {},
                            )
                            }
                        }
                    }
                },
            )
        },
        floatingActionButton = {
            FloatingActionButton(onClick = onNavigateToScan) {
                Icon(Icons.Filled.Add, contentDescription = strings.scanFolder)
            }
        },
        ) {
            BoxWithConstraints(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(it)
                    .onPreviewKeyEvent { event ->
                        if (event.type != KeyEventType.KeyDown) return@onPreviewKeyEvent false
                        when {
                            event.key == Key.Escape && searchText.isNotBlank() -> {
                                clearSearch()
                                true
                            }
                            event.key == Key.Escape && searchFocused -> {
                                closeSearch()
                                true
                            }
                            event.key.isEnterKey() && searchFocused && searchText.isBlank() -> {
                                closeSearch()
                                true
                            }
                            else -> false
                        }
                    }
                    .background(readerBackground()),
            ) {
            val wide = maxWidth >= 800.dp
            if (visibleItems.isEmpty() && (loadingLibrary || searching)) {
                Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                    CircularProgressIndicator()
                }
            } else if (visibleItems.isEmpty()) {
                if (searchActive) {
                    EmptySearchPane(modifier = Modifier.fillMaxSize().padding(if (wide) 24.dp else 14.dp))
                } else {
                    EmptyLibraryPane(
                        filter = selectedFilter,
                        modifier = Modifier.fillMaxSize().padding(if (wide) 24.dp else 14.dp),
                    )
                }
            } else {
                LazyColumn(
                    modifier = Modifier.fillMaxSize(),
                    contentPadding = PaddingValues(bottom = if (activeScan != null) 88.dp else 0.dp),
                    verticalArrangement = Arrangement.spacedBy(4.dp),
                ) {
                    fun itemActions(item: LibraryItem) = LibraryItemActions(
                        onOpen = {
                            scope.launch {
                                busy = true
                                openingBook = true
                                try {
                                    val next = runCatching {
                                        val bytes = openLibraryBook(item.fileId) ?: error(strings.bookFileNotAvailable)
                                        val book = parseBookInBackground(bytes, item.storageUri ?: item.title)
                                        var readerLibraryItems = visibleItems
                                        refreshLibraryItemFromParsedBook(item.fileId, book)?.let { updatedItem ->
                                            items = items.replaceLibraryItem(updatedItem)
                                            searchResults = searchResults.replaceLibraryItem(updatedItem)
                                            readerLibraryItems = readerLibraryItems.replaceLibraryItem(updatedItem)
                                            coverCache[updatedItem.fileId] = loadLibraryItemCover(updatedItem.fileId)
                                        }
                                        if (item.readingStatus.shouldBecomeReadingOnOpen() &&
                                            markLibraryReadingStatus(item.fileId, BookReadingStatus.READING)
                                        ) {
                                            val readingItem = loadLibraryItem(item.fileId)
                                                ?: item.copy(readingStatus = BookReadingStatus.READING)
                                            items = items.replaceLibraryItem(readingItem)
                                            searchResults = searchResults.replaceLibraryItem(readingItem)
                                            readerLibraryItems = readerLibraryItems.replaceLibraryItem(readingItem)
                                        }
                                        saveActiveReadingFileId(item.fileId)
                                        AppState.Reader(
                                            fileId = item.fileId,
                                            book = book,
                                            libraryItems = readerLibraryItems,
                                            scanPath = state.scanPath,
                                            message = message,
                                            selectedFilter = state.selectedFilter,
                                        )
                                    }.getOrElse {
                                        AppState.Library(
                                            visibleItems,
                                            state.scanPath,
                                            it.message ?: strings.couldNotOpenBook,
                                            state.selectedFilter,
                                        )
                                    }
                                    onStateChange(next)
                                } finally {
                                    openingBook = false
                                    busy = false
                                }
                            }
                        },
                        onMarkAsRead = {
                            scope.launch {
                                busy = true
                                try {
                                    applyReadingStatus(item, BookReadingStatus.READ, strings.markedAsRead(item.title))
                                } finally {
                                    busy = false
                                }
                            }
                        },
                        onMarkAsUnread = {
                            scope.launch {
                                busy = true
                                try {
                                    applyReadingStatus(item, BookReadingStatus.NEW, strings.markedAsUnread(item.title))
                                } finally {
                                    busy = false
                                }
                            }
                        },
                        onRemoveMarks = {
                            scope.launch {
                                busy = true
                                try {
                                    applyReadingStatus(item, BookReadingStatus.NEW, strings.removedMarks(item.title))
                                } finally {
                                    busy = false
                                }
                            }
                        },
                        onMarkToRead = {
                            scope.launch {
                                busy = true
                                try {
                                    applyReadingStatus(item, BookReadingStatus.TO_READ, strings.markedToRead(item.title))
                                } finally {
                                    busy = false
                                }
                            }
                        },
                        onNotInterested = {
                            scope.launch {
                                busy = true
                                try {
                                    applyReadingStatus(item, BookReadingStatus.NOT_INTERESTED, strings.markedNotInterested(item.title))
                                } finally {
                                    busy = false
                                }
                            }
                        },
                        onFavoriteChange = { favorite ->
                            scope.launch {
                                busy = true
                                try {
                                    val label = if (favorite) strings.addedToFavorites(item.title) else strings.removedFromFavorites(item.title)
                                    applyFavorite(item, favorite, label)
                                } finally {
                                    busy = false
                                }
                            }
                        },
                        onShare = {
                            scope.launch {
                                busy = true
                                try {
                                    message = shareLibraryBook(item.fileId)
                                } finally {
                                    busy = false
                                }
                            }
                        },
                        onViewFile = {
                            scope.launch {
                                busy = true
                                try {
                                    message = viewLibraryBook(item.fileId)
                                } finally {
                                    busy = false
                                }
                            }
                        },
                        onDelete = {
                            val previousItems = items
                            val previousSearchResults = searchResults
                            val previousRecentlyAddedItems = recentlyAddedItems
                            val previousCover = coverCache[item.fileId]
                            val hadCover = coverCache.containsKey(item.fileId)
                            onDeleteRequested(
                                LibraryDeleteRequest(item.fileId, item.title),
                                {
                                    message = null
                                    items = items.filterNot { it.fileId == item.fileId }
                                    searchResults = searchResults.filterNot { it.fileId == item.fileId }
                                    recentlyAddedItems = recentlyAddedItems.filterNot { it.fileId == item.fileId }
                                    coverCache.remove(item.fileId)
                                },
                                {
                                    items = previousItems
                                    searchResults = previousSearchResults
                                    recentlyAddedItems = previousRecentlyAddedItems
                                    if (hadCover) {
                                        coverCache[item.fileId] = previousCover
                                    }
                                },
                            )
                        },
                    )

                    fun LazyListScope.libraryRows(rowItems: List<LibraryItem>) {
                        itemsIndexed(rowItems, key = { _, item -> item.fileId }) { _, item ->
                            LibraryRow(
                                item = item,
                                coverCache = coverCache,
                                enabled = !busy,
                                showShareAction = showShareAction,
                                showViewFileAction = showViewFileAction,
                                actions = itemActions(item),
                            )
                        }
                    }

                    libraryRows(visibleItems)
                }
            }
            activeScan?.let { progress ->
                LibraryScanStatusPanel(
                    progress = progress,
                    modifier = Modifier
                        .align(Alignment.BottomCenter)
                        .padding(horizontal = if (wide) 24.dp else 14.dp, vertical = 14.dp),
                )
            }
            }
        }
        if (openingBook) {
            LoadingOverlay(strings.loadingOpeningBook)
        }
    }
}

@Composable
private fun LibraryFilterSelect(
    selected: LibraryFilter,
    onSelected: (LibraryFilter) -> Unit,
    modifier: Modifier = Modifier,
) {
    var open by remember { mutableStateOf(false) }
    Box(modifier = modifier) {
        Row(
            modifier = Modifier
                .height(42.dp)
                .clip(RoundedCornerShape(16.dp))
                .clickable { open = true }
                .background(MaterialTheme.colorScheme.surface)
                .padding(start = 14.dp, end = 10.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(4.dp),
        ) {
            Text(
                selected.label,
                style = MaterialTheme.typography.bodyMedium,
                color = MaterialTheme.colorScheme.onSurface,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
            )
            Icon(
                Icons.Filled.KeyboardArrowDown,
                contentDescription = strings.chooseLibraryFilter,
                tint = MaterialTheme.colorScheme.outline,
                modifier = Modifier.size(18.dp),
            )
        }
        DropdownMenu(expanded = open, onDismissRequest = { open = false }) {
            LibraryFilter.entries.forEach { filter ->
                DropdownMenuItem(
                    text = { Text(filter.label) },
                    onClick = {
                        open = false
                        onSelected(filter)
                    },
                )
            }
        }
    }
}

@Composable
private fun LibrarySearchField(
    value: String,
    focused: Boolean,
    onValueChange: (String) -> Unit,
    onClear: () -> Unit,
    onClose: () -> Unit,
    onFocusChanged: (Boolean) -> Unit,
    modifier: Modifier = Modifier,
) {
    val shape = RoundedCornerShape(16.dp)
    Row(
        modifier = modifier
            .height(42.dp)
            .clip(shape)
            .background(MaterialTheme.colorScheme.surface)
            .padding(horizontal = 12.dp)
            .onPreviewKeyEvent { event ->
                if (event.type != KeyEventType.KeyDown) return@onPreviewKeyEvent false
                when {
                    event.key == Key.Escape && value.isNotBlank() -> {
                        onClear()
                        true
                    }
                    event.key == Key.Escape && focused -> {
                        onClose()
                        true
                    }
                    event.key.isEnterKey() && focused && value.isBlank() -> {
                        onClose()
                        true
                    }
                    else -> false
                }
            },
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Icon(
            Icons.Filled.Search,
            contentDescription = strings.searchLibrary,
            modifier = Modifier.size(18.dp),
            tint = MaterialTheme.colorScheme.outline,
        )
        Box(
            modifier = Modifier
                .weight(1f)
                .padding(start = 8.dp, end = 6.dp),
            contentAlignment = Alignment.CenterStart,
        ) {
            BasicTextField(
                value = value,
                onValueChange = onValueChange,
                singleLine = true,
                textStyle = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onSurface),
                cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
                modifier = Modifier
                    .fillMaxWidth()
                    .onFocusChanged { onFocusChanged(it.isFocused) },
            )
            if (value.isBlank()) {
                Text(
                    strings.searchLibrary,
                    style = MaterialTheme.typography.bodyMedium,
                    color = MaterialTheme.colorScheme.outline.copy(alpha = 0.72f),
                    maxLines = 1,
                )
            }
        }
        if (value.isNotBlank() || focused) {
            IconButton(
                onClick = {
                    if (value.isBlank()) onClose() else onClear()
                },
                modifier = Modifier.size(30.dp),
            ) {
                Icon(
                    Icons.Filled.Close,
                    contentDescription = if (value.isBlank()) strings.closeSearch else strings.clearSearch,
                    modifier = Modifier.size(17.dp),
                )
            }
        }
    }
}

@Composable
private fun EmptyLibraryPane(filter: LibraryFilter, modifier: Modifier = Modifier) {
    Box(modifier, contentAlignment = Alignment.Center) {
        Card(shape = RoundedCornerShape(8.dp), colors = quietCardColors()) {
            Column(Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
                Text(strings.noBooksIn(filter.label), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
                Text(strings.scanFolderOrChooseFilter, style = MaterialTheme.typography.bodyMedium)
            }
        }
    }
}

@Composable
private fun EmptySearchPane(modifier: Modifier = Modifier) {
    Box(modifier, contentAlignment = Alignment.Center) {
        Text(strings.noMatches, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.outline)
    }
}

@Composable
private fun LibraryScanStatusPanel(progress: LibraryScanProgress, modifier: Modifier = Modifier) {
    Card(
        shape = RoundedCornerShape(8.dp),
        colors = quietCardColors(),
        modifier = modifier.fillMaxWidth(),
    ) {
        Row(
            modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 12.dp),
            horizontalArrangement = Arrangement.spacedBy(12.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            CircularProgressIndicator(modifier = Modifier.width(22.dp).height(22.dp), strokeWidth = 2.dp)
            Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
                Text(
                    progress.toCatalogScanMessage(),
                    style = MaterialTheme.typography.bodyMedium,
                    color = MaterialTheme.colorScheme.primary,
                    fontWeight = FontWeight.SemiBold,
                )
                Text(
                    strings.importedSkippedFailed(progress.importedFiles, progress.skippedFiles, progress.failedFiles),
                    style = MaterialTheme.typography.bodySmall,
                    color = MaterialTheme.colorScheme.outline,
                )
            }
        }
    }
}

@Composable
private fun LibraryRow(
    item: LibraryItem,
    coverCache: MutableMap<String, LibraryCover?>,
    enabled: Boolean,
    showShareAction: Boolean,
    showViewFileAction: Boolean,
    actions: LibraryItemActions,
) {
    var menuOpen by remember { mutableStateOf(false) }
    val titleStyle = MaterialTheme.typography.titleMedium
    val favoriteIconSize = with(LocalDensity.current) { titleStyle.lineHeight.toDp() - 2.dp }

    Card(shape = RoundedCornerShape(6.dp), colors = quietCardColors(), modifier = Modifier.fillMaxWidth()) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .clickable(enabled = enabled, onClick = actions.onOpen)
                .padding(horizontal = 8.dp, vertical = 6.dp),
            horizontalArrangement = Arrangement.spacedBy(10.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            LibraryCover(item, coverCache, modifier = Modifier.width(46.dp).aspectRatio(0.68f))
            Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
                Row(
                    horizontalArrangement = Arrangement.spacedBy(4.dp),
                    verticalAlignment = Alignment.CenterVertically,
                ) {
                    Text(
                        item.title,
                        style = titleStyle,
                        fontWeight = FontWeight.SemiBold,
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis,
                        modifier = Modifier.weight(1f, fill = false),
                    )
                    if (item.favorite) {
                        Icon(
                            Icons.Filled.Favorite,
                            contentDescription = strings.favorite,
                            tint = Color(0xFFD32F2F),
                            modifier = Modifier.size(favoriteIconSize),
                        )
                    }
                }
                Text(
                    item.authors.joinToString().ifBlank { strings.unknownAuthor },
                    style = MaterialTheme.typography.bodyMedium,
                    color = MaterialTheme.colorScheme.secondary,
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                )
                Text(
                    item.libraryMetadataLine(),
                    style = MaterialTheme.typography.bodySmall,
                    color = MaterialTheme.colorScheme.outline,
                    maxLines = 1,
                )
            }
            Box {
                IconButton(onClick = { menuOpen = true }, enabled = enabled) {
                    Icon(Icons.Filled.MoreVert, contentDescription = strings.bookMenuFor(item.title))
                }
                DropdownMenu(expanded = menuOpen, onDismissRequest = { menuOpen = false }) {
                    DropdownMenuItem(
                        leadingIcon = {
                            Icon(Icons.Filled.MenuBook, contentDescription = null)
                        },
                        text = { Text(strings.open) },
                        onClick = {
                            menuOpen = false
                            actions.onOpen()
                        },
                    )
                    HorizontalDivider()
                    if (item.readingStatus != BookReadingStatus.READ) {
                        DropdownMenuItem(
                            leadingIcon = {
                                Icon(Icons.Filled.DoneAll, contentDescription = null)
                            },
                            text = { Text(strings.markAsRead) },
                            onClick = {
                                menuOpen = false
                                actions.onMarkAsRead()
                            },
                        )
                    }
                    if (item.readingStatus == BookReadingStatus.READ) {
                        DropdownMenuItem(
                            leadingIcon = {
                                Icon(Icons.Filled.RemoveDone, contentDescription = null)
                            },
                            text = { Text(strings.markAsUnread) },
                            onClick = {
                                menuOpen = false
                                actions.onMarkAsUnread()
                            },
                        )
                    }
                    if (item.readingStatus != BookReadingStatus.TO_READ) {
                        DropdownMenuItem(
                            leadingIcon = {
                                Icon(Icons.Filled.BookmarkAdd, contentDescription = null)
                            },
                            text = { Text(strings.markToRead) },
                            onClick = {
                                menuOpen = false
                                actions.onMarkToRead()
                            },
                        )
                    }
                    if (item.readingStatus != BookReadingStatus.NEW) {
                        DropdownMenuItem(
                            leadingIcon = {
                                Icon(Icons.Filled.BookmarkRemove, contentDescription = null)
                            },
                            text = { Text(if (item.readingStatus == BookReadingStatus.TO_READ) strings.removeToRead else strings.removeMarks) },
                            onClick = {
                                menuOpen = false
                                actions.onRemoveMarks()
                            },
                        )
                    }
                    DropdownMenuItem(
                        leadingIcon = {
                            Icon(
                                if (item.favorite) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder,
                                contentDescription = null,
                            )
                        },
                        text = { Text(if (item.favorite) strings.removeFavorite else strings.addFavorite) },
                        onClick = {
                            menuOpen = false
                            actions.onFavoriteChange(!item.favorite)
                        },
                    )
                    DropdownMenuItem(
                        leadingIcon = {
                            Icon(Icons.Filled.VisibilityOff, contentDescription = null)
                        },
                        text = { Text(strings.notInterested) },
                        onClick = {
                            menuOpen = false
                            actions.onNotInterested()
                        },
                    )
                    if (showShareAction || showViewFileAction) {
                        HorizontalDivider()
                    }
                    if (showShareAction) {
                        DropdownMenuItem(
                            leadingIcon = {
                                Icon(Icons.Filled.Share, contentDescription = null)
                            },
                            text = { Text(strings.share) },
                            onClick = {
                                menuOpen = false
                                actions.onShare()
                            },
                        )
                    }
                    if (showViewFileAction) {
                        DropdownMenuItem(
                            leadingIcon = {
                                Icon(Icons.Filled.FolderOpen, contentDescription = null)
                            },
                            text = { Text(strings.viewFile) },
                            onClick = {
                                menuOpen = false
                                actions.onViewFile()
                            },
                        )
                    }
                    HorizontalDivider()
                    DropdownMenuItem(
                        leadingIcon = {
                            Icon(Icons.Filled.Delete, contentDescription = null)
                        },
                        text = { Text(strings.delete) },
                        onClick = {
                            menuOpen = false
                            actions.onDelete()
                        },
                    )
                }
            }
        }
    }
}

private data class LibraryItemActions(
    val onOpen: () -> Unit,
    val onMarkAsRead: () -> Unit,
    val onMarkAsUnread: () -> Unit,
    val onRemoveMarks: () -> Unit,
    val onMarkToRead: () -> Unit,
    val onNotInterested: () -> Unit,
    val onFavoriteChange: (Boolean) -> Unit,
    val onShare: () -> Unit,
    val onViewFile: () -> Unit,
    val onDelete: () -> Unit,
)

internal enum class LibraryFilter {
    ReadingNow,
    RecentlyAdded,
    MyLibrary,
    ToRead,
    Favorites,
    Read,
    NotInterested,
    ;

    fun apply(
        sourceItems: List<LibraryItem>,
        recentlyAddedItems: List<LibraryItem>,
        searchActive: Boolean,
    ): List<LibraryItem> {
        val recentlyAddedIds = recentlyAddedItems
            .filter { it.readingStatus == BookReadingStatus.NEW }
            .mapTo(mutableSetOf()) { it.fileId }
        return when (this) {
            ReadingNow -> sourceItems
                .filter { it.readingStatus == BookReadingStatus.READING }
                .sortedByLastReadThenTitle()
            RecentlyAdded -> if (searchActive) {
                sourceItems
                    .filter { it.fileId in recentlyAddedIds && it.readingStatus == BookReadingStatus.NEW }
                    .sortedByImportedThenTitle()
            } else {
                recentlyAddedItems
                    .filter { it.readingStatus == BookReadingStatus.NEW }
                    .sortedByImportedThenTitle()
            }
            MyLibrary -> sourceItems.myLibraryItems()
            ToRead -> sourceItems
                .filter { it.readingStatus == BookReadingStatus.TO_READ }
                .sortedByLastReadThenTitle()
            Favorites -> sourceItems
                .filter { it.favorite }
                .sortedByLastReadThenTitle()
            Read -> sourceItems
                .filter { it.readingStatus == BookReadingStatus.READ }
                .sortedByTitleNaturally()
            NotInterested -> sourceItems
                .filter { it.readingStatus == BookReadingStatus.NOT_INTERESTED }
                .sortedByTitleNaturally()
        }
    }
}

private fun List<LibraryItem>.sortedByLastReadThenTitle(): List<LibraryItem> =
    sortedWith(
        compareByDescending<LibraryItem> { it.lastReadAt ?: Long.MIN_VALUE }
            .then(LibraryItemNaturalTitleComparator)
    )

private fun List<LibraryItem>.sortedByImportedThenTitle(): List<LibraryItem> =
    sortedWith(
        compareByDescending<LibraryItem> { it.importedAt ?: Long.MIN_VALUE }
            .then(LibraryItemNaturalTitleComparator)
    )

private fun List<LibraryItem>.sortedByTitleNaturally(): List<LibraryItem> =
    sortedWith(LibraryItemNaturalTitleComparator)

internal fun List<LibraryItem>.myLibraryItems(): List<LibraryItem> =
    filter { it.readingStatus != BookReadingStatus.NOT_INTERESTED }
        .sortedByTitleNaturally()

private val LibraryItemNaturalTitleComparator = Comparator<LibraryItem> { left, right ->
    val titleCompare = naturalCompare(left.title, right.title)
    if (titleCompare != 0) titleCompare else left.fileId.compareTo(right.fileId)
}

private fun naturalCompare(left: String, right: String): Int {
    var leftIndex = 0
    var rightIndex = 0
    var exactFallback = 0
    while (leftIndex < left.length && rightIndex < right.length) {
        val leftChar = left[leftIndex]
        val rightChar = right[rightIndex]
        if (leftChar.isDigit() && rightChar.isDigit()) {
            val leftStart = leftIndex
            val rightStart = rightIndex
            while (leftIndex < left.length && left[leftIndex].isDigit()) leftIndex += 1
            while (rightIndex < right.length && right[rightIndex].isDigit()) rightIndex += 1

            val leftDigits = left.substring(leftStart, leftIndex).trimStart('0')
            val rightDigits = right.substring(rightStart, rightIndex).trimStart('0')
            val leftNumber = leftDigits.ifEmpty { "0" }
            val rightNumber = rightDigits.ifEmpty { "0" }
            val lengthCompare = leftNumber.length.compareTo(rightNumber.length)
            if (lengthCompare != 0) return lengthCompare
            val numberCompare = leftNumber.compareTo(rightNumber)
            if (numberCompare != 0) return numberCompare
            val rawLengthCompare = (leftIndex - leftStart).compareTo(rightIndex - rightStart)
            if (rawLengthCompare != 0) return rawLengthCompare
        } else {
            val foldedCompare = leftChar.lowercaseChar().compareTo(rightChar.lowercaseChar())
            if (foldedCompare != 0) return foldedCompare
            if (exactFallback == 0) exactFallback = leftChar.compareTo(rightChar)
            leftIndex += 1
            rightIndex += 1
        }
    }
    return left.length.compareTo(right.length).takeIf { it != 0 } ?: exactFallback
}

private val LibraryFilter.label: String
    get() = when (this) {
        LibraryFilter.ReadingNow -> strings.filterReadingNow
        LibraryFilter.RecentlyAdded -> strings.filterRecentlyAdded
        LibraryFilter.MyLibrary -> strings.filterMyLibrary
        LibraryFilter.ToRead -> strings.filterToRead
        LibraryFilter.Favorites -> strings.favorite
        LibraryFilter.Read -> strings.filterRead
        LibraryFilter.NotInterested -> strings.notInterested
    }

@Composable
private fun LibraryCover(
    item: LibraryItem,
    coverCache: MutableMap<String, LibraryCover?>,
    modifier: Modifier = Modifier.width(54.dp).aspectRatio(0.68f),
) {
    LaunchedEffect(item.fileId) {
        if (!coverCache.containsKey(item.fileId)) {
            coverCache[item.fileId] = loadLibraryItemCover(item.fileId)
        }
    }
    val cover = coverCache[item.fileId]
    val bitmap = remember(item.fileId, cover?.image) {
        cover?.image?.let(::decodeImageBytes)
    }
    Box(
        modifier = modifier
            .clip(RoundedCornerShape(6.dp))
            .background(MaterialTheme.colorScheme.surfaceVariant),
        contentAlignment = Alignment.Center,
    ) {
        if (bitmap != null) {
            Image(
                bitmap = bitmap,
                contentDescription = item.title,
                modifier = Modifier.fillMaxSize(),
                contentScale = ContentScale.Crop,
            )
        } else {
            Text(
                item.title.firstOrNull()?.uppercase() ?: "",
                style = MaterialTheme.typography.titleMedium,
                color = MaterialTheme.colorScheme.outline,
            )
        }
    }
}

private fun LibraryItem.libraryMetadataLine(): String =
    listOfNotNull(
        strings.favorite.takeIf { favorite },
        strings.readingStatus(readingStatus),
        lastReadAt?.formatLastRead(),
        date?.yearOrRaw(),
        language?.uppercase(),
        format?.uppercase(),
        sizeBytes?.formatBytes(),
    ).joinToString("  |  ").ifBlank { strings.noMetadata }

private fun Long.formatLastRead(): String = "${strings.lastReadPrefix} ${formatLibraryLastReadTime(this)}"

private fun String.yearOrRaw(): String =
    Regex("""\d{4}""").find(this)?.value ?: this

private fun Long.formatBytes(): String =
    when {
        this >= 1024L * 1024L -> "${(this / (1024.0 * 1024.0)).roundToInt()} MB"
        this >= 1024L -> "${(this / 1024.0).roundToInt()} KB"
        else -> "$this B"
    }

private fun List<LibraryItem>.replaceLibraryItem(item: LibraryItem): List<LibraryItem> =
    map { current -> if (current.fileId == item.fileId) item else current }

internal fun List<LibraryItem>.withoutDuplicateFileIds(): List<LibraryItem> =
    distinctBy { it.fileId }

internal fun BookReadingStatus.shouldBecomeReadingOnOpen(): Boolean =
    this == BookReadingStatus.NEW || this == BookReadingStatus.NOT_INTERESTED

private fun Key.isEnterKey(): Boolean = this == Key.Enter || this == Key.NumPadEnter

private fun LibraryScanProgress.toCatalogScanMessage(): String {
    val total = totalFiles ?: return strings.scannedBooks(scannedFiles)
    val percent = if (total <= 0) {
        100
    } else {
        ((scannedFiles.toDouble() / total.toDouble()) * 100.0).roundToInt().coerceIn(0, 100)
    }
    return strings.scannedProgress(scannedFiles, total, percent)
}

private const val SearchResultLimit: Int = 100
private const val RecentlyAddedLimit: Int = 50
private const val RecentlyAddedWindowMillis: Long = 30L * 60L * 60L * 1000L
