package net.sergeych.toread

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.FormatListBulleted
import androidx.compose.material.icons.automirrored.filled.VolumeUp
import androidx.compose.material.icons.filled.BookmarkAdd
import androidx.compose.material.icons.filled.BookmarkRemove
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.FastForward
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.MoreVert
import androidx.compose.material.icons.filled.Palette
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Replay
import androidx.compose.material.icons.filled.RestartAlt
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.filled.Stop
import androidx.compose.material.icons.filled.UnfoldLess
import androidx.compose.material.icons.filled.UnfoldMore
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.AlertDialog
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.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.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Slider
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import net.sergeych.toread.fb2.Fb2Book
import net.sergeych.toread.storage.BookReadingStatus
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.roundToInt

private data class ReaderBookPreparation(
    val stats: BookStats,
    val contentPlan: ReaderContentPlan,
)

private enum class ReaderColorPickerTarget {
    BACKGROUND,
    TEXT,
}

@Composable
@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class)
internal fun BookView(
    fileId: String,
    book: Fb2Book,
    onImageOpen: (ViewedBookImage) -> Unit,
    onThemeToggle: () -> Unit,
    onBookChanged: (String) -> Unit,
    onBookInfo: () -> Unit,
    onDeleted: (String?) -> Unit,
    onDeleteRequested: (
        request: LibraryDeleteRequest,
        remove: () -> Unit,
        restore: () -> Unit,
    ) -> Unit,
    onBack: () -> Unit,
) {
    val listState = remember(fileId) { LazyListState() }
    val scope = rememberCoroutineScope()
    val snackbarHostState = remember { SnackbarHostState() }
    var preparation by remember(book) { mutableStateOf<ReaderBookPreparation?>(null) }
    var restored by remember(fileId) { mutableStateOf(false) }
    var markedRead by remember(fileId) { mutableStateOf(false) }
    var libraryItem by remember(fileId) { mutableStateOf<LibraryItem?>(null) }
    var readAloudPanelVisible by remember(fileId) { mutableStateOf(false) }
    var readAloudSettingsVisible by remember(fileId) { mutableStateOf(false) }
    var readerSettingsPanelVisible by remember(fileId) { mutableStateOf(false) }
    var readerExtraSettingsVisible by remember(fileId) { mutableStateOf(false) }
    var readerColorPickerTarget by remember(fileId) { mutableStateOf<ReaderColorPickerTarget?>(null) }
    var fullScreenReader by remember { mutableStateOf(false) }
    var tableOfContentsVisible by remember(fileId) { mutableStateOf(false) }
    var tableOfContentsBackStack by remember(fileId) { mutableStateOf<List<ReadingPosition>>(emptyList()) }
    var tableOfContentsBackPosition by remember(fileId) { mutableStateOf<ReadingPosition?>(null) }
    var readerFontSettings by remember { mutableStateOf(defaultReaderFontSettings()) }
    var readAloudResumeSentenceIndex by remember(fileId) { mutableStateOf<Int?>(null) }
    var userScrollGeneration by remember(fileId) { mutableStateOf(0) }
    var selectedNoteId by remember(fileId) { mutableStateOf<String?>(null) }
    val readAloudState by ReadAloudPlatform.state.collectAsState()
    val readAloudSettings by ReadAloudPlatform.settingsState.collectAsState()
    val useDarkReaderTheme = LocalReaderThemeIsDark.current

    LaunchedEffect(book) {
        preparation = withContext(Dispatchers.Default) {
            ReaderBookPreparation(
                stats = BookStats.from(book),
                contentPlan = buildReaderContentPlan(book),
            )
        }
    }

    val preparedBook = preparation
    if (preparedBook == null) {
        LoadingScreen(strings.loadingOpeningBook)
        return
    }

    val stats = preparedBook.stats
    val contentPlan = preparedBook.contentPlan
    val showShareAction = canShareLibraryBookFile()
    val showViewFileAction = canViewLibraryBookFile()
    val showReadAloudAction = ReadAloudPlatform.isSupported && contentPlan.sentences.isNotEmpty()
    val fullScreenProgressTitle = remember(book) { book.fullScreenProgressTitle() }
    val readingProgress by remember(listState) {
        derivedStateOf { listState.readerProgress() }
    }
    val readerBackgroundColor = readerFontSettings.themeBackgroundArgb(useDarkReaderTheme)
        ?.toComposeColor()
        ?: MaterialTheme.colorScheme.surface
    val readerTextColor = readerFontSettings.themeTextArgb(useDarkReaderTheme)
        ?.toComposeColor()
        ?: MaterialTheme.colorScheme.onSurface
    val activeReadAloudSentence = readAloudState.sentenceIndex
        ?.let { index -> contentPlan.sentences.getOrNull(index) }
        ?.takeIf { readAloudState.active }
    val highlightedSentence = activeReadAloudSentence
        ?.takeIf { readAloudPanelVisible }

    fun showMessage(message: String) {
        scope.launch {
            snackbarHostState.showSnackbar(message, duration = SnackbarDuration.Short)
        }
    }

    fun openReaderLink(href: String) {
        if (href.isWebUrl()) {
            openPlatformUrl(href)
        } else {
            selectedNoteId = href.removePrefix("#")
        }
    }

    fun updateReaderFontSettings(transform: (ReaderFontSettings) -> ReaderFontSettings) {
        val next = transform(readerFontSettings).coerced()
        if (next == readerFontSettings) return
        readerFontSettings = next
        scope.launch {
            saveReaderFontSettings(next)
        }
    }

    fun updateReaderFullScreen(fullScreen: Boolean) {
        if (fullScreen == fullScreenReader) return
        fullScreenReader = fullScreen
        scope.launch {
            saveReaderFullScreen(fullScreen)
        }
    }

    fun currentReadingPosition(backStack: List<ReadingPosition> = tableOfContentsBackStack): ReadingPosition =
        ReadingPosition(
            listState.firstVisibleItemIndex,
            listState.firstVisibleItemScrollOffset,
            readAloudResumeSentenceIndex,
            backStack,
        )
    val currentReadingPositionProvider by rememberUpdatedState { currentReadingPosition() }

    fun openPosition(position: ReadingPosition, backStack: List<ReadingPosition>) {
        scope.launch {
            val target = position.copy(backStack = backStack)
            readAloudResumeSentenceIndex = target.readAloudSentenceIndex
                ?: contentPlan.sentenceIndexAtOrAfterItem(target.itemIndex)
            listState.animateScrollToItem(target.itemIndex, target.scrollOffset)
            saveLibraryReadingPosition(fileId, target)
        }
    }

    fun openTableOfContents() {
        scope.launch {
            val current = currentReadingPosition()
            val savedStack = loadLibraryReadingPosition(fileId)?.backStack.orEmpty()
            val nextStack = (listOf(current) + savedStack)
                .withoutAdjacentReadingDuplicates()
                .take(MaxTableOfContentsBackStack)
            tableOfContentsBackStack = nextStack
            tableOfContentsBackPosition = nextStack.backPositionFrom(current)
            saveLibraryReadingPosition(fileId, current.copy(backStack = nextStack))
            tableOfContentsVisible = true
        }
    }

    fun setReadingStatus(status: BookReadingStatus, successMessage: String) {
        scope.launch {
            if (markLibraryReadingStatus(fileId, status)) {
                libraryItem = loadLibraryItem(fileId) ?: libraryItem?.copy(readingStatus = status)
                onBookChanged(fileId)
                if (status == BookReadingStatus.READ) markedRead = true
                if (status == BookReadingStatus.NEW) markedRead = false
                if (status == BookReadingStatus.NOT_INTERESTED) {
                    saveLibraryReadingPosition(
                        fileId,
                        currentReadingPosition(),
                    )
                    saveActiveReadingFileId(null)
                    onBack()
                    return@launch
                }
                showMessage(successMessage)
            } else {
                showMessage(strings.couldNotUpdateBook)
            }
        }
    }

    LaunchedEffect(fileId) {
        val item = loadLibraryItem(fileId)
        libraryItem = item
        markedRead = item?.readingStatus == BookReadingStatus.READ
        if (item?.readingStatus?.shouldBecomeReadingOnOpen() == true && markLibraryReadingStatus(fileId, BookReadingStatus.READING)) {
            libraryItem = loadLibraryItem(fileId) ?: item.copy(readingStatus = BookReadingStatus.READING)
            onBookChanged(fileId)
        }
    }

    LaunchedEffect(Unit) {
        readerFontSettings = loadReaderFontSettings()
        fullScreenReader = loadReaderFullScreen()
    }

    DisposableEffect(fileId) {
        onDispose {
            if (restored) {
                scope.launch {
                    saveLibraryReadingPosition(fileId, currentReadingPositionProvider())
                }
            }
            ReadAloudPlatform.stop()
        }
    }

    LaunchedEffect(fileId) {
        loadLibraryReadingPosition(fileId)?.let { position ->
            tableOfContentsBackStack = position.backStack.take(MaxTableOfContentsBackStack)
            tableOfContentsBackPosition = tableOfContentsBackStack.backPositionFrom(position)
            readAloudResumeSentenceIndex = position.readAloudSentenceIndex
            listState.scrollToItem(position.itemIndex, position.scrollOffset)
        }
        restored = true
    }

    LaunchedEffect(fileId, listState, readAloudState.active, userScrollGeneration) {
        if (readAloudState.active) return@LaunchedEffect
        snapshotFlow {
            currentReadingPosition()
        }
            .filter { restored && userScrollGeneration > 0 }
            .distinctUntilChanged()
            .debounce(750)
            .collect { position ->
                saveLibraryReadingPosition(
                    fileId,
                    position.copy(readAloudSentenceIndex = readAloudResumeSentenceIndex),
                )
            }
    }

    LaunchedEffect(fileId, listState) {
        snapshotFlow {
            val layoutInfo = listState.layoutInfo
            val lastVisible = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -1
            layoutInfo.totalItemsCount > 0 && lastVisible >= layoutInfo.totalItemsCount - 1
        }
            .filter { restored && it && !markedRead && libraryItem?.readingStatus != BookReadingStatus.TO_READ }
            .collect {
                markedRead = true
                if (markLibraryReadingStatus(fileId, BookReadingStatus.READ)) {
                    libraryItem = loadLibraryItem(fileId) ?: libraryItem?.copy(readingStatus = BookReadingStatus.READ)
                    onBookChanged(fileId)
                }
            }
    }

    LaunchedEffect(fileId, readAloudState.active, readAloudState.sentenceIndex) {
        val sentence = activeReadAloudSentence ?: return@LaunchedEffect
        readAloudResumeSentenceIndex = sentence.index
        val itemIndex = sentence.itemIndex
        saveLibraryReadingPosition(fileId, ReadingPosition(itemIndex, 0, sentence.index, tableOfContentsBackStack))
        if (listState.firstVisibleItemIndex != itemIndex || listState.firstVisibleItemScrollOffset != 0) {
            listState.animateScrollToItem(itemIndex, 0)
        }
    }

    Scaffold(
        contentWindowInsets = WindowInsets(0, 0, 0, 0),
        snackbarHost = { SnackbarHost(snackbarHostState) },
        topBar = {
            AnimatedVisibility(
                visible = !fullScreenReader,
                enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(),
                exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut(),
            ) {
                CompactReaderTopBar(
                    title = book.title,
                    onThemeToggle = onThemeToggle,
                    onBookInfo = {
                        scope.launch {
                            saveLibraryReadingPosition(
                                fileId,
                                currentReadingPosition(),
                            )
                            onBookInfo()
                        }
                    },
                    onTableOfContents = ::openTableOfContents,
                    onMarkAsRead = {
                        setReadingStatus(BookReadingStatus.READ, strings.markedAsRead())
                    },
                    onMarkToRead = {
                        setReadingStatus(BookReadingStatus.TO_READ, strings.markedToRead())
                    },
                    onNotInterested = {
                        setReadingStatus(BookReadingStatus.NOT_INTERESTED, strings.markedNotInterested())
                    },
                    onClearMarks = {
                        setReadingStatus(BookReadingStatus.NEW, strings.removedMarks())
                    },
                    readingStatus = libraryItem?.readingStatus,
                    favorite = libraryItem?.favorite == true,
                    onFavoriteChange = { favorite ->
                        scope.launch {
                            if (markLibraryFavorite(fileId, favorite)) {
                                libraryItem = loadLibraryItem(fileId) ?: libraryItem?.copy(favorite = favorite)
                                onBookChanged(fileId)
                                showMessage(if (favorite) strings.addedToFavorites() else strings.removedFromFavorites())
                            } else {
                                showMessage(strings.couldNotUpdateBook)
                            }
                        }
                    },
                    fullScreen = fullScreenReader,
                    onFullScreenChange = ::updateReaderFullScreen,
                    showShareAction = showShareAction,
                    onShare = {
                        scope.launch {
                            showMessage(shareLibraryBook(fileId))
                        }
                    },
                    showViewFileAction = showViewFileAction,
                    onViewFile = {
                        scope.launch {
                            showMessage(viewLibraryBook(fileId))
                        }
                    },
                    onReaderSettings = {
                        readerSettingsPanelVisible = true
                    },
                    onReaderExtraSettings = {
                        readerExtraSettingsVisible = true
                    },
                    showReadAloudAction = showReadAloudAction,
                    onReadAloud = {
                        val position = currentReadingPosition()
                        val startIndex = contentPlan.resumeSentenceIndex(position)
                        ReadAloudPlatform.prepare(fileId, book.title, contentPlan.sentences, startIndex)
                        readAloudPanelVisible = true
                        ReadAloudPlatform.play()
                    },
                    onDelete = {
                        onDeleteRequested(
                            LibraryDeleteRequest(fileId, book.title),
                            {
                                scope.launch {
                                    saveActiveReadingFileId(null)
                                }
                                onDeleted(null)
                            },
                            {
                                scope.launch {
                                    saveActiveReadingFileId(fileId)
                                }
                            },
                        )
                    },
                    onBack = {
                        scope.launch {
                            saveLibraryReadingPosition(
                                fileId,
                                currentReadingPosition(),
                            )
                            saveActiveReadingFileId(null)
                            onBack()
                        }
                    },
                )
            }
        },
    ) {
        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(it)
                .background(readerBackgroundColor),
        ) {
            Column(Modifier.fillMaxSize()) {
                ContinuousBookReader(
                    book = book,
                    stats = stats,
                    modifier = Modifier.weight(1f),
                    listState = listState,
                    contentPlan = contentPlan,
                    highlightedSentence = highlightedSentence,
                    readerFontSettings = readerFontSettings,
                    readerBackgroundColor = readerBackgroundColor,
                    readerTextColor = readerTextColor,
                    onReaderFontZoom = { steps ->
                        updateReaderFontSettings { settings -> settings.adjustFontSize(steps) }
                    },
                    onUserScroll = { userScrollGeneration += 1 },
                    onImageOpen = onImageOpen,
                    onNoteOpen = ::openReaderLink,
                )
                if (readerSettingsPanelVisible) {
                    ReaderFontSettingsPanel(
                        settings = readerFontSettings,
                        onFontSizeDecrease = {
                            updateReaderFontSettings { settings -> settings.adjustFontSize(-1) }
                        },
                        onFontSizeIncrease = {
                            updateReaderFontSettings { settings -> settings.adjustFontSize(1) }
                        },
                        onLineHeightDecrease = {
                            updateReaderFontSettings { settings -> settings.adjustLineHeight(-1) }
                        },
                        onLineHeightIncrease = {
                            updateReaderFontSettings { settings -> settings.adjustLineHeight(1) }
                        },
                        onFontWeightDecrease = {
                            updateReaderFontSettings { settings -> settings.adjustFontWeight(-1) }
                        },
                        onFontWeightIncrease = {
                            updateReaderFontSettings { settings -> settings.adjustFontWeight(1) }
                        },
                        onBackgroundColorChange = {
                            updateReaderFontSettings { settings ->
                                settings.withThemeBackgroundArgb(
                                    useDarkReaderTheme,
                                    nextReaderBackgroundColorArgb(
                                        currentArgb = settings.themeBackgroundArgb(useDarkReaderTheme),
                                        customArgb = settings.themeCustomBackgroundArgb(useDarkReaderTheme),
                                        useDarkTheme = useDarkReaderTheme,
                                    ),
                                )
                            }
                        },
                        onBackgroundColorPick = {
                            readerColorPickerTarget = ReaderColorPickerTarget.BACKGROUND
                        },
                        onTextColorChange = {
                            updateReaderFontSettings { settings ->
                                settings.withThemeTextArgb(
                                    useDarkReaderTheme,
                                    nextReaderTextColorArgb(
                                        currentArgb = settings.themeTextArgb(useDarkReaderTheme),
                                        customArgb = settings.themeCustomTextArgb(useDarkReaderTheme),
                                        useDarkTheme = useDarkReaderTheme,
                                    ),
                                )
                            }
                        },
                        onTextColorPick = {
                            readerColorPickerTarget = ReaderColorPickerTarget.TEXT
                        },
                        onClose = { readerSettingsPanelVisible = false },
                    )
                }
                if (readAloudPanelVisible && readAloudState.active) {
                    ReadAloudPanel(
                        playing = readAloudState.playing,
                        onPlayStop = {
                            if (readAloudState.playing) {
                                ReadAloudPlatform.stop()
                                readAloudPanelVisible = false
                            } else {
                                ReadAloudPlatform.play()
                            }
                        },
                        onBack = { ReadAloudPlatform.skip(-1) },
                        onForward = { ReadAloudPlatform.skip(1) },
                        onSettings = {
                            ReadAloudPlatform.refreshSettings()
                            readAloudSettingsVisible = true
                        },
                    )
                }
                ReaderProgressPane(
                    progress = readingProgress,
                    fullScreen = fullScreenReader,
                    fullScreenTitle = fullScreenProgressTitle,
                    backgroundColor = readerBackgroundColor,
                    textColor = readerTextColor,
                    onFullScreenToggle = { updateReaderFullScreen(!fullScreenReader) },
                )
            }
        }
    }

    if (readAloudSettingsVisible) {
        ReadAloudSettingsDialog(
            state = readAloudSettings,
            onDismiss = { readAloudSettingsVisible = false },
            onEngineSelected = { ReadAloudPlatform.selectEngine(it) },
            onVoiceSelected = { ReadAloudPlatform.selectVoice(it) },
        )
    }

    if (readerExtraSettingsVisible) {
        ReaderExtraSettingsDialog(
            onRevertColorScheme = {
                updateReaderFontSettings { settings -> settings.resetThemeColorScheme(useDarkReaderTheme) }
            },
            onDismiss = { readerExtraSettingsVisible = false },
        )
    }

    readerColorPickerTarget?.let { target ->
        ReaderColorPickerDialog(
            title = when (target) {
                ReaderColorPickerTarget.BACKGROUND -> strings.readerBackgroundColor
                ReaderColorPickerTarget.TEXT -> strings.readerTextColor
            },
            initialArgb = when (target) {
                ReaderColorPickerTarget.BACKGROUND ->
                    readerFontSettings.themeBackgroundArgb(useDarkReaderTheme) ?: readerBackgroundColor.toReaderArgb()
                ReaderColorPickerTarget.TEXT ->
                    readerFontSettings.themeTextArgb(useDarkReaderTheme) ?: readerTextColor.toReaderArgb()
            },
            presets = when (target) {
                ReaderColorPickerTarget.BACKGROUND -> readerBackgroundPalette(useDarkReaderTheme)
                ReaderColorPickerTarget.TEXT -> readerTextPalette(useDarkReaderTheme)
            },
            customArgb = when (target) {
                ReaderColorPickerTarget.BACKGROUND -> readerFontSettings.themeCustomBackgroundArgb(useDarkReaderTheme)
                ReaderColorPickerTarget.TEXT -> readerFontSettings.themeCustomTextArgb(useDarkReaderTheme)
            },
            onDismiss = { readerColorPickerTarget = null },
            onApply = { argb ->
                updateReaderFontSettings { settings ->
                    when (target) {
                        ReaderColorPickerTarget.BACKGROUND ->
                            settings.withThemeCustomBackgroundArgb(useDarkReaderTheme, argb)
                        ReaderColorPickerTarget.TEXT ->
                            settings.withThemeCustomTextArgb(useDarkReaderTheme, argb)
                    }
                }
                readerColorPickerTarget = null
            },
        )
    }

    selectedNoteId?.let { noteId ->
        Fb2NoteReferenceDialog(
            book = book,
            noteId = noteId,
            onDismiss = { selectedNoteId = null },
            onImageOpen = onImageOpen,
            onNoteOpen = ::openReaderLink,
        )
    }

    if (tableOfContentsVisible) {
        val currentTocItemIndex = contentPlan.tocEntries
            .lastOrNull { it.itemIndex <= listState.firstVisibleItemIndex }
            ?.itemIndex
        TableOfContentsDialog(
            contentPlan = contentPlan,
            currentItemIndex = currentTocItemIndex,
            backPosition = tableOfContentsBackPosition,
            onOpenPosition = { position ->
                tableOfContentsVisible = false
                val targetIndex = tableOfContentsBackStack.indexOfFirst { it.sameReadingPlace(position) }
                val remainingStack = if (targetIndex >= 0) {
                    tableOfContentsBackStack.drop(targetIndex + 1)
                } else {
                    tableOfContentsBackStack
                }
                tableOfContentsBackStack = remainingStack
                tableOfContentsBackPosition = remainingStack.backPositionFrom(position)
                openPosition(position, remainingStack)
            },
            onOpenEntry = { entry ->
                tableOfContentsVisible = false
                val sentenceIndex = contentPlan.sentenceIndexAtOrAfterItem(entry.itemIndex)
                openPosition(ReadingPosition(entry.itemIndex, 0, sentenceIndex), tableOfContentsBackStack)
            },
            onDismiss = { tableOfContentsVisible = false },
        )
    }
}

