package com.aletheia.app.ui.reader

import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.StateListDrawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.ViewGroup
import android.view.WindowManager
import android.webkit.WebView
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.aletheia.app.AletheiaApplication
import com.aletheia.app.R
import com.aletheia.app.databinding.ActivityReaderBinding
import com.aletheia.app.model.Book
import com.aletheia.app.model.ReaderChapterItem
import com.aletheia.app.model.ReadingBookmark
import com.aletheia.app.model.ReadingNote
import com.aletheia.app.ui.reader.tts.ReaderSpeechController
import com.aletheia.app.ui.reader.tts.ReaderSpeechSettingsDialog
import com.google.android.material.button.MaterialButton
import java.io.File
import kotlin.math.abs
import kotlin.math.roundToInt
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.json.JSONObject

class ReaderActivity : AppCompatActivity() {
    private lateinit var binding: ActivityReaderBinding
    private lateinit var webController: ReaderWebController
    private lateinit var speechController: ReaderSpeechController
    private val app by lazy { application as AletheiaApplication }
    private val paginationCache by lazy { ReaderPaginationCache(this) }

    private lateinit var chapterAdapter: ChapterAdapter
    private lateinit var bookmarkAdapter: BookmarkAdapter
    private lateinit var quoteAdapter: ReadingNoteAdapter

    private var currentBook: Book? = null
    private var preferences = ReaderPreferences()
    private var position = ReaderPosition()
    private var restoredPosition: ReaderPosition? = null
    private var chapters: List<ReaderChapterItem> = emptyList()
    private var bookmarks: List<ReadingBookmark> = emptyList()
    private var quotes: List<ReadingNote> = emptyList()
    private var currentSelection: ReaderSelection? = null
    private var pendingHighlightColor: String? = null
    private var contentsTab = ContentsTab.TOC
    private var bookReady = false
    private var updatingSettingsViews = false
    private var updatingProgressSlider = false
    private var progressSaveJob: Job? = null
    private var settingsSaveJob: Job? = null
    private var orientationJob: Job? = null
    private var finishJob: Job? = null
    private var paginationCacheKey: String? = null
    private var finishingReader = false
    private var retryingReader = false
    private var readerTouchActive = false
    private var readerTouchMoved = false
    private var readerTouchDownX = 0f
    private var readerTouchDownY = 0f
    private var readerTouchDownAt = 0L
    private val readerTapSlop by lazy(LazyThreadSafetyMode.NONE) {
        ViewConfiguration.get(this).scaledTouchSlop.toFloat()
    }
    private var pageHeaderBasePadding = Insets()
    private var controlsBasePadding = Insets()
    private var settingsBasePadding = Insets()
    private var contentsBasePadding = Insets()
    private var selectionBasePadding = Insets()
    private var noteBasePadding = Insets()
    private var safeInsets = Insets()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (savedInstanceState?.getBoolean(STATE_FINISHING, false) == true) {
            finishingReader = true
            finish()
            return
        }
        binding = ActivityReaderBinding.inflate(layoutInflater)
        setContentView(binding.root)

        preferences = ReaderPreferences.fromStateJson(savedInstanceState?.getString(STATE_PREFERENCES))
            ?: ReaderPreferences.from(app.settingsRepository)
        restoredPosition = savedInstanceState?.restoreReaderPosition()
        setupWindow()
        setupLists()
        setupControls()
        renderSettings()
        applyNativePreferences()

        webController = ReaderWebController(this, binding.readerWebView, ::handleReaderEvent)
        speechController = ReaderSpeechController(
            context = this,
            scope = lifecycleScope,
            reader = webController,
            onStateChanged = ::renderSpeechState,
            onMessage = { message -> Toast.makeText(this, message, Toast.LENGTH_LONG).show() }
        )
        applyRequestedOrientation()
        showLoading("Подготавливаю книгу…")

        onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() = handleBack()
        })

        val bookId = intent.getLongExtra(EXTRA_BOOK_ID, 0L)
        if (bookId <= 0L) {
            finish()
            return
        }
        loadBook(bookId)
    }

    override fun onResume() {
        super.onResume()
        applySystemUi()
    }

    override fun onConfigurationChanged(newConfig: Configuration) {
        super.onConfigurationChanged(newConfig)
        ViewCompat.requestApplyInsets(binding.root)
        applySystemUi()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        outState.putBoolean(STATE_FINISHING, finishingReader)
        outState.putString(STATE_PREFERENCES, preferences.toStateJson())
        currentBook?.let { book ->
            outState.putLong(STATE_BOOK_ID, book.id)
            outState.putDouble(STATE_PROGRESS, position.progress)
            outState.putString(STATE_LOCATOR, position.locator)
            outState.putString(STATE_CHAPTER, position.chapter)
            outState.putInt(STATE_CURRENT_PAGE, position.currentPage)
            outState.putInt(STATE_TOTAL_PAGES, position.totalPages)
            outState.putInt(STATE_CHAPTER_CURRENT_PAGE, position.chapterCurrentPage)
            outState.putInt(STATE_CHAPTER_TOTAL_PAGES, position.chapterTotalPages)
        }
        super.onSaveInstanceState(outState)
    }

    override fun onPause() {
        if (!finishingReader) {
            scheduleProgressSave(immediate = true)
            scheduleSettingsSave(immediate = true)
        }
        super.onPause()
    }

    override fun onDestroy() {
        try {
            if (::speechController.isInitialized) speechController.destroy()
            if (::webController.isInitialized) webController.destroy()
        } finally {
            super.onDestroy()
        }
    }

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if (bookReady && preferences.volumeButtons && !preferences.verticalScroll) {
            when (keyCode) {
                KeyEvent.KEYCODE_VOLUME_DOWN -> {
                    webController.previous()
                    return true
                }
                KeyEvent.KEYCODE_VOLUME_UP -> {
                    webController.next()
                    return true
                }
            }
        }
        return super.onKeyDown(keyCode, event)
    }

    private fun setupWindow() {
        WindowCompat.setDecorFitsSystemWindows(window, false)
        window.statusBarColor = Color.TRANSPARENT
        window.navigationBarColor = Color.TRANSPARENT
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            window.isNavigationBarContrastEnforced = false
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            window.attributes = window.attributes.apply {
                layoutInDisplayCutoutMode =
                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
            }
        }

        pageHeaderBasePadding = binding.pageHeader.paddingInsets()
        controlsBasePadding = binding.readerControlsOverlay.paddingInsets()
        settingsBasePadding = binding.readerSettingsOverlay.paddingInsets()
        contentsBasePadding = binding.readerContentsOverlay.paddingInsets()
        selectionBasePadding = binding.readerSelectionOverlay.paddingInsets()
        noteBasePadding = binding.noteEditorOverlay.paddingInsets()

        ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
            val safe = insets.getInsetsIgnoringVisibility(
                WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
            )
            val status = insets.getInsetsIgnoringVisibility(
                WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.displayCutout()
            )
            val tappable = insets.getInsetsIgnoringVisibility(
                WindowInsetsCompat.Type.tappableElement()
            )
            val ime = insets.getInsets(WindowInsetsCompat.Type.ime())
            safeInsets = Insets(safe.left, safe.top, safe.right, safe.bottom)
            binding.readerStatusBarBackground.updateLayoutParams<FrameLayout.LayoutParams> {
                height = status.top
            }
            binding.readerNavigationBarBottomBackground.updateLayoutParams<FrameLayout.LayoutParams> {
                height = tappable.bottom
            }
            binding.readerNavigationBarLeftBackground.updateLayoutParams<FrameLayout.LayoutParams> {
                width = tappable.left
            }
            binding.readerNavigationBarRightBackground.updateLayoutParams<FrameLayout.LayoutParams> {
                width = tappable.right
            }
            binding.pageHeader.applyInsets(pageHeaderBasePadding, safe.left, 0, safe.right, 0)
            updateReaderViewportMargins()
            binding.readerControlsOverlay.applyInsets(
                controlsBasePadding,
                safe.left,
                safe.top,
                safe.right,
                safe.bottom
            )
            binding.readerSettingsOverlay.applyInsets(
                settingsBasePadding,
                safe.left,
                safe.top,
                safe.right,
                safe.bottom
            )
            binding.readerContentsOverlay.applyInsets(
                contentsBasePadding,
                safe.left,
                safe.top,
                safe.right,
                safe.bottom
            )
            binding.readerSelectionOverlay.applyInsets(
                selectionBasePadding,
                safe.left,
                safe.top,
                safe.right,
                safe.bottom
            )
            binding.noteEditorOverlay.applyInsets(
                noteBasePadding,
                safe.left,
                safe.top,
                safe.right,
                safe.bottom.coerceAtLeast(ime.bottom)
            )
            insets
        }
        ViewCompat.requestApplyInsets(binding.root)
    }

    private fun setupLists() {
        chapterAdapter = ChapterAdapter { chapter ->
            webController.goToChapter(chapter.href)
            closeContents()
        }
        bookmarkAdapter = BookmarkAdapter(
            onBookmarkClicked = { bookmark ->
                bookmark.cfi?.takeIf(String::isNotBlank)?.let(webController::goToLocator)
                    ?: webController.goToProgress(bookmark.progress)
                closeContents()
            },
            onDeleteClicked = ::deleteBookmark
        )
        quoteAdapter = ReadingNoteAdapter(
            onNoteClicked = { note ->
                note.cfi?.takeIf(String::isNotBlank)?.let(webController::goToLocator)
                    ?: webController.goToProgress(note.progress)
                closeContents()
            },
            onDeleteClicked = ::deleteQuote
        )

        binding.tocRecycler.layoutManager = LinearLayoutManager(this)
        binding.tocRecycler.adapter = chapterAdapter
        binding.bookmarksRecycler.layoutManager = LinearLayoutManager(this)
        binding.bookmarksRecycler.adapter = bookmarkAdapter
        binding.quotesRecycler.layoutManager = LinearLayoutManager(this)
        binding.quotesRecycler.adapter = quoteAdapter
    }

    private fun setupControls() {
        binding.readerWebView.setOnTouchListener { _, event ->
            observeNativeReaderTap(event)
            false
        }
        binding.readerBackButton.setOnClickListener { finishReader() }
        binding.readerVoiceButton.setOnClickListener {
            if (!bookReady) {
                Toast.makeText(this, "Дождитесь загрузки книги", Toast.LENGTH_SHORT).show()
            } else {
                speechController.toggle()
            }
        }
        binding.readerVoiceButton.setOnLongClickListener {
            if (::speechController.isInitialized) {
                ReaderSpeechSettingsDialog.show(this, speechController)
                true
            } else {
                false
            }
        }
        binding.readerSettingsButton.setOnClickListener { openSettings() }
        binding.readerContentsButton.setOnClickListener { openContents() }
        binding.readerBookmarkButton.setOnClickListener { toggleBookmark() }
        binding.readerRetryButton.setOnClickListener {
            retryReader()
        }

        binding.readerProgressSlider.addOnChangeListener { _, value, fromUser ->
            if (fromUser && !updatingProgressSlider && bookReady) {
                webController.goToProgress((value / 100f).toDouble())
            }
        }

        binding.settingsScrim.setOnClickListener { closeSettings() }
        binding.settingsCloseButton.setOnClickListener { closeSettings() }
        binding.allSettingsButton.setOnClickListener { showFullSettings(true) }
        binding.settingsBackButton.setOnClickListener { showFullSettings(false) }

        binding.themeLightButton.setOnClickListener {
            updatePreferences { copy(theme = ReaderPreferences.THEME_LIGHT) }
        }
        binding.themeSepiaButton.setOnClickListener {
            updatePreferences { copy(theme = ReaderPreferences.THEME_SEPIA) }
        }
        binding.themeDarkButton.setOnClickListener {
            updatePreferences { copy(theme = ReaderPreferences.THEME_DARK) }
        }
        binding.fullThemeLightButton.setOnClickListener {
            updatePreferences { copy(theme = ReaderPreferences.THEME_LIGHT) }
        }
        binding.fullThemeSepiaButton.setOnClickListener {
            updatePreferences { copy(theme = ReaderPreferences.THEME_SEPIA) }
        }
        binding.fullThemeDarkButton.setOnClickListener {
            updatePreferences { copy(theme = ReaderPreferences.THEME_DARK) }
        }
        binding.fontSizeMinusButton.setOnClickListener {
            updatePreferences { copy(fontSize = (fontSize - 2).coerceAtLeast(12)) }
        }
        binding.fontSizePlusButton.setOnClickListener {
            updatePreferences { copy(fontSize = (fontSize + 2).coerceAtMost(42)) }
        }
        binding.fullFontSizeMinusButton.setOnClickListener {
            updatePreferences { copy(fontSize = (fontSize - 2).coerceAtLeast(12)) }
        }
        binding.fullFontSizePlusButton.setOnClickListener {
            updatePreferences { copy(fontSize = (fontSize + 2).coerceAtMost(42)) }
        }
        binding.fontFamilyDropdown.setSimpleItems(ReaderPreferences.FONT_NAMES.toTypedArray())
        binding.fontFamilyDropdown.setOnItemClickListener { _, _, index, _ ->
            if (!updatingSettingsViews) {
                updatePreferences { copy(fontName = ReaderPreferences.FONT_NAMES[index]) }
            }
        }
        binding.fullFontFamilyDropdown.setSimpleItems(ReaderPreferences.FONT_NAMES.toTypedArray())
        binding.fullFontFamilyDropdown.setOnItemClickListener { _, _, index, _ ->
            if (!updatingSettingsViews) {
                updatePreferences { copy(fontName = ReaderPreferences.FONT_NAMES[index]) }
            }
        }
        binding.brightnessSlider.addOnChangeListener { _, value, fromUser ->
            if (fromUser && !updatingSettingsViews) {
                updatePreferences { copy(brightness = value.roundToInt().coerceIn(10, 100)) }
            }
        }
        binding.fullBrightnessSlider.addOnChangeListener { _, value, fromUser ->
            if (fromUser && !updatingSettingsViews) {
                updatePreferences { copy(brightness = value.roundToInt().coerceIn(10, 100)) }
            }
        }
        binding.systemBrightnessSwitch.setOnCheckedChangeListener { _, checked ->
            if (!updatingSettingsViews) updatePreferences { copy(systemBrightness = checked) }
        }
        binding.fullSystemBrightnessSwitch.setOnCheckedChangeListener { _, checked ->
            if (!updatingSettingsViews) updatePreferences { copy(systemBrightness = checked) }
        }
        binding.verticalScrollSwitch.setOnCheckedChangeListener { _, checked ->
            if (!updatingSettingsViews) updatePreferences { copy(verticalScroll = checked) }
        }
        binding.fullVerticalScrollSwitch.setOnCheckedChangeListener { _, checked ->
            if (!updatingSettingsViews) updatePreferences { copy(verticalScroll = checked) }
        }

        val orientations = arrayOf("Автоматически", "Портретная", "Альбомная")
        binding.orientationDropdown.setSimpleItems(orientations)
        binding.orientationDropdown.setOnItemClickListener { _, _, index, _ ->
            if (!updatingSettingsViews) {
                updatePreferences {
                    copy(
                        orientation = when (index) {
                            1 -> ReaderPreferences.ORIENTATION_PORTRAIT
                            2 -> ReaderPreferences.ORIENTATION_LANDSCAPE
                            else -> ReaderPreferences.ORIENTATION_AUTO
                        }
                    )
                }
            }
        }
        binding.alignJustifyButton.setOnClickListener {
            updatePreferences { copy(textAlign = ReaderPreferences.ALIGN_JUSTIFY) }
        }
        binding.alignLeftButton.setOnClickListener {
            updatePreferences { copy(textAlign = ReaderPreferences.ALIGN_LEFT) }
        }
        binding.lineHeightMinusButton.setOnClickListener {
            updatePreferences { copy(lineHeight = (lineHeight - 0.1).coerceAtLeast(1.1)) }
        }
        binding.lineHeightPlusButton.setOnClickListener {
            updatePreferences { copy(lineHeight = (lineHeight + 0.1).coerceAtMost(2.4)) }
        }
        binding.marginMinusButton.setOnClickListener {
            updatePreferences { copy(margin = (margin - 4).coerceAtLeast(8)) }
        }
        binding.marginPlusButton.setOnClickListener {
            updatePreferences { copy(margin = (margin + 4).coerceAtMost(72)) }
        }

        binding.volumeButtonsSwitch.setPreferenceListener { copy(volumeButtons = it) }
        binding.invertZonesSwitch.setPreferenceListener { copy(invertZones = it) }
        binding.brightnessGestureSwitch.setPreferenceListener { copy(brightnessGesture = it) }
        binding.keepScreenOnSwitch.setPreferenceListener { copy(keepScreenOn = it) }
        binding.showTitleSwitch.setPreferenceListener { copy(showTitle = it) }
        binding.showStatusSwitch.setPreferenceListener { copy(showStatus = it) }
        binding.pageTurnTapSwipeButton.setOnClickListener {
            updatePreferences { copy(pageTurnMode = ReaderPreferences.PAGE_TURN_TAP_SWIPE) }
        }
        binding.pageTurnSwipeButton.setOnClickListener {
            updatePreferences { copy(pageTurnMode = ReaderPreferences.PAGE_TURN_SWIPE) }
        }
        binding.pageTurnTapButton.setOnClickListener {
            updatePreferences { copy(pageTurnMode = ReaderPreferences.PAGE_TURN_TAP) }
        }

        binding.contentsCloseButton.setOnClickListener { closeContents() }
        binding.tabTocButton.setOnClickListener { showContentsTab(ContentsTab.TOC) }
        binding.tabBookmarksButton.setOnClickListener { showContentsTab(ContentsTab.BOOKMARKS) }
        binding.tabQuotesButton.setOnClickListener { showContentsTab(ContentsTab.QUOTES) }

        binding.selectionScrim.setOnClickListener { closeSelection() }
        binding.selectionCloseButton.setOnClickListener { closeSelection() }
        binding.selectionShareButton.setOnClickListener { shareSelection() }
        binding.selectionCopyButton.setOnClickListener { copySelection() }
        binding.selectionNoteButton.setOnClickListener { openNoteEditor() }
        binding.colorNoneButton.setOnClickListener { selectHighlightColor(null) }
        binding.colorYellowButton.setOnClickListener { selectHighlightColor(COLOR_YELLOW) }
        binding.colorGreenButton.setOnClickListener { selectHighlightColor(COLOR_GREEN) }
        binding.colorBlueButton.setOnClickListener { selectHighlightColor(COLOR_BLUE) }
        binding.colorPurpleButton.setOnClickListener { selectHighlightColor(COLOR_PURPLE) }
        binding.colorPinkButton.setOnClickListener { selectHighlightColor(COLOR_PINK) }
        binding.saveQuoteButton.setOnClickListener { saveAnnotation("", ReadingNote.KIND_QUOTE) }

        binding.noteCancelButton.setOnClickListener { closeNoteEditor() }
        binding.noteSaveButton.setOnClickListener {
            val note = binding.noteInput.text?.toString().orEmpty().trim()
            if (note.isBlank()) {
                binding.noteInput.error = "Введите текст заметки"
            } else {
                saveAnnotation(note, ReadingNote.KIND_NOTE)
            }
        }
    }

    private fun renderSpeechState(state: ReaderSpeechController.State) {
        val active = state == ReaderSpeechController.State.PLAYING ||
            state == ReaderSpeechController.State.INITIALIZING
        binding.readerVoiceButton.isSelected = active
        binding.readerVoiceButton.contentDescription = if (active) {
            "Приостановить озвучивание"
        } else {
            "Слушать книгу. Удерживайте для настройки скорости и интонации"
        }
    }

    private fun loadBook(bookId: Long) {
        lifecycleScope.launch {
            val data = withContext(Dispatchers.IO) {
                app.readerPersistenceMutex.withLock {
                    val book = app.bookRepository.getBookById(bookId)
                    Triple(
                        book,
                        book?.let { app.bookRepository.getBookmarks(it.id) }.orEmpty(),
                        book?.let { app.bookRepository.getNotes(it.id) }.orEmpty()
                    )
                }
            }
            val book = data.first
            if (book == null || !File(book.filePath).isFile) {
                showError("Файл книги не найден", retryAllowed = false)
                return@launch
            }
            currentBook = book
            bookmarks = data.second
            quotes = data.third
            val databasePosition = ReaderPosition(
                progress = book.readingProgress.coerceIn(0.0, 1.0),
                locator = book.lastCfi,
                chapter = book.lastChapter,
                currentPage = book.currentPage.coerceAtLeast(1),
                totalPages = book.totalPages.coerceAtLeast(1)
            )
            position = restoredPosition
                ?: app.readerStateCoordinator.latest(book.id)?.toReaderPosition()
                ?: databasePosition
            restoredPosition = null
            publishPosition()
            binding.pageHeaderTitle.text = book.title
            binding.readerBottomTitle.text = book.title
            binding.contentsBookTitle.text = listOf(book.author, book.title)
                .filter(String::isNotBlank)
                .joinToString(". ")
            renderProgress()
            renderCollections()
            val cacheKey = paginationCache.key(
                file = File(book.filePath),
                preferences = preferences,
                viewportWidth = resources.displayMetrics.widthPixels,
                viewportHeight = resources.displayMetrics.heightPixels
            )
            paginationCacheKey = cacheKey
            val cachedLocations = withContext(Dispatchers.IO) { paginationCache.read(cacheKey) }
            runCatching {
                webController.loadBook(
                    file = File(book.filePath),
                    format = book.format,
                    locator = position.locator,
                    progress = position.progress,
                    cachedLocations = cachedLocations,
                    preferences = preferences,
                    title = book.title
                )
            }.onFailure { error ->
                showError(
                    error.message ?: "Не удалось подготовить книгу",
                    retryAllowed = false
                )
            }
        }
    }

    private fun handleReaderEvent(event: ReaderEvent) {
        if (finishingReader) return
        when (event) {
            ReaderEvent.ShellReady -> binding.readerLoadingText.text = "Открываю книгу…"
            ReaderEvent.ToggleControls -> toggleControls()
            ReaderEvent.Navigation -> Unit
            is ReaderEvent.BookReady -> {
                bookReady = true
                if (event.totalPages > 1) position = position.copy(totalPages = event.totalPages)
                publishPosition()
                hideLoading()
                renderProgress()
                webController.setHighlights(quotes)
            }
            is ReaderEvent.Progress -> {
                position = ReaderPosition(
                    progress = event.progress,
                    locator = event.locator ?: position.locator,
                    chapter = event.chapter ?: position.chapter,
                    currentPage = event.currentPage,
                    totalPages = event.totalPages,
                    chapterCurrentPage = event.chapterCurrentPage,
                    chapterTotalPages = event.chapterTotalPages
                )
                publishPosition()
                renderProgress()
                updateCurrentChapter()
                scheduleProgressSave(immediate = false)
            }
            is ReaderEvent.Toc -> {
                chapters = event.chapters.mapIndexed { index, item ->
                    ReaderChapterItem(item.label, item.href, index)
                }
                updateCurrentChapter()
                renderCollections()
            }
            is ReaderEvent.Selection -> {
                val snapshot = event.snapshot?.copy(
                    progress = position.progress,
                    currentPage = position.currentPage,
                    totalPages = position.totalPages,
                    chapter = position.chapter
                )
                currentSelection = snapshot
                if (snapshot == null) {
                    binding.readerSelectionOverlay.isVisible = false
                } else {
                    pendingHighlightColor = null
                    renderSelectionPalette()
                    binding.readerSelectionOverlay.isVisible = true
                    binding.readerControlsOverlay.isVisible = false
                    applySystemUi()
                }
            }
            is ReaderEvent.ExternalLink -> openExternalLink(event.url)
            is ReaderEvent.BrightnessDelta -> {
                if (preferences.brightnessGesture && !preferences.systemBrightness) {
                    updatePreferences {
                        copy(brightness = (brightness + event.delta).roundToInt().coerceIn(10, 100))
                    }
                }
            }
            is ReaderEvent.PaginationCache -> {
                val key = paginationCacheKey ?: return
                lifecycleScope.launch(Dispatchers.IO) {
                    runCatching { paginationCache.write(key, event.locations) }
                }
            }
            is ReaderEvent.Error -> {
                val fatal = !bookReady || event.stage in FATAL_ERROR_STAGES
                if (fatal) {
                    bookReady = false
                    showError(event.message, retryAllowed = event.recoverable)
                } else {
                    Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show()
                }
            }
            is ReaderEvent.Search -> Unit
        }
    }

    private fun toggleControls() {
        if (binding.readerSettingsOverlay.isVisible || binding.readerContentsOverlay.isVisible ||
            binding.readerSelectionOverlay.isVisible || binding.noteEditorOverlay.isVisible
        ) return
        binding.readerControlsOverlay.isVisible = !binding.readerControlsOverlay.isVisible
        renderChromeVisibility()
    }

    private fun observeNativeReaderTap(event: MotionEvent) {
        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                readerTouchActive = event.pointerCount == 1
                readerTouchMoved = false
                readerTouchDownX = event.x
                readerTouchDownY = event.y
                readerTouchDownAt = event.eventTime
            }
            MotionEvent.ACTION_POINTER_DOWN,
            MotionEvent.ACTION_CANCEL -> {
                readerTouchActive = false
                readerTouchMoved = false
            }
            MotionEvent.ACTION_MOVE -> {
                if (readerTouchActive && (
                        abs(event.x - readerTouchDownX) > readerTapSlop ||
                            abs(event.y - readerTouchDownY) > readerTapSlop
                        )
                ) {
                    readerTouchMoved = true
                }
            }
            MotionEvent.ACTION_UP -> {
                val elapsed = event.eventTime - readerTouchDownAt
                val modalVisible = binding.readerLoadingOverlay.isVisible ||
                    binding.readerSettingsOverlay.isVisible ||
                    binding.readerContentsOverlay.isVisible ||
                    binding.readerSelectionOverlay.isVisible ||
                    binding.noteEditorOverlay.isVisible
                val hitType = binding.readerWebView.hitTestResult.type
                val interactiveTarget = hitType == WebView.HitTestResult.SRC_ANCHOR_TYPE ||
                    hitType == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE ||
                    hitType == WebView.HitTestResult.EDIT_TEXT_TYPE ||
                    hitType == WebView.HitTestResult.EMAIL_TYPE ||
                    hitType == WebView.HitTestResult.PHONE_TYPE ||
                    hitType == WebView.HitTestResult.GEO_TYPE
                val shouldHandle = readerTouchActive &&
                    !readerTouchMoved &&
                    elapsed in 0..NATIVE_TAP_MAX_DURATION_MS &&
                    bookReady &&
                    !finishingReader &&
                    currentSelection == null &&
                    !modalVisible &&
                    !interactiveTarget
                readerTouchActive = false
                readerTouchMoved = false
                if (!shouldHandle) return

                val normalizedX = event.x / binding.readerWebView.width.coerceAtLeast(1)
                when {
                    normalizedX in READER_CENTER_ZONE_START..READER_CENTER_ZONE_END -> toggleControls()
                    preferences.pageTurnMode != ReaderPreferences.PAGE_TURN_TAP &&
                        preferences.pageTurnMode != ReaderPreferences.PAGE_TURN_TAP_SWIPE -> Unit
                    normalizedX < READER_CENTER_ZONE_START -> {
                        if (preferences.invertZones) webController.next() else webController.previous()
                    }
                    else -> {
                        if (preferences.invertZones) webController.previous() else webController.next()
                    }
                }
            }
        }
    }

    private fun renderChromeVisibility() {
        binding.pageHeader.isVisible = preferences.showTitle &&
            !binding.readerControlsOverlay.isVisible &&
            !binding.readerContentsOverlay.isVisible
        updateReaderViewportMargins()
        applySystemUi()
    }

    private fun openSettings() {
        showFullSettings(false)
        renderSettings()
        binding.readerSettingsOverlay.isVisible = true
        applySystemUi()
    }

    private fun closeSettings() {
        binding.readerSettingsOverlay.isVisible = false
        renderChromeVisibility()
    }

    private fun showFullSettings(full: Boolean) {
        binding.settingsSheetGuideline.setGuidelinePercent(0.529f)
        binding.settingsHeader.isVisible = false
        binding.quickSettingsContainer.isVisible = !full
        binding.fullSettingsContainer.isVisible = full
        if (full) binding.fullSettingsContainer.post { binding.fullSettingsContainer.scrollTo(0, 0) }
    }

    private fun openContents() {
        binding.readerControlsOverlay.isVisible = false
        binding.readerSettingsOverlay.isVisible = false
        binding.readerContentsOverlay.isVisible = true
        showContentsTab(contentsTab)
        renderCollections()
        renderChromeVisibility()
    }

    private fun closeContents() {
        binding.readerContentsOverlay.isVisible = false
        binding.readerControlsOverlay.isVisible = true
        renderChromeVisibility()
    }

    private fun showContentsTab(tab: ContentsTab) {
        contentsTab = tab
        binding.tocRecycler.isVisible = tab == ContentsTab.TOC && chapters.isNotEmpty()
        binding.bookmarksRecycler.isVisible = tab == ContentsTab.BOOKMARKS && bookmarks.isNotEmpty()
        binding.quotesRecycler.isVisible = tab == ContentsTab.QUOTES && quotes.isNotEmpty()
        binding.tocEmptyText.isVisible = tab == ContentsTab.TOC && chapters.isEmpty()
        binding.bookmarksEmptyText.isVisible = tab == ContentsTab.BOOKMARKS && bookmarks.isEmpty()
        binding.quotesEmptyText.isVisible = tab == ContentsTab.QUOTES && quotes.isEmpty()
        binding.tabTocButton.isSelected = tab == ContentsTab.TOC
        binding.tabBookmarksButton.isSelected = tab == ContentsTab.BOOKMARKS
        binding.tabQuotesButton.isSelected = tab == ContentsTab.QUOTES
        val activeColor = Color.parseColor(PRIMARY_GRAY)
        val inactiveColor = Color.parseColor(SECONDARY_ON_LIGHT)
        binding.tabTocButton.setTextColor(if (tab == ContentsTab.TOC) activeColor else inactiveColor)
        binding.tabBookmarksButton.setTextColor(if (tab == ContentsTab.BOOKMARKS) activeColor else inactiveColor)
        binding.tabQuotesButton.setTextColor(if (tab == ContentsTab.QUOTES) activeColor else inactiveColor)
    }

    private fun renderCollections() {
        chapterAdapter.submitList(chapters)
        bookmarkAdapter.submitList(bookmarks)
        quoteAdapter.submitList(quotes)
        showContentsTab(contentsTab)
        syncBookmarkButton()
    }

    private fun updateCurrentChapter() {
        val current = position.chapter.orEmpty().trim()
        chapters.forEach { chapter -> chapter.isCurrent = current.isNotBlank() && chapter.label.trim() == current }
        chapterAdapter.submitList(chapters)
    }

    private fun renderProgress() {
        val remaining = (position.chapterTotalPages - position.chapterCurrentPage).coerceAtLeast(0)
        binding.pageHeaderRemaining.text = "Еще $remaining стр."
        binding.readerBottomChapter.text = position.chapter.orEmpty().ifBlank { " " }
        binding.readerPageCount.text = "${position.currentPage} из ${position.totalPages}"
        binding.readerChapterRemaining.text = "Еще $remaining стр."
        updatingProgressSlider = true
        binding.readerProgressSlider.value = (position.progress * 100).toFloat().coerceIn(0f, 100f)
        updatingProgressSlider = false
        binding.readerPersistentProgress.setProgressCompat(
            (position.progress * 10_000).roundToInt().coerceIn(0, 10_000),
            true
        )
        syncBookmarkButton()
    }

    private fun toggleBookmark() {
        val book = currentBook ?: return
        val existing = currentBookmark()
        lifecycleScope.launch {
            bookmarks = withContext(Dispatchers.IO) {
                if (existing != null) {
                    app.bookRepository.deleteBookmark(existing)
                } else {
                    app.bookRepository.addBookmark(
                        bookId = book.id,
                        cfi = position.locator,
                        progress = position.progress,
                        currentPage = position.currentPage,
                        totalPages = position.totalPages,
                        chapter = position.chapter,
                        label = position.chapter.orEmpty().ifBlank { "Страница ${position.currentPage}" }
                    )
                }
                app.bookRepository.getBookmarks(book.id)
            }
            renderCollections()
            Toast.makeText(
                this@ReaderActivity,
                if (existing == null) "Закладка сохранена" else "Закладка удалена",
                Toast.LENGTH_SHORT
            ).show()
        }
    }

    private fun currentBookmark(): ReadingBookmark? = bookmarks.firstOrNull { bookmark ->
        val bookmarkLocator = bookmark.cfi?.takeIf(String::isNotBlank)
        val positionLocator = position.locator?.takeIf(String::isNotBlank)
        if (bookmarkLocator != null && positionLocator != null) {
            stableLocatorKey(bookmarkLocator) == stableLocatorKey(positionLocator)
        } else {
            abs(bookmark.progress - position.progress) < BOOKMARK_PROGRESS_TOLERANCE
        }
    }

    private fun stableLocatorKey(rawLocator: String): String {
        val raw = rawLocator.trim()
        if (raw.startsWith("epubcfi(")) return "epub:$raw"
        if (raw.startsWith("fb2:")) {
            val parts = raw.split(':')
            if (parts.size in 3..4) {
                val offset = parts[2].toIntOrNull()
                val endOffset = parts.getOrNull(3)?.toIntOrNull() ?: offset
                val sectionId = Uri.decode(parts[1])
                if (sectionId.isNotBlank() && offset != null && endOffset != null) {
                    return "fb2:$sectionId:$offset:$sectionId:$endOffset"
                }
            }
            return "raw:$raw"
        }
        val json = runCatching { JSONObject(raw) }.getOrNull() ?: return "raw:$raw"
        return when (json.optString("type").lowercase()) {
            "epub" -> json.optString("cfi")
                .takeIf(String::isNotBlank)
                ?.let { "epub:$it" }
                ?: "raw:$raw"
            "fb2" -> {
                val sectionId = json.optString("sectionId").takeIf(String::isNotBlank)
                    ?: return "raw:$raw"
                val offset = json.optInt("offset", 0).coerceAtLeast(0)
                val endSectionId = json.optString("endSectionId").ifBlank { sectionId }
                val endOffset = json.optInt("endOffset", offset).coerceAtLeast(0)
                "fb2:$sectionId:$offset:$endSectionId:$endOffset"
            }
            else -> "raw:$raw"
        }
    }

    private fun syncBookmarkButton() {
        val active = currentBookmark() != null
        binding.readerBookmarkButton.isSelected = active
        binding.readerBookmarkButton.imageTintList = ColorStateList.valueOf(
            if (active) Color.parseColor(ACCENT_COLOR) else readerPalette().chromeIcon
        )
    }

    private fun deleteBookmark(bookmark: ReadingBookmark) {
        val bookId = currentBook?.id ?: return
        lifecycleScope.launch {
            bookmarks = withContext(Dispatchers.IO) {
                app.bookRepository.deleteBookmark(bookmark)
                app.bookRepository.getBookmarks(bookId)
            }
            renderCollections()
        }
    }

    private fun selectHighlightColor(color: String?) {
        pendingHighlightColor = color
        renderSelectionPalette()
    }

    private fun renderSelectionPalette() {
        binding.colorNoneButton.isSelected = pendingHighlightColor == null
        binding.colorYellowButton.isSelected = pendingHighlightColor == COLOR_YELLOW
        binding.colorGreenButton.isSelected = pendingHighlightColor == COLOR_GREEN
        binding.colorBlueButton.isSelected = pendingHighlightColor == COLOR_BLUE
        binding.colorPurpleButton.isSelected = pendingHighlightColor == COLOR_PURPLE
        binding.colorPinkButton.isSelected = pendingHighlightColor == COLOR_PINK
    }

    private fun shareSelection() {
        val selection = currentSelection ?: return
        val intent = Intent(Intent.ACTION_SEND).apply {
            type = "text/plain"
            putExtra(Intent.EXTRA_TEXT, selection.text)
        }
        startActivity(Intent.createChooser(intent, "Поделиться цитатой"))
    }

    private fun copySelection() {
        val selection = currentSelection ?: return
        val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
        clipboard.setPrimaryClip(ClipData.newPlainText("Цитата", selection.text))
        Toast.makeText(this, "Цитата скопирована", Toast.LENGTH_SHORT).show()
    }

    private fun openNoteEditor() {
        val selection = currentSelection ?: return
        binding.noteQuoteText.text = selection.text
        binding.noteInput.setText("")
        binding.noteEditorOverlay.isVisible = true
        binding.readerSelectionOverlay.isVisible = false
        binding.noteInput.requestFocus()
        applySystemUi()
    }

    private fun closeNoteEditor() {
        binding.noteEditorOverlay.isVisible = false
        binding.readerSelectionOverlay.isVisible = currentSelection != null
        applySystemUi()
    }

    private fun saveAnnotation(noteText: String, kind: String) {
        val book = currentBook ?: return
        val selection = currentSelection ?: return
        lifecycleScope.launch {
            quotes = withContext(Dispatchers.IO) {
                app.bookRepository.addNote(
                    bookId = book.id,
                    cfi = selection.locator,
                    progress = selection.progress,
                    currentPage = selection.currentPage,
                    totalPages = selection.totalPages,
                    chapter = selection.chapter,
                    selectedText = selection.text,
                    noteText = noteText,
                    highlightColor = pendingHighlightColor,
                    kind = kind
                )
                app.bookRepository.getNotes(book.id)
            }
            selection.locator?.takeIf(String::isNotBlank)?.let { locator ->
                pendingHighlightColor?.let { color -> webController.addHighlight(locator, color) }
            }
            binding.noteEditorOverlay.isVisible = false
            binding.readerSelectionOverlay.isVisible = false
            currentSelection = null
            webController.clearSelection()
            webController.setHighlights(quotes)
            renderCollections()
            applySystemUi()
        }
    }

    private fun deleteQuote(note: ReadingNote) {
        val bookId = currentBook?.id ?: return
        lifecycleScope.launch {
            quotes = withContext(Dispatchers.IO) {
                app.bookRepository.deleteNote(note)
                app.bookRepository.getNotes(bookId)
            }
            note.cfi?.takeIf(String::isNotBlank)?.let(webController::removeHighlight)
            webController.setHighlights(quotes)
            renderCollections()
        }
    }

    private fun closeSelection() {
        binding.readerSelectionOverlay.isVisible = false
        currentSelection = null
        webController.clearSelection()
        renderChromeVisibility()
    }

    private fun updatePreferences(transform: ReaderPreferences.() -> ReaderPreferences) {
        if (finishingReader) return
        val previousPreferences = preferences
        val previousOrientation = preferences.orientation
        val nextPreferences = preferences.transform()
        if (nextPreferences == previousPreferences) return
        val enginePreferencesChanged = !nextPreferences.hasSameEnginePreferences(previousPreferences)
        preferences = nextPreferences
        if (enginePreferencesChanged) currentBook?.let { book ->
            paginationCacheKey = paginationCache.key(
                file = File(book.filePath),
                preferences = preferences,
                viewportWidth = resources.displayMetrics.widthPixels,
                viewportHeight = resources.displayMetrics.heightPixels
            )
        }
        renderSettings()
        applyNativePreferences()
        if (bookReady && enginePreferencesChanged) {
            webController.setPreferences(preferences)
        }
        if (preferences.orientation != previousOrientation || orientationJob?.isActive == true) {
            persistStateThenApplyOrientation()
        } else {
            scheduleSettingsSave(immediate = false)
        }
    }

    private fun renderSettings() {
        updatingSettingsViews = true
        binding.fontSizeValue.text = preferences.fontSize.toString()
        binding.fullFontSizeValue.text = preferences.fontSize.toString()
        binding.fontFamilyDropdown.setText(preferences.fontName, false)
        binding.fullFontFamilyDropdown.setText(preferences.fontName, false)
        binding.brightnessSlider.value = preferences.brightness.toFloat()
        binding.fullBrightnessSlider.value = preferences.brightness.toFloat()
        binding.brightnessSlider.isEnabled = !preferences.systemBrightness
        binding.fullBrightnessSlider.isEnabled = !preferences.systemBrightness
        binding.systemBrightnessSwitch.isChecked = preferences.systemBrightness
        binding.fullSystemBrightnessSwitch.isChecked = preferences.systemBrightness
        binding.verticalScrollSwitch.isChecked = preferences.verticalScroll
        binding.fullVerticalScrollSwitch.isChecked = preferences.verticalScroll
        binding.orientationDropdown.setText(
            when (preferences.orientation) {
                ReaderPreferences.ORIENTATION_PORTRAIT -> "Портретная"
                ReaderPreferences.ORIENTATION_LANDSCAPE -> "Альбомная"
                else -> "Автоматически"
            },
            false
        )
        binding.lineHeightValue.text = String.format(java.util.Locale.getDefault(), "%.1f", preferences.lineHeight)
        binding.marginValue.text = "${preferences.margin}"
        binding.volumeButtonsSwitch.isChecked = preferences.volumeButtons
        binding.invertZonesSwitch.isChecked = preferences.invertZones
        binding.brightnessGestureSwitch.isChecked = preferences.brightnessGesture
        binding.keepScreenOnSwitch.isChecked = preferences.keepScreenOn
        binding.showTitleSwitch.isChecked = preferences.showTitle
        binding.showStatusSwitch.isChecked = preferences.showStatus
        binding.themeLightButton.isSelected = preferences.theme == ReaderPreferences.THEME_LIGHT
        binding.themeSepiaButton.isSelected = preferences.theme == ReaderPreferences.THEME_SEPIA
        binding.themeDarkButton.isSelected = preferences.theme == ReaderPreferences.THEME_DARK
        binding.fullThemeLightButton.isSelected = preferences.theme == ReaderPreferences.THEME_LIGHT
        binding.fullThemeSepiaButton.isSelected = preferences.theme == ReaderPreferences.THEME_SEPIA
        binding.fullThemeDarkButton.isSelected = preferences.theme == ReaderPreferences.THEME_DARK
        binding.alignJustifyButton.isSelected = preferences.textAlign == ReaderPreferences.ALIGN_JUSTIFY
        binding.alignLeftButton.isSelected = preferences.textAlign == ReaderPreferences.ALIGN_LEFT
        binding.pageTurnTapSwipeButton.isSelected = preferences.pageTurnMode == ReaderPreferences.PAGE_TURN_TAP_SWIPE
        binding.pageTurnSwipeButton.isSelected = preferences.pageTurnMode == ReaderPreferences.PAGE_TURN_SWIPE
        binding.pageTurnTapButton.isSelected = preferences.pageTurnMode == ReaderPreferences.PAGE_TURN_TAP
        updatingSettingsViews = false
    }

    private fun applyNativePreferences() {
        val palette = readerPalette()

        binding.root.setBackgroundColor(palette.canvas)
        binding.readerWebView.setBackgroundColor(palette.canvas)
        binding.readerLoadingOverlay.setBackgroundColor(palette.canvas)
        binding.pageHeader.setBackgroundColor(palette.canvas)
        binding.readerTopControlsPanel.setBackgroundColor(palette.chrome)
        binding.readerBottomControlsPanel.setBackgroundColor(palette.chrome)
        binding.settingsPanel.setBackgroundColor(palette.chrome)
        binding.selectionPanel.setCardBackgroundColor(palette.chrome)
        binding.readerContentsOverlay.setBackgroundColor(palette.chrome)
        binding.contentsTopBar.setBackgroundColor(palette.chrome)
        binding.contentsBookTitle.setBackgroundColor(palette.chrome)
        binding.tocRecycler.setBackgroundColor(palette.canvas)
        binding.bookmarksRecycler.setBackgroundColor(palette.chrome)
        binding.quotesRecycler.setBackgroundColor(palette.chrome)
        binding.readerStatusBarBackground.setBackgroundColor(palette.chrome)
        listOf(
            binding.readerNavigationBarBottomBackground,
            binding.readerNavigationBarLeftBackground,
            binding.readerNavigationBarRightBackground
        ).forEach { it.setBackgroundColor(palette.navigation) }

        applySettingsPalette(binding.settingsPanel, palette)
        listOf(
            binding.fontFamilyDropdown,
            binding.fullFontFamilyDropdown,
            binding.orientationDropdown
        ).forEach { it.setTextColor(palette.settingsSecondary) }

        binding.pageHeaderTitle.setTextColor(palette.bottomText)
        binding.pageHeaderRemaining.setTextColor(palette.bottomText)
        binding.readerBottomTitle.setTextColor(palette.bottomText)
        binding.readerBottomChapter.setTextColor(palette.bottomText)
        binding.readerPageCount.setTextColor(palette.bottomText)
        binding.readerChapterRemaining.setTextColor(palette.bottomText)
        binding.readerLoadingText.setTextColor(palette.bottomText)
        binding.readerErrorText.setTextColor(palette.bottomText)
        binding.settingsTitle.setTextColor(palette.settingsText)
        binding.selectionTitle.setTextColor(palette.settingsText)
        binding.contentsBookTitle.setTextColor(palette.bottomText)
        binding.tocEmptyText.setTextColor(palette.bottomText)
        binding.bookmarksEmptyText.setTextColor(palette.bottomText)
        binding.quotesEmptyText.setTextColor(palette.bottomText)

        binding.readerBackButton.imageTintList = ColorStateList.valueOf(palette.backIcon)
        listOf(
            binding.readerVoiceButton,
            binding.readerSearchButton,
            binding.readerSettingsButton,
            binding.readerContentsButton,
            binding.readerBookmarkButton
        ).forEach { it.imageTintList = ColorStateList.valueOf(palette.chromeIcon) }
        listOf(
            binding.settingsBackButton,
            binding.settingsCloseButton,
            binding.contentsCloseButton,
            binding.selectionCloseButton
        ).forEach { it.imageTintList = ColorStateList.valueOf(palette.chromeIcon) }
        listOf(
            binding.selectionShareButton,
            binding.selectionCopyButton,
            binding.selectionNoteButton
        ).forEach {
            it.iconTint = ColorStateList.valueOf(palette.chromeIcon)
            it.setTextColor(palette.settingsText)
        }

        binding.themeLightButton.setTextColor(Color.BLACK)
        binding.fullThemeLightButton.setTextColor(Color.BLACK)
        binding.themeSepiaButton.setTextColor(Color.parseColor(SEPIA_TEXT))
        binding.fullThemeSepiaButton.setTextColor(Color.parseColor(SEPIA_TEXT))
        binding.themeDarkButton.setTextColor(Color.WHITE)
        binding.fullThemeDarkButton.setTextColor(Color.WHITE)
        binding.allSettingsButton.backgroundTintList = ColorStateList.valueOf(palette.chrome)
        binding.allSettingsButton.setTextColor(Color.parseColor(ACCENT_COLOR))

        listOf(binding.brightnessSlider, binding.fullBrightnessSlider).forEach { slider ->
            slider.thumbTintList = ColorStateList.valueOf(palette.controlIcon)
            slider.trackActiveTintList = ColorStateList.valueOf(palette.controlIcon)
            slider.trackInactiveTintList = ColorStateList.valueOf(palette.settingsSecondary)
        }
        binding.readerProgressSlider.thumbTintList = ColorStateList.valueOf(palette.bottomText)
        binding.readerProgressSlider.trackActiveTintList = ColorStateList.valueOf(palette.bottomText)
        binding.readerProgressSlider.trackInactiveTintList = ColorStateList.valueOf(palette.bottomText)

        if (preferences.systemBrightness) {
            window.attributes = window.attributes.apply {
                screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
            }
        } else {
            window.attributes = window.attributes.apply {
                screenBrightness = (preferences.brightness / 100f).coerceIn(0.1f, 1f)
            }
        }
        if (preferences.keepScreenOn) {
            window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        } else {
            window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        }
        showContentsTab(contentsTab)
        syncBookmarkButton()
        renderChromeVisibility()
    }

    private fun applySettingsPalette(view: View, palette: ReaderPalette) {
        val paletteTag = view.tag as? String
        when (paletteTag) {
            PALETTE_TAG_ROW -> {
                if (view is MaterialButton) {
                    view.backgroundTintList = ColorStateList.valueOf(palette.chrome)
                } else {
                    view.background = rowBackground(palette.chrome, palette.separator)
                }
            }
            PALETTE_TAG_SEPARATOR -> view.setBackgroundColor(palette.separator)
            PALETTE_TAG_CONTROL -> view.background = roundedBackground(palette.control, 4f)
            PALETTE_TAG_OPTION -> view.background = selectableOptionBackground(palette)
            PALETTE_TAG_MODE -> view.background = selectableModeBackground(palette)
        }

        when (view) {
            is MaterialButton -> if (paletteTag == PALETTE_TAG_MODE) {
                view.setTextColor(palette.settingsText)
            }
            is TextView -> view.setTextColor(palette.settingsText)
        }
        if (view is ImageView && (paletteTag == PALETTE_TAG_CONTROL || paletteTag == PALETTE_TAG_OPTION)) {
            view.imageTintList = ColorStateList.valueOf(palette.controlIcon)
        }
        if (view is ViewGroup) view.children.forEach { child -> applySettingsPalette(child, palette) }
    }

    private fun rowBackground(background: Int, separator: Int): LayerDrawable {
        return LayerDrawable(arrayOf(ColorDrawable(background), ColorDrawable(separator))).apply {
            setLayerGravity(1, Gravity.BOTTOM)
            setLayerHeight(1, dp(1f))
        }
    }

    private fun roundedBackground(color: Int, radiusDp: Float, strokeColor: Int? = null): GradientDrawable {
        return GradientDrawable().apply {
            shape = GradientDrawable.RECTANGLE
            setColor(color)
            cornerRadius = resources.displayMetrics.density * radiusDp
            strokeColor?.let { setStroke(dp(2f), it) }
        }
    }

    private fun selectableOptionBackground(palette: ReaderPalette): StateListDrawable {
        return StateListDrawable().apply {
            addState(
                intArrayOf(android.R.attr.state_selected),
                roundedBackground(palette.control, 2f, Color.parseColor(ACCENT_COLOR))
            )
            addState(IntArray(0), roundedBackground(palette.control, 4f))
        }
    }

    private fun selectableModeBackground(palette: ReaderPalette): StateListDrawable {
        return StateListDrawable().apply {
            addState(
                intArrayOf(android.R.attr.state_selected),
                roundedBackground(palette.chrome, 0f, Color.parseColor(ACCENT_COLOR))
            )
            addState(IntArray(0), roundedBackground(palette.chrome, 0f))
        }
    }

    private fun dp(value: Float): Int = (resources.displayMetrics.density * value).roundToInt().coerceAtLeast(1)

    private fun readerPalette(): ReaderPalette = when (preferences.theme) {
        ReaderPreferences.THEME_SEPIA -> ReaderPalette(
            canvas = Color.parseColor(SEPIA_CANVAS),
            chrome = Color.parseColor(SEPIA_CHROME),
            settingsText = Color.parseColor(SEPIA_TEXT),
            settingsSecondary = Color.parseColor(SEPIA_SECONDARY),
            chromeIcon = Color.parseColor(SEPIA_CHROME_ICON),
            backIcon = Color.parseColor(SEPIA_CHROME_ICON),
            bottomText = Color.parseColor(SEPIA_BOTTOM_TEXT),
            separator = Color.parseColor(SEPIA_SEPARATOR),
            control = Color.parseColor(SEPIA_CONTROL),
            controlIcon = Color.parseColor(SEPIA_CONTROL_ICON),
            settingsActive = Color.parseColor(SEPIA_SETTINGS_ACTIVE),
            navigation = Color.parseColor(LIGHT_NAVIGATION)
        )
        ReaderPreferences.THEME_DARK -> ReaderPalette(
            canvas = Color.parseColor(DARK_CANVAS),
            chrome = Color.parseColor(DARK_CHROME),
            settingsText = Color.parseColor(DARK_SETTINGS_TEXT),
            settingsSecondary = Color.parseColor(DARK_SETTINGS_SECONDARY),
            chromeIcon = Color.parseColor(DARK_CHROME_ICON),
            backIcon = Color.parseColor(DARK_CHROME_ICON),
            bottomText = Color.parseColor(DARK_BOTTOM_TEXT),
            separator = Color.parseColor(DARK_SEPARATOR),
            control = Color.parseColor(DARK_CONTROL),
            controlIcon = Color.parseColor(DARK_CONTROL_ICON),
            settingsActive = Color.parseColor(DARK_SETTINGS_ACTIVE),
            navigation = Color.parseColor(DARK_NAVIGATION)
        )
        else -> ReaderPalette(
            canvas = Color.WHITE,
            chrome = Color.parseColor(CHROME_COLOR),
            settingsText = Color.BLACK,
            settingsSecondary = Color.parseColor(LIGHT_SETTINGS_SECONDARY),
            chromeIcon = Color.parseColor(PRIMARY_GRAY),
            backIcon = Color.parseColor(SECONDARY_GRAY),
            bottomText = Color.parseColor(PRIMARY_GRAY),
            separator = Color.parseColor(LIGHT_SEPARATOR),
            control = Color.parseColor(LIGHT_CONTROL),
            controlIcon = Color.parseColor(LIGHT_CONTROL_ICON),
            settingsActive = Color.parseColor(LIGHT_SETTINGS_ACTIVE),
            navigation = Color.parseColor(LIGHT_NAVIGATION)
        )
    }

    private fun applyRequestedOrientation(snapshot: ReaderPreferences = preferences) {
        val orientation = when (snapshot.orientation) {
            ReaderPreferences.ORIENTATION_PORTRAIT -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
            ReaderPreferences.ORIENTATION_LANDSCAPE -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
            else -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
        }
        if (requestedOrientation != orientation) requestedOrientation = orientation
    }

    private fun applySystemUi() {
        val controller = WindowCompat.getInsetsController(window, binding.root)
        controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
        val palette = readerPalette()
        val modalVisible = binding.readerControlsOverlay.isVisible ||
            binding.readerSettingsOverlay.isVisible ||
            binding.readerContentsOverlay.isVisible ||
            binding.readerSelectionOverlay.isVisible ||
            binding.noteEditorOverlay.isVisible
        binding.readerSystemBarBackgrounds.isVisible = modalVisible
        val contentsVisible = binding.readerContentsOverlay.isVisible
        val statusBackground = if (contentsVisible) Color.WHITE else palette.chrome
        val navigationBackground = if (contentsVisible) Color.WHITE else palette.navigation
        binding.readerStatusBarBackground.setBackgroundColor(statusBackground)
        listOf(
            binding.readerNavigationBarBottomBackground,
            binding.readerNavigationBarLeftBackground,
            binding.readerNavigationBarRightBackground
        ).forEach { it.setBackgroundColor(navigationBackground) }
        controller.isAppearanceLightStatusBars = modalVisible || preferences.theme != ReaderPreferences.THEME_DARK
        controller.isAppearanceLightNavigationBars = true
        binding.readerSettingsButton.imageTintList = ColorStateList.valueOf(
            if (binding.readerSettingsOverlay.isVisible) palette.settingsActive else palette.chromeIcon
        )
        when {
            modalVisible -> controller.show(WindowInsetsCompat.Type.systemBars())
            preferences.showStatus -> {
                controller.show(WindowInsetsCompat.Type.statusBars())
                controller.hide(WindowInsetsCompat.Type.navigationBars())
            }
            else -> controller.hide(WindowInsetsCompat.Type.systemBars())
        }
    }

    private fun scheduleProgressSave(immediate: Boolean) {
        val book = currentBook ?: return
        if (!bookReady) return
        progressSaveJob?.cancel()
        val snapshot = position
        progressSaveJob = app.applicationScope.launch {
            try {
                if (!immediate) delay(PROGRESS_SAVE_DEBOUNCE_MS)
                persistPosition(snapshot, book)
            } catch (error: CancellationException) {
                throw error
            } catch (error: Exception) {
                Log.e(TAG, "Не удалось сохранить позицию чтения", error)
            }
        }
    }

    private fun scheduleSettingsSave(immediate: Boolean) {
        settingsSaveJob?.cancel()
        val snapshot = preferences
        settingsSaveJob = app.applicationScope.launch {
            try {
                if (!immediate) delay(SETTINGS_SAVE_DEBOUNCE_MS)
                persistPreferences(snapshot)
            } catch (error: CancellationException) {
                throw error
            } catch (error: Exception) {
                Log.e(TAG, "Не удалось сохранить настройки читалки", error)
            }
        }
    }

    private suspend fun persistPreferences(snapshot: ReaderPreferences) {
        withContext(Dispatchers.IO) {
            app.readerPersistenceMutex.withLock {
                snapshot.persist(app.settingsRepository)
            }
        }
    }

    private suspend fun persistPosition(snapshot: ReaderPosition, book: Book) {
        withContext(Dispatchers.IO) {
            app.readerPersistenceMutex.withLock {
                val effectiveSnapshot = app.readerStateCoordinator.latest(book.id)?.toReaderPosition()
                    ?: snapshot
                val signature = listOf(
                    book.id,
                    effectiveSnapshot.progress,
                    effectiveSnapshot.locator,
                    effectiveSnapshot.chapter,
                    effectiveSnapshot.currentPage,
                    effectiveSnapshot.totalPages
                ).joinToString("|")
                if (signature == app.readerStateCoordinator.lastPersistedSignature(book.id)) {
                    return@withLock
                }
                app.bookRepository.saveProgress(
                    bookId = book.id,
                    progress = effectiveSnapshot.progress,
                    cfi = effectiveSnapshot.locator,
                    chapter = effectiveSnapshot.chapter,
                    currentPage = effectiveSnapshot.currentPage,
                    totalPages = effectiveSnapshot.totalPages
                )
                app.readerStateCoordinator.markPersisted(book.id, signature)
            }
        }
    }

    private fun persistStateThenApplyOrientation() {
        val oldSettingsJob = settingsSaveJob
        val oldProgressJob = progressSaveJob
        val oldOrientationJob = orientationJob
        val preferenceSnapshot = preferences
        val positionSnapshot = position
        val bookSnapshot = currentBook
        orientationJob = lifecycleScope.launch {
            var applyOrientation = true
            try {
                oldOrientationJob?.cancelAndJoin()
                oldSettingsJob?.cancelAndJoin()
                oldProgressJob?.cancelAndJoin()
                persistPreferences(preferenceSnapshot)
                if (bookSnapshot != null) {
                    persistPosition(positionSnapshot, bookSnapshot)
                }
            } catch (error: CancellationException) {
                applyOrientation = false
                throw error
            } catch (error: Exception) {
                Log.e(TAG, "Не удалось сохранить состояние перед поворотом", error)
                Toast.makeText(
                    this@ReaderActivity,
                    "Настройки применены, но сохранить их не удалось",
                    Toast.LENGTH_SHORT
                ).show()
            } finally {
                if (applyOrientation && preferences.orientation == preferenceSnapshot.orientation) {
                    applyRequestedOrientation(preferenceSnapshot)
                }
            }
        }
    }

    private fun retryReader() {
        val book = currentBook ?: return
        if (retryingReader) return
        retryingReader = true
        val pendingOrientationJob = orientationJob?.takeIf { it.isActive }
        pendingOrientationJob?.cancel()
        showLoading("Повторяю загрузку…")
        lifecycleScope.launch {
            try {
                pendingOrientationJob?.cancelAndJoin()
                progressSaveJob?.cancelAndJoin()
                settingsSaveJob?.cancelAndJoin()
                try {
                    persistPreferences(preferences)
                    persistPosition(position, book)
                } catch (error: CancellationException) {
                    throw error
                } catch (error: Exception) {
                    Log.e(TAG, "Не удалось сохранить состояние перед повторной загрузкой", error)
                    Toast.makeText(
                        this@ReaderActivity,
                        "Не удалось сохранить текущую позицию",
                        Toast.LENGTH_SHORT
                    ).show()
                }
                webController.updatePendingPosition(position.locator, position.progress, preferences)
                bookReady = false
                val previousRequestedOrientation = requestedOrientation
                applyRequestedOrientation(preferences)
                if (requestedOrientation == previousRequestedOrientation && !webController.reload()) recreate()
            } catch (error: CancellationException) {
                throw error
            } catch (error: Exception) {
                Log.e(TAG, "Повторная загрузка читалки завершилась ошибкой", error)
                showError("Не удалось повторно открыть книгу", retryAllowed = true)
            } finally {
                retryingReader = false
            }
        }
    }

    private fun finishReader() {
        if (finishingReader) return
        finishingReader = true
        bookReady = false
        val bookSnapshot = currentBook
        val positionSnapshot = position
        val preferenceSnapshot = preferences
        val progressJobSnapshot = progressSaveJob
        val settingsJobSnapshot = settingsSaveJob
        val orientationJobSnapshot = orientationJob
        orientationJobSnapshot?.cancel()
        finishJob = app.applicationScope.launch {
            var saveError: Exception? = null
            try {
                orientationJobSnapshot?.cancelAndJoin()
                progressJobSnapshot?.cancelAndJoin()
                settingsJobSnapshot?.cancelAndJoin()
                persistPreferences(preferenceSnapshot)
                if (bookSnapshot != null) persistPosition(positionSnapshot, bookSnapshot)
            } catch (error: CancellationException) {
                throw error
            } catch (error: Exception) {
                saveError = error
                Log.e(TAG, "Не удалось сохранить состояние при закрытии читалки", error)
            } finally {
                withContext(NonCancellable + Dispatchers.Main.immediate) {
                    if (saveError != null && !isDestroyed) {
                        Toast.makeText(
                            this@ReaderActivity,
                            "Не удалось сохранить текущую позицию",
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                    if (!isDestroyed) finish()
                }
            }
        }
    }

    private fun handleBack() {
        finishReader()
    }

    private fun openExternalLink(url: String) {
        val uri = runCatching { Uri.parse(url) }.getOrNull() ?: return
        val intent = when (uri.scheme?.lowercase()) {
            "http", "https" -> Intent(Intent.ACTION_VIEW, uri)
            "mailto" -> Intent(Intent.ACTION_SENDTO, uri)
            else -> return
        }
        runCatching { startActivity(intent) }
            .onFailure { Toast.makeText(this, "Не удалось открыть ссылку", Toast.LENGTH_SHORT).show() }
    }

    private fun showLoading(message: String) {
        binding.readerLoadingOverlay.isVisible = true
        binding.readerLoadingText.isVisible = true
        binding.readerLoadingText.text = message
        binding.readerErrorText.isVisible = false
        binding.readerRetryButton.isVisible = false
    }

    private fun hideLoading() {
        binding.readerLoadingOverlay.isVisible = false
    }

    private fun showError(message: String, retryAllowed: Boolean = true) {
        binding.readerLoadingOverlay.isVisible = true
        binding.readerLoadingText.isVisible = false
        binding.readerErrorText.isVisible = true
        binding.readerErrorText.text = message
        binding.readerRetryButton.isVisible = retryAllowed
    }

    private fun View.paddingInsets() = Insets(paddingLeft, paddingTop, paddingRight, paddingBottom)

    private fun Bundle.restoreReaderPosition(): ReaderPosition? {
        val requestedBookId = intent.getLongExtra(EXTRA_BOOK_ID, 0L)
        if (getLong(STATE_BOOK_ID, 0L) != requestedBookId || !containsKey(STATE_PROGRESS)) return null
        val restoredProgress = getDouble(STATE_PROGRESS, 0.0)
        return ReaderPosition(
            progress = if (restoredProgress.isFinite()) restoredProgress.coerceIn(0.0, 1.0) else 0.0,
            locator = getString(STATE_LOCATOR),
            chapter = getString(STATE_CHAPTER),
            currentPage = getInt(STATE_CURRENT_PAGE, 1).coerceAtLeast(1),
            totalPages = getInt(STATE_TOTAL_PAGES, 1).coerceAtLeast(1),
            chapterCurrentPage = getInt(STATE_CHAPTER_CURRENT_PAGE, 1).coerceAtLeast(1),
            chapterTotalPages = getInt(STATE_CHAPTER_TOTAL_PAGES, 1).coerceAtLeast(1)
        )
    }

    private fun publishPosition() {
        val bookId = currentBook?.id ?: return
        app.readerStateCoordinator.publish(
            bookId = bookId,
            progress = position.progress,
            locator = position.locator,
            chapter = position.chapter,
            currentPage = position.currentPage,
            totalPages = position.totalPages,
            chapterCurrentPage = position.chapterCurrentPage,
            chapterTotalPages = position.chapterTotalPages
        )
    }

    private fun ReaderSessionPosition.toReaderPosition() = ReaderPosition(
        progress = progress,
        locator = locator,
        chapter = chapter,
        currentPage = currentPage,
        totalPages = totalPages,
        chapterCurrentPage = chapterCurrentPage,
        chapterTotalPages = chapterTotalPages
    )

    private fun View.applyInsets(base: Insets, left: Int, top: Int, right: Int, bottom: Int) {
        updatePadding(
            left = base.left + left,
            top = base.top + top,
            right = base.right + right,
            bottom = base.bottom + bottom
        )
    }

    private fun updateReaderViewportMargins() {
        val headerHeight = if (preferences.showTitle) dp(25) else 0
        val contentTop = safeInsets.top + headerHeight
        (binding.pageHeader.layoutParams as? android.widget.FrameLayout.LayoutParams)?.let { params ->
            if (params.topMargin != safeInsets.top) {
                params.topMargin = safeInsets.top
                binding.pageHeader.layoutParams = params
            }
        }
        (binding.readerWebView.layoutParams as? android.widget.FrameLayout.LayoutParams)?.let { params ->
            if (
                params.topMargin != contentTop ||
                params.leftMargin != safeInsets.left ||
                params.rightMargin != safeInsets.right
            ) {
                params.topMargin = contentTop
                params.leftMargin = safeInsets.left
                params.rightMargin = safeInsets.right
                binding.readerWebView.layoutParams = params
            }
        }
        (binding.readerLoadingOverlay.layoutParams as? android.widget.FrameLayout.LayoutParams)?.let { params ->
            if (
                params.topMargin != contentTop ||
                params.leftMargin != safeInsets.left ||
                params.rightMargin != safeInsets.right
            ) {
                params.topMargin = contentTop
                params.leftMargin = safeInsets.left
                params.rightMargin = safeInsets.right
                binding.readerLoadingOverlay.layoutParams = params
            }
        }
        (binding.readerPersistentProgress.layoutParams as? android.widget.FrameLayout.LayoutParams)?.let { params ->
            if (params.leftMargin != safeInsets.left || params.rightMargin != safeInsets.right) {
                params.leftMargin = safeInsets.left
                params.rightMargin = safeInsets.right
                binding.readerPersistentProgress.layoutParams = params
            }
        }
    }

    private fun dp(value: Int): Int = (value * resources.displayMetrics.density).roundToInt()

    private fun com.google.android.material.materialswitch.MaterialSwitch.setPreferenceListener(
        transform: ReaderPreferences.(Boolean) -> ReaderPreferences
    ) {
        setOnCheckedChangeListener { _, checked ->
            if (!updatingSettingsViews) updatePreferences { transform(checked) }
        }
    }

    private data class ReaderPosition(
        val progress: Double = 0.0,
        val locator: String? = null,
        val chapter: String? = null,
        val currentPage: Int = 1,
        val totalPages: Int = 1,
        val chapterCurrentPage: Int = 1,
        val chapterTotalPages: Int = 1
    )

    private data class Insets(
        val left: Int = 0,
        val top: Int = 0,
        val right: Int = 0,
        val bottom: Int = 0
    )

    private data class ReaderPalette(
        val canvas: Int,
        val chrome: Int,
        val settingsText: Int,
        val settingsSecondary: Int,
        val chromeIcon: Int,
        val backIcon: Int,
        val bottomText: Int,
        val separator: Int,
        val control: Int,
        val controlIcon: Int,
        val settingsActive: Int,
        val navigation: Int
    )

    private enum class ContentsTab { TOC, BOOKMARKS, QUOTES }

    companion object {
        const val EXTRA_BOOK_ID = "book_id"
        private const val TAG = "ReaderActivity"
        private const val STATE_PREFERENCES = "reader.preferences"
        private const val STATE_FINISHING = "reader.finishing"
        private const val STATE_BOOK_ID = "reader.book_id"
        private const val STATE_PROGRESS = "reader.progress"
        private const val STATE_LOCATOR = "reader.locator"
        private const val STATE_CHAPTER = "reader.chapter"
        private const val STATE_CURRENT_PAGE = "reader.current_page"
        private const val STATE_TOTAL_PAGES = "reader.total_pages"
        private const val STATE_CHAPTER_CURRENT_PAGE = "reader.chapter_current_page"
        private const val STATE_CHAPTER_TOTAL_PAGES = "reader.chapter_total_pages"
        private const val PROGRESS_SAVE_DEBOUNCE_MS = 1_500L
        private const val SETTINGS_SAVE_DEBOUNCE_MS = 180L
        private const val NATIVE_TAP_MAX_DURATION_MS = 350L
        private const val READER_CENTER_ZONE_START = 1f / 3f
        private const val READER_CENTER_ZONE_END = 2f / 3f
        private val FATAL_ERROR_STAGES = setOf(
            "book.load",
            "api.loadBook",
            "shell.load",
            "renderer"
        )
        private const val BOOKMARK_PROGRESS_TOLERANCE = 0.0015
        private const val CHROME_COLOR = "#F4F4F5"
        private const val ACCENT_COLOR = "#EC622B"
        private const val PRIMARY_GRAY = "#626262"
        private const val SECONDARY_GRAY = "#9D9C9F"
        private const val SECONDARY_ON_LIGHT = "#767579"
        private const val LIGHT_SETTINGS_SECONDARY = "#767579"
        private const val LIGHT_SEPARATOR = "#DDDDDD"
        private const val LIGHT_CONTROL = "#DCDCDC"
        private const val LIGHT_CONTROL_ICON = "#666668"
        private const val LIGHT_SETTINGS_ACTIVE = "#EC9C6E"
        private const val LIGHT_NAVIGATION = "#FEFEFE"
        private const val SEPIA_CANVAS = "#F6F3E0"
        private const val SEPIA_CHROME = "#F0E8DA"
        private const val SEPIA_TEXT = "#503922"
        private const val SEPIA_SECONDARY = "#776A57"
        private const val SEPIA_CHROME_ICON = "#92826E"
        private const val SEPIA_BOTTOM_TEXT = "#8F806B"
        private const val SEPIA_SEPARATOR = "#E7DFD0"
        private const val SEPIA_CONTROL = "#DFD5C7"
        private const val SEPIA_CONTROL_ICON = "#776A57"
        private const val SEPIA_SETTINGS_ACTIVE = "#EB9764"
        private const val DARK_CANVAS = "#000000"
        private const val DARK_CHROME = "#1A1A1A"
        private const val DARK_SETTINGS_TEXT = "#9D9C9F"
        private const val DARK_SETTINGS_SECONDARY = "#828183"
        private const val DARK_CHROME_ICON = "#676668"
        private const val DARK_BOTTOM_TEXT = "#666666"
        private const val DARK_SEPARATOR = "#242325"
        private const val DARK_CONTROL = "#262626"
        private const val DARK_CONTROL_ICON = "#A0A0A0"
        private const val DARK_SETTINGS_ACTIVE = "#94431D"
        private const val DARK_NAVIGATION = "#E9E9E9"
        private const val PALETTE_TAG_ROW = "reader_v2_row"
        private const val PALETTE_TAG_SEPARATOR = "reader_v2_separator"
        private const val PALETTE_TAG_CONTROL = "reader_v2_control"
        private const val PALETTE_TAG_OPTION = "reader_v2_option"
        private const val PALETTE_TAG_MODE = "reader_v2_mode"
        private const val COLOR_YELLOW = "#F8D8A1"
        private const val COLOR_GREEN = "#AAD2A4"
        private const val COLOR_BLUE = "#A4CCFB"
        private const val COLOR_PURPLE = "#C99BF9"
        private const val COLOR_PINK = "#EC9EAC"
    }
}