@Composable
private fun ReaderProgressPane(
    progress: Float,
    fullScreen: Boolean,
    fullScreenTitle: String,
    backgroundColor: Color,
    textColor: Color,
    onFullScreenToggle: () -> Unit,
) {
    val percent = (progress.coerceIn(0f, 1f) * 100f).roundToInt()
    val trackColor = textColor.copy(alpha = 0.34f)
    val readColor = textColor.copy(alpha = 0.84f)
    val fullScreenFillColor = fullScreenProgressFillColor(textColor)
    val contentDescription = strings.progressLabel(progress.toDouble())
    val height by animateDpAsState(if (fullScreen) 36.dp else 24.dp)
    val progressRowHeight by animateDpAsState(24.dp)
    val canvasHeight by animateDpAsState(if (fullScreen) 12.dp else 8.dp)
    val trackStroke by animateDpAsState(1.dp)
    val readStroke by animateDpAsState(3.dp)

    Surface(
        tonalElevation = 2.dp,
        shadowElevation = 2.dp,
        color = backgroundColor,
        modifier = Modifier
            .fillMaxWidth()
            .clickable(onClick = onFullScreenToggle),
    ) {
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .height(height)
                .then(
                    if (fullScreen) {
                        Modifier.drawBehind {
                            drawRect(
                                color = fullScreenFillColor,
                                size = Size(size.width * progress.coerceIn(0f, 1f), size.height),
                            )
                        }
                    } else {
                        Modifier
                    },
                )
                .semantics { this.contentDescription = contentDescription },
        ) {
            if (fullScreen) {
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(progressRowHeight)
                        .align(Alignment.Center)
                        .padding(end = 10.dp),
                    verticalAlignment = Alignment.CenterVertically,
                ) {
                    Text(
                        text = fullScreenTitle,
                        style = MaterialTheme.typography.labelMedium,
                        color = textColor,
                        textAlign = TextAlign.Center,
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis,
                        modifier = Modifier
                            .weight(1f)
                    )
                    Spacer(Modifier.size(10.dp))
                    Text(
                        text = "$percent%",
                        style = MaterialTheme.typography.labelMedium,
                        fontWeight = FontWeight.SemiBold,
                        color = textColor,
                        maxLines = 1,
                    )
                }
            } else {
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(progressRowHeight)
                        .padding(start = 12.dp, end = 10.dp),
                    verticalAlignment = Alignment.CenterVertically,
                ) {
                    Canvas(
                        modifier = Modifier
                            .weight(1f)
                            .height(canvasHeight),
                    ) {
                        val centerY = size.height / 2f
                        drawLine(
                            color = trackColor,
                            start = Offset(0f, centerY),
                            end = Offset(size.width, centerY),
                            strokeWidth = trackStroke.toPx(),
                            cap = StrokeCap.Square,
                        )
                        if (progress > 0f) {
                            drawLine(
                                color = readColor,
                                start = Offset(0f, centerY),
                                end = Offset(size.width * progress.coerceIn(0f, 1f), centerY),
                                strokeWidth = readStroke.toPx(),
                                cap = StrokeCap.Square,
                            )
                        }
                    }
                    Spacer(Modifier.size(10.dp))
                    Text(
                        text = "$percent%",
                        style = MaterialTheme.typography.labelMedium,
                        fontWeight = FontWeight.SemiBold,
                        color = textColor,
                        maxLines = 1,
                    )
                }
            }
        }
    }
}

private fun fullScreenProgressFillColor(textColor: Color): Color {
    val luminance = textColor.readerLuminance()
    return if (luminance < 0.55f) {
        lerp(textColor, Color.White, 0.45f).copy(alpha = 0.22f)
    } else {
        textColor.copy(alpha = 0.12f)
    }
}

private fun Color.readerLuminance(): Float =
    0.2126f * red + 0.7152f * green + 0.0722f * blue

private fun Fb2Book.fullScreenProgressTitle(): String {
    val author = authors.joinToString { it.displayName }.ifBlank { strings.unknownAuthor }
    return "$title. $author."
}

private fun LazyListState.readerProgress(): Float {
    val layoutInfo = layoutInfo
    val totalItems = layoutInfo.totalItemsCount
    if (totalItems <= 1) return 0f

    val visibleItems = layoutInfo.visibleItemsInfo
    val lastVisibleItem = visibleItems.lastOrNull()
    if (
        lastVisibleItem?.index == totalItems - 1 &&
        lastVisibleItem.offset + lastVisibleItem.size <= layoutInfo.viewportEndOffset
    ) {
        return 1f
    }

    val firstIndex = firstVisibleItemIndex.coerceIn(0, totalItems - 1)
    val firstVisibleItemSize = visibleItems.firstOrNull { it.index == firstIndex }?.size
    val itemFraction = firstVisibleItemSize
        ?.takeIf { it > 0 }
        ?.let { (firstVisibleItemScrollOffset.toFloat() / it.toFloat()).coerceIn(0f, 1f) }
        ?: 0f
    return ((firstIndex + itemFraction) / (totalItems - 1).toFloat()).coerceIn(0f, 1f)
}

@Composable
private fun CompactReaderTopBar(
    title: String,
    onThemeToggle: () -> Unit,
    onBookInfo: () -> Unit,
    onTableOfContents: () -> Unit,
    onMarkAsRead: () -> Unit,
    onMarkToRead: () -> Unit,
    onNotInterested: () -> Unit,
    onClearMarks: () -> Unit,
    readingStatus: BookReadingStatus?,
    favorite: Boolean,
    onFavoriteChange: (Boolean) -> Unit,
    fullScreen: Boolean,
    onFullScreenChange: (Boolean) -> Unit,
    showShareAction: Boolean,
    onShare: () -> Unit,
    showViewFileAction: Boolean,
    onViewFile: () -> Unit,
    showReadAloudAction: Boolean,
    onReadAloud: () -> Unit,
    onReaderSettings: () -> Unit,
    onReaderExtraSettings: () -> Unit,
    onDelete: () -> Unit,
    onBack: () -> Unit,
) {
    var menuOpen by remember { mutableStateOf(false) }

    ThemedTopBarSurface {
        Row(
            modifier = Modifier.fillMaxWidth().height(48.dp).padding(horizontal = 4.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            IconButton(onClick = onBack, modifier = Modifier.size(36.dp)) {
                Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = strings.backToLibrary)
            }
            Text(
                title,
                style = MaterialTheme.typography.titleSmall,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
                modifier = Modifier.weight(1f).padding(horizontal = 4.dp),
            )
            if (showReadAloudAction) {
                IconButton(onClick = onReadAloud, modifier = Modifier.size(36.dp)) {
                    Icon(Icons.AutoMirrored.Filled.VolumeUp, contentDescription = strings.readAloud)
                }
            }
            IconButton(onClick = onThemeToggle, modifier = Modifier.size(36.dp)) {
                Icon(Icons.Filled.Palette, contentDescription = strings.readerTheme)
            }
            IconButton(onClick = onTableOfContents, modifier = Modifier.size(36.dp)) {
                Icon(Icons.AutoMirrored.Filled.FormatListBulleted, contentDescription = strings.tableOfContents)
            }
            Box {
                IconButton(onClick = { menuOpen = true }, modifier = Modifier.size(36.dp)) {
                    Icon(Icons.Filled.MoreVert, contentDescription = strings.readerMenu)
                }
                DropdownMenu(expanded = menuOpen, onDismissRequest = { menuOpen = false }) {
                    DropdownMenuItem(
                        leadingIcon = {
                            Icon(Icons.Filled.Info, contentDescription = null)
                        },
                        text = { Text(strings.info) },
                        onClick = {
                            menuOpen = false
                            onBookInfo()
                        },
                    )
                    DropdownMenuItem(
                        leadingIcon = {
                            Icon(Icons.Filled.Settings, contentDescription = null)
                        },
                        text = { Text(strings.readerSettings) },
                        onClick = {
                            menuOpen = false
                            onReaderSettings()
                        },
                    )
                    DropdownMenuItem(
                        leadingIcon = {
                            Icon(Icons.Filled.Settings, contentDescription = null)
                        },
                        text = { Text(strings.extraReaderSettings) },
                        onClick = {
                            menuOpen = false
                            onReaderExtraSettings()
                        },
                    )
                    DropdownMenuItem(
                        leadingIcon = {
                            Checkbox(checked = fullScreen, onCheckedChange = null)
                        },
                        text = { Text(strings.fullScreen) },
                        onClick = {
                            menuOpen = false
                            onFullScreenChange(!fullScreen)
                        },
                    )
                    HorizontalDivider()
                    DropdownMenuItem(
                        leadingIcon = {
                            Icon(Icons.Filled.DoneAll, contentDescription = null)
                        },
                        text = { Text(strings.markAsRead) },
                        onClick = {
                            menuOpen = false
                            onMarkAsRead()
                        },
                    )
                    if (readingStatus == BookReadingStatus.TO_READ) {
                        DropdownMenuItem(
                            leadingIcon = {
                                Icon(Icons.Filled.BookmarkRemove, contentDescription = null)
                            },
                            text = { Text(strings.removeToRead) },
                            onClick = {
                                menuOpen = false
                                onClearMarks()
                            },
                        )
                    } else {
                        DropdownMenuItem(
                            leadingIcon = {
                                Icon(Icons.Filled.BookmarkAdd, contentDescription = null)
                            },
                            text = { Text(strings.markToRead) },
                            onClick = {
                                menuOpen = false
                                onMarkToRead()
                            },
                        )
                    }
                    DropdownMenuItem(
                        leadingIcon = {
                            Icon(Icons.Filled.VisibilityOff, contentDescription = null)
                        },
                        text = { Text(strings.notInterested) },
                        onClick = {
                            menuOpen = false
                            onNotInterested()
                        },
                    )
                    DropdownMenuItem(
                        leadingIcon = {
                            Icon(Icons.Filled.BookmarkRemove, contentDescription = null)
                        },
                        text = { Text(strings.clearMarks) },
                        onClick = {
                            menuOpen = false
                            onClearMarks()
                        },
                    )
                    DropdownMenuItem(
                        leadingIcon = {
                            Icon(
                                if (favorite) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder,
                                contentDescription = null,
                            )
                        },
                        text = { Text(if (favorite) strings.removeFavorite else strings.addFavorite) },
                        onClick = {
                            menuOpen = false
                            onFavoriteChange(!favorite)
                        },
                    )
                    if (showShareAction || showViewFileAction) {
                        HorizontalDivider()
                    }
                    if (showShareAction) {
                        DropdownMenuItem(
                            leadingIcon = {
                                Icon(Icons.Filled.Share, contentDescription = null)
                            },
                            text = { Text(strings.share) },
                            onClick = {
                                menuOpen = false
                                onShare()
                            },
                        )
                    }
                    if (showViewFileAction) {
                        DropdownMenuItem(
                            leadingIcon = {
                                Icon(Icons.Filled.FolderOpen, contentDescription = null)
                            },
                            text = { Text(strings.viewFile) },
                            onClick = {
                                menuOpen = false
                                onViewFile()
                            },
                        )
                    }
                    HorizontalDivider()
                    DropdownMenuItem(
                        leadingIcon = {
                            Icon(Icons.Filled.Delete, contentDescription = null)
                        },
                        text = { Text(strings.delete) },
                        onClick = {
                            menuOpen = false
                            onDelete()
                        },
                    )
                }
            }
        }
    }
}

@Composable
private fun TableOfContentsDialog(
    contentPlan: ReaderContentPlan,
    currentItemIndex: Int?,
    backPosition: ReadingPosition?,
    onOpenPosition: (ReadingPosition) -> Unit,
    onOpenEntry: (ReaderTocEntry) -> Unit,
    onDismiss: () -> Unit,
) {
    val selectedEntryIndex = currentItemIndex?.let { itemIndex ->
        contentPlan.tocEntries.indexOfFirst { it.itemIndex == itemIndex }.takeIf { it >= 0 }
    }
    val listState = rememberLazyListState(
        initialFirstVisibleItemIndex = selectedEntryIndex?.let { it + 1 } ?: 0,
    )

    LaunchedEffect(contentPlan, selectedEntryIndex) {
        selectedEntryIndex?.let { listState.scrollToItem(it + 1) }
    }

    AlertDialog(
        onDismissRequest = onDismiss,
        title = { Text(strings.tableOfContents) },
        text = {
            LazyColumn(
                state = listState,
                modifier = Modifier.fillMaxWidth().heightIn(max = 480.dp),
            ) {
                item(key = "back") {
                    TableOfContentsBackRow(
                        enabled = backPosition != null,
                        onClick = { backPosition?.let(onOpenPosition) },
                    )
                    HorizontalDivider()
                }
                items(contentPlan.tocEntries, key = { it.itemIndex }) { entry ->
                    TableOfContentsEntryRow(
                        entry = entry,
                        selected = entry.itemIndex == currentItemIndex,
                        onClick = { onOpenEntry(entry) },
                    )
                }
            }
        },
        confirmButton = {
            TextButton(onClick = onDismiss) {
                Text(strings.done)
            }
        },
    )
}

@Composable
private fun TableOfContentsBackRow(enabled: Boolean, onClick: () -> Unit) {
    val contentColor = if (enabled) {
        MaterialTheme.colorScheme.onSurface
    } else {
        MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
    }
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .clickable(enabled = enabled, onClick = onClick)
            .padding(horizontal = 12.dp, vertical = 12.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Icon(
            Icons.AutoMirrored.Filled.ArrowBack,
            contentDescription = null,
            tint = contentColor,
            modifier = Modifier.size(20.dp),
        )
        Text(
            text = strings.backToLastPosition,
            style = MaterialTheme.typography.bodyMedium,
            fontWeight = FontWeight.SemiBold,
            color = contentColor,
            modifier = Modifier.padding(start = 12.dp),
        )
    }
}

@Composable
private fun TableOfContentsEntryRow(
    entry: ReaderTocEntry,
    selected: Boolean,
    onClick: () -> Unit,
) {
    val rowModifier = Modifier
        .fillMaxWidth()
        .then(
            if (selected) {
                Modifier.background(MaterialTheme.colorScheme.primaryContainer)
            } else {
                Modifier
            },
        )
        .clickable(onClick = onClick)
        .padding(
            start = (12 + entry.depth * 16).dp,
            top = 10.dp,
            end = 12.dp,
            bottom = 10.dp,
        )
    Text(
        text = entry.title,
        style = MaterialTheme.typography.bodyMedium,
        fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Normal,
        color = if (selected) {
            MaterialTheme.colorScheme.onPrimaryContainer
        } else {
            MaterialTheme.colorScheme.onSurface
        },
        maxLines = 2,
        overflow = TextOverflow.Ellipsis,
        modifier = rowModifier,
    )
}

private fun List<ReadingPosition>.withoutAdjacentReadingDuplicates(): List<ReadingPosition> =
    fold(emptyList()) { acc, position ->
        if (acc.lastOrNull()?.sameReadingPlace(position) == true) acc else acc + position.withoutReadingBackStack()
    }

private fun ReadingPosition.sameReadingPlace(other: ReadingPosition): Boolean =
    itemIndex == other.itemIndex &&
        scrollOffset == other.scrollOffset &&
        readAloudSentenceIndex == other.readAloudSentenceIndex

private fun ReadingPosition.withoutReadingBackStack(): ReadingPosition =
    copy(backStack = emptyList())

private fun List<ReadingPosition>.backPositionFrom(current: ReadingPosition): ReadingPosition? =
    firstOrNull { !it.sameReadingPlace(current) }

private const val MaxTableOfContentsBackStack = 10

@Composable
private fun ReaderFontSettingsPanel(
    settings: ReaderFontSettings,
    onFontSizeDecrease: () -> Unit,
    onFontSizeIncrease: () -> Unit,
    onLineHeightDecrease: () -> Unit,
    onLineHeightIncrease: () -> Unit,
    onFontWeightDecrease: () -> Unit,
    onFontWeightIncrease: () -> Unit,
    onBackgroundColorChange: () -> Unit,
    onBackgroundColorPick: () -> Unit,
    onTextColorChange: () -> Unit,
    onTextColorPick: () -> Unit,
    onClose: () -> Unit,
) {
    val useDarkTheme = LocalReaderThemeIsDark.current
    val backgroundSwatch = settings.themeBackgroundArgb(useDarkTheme)?.toComposeColor() ?: MaterialTheme.colorScheme.surface
    val textSwatch = settings.themeTextArgb(useDarkTheme)?.toComposeColor() ?: MaterialTheme.colorScheme.onSurface

    Surface(
        tonalElevation = 3.dp,
        shadowElevation = 4.dp,
        color = MaterialTheme.colorScheme.surface,
        modifier = Modifier.fillMaxWidth(),
    ) {
        Column {
            Row(
                modifier = Modifier.fillMaxWidth()
                    .padding(horizontal = 2.dp),
                horizontalArrangement = Arrangement.SpaceEvenly,
                verticalAlignment = Alignment.CenterVertically,
            ) {
//            Text("A ${settings.fontSizeSp.roundToInt()}", style = MaterialTheme.typography.labelMedium)
                ReaderSettingsButton(
                    contentDescription = strings.readerFontSizeDecrease,
                    onClick = onFontSizeDecrease,
                ) {
                    ReaderLetterChangeIcon(
                        letter = "A",
                        increase = false,
                        contentDescription = strings.readerFontSizeDecrease,
                    )
                }
                ReaderSettingsButton(
                    contentDescription = strings.readerFontSizeIncrease,
                    onClick = onFontSizeIncrease,
                ) {
                    ReaderLetterChangeIcon(
                        letter = "A",
                        increase = true,
                        contentDescription = strings.readerFontSizeIncrease,
                    )
                }
                ReaderSettingsButton(
                    contentDescription = strings.readerLineHeightDecrease,
                    onClick = onLineHeightDecrease,
                ) {
                    Icon(Icons.Filled.UnfoldLess, contentDescription = null)
                }
                ReaderSettingsButton(
                    contentDescription = strings.readerLineHeightIncrease,
                    onClick = onLineHeightIncrease,
                ) {
                    Icon(Icons.Filled.UnfoldMore, contentDescription = null)
                }
//            Text("W ${settings.fontWeight}", style = MaterialTheme.typography.labelMedium)
                ReaderSettingsButton(
                    contentDescription = strings.readerFontWeightDecrease,
                    onClick = onFontWeightDecrease,
                ) {
                    ReaderLetterChangeIcon(
                        letter = "B",
                        increase = false,
                        contentDescription = strings.readerFontWeightDecrease,
                        fontWeight = FontWeight.Bold,
                    )
                }
                ReaderSettingsButton(
                    contentDescription = strings.readerFontWeightIncrease,
                    onClick = onFontWeightIncrease,
                ) {
                    ReaderLetterChangeIcon(
                        letter = "B",
                        increase = true,
                        contentDescription = strings.readerFontWeightIncrease,
                        fontWeight = FontWeight.Bold,
                    )
                }
                ColorSettingButton(
                    contentDescription = strings.readerBackgroundColor,
                    onClick = onBackgroundColorChange,
                    onLongClick = onBackgroundColorPick,
                ) {
                    ColorSwatchIcon(color = backgroundSwatch)
                }
                ColorSettingButton(
                    contentDescription = strings.readerTextColor,
                    onClick = onTextColorChange,
                    onLongClick = onTextColorPick,
                ) {
                    ReaderLetterChangeIcon(
                        letter = "T",
                        increase = true,
                        contentDescription = strings.readerTextColor,
                        fontWeight = FontWeight.Bold,
                        color = textSwatch,
                    )
                }
                ReaderSettingsButton(
                    contentDescription = strings.closeReaderSettings,
                    onClick = onClose,
                ) {
                    Icon(Icons.Filled.Close, contentDescription = null)
                }
            }
//            Row(Modifier.fillMaxWidth().padding(bottom = 2.dp, top = 0.dp), horizontalArrangement = Arrangement.Center) {
//                Text(
//                    "size: ${settings.fontSizeSp.roundToInt()} height: ${settings.lineHeightSp.roundToInt()}, weight: ${settings.fontWeight}",
//                    style = MaterialTheme.typography.bodySmall,
//                    maxLines = 1,
//                    overflow = TextOverflow.Ellipsis,
//                )
//            }
        }
    }
}

@Composable
private fun ReaderLetterChangeIcon(
    letter: String,
    increase: Boolean,
    contentDescription: String,
    fontWeight: FontWeight = FontWeight.SemiBold,
    color: Color = Color.Unspecified,
) {
    Text(
        text = letter + if (increase) "⁺" else "⁻",
        style = MaterialTheme.typography.titleMedium,
        fontWeight = fontWeight,
        color = color,
        modifier = Modifier.semantics {
            this.contentDescription = contentDescription
        },
    )
}

@Composable
private fun RowScope.ReaderSettingsButton(
    contentDescription: String,
    onClick: () -> Unit,
    content: @Composable () -> Unit,
) {
    Box(
        modifier = Modifier
            .weight(1f)
            .height(48.dp)
            .clickable(onClick = onClick)
            .semantics {
                this.contentDescription = contentDescription
            },
        contentAlignment = Alignment.Center,
    ) {
        content()
    }
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun RowScope.ColorSettingButton(
    contentDescription: String,
    onClick: () -> Unit,
    onLongClick: () -> Unit,
    content: @Composable () -> Unit,
) {
    Box(
        modifier = Modifier
            .weight(1f)
            .height(48.dp)
            .combinedClickable(
                onClick = onClick,
                onLongClick = onLongClick,
            )
            .semantics {
                this.contentDescription = contentDescription
            },
        contentAlignment = Alignment.Center,
    ) {
        content()
    }
}

@Composable
private fun ColorSwatchIcon(color: Color) {
    Box(
        modifier = Modifier
            .size(22.dp)
            .background(color),
    )
}

@Composable
private fun ReaderExtraSettingsDialog(
    onRevertColorScheme: () -> Unit,
    onDismiss: () -> Unit,
) {
    AlertDialog(
        onDismissRequest = onDismiss,
        title = { Text(strings.extraReaderSettings) },
        text = {
            Column(Modifier.fillMaxWidth()) {
                ReaderExtraSettingsActionRow(
                    text = strings.revertColorScheme,
                    onClick = onRevertColorScheme,
                )
                HorizontalDivider()
                readerExtraSettingOptions().forEach { option ->
                    Row(
                        modifier = Modifier
                            .fillMaxWidth()
                            .clickable { }
                            .padding(horizontal = 4.dp, vertical = 12.dp),
                        verticalAlignment = Alignment.CenterVertically,
                    ) {
                        Text(
                            text = option,
                            style = MaterialTheme.typography.bodyLarge,
                            modifier = Modifier.weight(1f),
                        )
                        Text(
                            text = ">",
                            style = MaterialTheme.typography.bodyLarge,
                            color = MaterialTheme.colorScheme.outline,
                        )
                    }
                    HorizontalDivider()
                }
            }
        },
        confirmButton = {
            TextButton(onClick = onDismiss) {
                Text(strings.done)
            }
        },
    )
}

@Composable
private fun ReaderExtraSettingsActionRow(text: String, onClick: () -> Unit) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .clickable(onClick = onClick)
            .padding(horizontal = 4.dp, vertical = 12.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Text(
            text = text,
            style = MaterialTheme.typography.bodyLarge,
            modifier = Modifier.weight(1f),
        )
    }
}

@Composable
private fun ReaderColorPickerDialog(
    title: String,
    initialArgb: Long,
    presets: List<Long>,
    customArgb: Long?,
    onDismiss: () -> Unit,
    onApply: (Long) -> Unit,
) {
    val initialRgb = remember(initialArgb) { initialArgb.toRgbComponents() }
    var red by remember(initialArgb) { mutableStateOf(initialRgb.red) }
    var green by remember(initialArgb) { mutableStateOf(initialRgb.green) }
    var blue by remember(initialArgb) { mutableStateOf(initialRgb.blue) }
    val selectedArgb = rgbToReaderArgb(red, green, blue)
    val swatches = remember(presets, customArgb) { presets.withCustomColor(customArgb) }

    AlertDialog(
        onDismissRequest = onDismiss,
        title = { Text(title) },
        text = {
            Column(
                modifier = Modifier.fillMaxWidth(),
                verticalArrangement = Arrangement.spacedBy(12.dp),
            ) {
                Surface(
                    color = selectedArgb.toComposeColor(),
                    modifier = Modifier.fillMaxWidth().height(48.dp),
                ) {}
                if (customArgb != null) {
                    Text(strings.customColor, style = MaterialTheme.typography.labelMedium)
                }
                Row(
                    modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()),
                    horizontalArrangement = Arrangement.spacedBy(8.dp),
                ) {
                    swatches.forEach { argb ->
                        Box(
                            modifier = Modifier
                                .size(36.dp)
                                .background(argb.toComposeColor())
                                .clickable {
                                    val rgb = argb.toRgbComponents()
                                    red = rgb.red
                                    green = rgb.green
                                    blue = rgb.blue
                                },
                        )
                    }
                }
                ReaderColorSlider(label = strings.red, value = red, onValueChange = { red = it })
                ReaderColorSlider(label = strings.green, value = green, onValueChange = { green = it })
                ReaderColorSlider(label = strings.blue, value = blue, onValueChange = { blue = it })
            }
        },
        dismissButton = {
            TextButton(onClick = onDismiss) {
                Text(strings.cancel)
            }
        },
        confirmButton = {
            TextButton(onClick = { onApply(selectedArgb) }) {
                Text(strings.done)
            }
        },
    )
}

@Composable
private fun ReaderColorSlider(label: String, value: Int, onValueChange: (Int) -> Unit) {
    Row(
        modifier = Modifier.fillMaxWidth(),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Text(
            text = label,
            style = MaterialTheme.typography.labelMedium,
            modifier = Modifier.size(36.dp),
        )
        Slider(
            value = value.toFloat(),
            onValueChange = { onValueChange(it.roundToInt().coerceIn(0, 255)) },
            valueRange = 0f..255f,
            modifier = Modifier.weight(1f),
        )
        Text(
            text = value.toString(),
            style = MaterialTheme.typography.labelMedium,
            textAlign = TextAlign.End,
            modifier = Modifier.size(36.dp),
        )
    }
}

private fun readerExtraSettingOptions(): List<String> =
    listOf(
        strings.readerColors,
        strings.readerTypography,
        strings.readerLayout,
        strings.readerNavigation,
        strings.readerProgress,
    )

private fun nextReaderBackgroundColorArgb(currentArgb: Long?, customArgb: Long?, useDarkTheme: Boolean): Long? {
    val palette = readerBackgroundPalette(useDarkTheme).withCustomColor(customArgb)
    return nextReaderPaletteArgb(currentArgb, palette)
}

private fun nextReaderTextColorArgb(currentArgb: Long?, customArgb: Long?, useDarkTheme: Boolean): Long? {
    val palette = readerTextPalette(useDarkTheme).withCustomColor(customArgb)
    return nextReaderPaletteArgb(currentArgb, palette)
}

private fun nextReaderPaletteArgb(currentArgb: Long?, palette: List<Long>): Long? {
    val index = palette.indexOf(currentArgb)
    return when {
        index < 0 -> palette.firstOrNull()
        index < palette.lastIndex -> palette[index + 1]
        else -> null
    }
}

private fun readerBackgroundPalette(useDarkTheme: Boolean): List<Long> =
    if (useDarkTheme) DarkReaderBackgroundPalette else LightReaderBackgroundPalette

private fun readerTextPalette(useDarkTheme: Boolean): List<Long> =
    if (useDarkTheme) DarkReaderTextPalette else LightReaderTextPalette

private fun List<Long>.withCustomColor(customArgb: Long?): List<Long> =
    if (customArgb == null || customArgb in this) this else this + customArgb

private data class RgbComponents(
    val red: Int,
    val green: Int,
    val blue: Int,
)

private fun Long.toRgbComponents(): RgbComponents =
    RgbComponents(
        red = ((this shr 16) and 0xFF).toInt(),
        green = ((this shr 8) and 0xFF).toInt(),
        blue = (this and 0xFF).toInt(),
    )

private fun Color.toReaderArgb(): Long {
    fun Float.componentToByte(): Long = (coerceIn(0f, 1f) * 255f).roundToInt().coerceIn(0, 255).toLong()
    return (alpha.componentToByte() shl 24) or
        (red.componentToByte() shl 16) or
        (green.componentToByte() shl 8) or
        blue.componentToByte()
}

private fun rgbToReaderArgb(red: Int, green: Int, blue: Int): Long =
    (0xFFL shl 24) or
        (red.coerceIn(0, 255).toLong() shl 16) or
        (green.coerceIn(0, 255).toLong() shl 8) or
        blue.coerceIn(0, 255).toLong()

private val LightReaderBackgroundPalette = listOf(
    0xFFF7F2EAL,
    0xFFF4F0D8L,
    0xFFEAF3F0L,
    0xFFECEFF5L,
)

private val DarkReaderBackgroundPalette = listOf(
    0xFF171411L,
    0xFF1B2421L,
    0xFF181E29L,
    0xFF24211DL,
)

private val LightReaderTextPalette = listOf(
    0xFF005C51L,
    0xFF7A1F1FL,
    0xFF243A8FL,
    0xFF6B2A6BL,
)

private val DarkReaderTextPalette = listOf(
    0xFFD8E8E2L,
    0xFFE2C58FL,
    0xFFECEFF5L,
)

@Composable
private fun ReadAloudPanel(
    playing: Boolean,
    onPlayStop: () -> Unit,
    onBack: () -> Unit,
    onForward: () -> Unit,
    onSettings: () -> Unit,
) {
    Surface(
        tonalElevation = 3.dp,
        shadowElevation = 4.dp,
        color = MaterialTheme.colorScheme.surface,
        modifier = Modifier.fillMaxWidth(),
    ) {
        Row(
            modifier = Modifier.fillMaxWidth().height(56.dp).padding(horizontal = 12.dp),
            horizontalArrangement = Arrangement.Center,
            verticalAlignment = Alignment.CenterVertically,
        ) {
            IconButton(onClick = onBack) {
                Icon(Icons.Filled.Replay, contentDescription = strings.previousSentence)
            }
            IconButton(onClick = onPlayStop) {
                Icon(
                    if (playing) Icons.Filled.Stop else Icons.Filled.PlayArrow,
                    contentDescription = if (playing) strings.stopReading else strings.startReading,
                )
            }
            IconButton(onClick = onForward) {
                Icon(Icons.Filled.FastForward, contentDescription = strings.nextSentence)
            }
            IconButton(onClick = onSettings) {
                Icon(Icons.Filled.Settings, contentDescription = strings.readAloudSettings)
            }
        }
    }
}

@Composable
private fun ReadAloudSettingsDialog(
    state: ReadAloudSettingsState,
    onDismiss: () -> Unit,
    onEngineSelected: (String?) -> Unit,
    onVoiceSelected: (String?) -> Unit,
) {
    var engineMenuOpen by remember { mutableStateOf(false) }
    var voiceMenuOpen by remember { mutableStateOf(false) }
    val selectedEngineLabel = state.engines.firstOrNull { it.id == state.selectedEngineId }?.label ?: strings.systemDefault
    val selectedVoiceLabel = state.voices.firstOrNull { it.id == state.selectedVoiceId }
        ?.let { "${it.label} (${it.localeTag})" }
        ?: strings.autoRussian

    AlertDialog(
        onDismissRequest = onDismiss,
        title = { Text(strings.readAloud) },
        text = {
            Column {
                Text(strings.ttsEngine, style = MaterialTheme.typography.labelMedium)
                Box {
                    TextButton(onClick = { engineMenuOpen = true }) {
                        Text(selectedEngineLabel)
                    }
                    DropdownMenu(expanded = engineMenuOpen, onDismissRequest = { engineMenuOpen = false }) {
                        DropdownMenuItem(
                            text = { Text(strings.systemDefault) },
                            onClick = {
                                engineMenuOpen = false
                                onEngineSelected(null)
                            },
                        )
                        state.engines.forEach { engine ->
                            DropdownMenuItem(
                                text = { Text(engine.label) },
                                onClick = {
                                    engineMenuOpen = false
                                    onEngineSelected(engine.id)
                                },
                            )
                        }
                    }
                }

                Text(strings.offlineVoice, style = MaterialTheme.typography.labelMedium)
                Box {
                    TextButton(onClick = { voiceMenuOpen = true }, enabled = !state.loading && state.voices.isNotEmpty()) {
                        Text(selectedVoiceLabel)
                    }
                    DropdownMenu(expanded = voiceMenuOpen, onDismissRequest = { voiceMenuOpen = false }) {
                        DropdownMenuItem(
                            text = { Text(strings.autoRussian) },
                            onClick = {
                                voiceMenuOpen = false
                                onVoiceSelected(null)
                            },
                        )
                        state.voices.forEach { voice ->
                            DropdownMenuItem(
                                text = { Text("${voice.label} (${voice.localeTag})") },
                                onClick = {
                                    voiceMenuOpen = false
                                    onVoiceSelected(voice.id)
                                },
                            )
                        }
                    }
                }

                if (state.loading) {
                    Row(verticalAlignment = Alignment.CenterVertically) {
                        CircularProgressIndicator()
                        Text(strings.loadingVoices, modifier = Modifier.padding(start = 12.dp))
                    }
                }
                state.message?.let { Text(it, color = MaterialTheme.colorScheme.error) }
            }
        },
        confirmButton = {
            TextButton(onClick = onDismiss) {
                Text(strings.done)
            }
        },
    )
}
