/*
 * Episteme Reader - A native Android document reader.
 * Copyright (C) 2026 Episteme
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * mail: epistemereader@gmail.com
 */
package com.aryan.reader.paginatedreader

import android.content.Context
import android.os.Build
import timber.log.Timber
import androidx.annotation.RequiresApi
import androidx.collection.LruCache
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextMeasurer
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import com.aryan.reader.SearchResult
import com.aryan.reader.epub.EpubChapter
import com.aryan.reader.paginatedreader.data.BookCacheDao
import com.aryan.reader.paginatedreader.data.BookProcessingInput
import com.aryan.reader.paginatedreader.data.BookProcessingWorker
import com.aryan.reader.paginatedreader.data.ConfigurationCache
import com.aryan.reader.paginatedreader.data.LATEST_PAGE_CACHE_VERSION
import com.aryan.reader.paginatedreader.data.LATEST_PROCESSING_VERSION
import com.aryan.reader.paginatedreader.data.PageCacheEntry
import com.aryan.reader.paginatedreader.data.PageIndexEntry
import com.aryan.reader.paginatedreader.data.ProcessedBook
import com.aryan.reader.paginatedreader.data.ProcessedChapter
import com.aryan.reader.paginatedreader.data.SerializableEpubChapter
import com.aryan.reader.tts.PageCharacterRange
import com.aryan.reader.tts.splitTextIntoChunks
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import java.net.URI
import java.net.URLDecoder
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.PriorityBlockingQueue

private const val PRIORITY_HIGHEST = 0
private const val PRIORITY_HIGH = 1
private const val PRIORITY_MEDIUM = 2
private const val PRIORITY_LOW = 3
private const val TAG_STABLE_PAGE_NAV = "StablePageNav"

data class TimedWord(val word: String, val startTime: Double, val startOffset: Int)

data class TtsChunk(
    val text: String,
    val sourceCfi: String,
    val startOffsetInSource: Int,
    val timedWords: List<TimedWord> = emptyList(),
    val spokenText: String = text
)

private data class PaginationRequest(val chapterIndex: Int, val priority: Int) : Comparable<PaginationRequest> {
    override fun compareTo(other: PaginationRequest): Int {
        val priorityCompare = this.priority.compareTo(other.priority)
        return if (priorityCompare == 0) {
            this.chapterIndex.compareTo(other.chapterIndex)
        } else {
            priorityCompare
        }
    }
}

private const val PAGE_INDEX_ANCHOR_SEPARATOR = "\u001F"

private data class TextRangeIndex(
    val pageInChapter: Int,
    val blockIndex: Int,
    val startOffset: Int,
    val endOffset: Int
)

private data class PageNavigationEntry(
    val pageInChapter: Int,
    val firstBlockIndex: Int,
    val lastBlockIndex: Int,
    val firstTextBlockIndex: Int?,
    val firstTextCharOffset: Int,
    val firstTextEndOffset: Int,
    val firstCfi: String?,
    val anchors: Set<String>
)

@OptIn(ExperimentalSerializationApi::class)
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@Stable
class BookPaginator(
    private val coroutineScope: CoroutineScope,
    private val chapters: List<EpubChapter>,
    private val textMeasurer: TextMeasurer,
    private val constraints: Constraints,
    private val textStyle: TextStyle,
    private val extractionBasePath: String,
    private val density: Density,
    private val fontFamilyMap: Map<String, FontFamily>,
    private val isDarkTheme: Boolean,
    private val themeBackgroundColor: Color,
    private val themeTextColor: Color,
    private val bookId: String,
    private val initialChapterToPaginate: Int,
    private val bookCss: Map<String, String>,
    private val userAgentStylesheet: String,
    private val bookCacheDao: BookCacheDao,
    private val proto: ProtoBuf,
    private val allFontFaces: List<FontFaceInfo>,
    private val context: Context,
    private val mathMLRenderer: MathMLRenderer,
    private val userTextAlign: TextAlign?,
    private val paragraphGapMultiplier: Float,
    private val imageSizeMultiplier: Float,
    private val verticalMarginMultiplier: Float
) : IPaginator {
    override var totalPageCount by mutableIntStateOf(0)
        private set

    override var isLoading by mutableStateOf(true)
        private set

    override var generation by mutableIntStateOf(0)
        private set

    override val pageShiftRequest = MutableSharedFlow<Int>(extraBufferCapacity = 100, onBufferOverflow = BufferOverflow.DROP_OLDEST)
    private val currentUserChapterIndex = MutableStateFlow(initialChapterToPaginate)

    internal val chapterPageCounts = ConcurrentHashMap<Int, Int>()
    val chapterStartPageIndices = ConcurrentHashMap<Int, Int>()

    private val pageCache = object : LruCache<Int, List<Page>>(12) {
        override fun entryRemoved(evicted: Boolean, key: Int, oldValue: List<Page>, newValue: List<Page>?) {
            Timber.d("Chapter $key pages removed from cache. Evicted: $evicted")
        }
    }

    private val blockCache = object : LruCache<Int, List<ContentBlock>>(15) {
        override fun entryRemoved(evicted: Boolean, key: Int, oldValue: List<ContentBlock>, newValue: List<ContentBlock>?) {
            Timber.d("Chapter $key blocks removed from L2 blockCache. Evicted: $evicted")
        }
    }
    private val chapterCharacterIndex = ConcurrentHashMap<Int, List<PageCharacterRange>>()
    private val chapterCumulativeChars = ConcurrentHashMap<Int, List<Long>>()
    private val chapterTextRangeIndex = ConcurrentHashMap<Int, List<TextRangeIndex>>()
    private val chapterPageNavigationIndex = ConcurrentHashMap<Int, List<PageNavigationEntry>>()
    private val chapterAnchorPageIndex = ConcurrentHashMap<Int, Map<String, Int>>()

    private var pageCountsAreAccurate by mutableStateOf(false)
    private val finalizedChapterCounts = ConcurrentHashMap.newKeySet<Int>()
    private var currentConfigHash: Int = 0
    @Volatile
    private var chapterStartSnapshot: IntArray = IntArray(0)

    private val paginationQueue = PriorityBlockingQueue<PaginationRequest>()
    private val chaptersBeingProcessed = ConcurrentHashMap.newKeySet<Int>()
    private val chapterPaginationLocks = ConcurrentHashMap<Int, Mutex>()
    private val navigationCallbacks = ConcurrentHashMap<Int, MutableList<(List<Page>) -> Unit>>()
    private var paginationWorker: Job? = null

    internal fun getCharactersScrolledInChapter(chapterIndex: Int, pageInChapter: Int): Long {
        val cumulativeCharsList = chapterCumulativeChars[chapterIndex]
        if (cumulativeCharsList.isNullOrEmpty()) {
            Timber.w("[Paginator] getCharactersScrolledInChapter: No cumulative char index found for chapter $chapterIndex.")
            return 0L
        }

        val scrolledChars = if (pageInChapter > 0) {
            cumulativeCharsList.getOrNull(pageInChapter - 1) ?: cumulativeCharsList.lastOrNull() ?: 0L
        } else {
            0L
        }

        return scrolledChars
    }

    init {
        if (!constraints.hasBoundedHeight) {
            Timber.e("Paginator received UNBOUNDED HEIGHT. Pagination will fail.")
        } else {
            Timber.i("Paginator initializing with constraints: $constraints")
            coroutineScope.launch {
                isLoading = true
                Timber.d("Initialization started.")

                if (chapters.isEmpty()) {
                    totalPageCount = 0
                    pageCountsAreAccurate = true
                    isLoading = false
                    Timber.w("Paginator initialized with no chapters. Skipping pagination startup.")
                    return@launch
                }

                // 1. Book processing check (Keep existing logic)
                val bookRecord = bookCacheDao.getProcessedBook(bookId)
                if (bookRecord == null || bookRecord.processingVersion < LATEST_PROCESSING_VERSION) {
                    Timber.i("Book cache is new or stale. Creating initial record.")
                    bookCacheDao.deleteEntireBookCache(bookId)
                    val initialBook = ProcessedBook(bookId, LATEST_PROCESSING_VERSION, 0) // Temp 0
                    bookCacheDao.insertProcessedBook(initialBook)
                    enqueueBookProcessingWork()
                }

                // 2. GENERATE CONFIG HASH
                currentConfigHash = generateConfigurationHash()

                // 3. TRY LOAD EXACT COUNTS FROM DB
                val cachedConfig = bookCacheDao.getConfigurationCache(bookId, currentConfigHash)

                if (cachedConfig != null) {
                    Timber.i("Configuration Cache HIT. Using saved page counts.")
                    applyAccuratePageCounts(cachedConfig.chapterPageCounts)
                } else {
                    Timber.i("Configuration Cache MISS. Running instant estimator.")
                    runEstimator()
                }

                // 4. Start Worker
                paginationWorker = startPaginationWorker()

                // 5. Prioritize CURRENT chapter only
                // We no longer blindly queue neighbors immediately to keep startup fast.
                // We only queue the requested chapter.
                val startChapter = initialChapterToPaginate.coerceIn(0, chapters.lastIndex)

                // Trigger actual pagination for the current chapter to replace the estimate with reality
                triggerPagination(startChapter, PRIORITY_HIGHEST)

                // Queue neighbors with lower priority
                for (offset in 1..2) {
                    if (startChapter + offset < chapters.size) triggerPagination(startChapter + offset, PRIORITY_LOW)
                    if (startChapter - offset >= 0) triggerPagination(startChapter - offset, PRIORITY_LOW)
                }

                isLoading = false
                Timber.i("Paginator initialized. UI is ready.")
            }
        }
    }

    private fun runEstimator() {
        var runningTotal = 0

        // This loop is extremely fast (math only)
        chapters.forEachIndexed { index, chapter ->
            val estimatedCount = PageCountEstimator.estimateChapterPageCount(
                chapter = chapter,
                constraints = constraints,
                textStyle = textStyle,
                density = density
            )

            chapterPageCounts[index] = estimatedCount
            chapterStartPageIndices[index] = runningTotal

            runningTotal += estimatedCount
        }

        totalPageCount = runningTotal
        pageCountsAreAccurate = false
        rebuildChapterStartSnapshot()
        Timber.i("Estimator finished. Estimated total pages: $totalPageCount")
    }

    private fun getAllTextBlocks(blocks: List<ContentBlock>): List<TextContentBlock> {
        return blocks.flatMap { block ->
            when (block) {
                is WrappingContentBlock -> getAllTextBlocks(block.paragraphsToWrap)
                is FlexContainerBlock -> getAllTextBlocks(block.children)
                is TableBlock -> block.rows.flatten().flatMap { getAllTextBlocks(it.content) }
                is TextContentBlock -> listOf(block)
                else -> emptyList()
            }
        }
    }

    private fun generateConfigurationHash(): Int {
        val configString = buildString {
            append("w:${constraints.maxWidth}")
            append("-h:${constraints.maxHeight}")
            append("-fs:${textStyle.fontSize.value}")
            append("-lh:${textStyle.lineHeight.value}")
            append("-ff:${textStyle.fontFamily}")
            append("-ta:$userTextAlign")
            append("-pg:$paragraphGapMultiplier")
            append("-img:$imageSizeMultiplier")
            append("-vm:$verticalMarginMultiplier")
            append("-proc:$LATEST_PROCESSING_VERSION")
            append("-pageCache:$LATEST_PAGE_CACHE_VERSION")
            append("-ua:${userAgentStylesheet.hashCode()}")
            append("-css:${bookCss.hashCode()}")
            append("-fonts:${allFontFaces.hashCode()}")
        }
        val hash = configString.hashCode()
        return hash
    }

    private fun applyAccuratePageCounts(countsString: String?) {
        val countsMap = if (!countsString.isNullOrBlank()) {
            try {
                countsString.split(',').filter { it.contains(':') }.associate {
                    val (index, count) = it.split(':')
                    index.toInt() to count.toInt()
                }
            } catch (e: Exception) {
                Timber.e(e, "Failed to parse chapter page counts string.")
                mapOf()
            }
        } else {
            mapOf()
        }

        if (countsMap.isEmpty()) {
            runEstimator() // Fallback if string was empty
            return
        }

        var runningTotal = 0
        chapters.forEachIndexed { index, chapter ->
            // Use cached count if available, otherwise estimate
            val pageCount = countsMap[index] ?: PageCountEstimator.estimateChapterPageCount(
                chapter, constraints, textStyle, density
            )

            chapterPageCounts[index] = pageCount
            chapterStartPageIndices[index] = runningTotal
            runningTotal += pageCount
        }
        totalPageCount = runningTotal
        pageCountsAreAccurate = countsMap.size == chapters.size
        rebuildChapterStartSnapshot()
    }

    private suspend fun updateAndSaveConfigurationCache() {
        val countsString = chapterPageCounts.entries
            .sortedBy { it.key }
            .joinToString(",") { "${it.key}:${it.value}" }

        val newCache = ConfigurationCache(
            bookId = bookId,
            configHash = currentConfigHash,
            chapterPageCounts = countsString
        )
        bookCacheDao.insertConfigurationCache(newCache)

        bookCacheDao.cleanupOldConfigurations(bookId)
        bookCacheDao.cleanupOldPageCaches(bookId)

        if (finalizedChapterCounts.size >= chapters.size) {
            pageCountsAreAccurate = true
        }
    }

    private fun rebuildChapterStartSnapshot() {
        chapterStartSnapshot = IntArray(chapters.size) { index ->
            chapterStartPageIndices[index] ?: 0
        }
    }

    private fun chapterContentVersion(chapter: EpubChapter): Int {
        val backingFile = java.io.File(extractionBasePath, chapter.htmlFilePath)
        return buildString {
            append(chapter.absPath)
            append('|')
            append(chapter.htmlFilePath)
            append('|')
            append(chapter.htmlContent.length)
            append('|')
            append(chapter.htmlContent.hashCode())
            append('|')
            append(chapter.plainTextContent.length)
            append('|')
            append(chapter.plainTextContent.hashCode())
            append('|')
            if (backingFile.exists()) {
                append(backingFile.length())
                append('|')
                append(backingFile.lastModified())
            }
        }.hashCode()
    }

    private suspend fun loadCachedPagesForChapter(chapter: EpubChapter, chapterIndex: Int): List<Page>? {
        val cachedPages = bookCacheDao.getPageCache(bookId, currentConfigHash, chapterIndex) ?: return null
        val expectedContentVersion = chapterContentVersion(chapter)
        val isCompatible = cachedPages.processingVersion == LATEST_PROCESSING_VERSION &&
            cachedPages.pageCacheVersion == LATEST_PAGE_CACHE_VERSION &&
            cachedPages.contentVersion == expectedContentVersion

        if (!isCompatible) {
            Timber.d("Page cache stale for chapter $chapterIndex. Ignoring cached pages.")
            return null
        }

        return try {
            val pages = proto.decodeFromByteArray<List<Page>>(cachedPages.pagesProto)
            if (pages.size != cachedPages.pageCount) {
                Timber.w("Page cache count mismatch for chapter $chapterIndex. Ignoring cached pages.")
                null
            } else {
                applyPageRuntimeIndexes(chapterIndex, pages)
                updatePageCountsOnMain(chapterIndex, pages.size)
                pageCache.put(chapterIndex, pages)
                Timber.i("Page cache HIT for chapter $chapterIndex. Loaded ${pages.size} measured pages.")
                pages
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to deserialize page cache for chapter $chapterIndex")
            null
        }
    }

    private fun savePageCacheAsync(chapter: EpubChapter, chapterIndex: Int, pages: List<Page>) {
        coroutineScope.launch(Dispatchers.IO) {
            try {
                val pageIndexEntries = buildPersistentPageIndexEntries(chapterIndex, pages)
                val cacheEntry = PageCacheEntry(
                    bookId = bookId,
                    configHash = currentConfigHash,
                    chapterIndex = chapterIndex,
                    processingVersion = LATEST_PROCESSING_VERSION,
                    pageCacheVersion = LATEST_PAGE_CACHE_VERSION,
                    contentVersion = chapterContentVersion(chapter),
                    pageCount = pages.size,
                    pagesProto = proto.encodeToByteArray(pages)
                )
                bookCacheDao.insertPageCache(cacheEntry, pageIndexEntries)
                Timber.d("Saved measured page cache for chapter $chapterIndex (${pages.size} pages).")
            } catch (e: Exception) {
                Timber.e(e, "Failed to persist page cache for chapter $chapterIndex")
            }
        }
    }

    private fun getAllBlocks(blocks: List<ContentBlock>): List<ContentBlock> {
        return blocks.flatMap { block ->
            when (block) {
                is WrappingContentBlock -> listOf(block, block.floatedImage) + getAllBlocks(block.paragraphsToWrap)
                is FlexContainerBlock -> listOf(block) + getAllBlocks(block.children)
                is TableBlock -> listOf(block) + block.rows.flatten().flatMap { getAllBlocks(it.content) }
                else -> listOf(block)
            }
        }
    }

    private fun applyPageRuntimeIndexes(chapterIndex: Int, pages: List<Page>) {
        val characterIndex = mutableListOf<PageCharacterRange>()
        val textRangeIndex = mutableListOf<TextRangeIndex>()
        val navigationEntries = mutableListOf<PageNavigationEntry>()
        val anchorPageMap = linkedMapOf<String, Int>()
        val cumulativeCharsPerPage = mutableListOf<Long>()
        var runningTotalChars = 0L

        pages.forEachIndexed { pageInChapterIndex, page ->
            val allBlocksOnPage = getAllBlocks(page.content)
            val allTextBlocksOnPage = getAllTextBlocks(page.content)
            val anchors = allBlocksOnPage.flatMap { findAllIds(it) }.toSet()
            anchors.forEach { anchorPageMap.putIfAbsent(it, pageInChapterIndex) }

            allTextBlocksOnPage.forEach { block ->
                if (block.cfi != null && block.startCharOffsetInSource >= 0 && block.content.isNotEmpty()) {
                    val startOffset = block.startCharOffsetInSource
                    val endOffset = startOffset + block.content.text.length
                    characterIndex.add(
                        PageCharacterRange(
                            pageInChapter = pageInChapterIndex,
                            cfi = block.cfi!!,
                            startOffset = startOffset,
                            endOffset = endOffset
                        )
                    )
                    textRangeIndex.add(
                        TextRangeIndex(
                            pageInChapter = pageInChapterIndex,
                            blockIndex = block.blockIndex,
                            startOffset = startOffset,
                            endOffset = endOffset
                        )
                    )
                }
            }

            val firstTextBlock = allTextBlocksOnPage.firstOrNull { it.content.text.isNotBlank() }
                ?: allTextBlocksOnPage.firstOrNull()
            val firstBlock = allBlocksOnPage.firstOrNull()
            val blockIndices = allBlocksOnPage.map { it.blockIndex }
            navigationEntries.add(
                PageNavigationEntry(
                    pageInChapter = pageInChapterIndex,
                    firstBlockIndex = blockIndices.minOrNull() ?: firstBlock?.blockIndex ?: -1,
                    lastBlockIndex = blockIndices.maxOrNull() ?: firstBlock?.blockIndex ?: -1,
                    firstTextBlockIndex = firstTextBlock?.blockIndex,
                    firstTextCharOffset = firstTextBlock?.startCharOffsetInSource ?: 0,
                    firstTextEndOffset = firstTextBlock?.let { it.startCharOffsetInSource + it.content.text.length } ?: 0,
                    firstCfi = firstTextBlock?.cfi ?: firstBlock?.cfi,
                    anchors = anchors
                )
            )

            runningTotalChars += allTextBlocksOnPage.sumOf { it.content.text.length.toLong() }
            cumulativeCharsPerPage.add(runningTotalChars)
        }

        chapterCharacterIndex[chapterIndex] = characterIndex
        chapterTextRangeIndex[chapterIndex] = textRangeIndex
        chapterPageNavigationIndex[chapterIndex] = navigationEntries
        chapterAnchorPageIndex[chapterIndex] = anchorPageMap
        chapterCumulativeChars[chapterIndex] = cumulativeCharsPerPage
    }

    private fun buildPersistentPageIndexEntries(chapterIndex: Int, pages: List<Page>): List<PageIndexEntry> {
        val entries = chapterPageNavigationIndex[chapterIndex] ?: run {
            applyPageRuntimeIndexes(chapterIndex, pages)
            chapterPageNavigationIndex[chapterIndex].orEmpty()
        }

        return entries.map { entry ->
            PageIndexEntry(
                bookId = bookId,
                configHash = currentConfigHash,
                chapterIndex = chapterIndex,
                pageInChapter = entry.pageInChapter,
                firstBlockIndex = entry.firstBlockIndex,
                lastBlockIndex = entry.lastBlockIndex,
                firstTextBlockIndex = entry.firstTextBlockIndex,
                firstTextCharOffset = entry.firstTextCharOffset,
                firstTextEndOffset = entry.firstTextEndOffset,
                firstCfi = entry.firstCfi,
                anchors = entry.anchors.sorted().joinToString(PAGE_INDEX_ANCHOR_SEPARATOR)
            )
        }
    }

    private suspend fun updatePageCountsOnMain(chapterIndex: Int, actualPageCount: Int) {
        withContext(Dispatchers.Main) {
            if (chapterPageCounts[chapterIndex] != actualPageCount) {
                updatePageCounts(chapterIndex, actualPageCount)
            } else if (finalizedChapterCounts.add(chapterIndex)) {
                coroutineScope.launch(Dispatchers.IO) { updateAndSaveConfigurationCache() }
            }
            generation++
        }
    }

    private suspend fun ensureChapterPaginated(chapterIndex: Int): List<Page>? {
        if (chapterIndex !in chapters.indices) {
            Timber.w("ensureChapterPaginated: Ignoring invalid chapter index $chapterIndex.")
            return null
        }

        pageCache[chapterIndex]?.let {
            Timber.tag(TAG_STABLE_PAGE_NAV)
                .d("ensure_chapter hit_memory chapter=$chapterIndex pages=${it.size}")
            return it
        }

        val lock = chapterPaginationLocks.computeIfAbsent(chapterIndex) { Mutex() }
        return lock.withLock {
            pageCache[chapterIndex]?.also {
                Timber.tag(TAG_STABLE_PAGE_NAV)
                    .d("ensure_chapter hit_after_wait chapter=$chapterIndex pages=${it.size}")
            } ?: run {
                Timber.tag(TAG_STABLE_PAGE_NAV)
                    .d("ensure_chapter paginate chapter=$chapterIndex finalized=${chapterIndex in finalizedChapterCounts}")
                paginateChapter(chapterIndex)
            }
        }
    }

    private suspend fun ensureStableStartPageForChapter(chapterIndex: Int): Int? {
        Timber.tag(TAG_STABLE_PAGE_NAV).d(
            "stable_start request chapter=$chapterIndex countsAccurate=$pageCountsAreAccurate finalized=${chapterIndex in finalizedChapterCounts}"
        )
        return resolveStableChapterStartPage(
            chapterIndex = chapterIndex,
            chapterCount = chapters.size,
            pageCountsAreAccurate = pageCountsAreAccurate,
            chapterStartPage = { chapterStartPageIndices[it] },
            isChapterFinalized = { it in finalizedChapterCounts },
            ensureChapterPaginated = { ensureChapterPaginated(it) != null }
        )
    }

    suspend fun findStableChapterStartPage(chapterIndex: Int): Int? = withContext(Dispatchers.IO) {
        val stableStart = ensureStableStartPageForChapter(chapterIndex) ?: return@withContext null
        val targetPages = ensureChapterPaginated(chapterIndex) ?: return@withContext null
        Timber.tag(TAG_STABLE_PAGE_NAV).d(
            "stable_chapter_start resolved chapter=$chapterIndex page=$stableStart targetPages=${targetPages.size}"
        )
        stableStart.takeIf { targetPages.isNotEmpty() }
    }

    suspend fun findStablePageForLocator(locator: Locator): Int? = withContext(Dispatchers.IO) {
        val targetChapterIndex = locator.chapterIndex
        Timber.tag("POS_DIAG").d("findStablePageForLocator: Searching for $locator")
        Timber.tag(TAG_STABLE_PAGE_NAV).d("stable_locator request locator=$locator")

        val chapterPages = ensureChapterPaginated(targetChapterIndex)
        val chapterStartPage = ensureStableStartPageForChapter(targetChapterIndex)

        Timber.tag("POS_DIAG").d(
            "findStablePageForLocator: targetChapterIndex=$targetChapterIndex, stableStart=$chapterStartPage, chapterPages.size=${chapterPages?.size}"
        )

        if (chapterPages.isNullOrEmpty() || chapterStartPage == null) {
            Timber.e("Stable locator navigation failed: Could not stabilize target chapter $targetChapterIndex.")
            return@withContext null
        }

        val pageInChapter = findPageInChapterForLocator(locator, chapterPages) ?: run {
            Timber.tag("POS_DIAG").e("findStablePageForLocator: FAILED to resolve locator in chapter $targetChapterIndex")
            return@withContext null
        }

        val finalPageIndex = chapterStartPage + pageInChapter
        Timber.tag("POS_DIAG").i("findStablePageForLocator: FOUND absolute page $finalPageIndex")
        Timber.tag(TAG_STABLE_PAGE_NAV).d(
            "stable_locator resolved locator=$locator page=$finalPageIndex chapterStart=$chapterStartPage pageInChapter=$pageInChapter"
        )
        finalPageIndex
    }

    suspend fun getTtsChunksForChapter(chapterIndex: Int, startingFromPageInChapter: Int = 0): List<TtsChunk>? {
        val pages = ensureChapterPaginated(chapterIndex)
        if (pages.isNullOrEmpty()) {
            Timber.w("PAGINATOR: Chapter $chapterIndex has no pages or could not be paginated.")
            return null
        }

        val blocks = pages.drop(startingFromPageInChapter).flatMap { it.content }

        if (blocks.isEmpty()) {
            Timber.w("PAGINATOR: Chapter $chapterIndex from page $startingFromPageInChapter has no blocks.")
            return null
        }

        val allTtsChunks = mutableListOf<TtsChunk>()

        val textBlocks = getAllTextBlocks(blocks)

        textBlocks.forEach { block ->
            if (block.cfi != null) {
                val blockText = block.content.text
                if (blockText.isNotBlank()) {
                    val textChunksInBlock = splitTextIntoChunks(blockText)

                    var currentSearchIndex = 0
                    textChunksInBlock.forEach { chunkText ->
                        val firstWord = chunkText.trim().substringBefore(' ')
                        val relativeOffset = if (firstWord.isNotEmpty()) {
                            val idx = blockText.indexOf(firstWord, currentSearchIndex)
                            if (idx != -1) idx else currentSearchIndex
                        } else {
                            currentSearchIndex
                        }

                        val chunk = TtsChunk(
                            text = chunkText,
                            sourceCfi = block.cfi!!,
                            startOffsetInSource = block.startCharOffsetInSource + relativeOffset
                        )
                        allTtsChunks.add(chunk)
                        currentSearchIndex = relativeOffset + chunkText.length
                    }
                } else {
                    Timber.d("PAGINATOR: Skipping blank text block. CFI: ${block.cfi}, startOffset: ${block.startCharOffsetInSource}")
                }
            } else {
                Timber.w("PAGINATOR: Skipping text block with null CFI. Text: '${block.content.text.take(70).replace("\n", " ")}...'")
            }
        }
        Timber.i("PAGINATOR: Generated ${allTtsChunks.size} TTS chunks for chapter $chapterIndex starting from page $startingFromPageInChapter.")
        return allTtsChunks
    }

    private fun enqueueBookProcessingWork() {
        val serializableChapters = chapters.map {
            SerializableEpubChapter(
                htmlContent = it.htmlContent,
                title = it.title,
                absPath = it.absPath,
                htmlFilePath = it.htmlFilePath
            )
        }

        val input = BookProcessingInput(
            chapters = serializableChapters,
            userAgentStylesheet = userAgentStylesheet,
            bookCss = bookCss,
            baseFontSizeSp = textStyle.fontSize.value,
            density = density.density,
            constraintsMaxWidth = constraints.maxWidth,
            constraintsMaxHeight = constraints.maxHeight,
            fontFaces = this.allFontFaces
        )

        BookProcessingWorker.enqueue(
            context = context,
            bookId = bookId,
            extractionBasePath = extractionBasePath,
            estimatedTotalPages = totalPageCount,
            processingInput = input,
            startChapterIndex = initialChapterToPaginate
        )
    }

    private suspend fun getBlocksForChapter(chapter: EpubChapter, chapterIndex: Int): List<ContentBlock> {
        Timber.d("BookPaginator: getBlocksForChapter($chapterIndex). Using stored textStyle: FontSize=${textStyle.fontSize}, LineHeight=${textStyle.lineHeight}")
        val styler = ContentStyler(
            baseTextStyle = textStyle,
            fontFamilyMap = fontFamilyMap,
            density = density,
            isDarkTheme = isDarkTheme,
            themeBackgroundColor = themeBackgroundColor,
            themeTextColor = themeTextColor,
            chapterAbsPath = chapter.absPath,
            extractionBasePath = extractionBasePath,
            userTextAlign = userTextAlign,
            paragraphGapMultiplier = paragraphGapMultiplier,
            adaptThemeColors = false
        )

        bookCacheDao.getProcessedChapter(bookId, chapterIndex)?.let { cachedChapter ->
            if (cachedChapter.contentBlocksProto.isNotEmpty()) {
                try {
                    val semanticBlocks = proto.decodeFromByteArray<List<SemanticBlock>>(cachedChapter.contentBlocksProto)

                    val isCacheEmpty = semanticBlocks.isEmpty()
                    val isLazyChapter = chapter.htmlContent.isEmpty()

                    var shouldIgnoreCache = false
                    if (isCacheEmpty && isLazyChapter) {
                        val file = java.io.File(extractionBasePath, chapter.htmlFilePath)
                        if (file.exists() && file.length() > 0) {
                            Timber.tag("ReflowPaginationDiag").w("getBlocksForChapter: Cache HIT but empty for lazy chapter $chapterIndex. Backing file exists (${file.length()} bytes). Ignoring cache.")
                            shouldIgnoreCache = true
                        }
                    }

                    if (!shouldIgnoreCache) {
                        Timber.d("getBlocksForChapter: Cache HIT for chapter $chapterIndex in DATABASE.")
                        return styler.style(semanticBlocks)
                    }
                } catch (e: Exception) {
                    Timber.e(e, "Failed to deserialize/style chapter $chapterIndex from DB. Reprocessing for this session.")
                }
            } else {
                Timber.d("getBlocksForChapter: Cached chapter $chapterIndex had no semantic payload. Reprocessing.")
            }
        }

        Timber.d("getBlocksForChapter: Cache MISS for chapter $chapterIndex. Parsing to Semantic IR.")

        var htmlToParse = chapter.htmlContent
        if (htmlToParse.isEmpty()) {
            val file = java.io.File(extractionBasePath, chapter.htmlFilePath)
            if (file.exists()) {
                Timber.tag("ReflowPaginationDiag").d("getBlocksForChapter: Lazy loading content from disk for chapter $chapterIndex: ${file.name} (${file.length()} bytes)")
                try {
                    htmlToParse = file.readText()
                } catch (e: Exception) {
                    Timber.tag("ReflowPaginationDiag").e(e, "Failed to read lazy HTML file")
                }
            } else {
                Timber.tag("ReflowPaginationDiag").w("getBlocksForChapter: htmlContent is empty and file not found: ${file.absolutePath}")
            }
        }

        val document = Jsoup.parse(htmlToParse, chapter.absPath)
        val mathElements = document.select("math")
        val svgResults = mutableMapOf<String, String>()

        if (mathElements.isNotEmpty()) {
            mathElements.forEachIndexed { i, element ->
                val uniqueId = "math-ch${chapterIndex}-eq${i}"
                val altText = element.attr("alttext").ifBlank { "Equation" }
                val placeholder = Element("math-placeholder").attr("id", uniqueId)

                when (val result = mathMLRenderer.render(element.outerHtml(), altText)) {
                    is RenderResult.Success -> svgResults[uniqueId] = result.svg
                    is RenderResult.Failure -> placeholder.attr("alttext", result.altText)
                }
                element.replaceWith(placeholder)
            }
        }
        val processedHtml = document.outerHtml()

        var parsingCssRules = OptimizedCssRules()
        val uaResult = CssParser.parse(cssContent = userAgentStylesheet, cssPath = null, baseFontSizeSp = textStyle.fontSize.value, density = density.density, constraints = constraints, isDarkTheme = false, adaptThemeColors = false)
        parsingCssRules = parsingCssRules.merge(uaResult.rules)
        bookCss.forEach { (path, content) ->
            val bookCssResult = CssParser.parse(cssContent = content, cssPath = path, baseFontSizeSp = textStyle.fontSize.value, density = density.density, constraints = constraints, isDarkTheme = false, adaptThemeColors = false)
            parsingCssRules = parsingCssRules.merge(bookCssResult.rules)
        }

        val semanticBlocks = androidHtmlToSemanticBlocks(
            html = processedHtml,
            cssRules = parsingCssRules,
            textStyle = textStyle.copy(color = Color.Black),
            chapterAbsPath = chapter.absPath,
            extractionBasePath = extractionBasePath,
            density = density,
            fontFamilyMap = fontFamilyMap,
            constraints = constraints,
            mathSvgCache = svgResults,
            adaptThemeColors = false
        )

        coroutineScope.launch(Dispatchers.IO) {
            try {
                val protoBytes = proto.encodeToByteArray(semanticBlocks)
                val newCacheEntry = ProcessedChapter(bookId, chapterIndex, protoBytes, chapterPageCounts[chapterIndex] ?: 0)
                bookCacheDao.insertProcessedChapters(listOf(newCacheEntry))
                Timber.i("Successfully cached SEMANTIC content for chapter $chapterIndex.")
            } catch (e: Exception) {
                Timber.e(e, "Failed to serialize and cache on-demand semantic chapter $chapterIndex")
            }
        }

        return styler.style(semanticBlocks)
    }

    private fun startPaginationWorker(): Job = coroutineScope.launch(Dispatchers.IO) {
        Timber.i("Pagination worker started.")
        while (isActive) {
            var request: PaginationRequest? = null
            try {
                request = paginationQueue.take()
                val chapterIndex = request.chapterIndex
                Timber.d("Worker: Took chapter $chapterIndex from queue with priority ${request.priority}.")

                if (pageCache[chapterIndex] != null) {
                    Timber.d("Worker: Chapter $chapterIndex is already in cache, skipping.")
                    continue
                }

                if (!chaptersBeingProcessed.add(chapterIndex)) {
                    Timber.w("Worker: Chapter $chapterIndex is already being processed, skipping.")
                    continue
                }

                Timber.i("Worker: Starting pagination for chapter $chapterIndex.")
                val pages = ensureChapterPaginated(chapterIndex)

                if (pages != null) {
                    Timber.i("Worker: Successfully finished pagination for chapter $chapterIndex.")
                    navigationCallbacks.remove(chapterIndex)?.forEach { callback ->
                        Timber.d("Worker: Fulfilling navigation callback for chapter $chapterIndex.")
                        callback(pages)
                    }
                } else {
                    Timber.e("Worker: Pagination for chapter $chapterIndex resulted in null.")
                }
            } catch (_: InterruptedException) {
                Timber.i("Pagination worker interrupted. Shutting down.")
                Thread.currentThread().interrupt()
                break
            } catch (e: Exception) {
                Timber.e(e, "Error in pagination worker for chapter ${request?.chapterIndex}")
            } finally {
                request?.let {
                    chaptersBeingProcessed.remove(it.chapterIndex)
                    Timber.d("Worker: Chapter ${it.chapterIndex} removed from processing set.")
                }
                yield()
            }
        }
        Timber.i("Pagination worker stopped.")
    }

    override fun onUserScrolledTo(pageIndex: Int) {
        findChapterIndexForPage(pageIndex)?.let {
            if (it != currentUserChapterIndex.value) {
                Timber.d("User location updated to chapter $it")
                currentUserChapterIndex.value = it
            }
        }
    }

    private fun updatePageCounts(chapterIndex: Int, actualPageCount: Int) {
        val estimatedPageCount = chapterPageCounts[chapterIndex] ?: actualPageCount
        val difference = actualPageCount - estimatedPageCount

        if (difference == 0) {
            Timber.tag(TAG_STABLE_PAGE_NAV).d(
                "page_count_noop chapter=$chapterIndex count=$actualPageCount currentUserChapter=${currentUserChapterIndex.value}"
            )
            if (!pageCountsAreAccurate && finalizedChapterCounts.add(chapterIndex)) {
                coroutineScope.launch(Dispatchers.IO) { updateAndSaveConfigurationCache() }
            }
            return
        }

        // Update the specific chapter
        chapterPageCounts[chapterIndex] = actualPageCount

        synchronized(this) {
            totalPageCount += difference

            // Shift all SUBSEQUENT chapters
            for (i in (chapterIndex + 1) until chapters.size) {
                chapterStartPageIndices[i] = (chapterStartPageIndices[i] ?: 0) + difference
            }
            rebuildChapterStartSnapshot()

            val currentUserChapter = currentUserChapterIndex.value
            val shouldShiftCurrentPage = chapterIndex < currentUserChapter
            Timber.tag(TAG_STABLE_PAGE_NAV).d(
                "page_count_update chapter=$chapterIndex estimated=$estimatedPageCount actual=$actualPageCount diff=$difference currentUserChapter=$currentUserChapter shiftCurrent=$shouldShiftCurrentPage total=$totalPageCount"
            )
            if (shouldShiftCurrentPage) {
                val emitted = pageShiftRequest.tryEmit(difference)
                Timber.tag(TAG_STABLE_PAGE_NAV).d(
                    "page_shift_emit chapter=$chapterIndex diff=$difference currentUserChapter=$currentUserChapter emitted=$emitted"
                )
            }
        }

        if (!pageCountsAreAccurate) {
            if (finalizedChapterCounts.add(chapterIndex)) {
                coroutineScope.launch(Dispatchers.IO) {
                    updateAndSaveConfigurationCache()
                }
            }
        }

        Timber.i("Correction: Ch $chapterIndex | Est: $estimatedPageCount -> Act: $actualPageCount | Diff: $difference | Total: $totalPageCount")
    }

    override fun getActiveAnchorForPage(pageIndex: Int, tocAnchors: List<String>): String? {
        val chapterIndex = findChapterIndexForPage(pageIndex) ?: return null
        val chapterPages = pageCache[chapterIndex] ?: return null
        val chapterStart = chapterStartPageIndices[chapterIndex] ?: 0
        val currentPageInChapter = pageIndex - chapterStart

        chapterAnchorPageIndex[chapterIndex]?.let { anchorPages ->
            val anchorSet = tocAnchors.toSet()
            return anchorPages
                .filter { (anchor, anchorPage) -> anchor in anchorSet && anchorPage <= currentPageInChapter }
                .maxByOrNull { it.value }
                ?.key
        }

        var lastFoundAnchor: String? = null
        val anchorSet = tocAnchors.toSet()

        for (pIdx in 0..currentPageInChapter) {
            val page = chapterPages.getOrNull(pIdx) ?: continue
            for (block in page.content) {
                val idsInBlock = findAllIds(block)
                for (id in idsInBlock) {
                    if (anchorSet.contains(id)) {
                        lastFoundAnchor = id
                    }
                }
            }
        }
        return lastFoundAnchor
    }

    override fun getPageContent(pageIndex: Int): Page? {
        Timber.v("getPageContent requested for pageIndex $pageIndex")
        val chapterIndex = findChapterIndexForPage(pageIndex)
        if (chapterIndex == null) {
            Timber.w("getPageContent: Could not find chapter for pageIndex $pageIndex.")
            return null
        }

        val chapterPages = pageCache[chapterIndex]
        if (chapterPages != null) {
            val pageInChapterIndex = pageIndex - (chapterStartPageIndices[chapterIndex] ?: 0)
            Timber.v("getPageContent: Cache HIT for page $pageIndex (Chapter $chapterIndex, PageInChapter $pageInChapterIndex).")
            prefetchChapters(chapterIndex)
            return chapterPages.getOrNull(pageInChapterIndex)
        } else {
            Timber.i("getPageContent: Cache MISS for page $pageIndex (Chapter $chapterIndex).")
            triggerPagination(chapterIndex, PRIORITY_HIGH)
            prefetchChapters(chapterIndex)
            return null
        }
    }

    override fun findChapterIndexForPage(pageIndex: Int): Int? {
        if (pageIndex !in 0..<totalPageCount) {
            Timber.w("findChapterIndexForPage: pageIndex $pageIndex is out of bounds (Total: $totalPageCount)."
            )
            return null
        }
        val starts = chapterStartSnapshot
        if (starts.isNotEmpty()) {
            val exactOrInsertionPoint = starts.binarySearch(pageIndex)
            val index = if (exactOrInsertionPoint >= 0) {
                exactOrInsertionPoint
            } else {
                -exactOrInsertionPoint - 2
            }
            if (index in chapters.indices) {
                return index
            }
        }

        val entry = chapterStartPageIndices.entries
            .filter { it.value <= pageIndex }
            .maxWithOrNull(compareBy({ it.value }, { it.key }))

        if (entry == null) {
            Timber.e("findChapterIndexForPage: Could not find a start page index for page $pageIndex. This is a critical error."
            )
        }
        return entry?.key
    }

    override fun getCfiForPage(pageIndex: Int): String? {
        val chapterIndex = findChapterIndexForPage(pageIndex) ?: return null
        val chapterStart = chapterStartPageIndices[chapterIndex] ?: 0
        val pageInChapterIndex = pageIndex - chapterStart
        chapterPageNavigationIndex[chapterIndex]
            ?.getOrNull(pageInChapterIndex)
            ?.firstCfi
            ?.let { cfi ->
                val offset = chapterPageNavigationIndex[chapterIndex]
                    ?.getOrNull(pageInChapterIndex)
                    ?.firstTextCharOffset
                    ?: 0
                return if (offset > 0 && !cfi.contains(':')) "$cfi:$offset" else cfi
            }

        val chapterPages = pageCache[chapterIndex]
        if (chapterPages == null) {
            Timber.w("getCfiForPage: Chapter $chapterIndex not in cache for page $pageIndex.")
            return null
        }
        val pageContent = chapterPages.getOrNull(pageInChapterIndex)?.content ?: return null

        val firstTextBlock = pageContent.firstOrNull { it is TextContentBlock } as? TextContentBlock
        val firstAnyBlock = pageContent.firstOrNull()

        if (firstTextBlock != null) {
            val baseCfi = firstTextBlock.cfi ?: return null
            val offset = firstTextBlock.startCharOffsetInSource
            return if (offset > 0) "$baseCfi:$offset" else baseCfi
        } else {
            return firstAnyBlock?.cfi
        }
    }

    private suspend fun paginateChapter(chapterIndex: Int): List<Page>? {
        pageCache[chapterIndex]?.let {
            Timber.d("paginateChapter: L1 Cache HIT for chapter $chapterIndex in MEMORY, returning cached pages.")
            return it
        }

        val chapter = chapters.getOrNull(chapterIndex) ?: run {
            Timber.e("paginateChapter: Chapter $chapterIndex not found in chapter list.")
            return null
        }

        loadCachedPagesForChapter(chapter, chapterIndex)?.let {
            Timber.d("paginateChapter: Persistent page cache HIT for chapter $chapterIndex.")
            return it
        }

        val blocks = blockCache[chapterIndex] ?: run {
            Timber.d("paginateChapter: L2 Cache MISS for chapter $chapterIndex. Loading from DB.")
            val blocksFromDb = getBlocksForChapter(chapter, chapterIndex)
            blockCache.put(chapterIndex, blocksFromDb) // Store in L2 cache
            blocksFromDb
        }

        Timber.d("paginateChapter: Chapter $chapterIndex retrieved/parsed into ${blocks.size} content blocks.")

        val measurementProvider = SuspendingAndroidBlockMeasurementProvider(
            textMeasurer = textMeasurer,
            constraints = constraints,
            textStyle = textStyle,
            density = density,
            imageSizeMultiplier = imageSizeMultiplier
        )
        Timber.d("paginateChapter: Calling PaginatorLogic for chapter $chapterIndex.")
        val pages = paginate(
            blocks = blocks,
            pageHeight = constraints.maxHeight,
            measurementProvider = measurementProvider,
            density = density
        )
        Timber.d("paginateChapter: PaginatorLogic returned ${pages.size} pages for chapter $chapterIndex.")

        applyPageRuntimeIndexes(chapterIndex, pages)
        savePageCacheAsync(chapter, chapterIndex, pages)

        updatePageCountsOnMain(chapterIndex, pages.size)
        pageCache.put(chapterIndex, pages)
        Timber.d("paginateChapter: Chapter $chapterIndex pages stored in L1 pageCache.")
        return pages
    }

    private fun triggerPagination(chapterIndex: Int, priority: Int) {
        if (chapterIndex !in chapters.indices) {
            Timber.w("Trigger: Ignoring invalid chapter index $chapterIndex. Chapter count: ${chapters.size}.")
            return
        }
        if (pageCache[chapterIndex] != null) {
            Timber.v("Trigger: Chapter $chapterIndex is already in cache. Ignoring.")
            return
        }
        if (chaptersBeingProcessed.contains(chapterIndex)) {
            Timber.v("Trigger: Chapter $chapterIndex is already being processed. Ignoring.")
            return
        }

        synchronized(paginationQueue) {
            val existingRequest = paginationQueue.find { it.chapterIndex == chapterIndex }

            if (existingRequest != null) {
                if (priority < existingRequest.priority) { // Lower number = higher priority
                    Timber.d("Upgrading priority for queued chapter $chapterIndex from ${existingRequest.priority} to $priority")
                    paginationQueue.remove(existingRequest)
                    paginationQueue.offer(PaginationRequest(chapterIndex, priority))
                } else {
                    Timber.v("Trigger: Chapter $chapterIndex already in queue with same or higher priority. Ignoring.")
                }
            } else {
                Timber.d("Queued chapter $chapterIndex for pagination with priority $priority.")
                paginationQueue.offer(PaginationRequest(chapterIndex, priority))
            }
        }
    }

    private fun prefetchChapters(currentChapterIndex: Int) {
        Timber.v("Prefetching chapters around index $currentChapterIndex.")
        for (offset in 1..2) {
            val nextChapterIndex = currentChapterIndex + offset
            if (nextChapterIndex < chapters.size) {
                triggerPagination(nextChapterIndex, PRIORITY_MEDIUM)
            }

            val prevChapterIndex = currentChapterIndex - offset
            if (prevChapterIndex >= 0) {
                triggerPagination(prevChapterIndex, PRIORITY_MEDIUM)
            }
        }
    }

    override fun getChapterPathForPage(pageIndex: Int): String? {
        val chapterIndex = findChapterIndexForPage(pageIndex)
        return chapters.getOrNull(chapterIndex ?: -1)?.absPath
    }

    override fun getPlainTextForChapter(chapterIndex: Int): String? {
        val chapter = chapters.getOrNull(chapterIndex) ?: return null
        Timber.tag("POS_DIAG").d("getPlainTextForChapter: chapterIndex=$chapterIndex, chapterTitle='${chapter.title}', hasInMemoryContent=${chapter.htmlContent.isNotEmpty()}")
        val htmlToParse = chapter.htmlContent.ifEmpty {
            try {
                val file = java.io.File(extractionBasePath, chapter.htmlFilePath)
                if (file.exists()) file.readText() else ""
            } catch (_: Exception) {
                ""
            }
        }
        if (htmlToParse.isBlank()) return null
        return Jsoup.parse(htmlToParse).body().text()
    }

    suspend fun findStablePageForAnchor(chapterIndex: Int, anchor: String?): Int? = withContext(Dispatchers.IO) {
        Timber.tag("TOC_NAV_DEBUG").d("Stable precision nav request for anchor: '$anchor'")

        if (anchor.isNullOrBlank()) {
            return@withContext findStableChapterStartPage(chapterIndex)
        }

        // 1. QUICK LOOKUP: Check the Anchor Index first
        val indexEntry = bookCacheDao.getAnchorIndex(bookId, anchor)

        val (targetChapter, targetBlock) = if (indexEntry != null) {
            Timber.tag("TOC_NAV_DEBUG").i("Index HIT: Anchor '$anchor' is in Chapter ${indexEntry.chapterIndex}, Block ${indexEntry.blockIndex}")
            indexEntry.chapterIndex to indexEntry.blockIndex
        } else {
            Timber.tag("TOC_NAV_DEBUG").w("Index MISS: Falling back to linear scan for '$anchor' in Chapter $chapterIndex")
            chapterIndex to null
        }

        // 2. ENSURE PAGINATION: Get pages for the determined chapter
        val chapterPages = ensureChapterPaginated(targetChapter)
        val chapterStartPage = ensureStableStartPageForChapter(targetChapter)

        if (chapterPages == null || chapterStartPage == null) {
            Timber.e("Anchor navigation failed: Could not stabilize target chapter $targetChapter.")
            return@withContext null
        }

        val indexedPageInChapter = targetBlock?.let { blockIndex ->
            chapterPageNavigationIndex[targetChapter]
                ?.firstOrNull { blockIndex in it.firstBlockIndex..it.lastBlockIndex }
                ?.pageInChapter
        } ?: chapterAnchorPageIndex[targetChapter]?.get(anchor)

        if (indexedPageInChapter != null) {
            val finalPage = chapterStartPage + indexedPageInChapter
            Timber.tag("TOC_NAV_DEBUG").d("Navigation resolved from page index to Absolute Page: $finalPage")
            return@withContext finalPage
        }

        // 3. FIND PAGE
        var targetPageInChapter = 0
        var found = false

        for ((pageIndex, page) in chapterPages.withIndex()) {
            val isMatch = if (targetBlock != null) {
                // Fast path: We know exactly which block we are looking for
                page.content.any { it.blockIndex == targetBlock }
            } else {
                // Slow path: Linear ID scan (fallback)
                page.content.any { containsAnchor(it, anchor) }
            }

            if (isMatch) {
                targetPageInChapter = pageIndex
                found = true
                break
            }
        }

        val finalPage = chapterStartPage + targetPageInChapter
        Timber.tag("TOC_NAV_DEBUG").d("Navigation resolved to Absolute Page: $finalPage (Found: $found)")
        finalPage
    }

    override fun findPageForAnchor(
        chapterIndex: Int,
        anchor: String?,
        onResult: (pageIndex: Int) -> Unit
    ) {
        coroutineScope.launch(Dispatchers.IO) {
            val page = findStablePageForAnchor(chapterIndex, anchor) ?: return@launch
            withContext(Dispatchers.Main) { onResult(page) }
        }
    }

    private fun containsAnchor(block: ContentBlock, anchor: String): Boolean {
        if (block.elementId == anchor) return true

        if (block is TextContentBlock) {
            val idAnnotations = block.content.getStringAnnotations("ID", 0, block.content.length)
            if (idAnnotations.any { it.item == anchor }) return true
        }

        return when (block) {
            is FlexContainerBlock -> block.children.any { containsAnchor(it, anchor) }
            is TableBlock -> block.rows.flatten().any { cell ->
                cell.content.any { containsAnchor(it, anchor) }
            }
            is WrappingContentBlock -> {
                containsAnchor(block.floatedImage, anchor) ||
                        block.paragraphsToWrap.any { containsAnchor(it, anchor) }
            }
            else -> false
        }
    }

    private fun findAllIds(block: ContentBlock): List<String> {
        val ids = mutableListOf<String>()
        block.elementId?.let { ids.add(it) }

        if (block is TextContentBlock) {
            val idAnnotations = block.content.getStringAnnotations("ID", 0, block.content.length)
            ids.addAll(idAnnotations.map { it.item })
        }

        when (block) {
            is FlexContainerBlock -> ids.addAll(block.children.flatMap { findAllIds(it) })
            is TableBlock -> ids.addAll(block.rows.flatten().flatMap { cell -> cell.content.flatMap { findAllIds(it) } })
            is WrappingContentBlock -> {
                ids.addAll(findAllIds(block.floatedImage))
                ids.addAll(block.paragraphsToWrap.flatMap { findAllIds(it) })
            }
            else -> {}
        }
        return ids
    }

    override fun navigateToHref(
        currentChapterAbsPath: String,
        href: String,
        onNavigationComplete: (pageIndex: Int) -> Unit
    ) {
        coroutineScope.launch(Dispatchers.IO) {
            val targetPage = findStablePageForHref(currentChapterAbsPath, href) ?: return@launch
            withContext(Dispatchers.Main) { onNavigationComplete(targetPage) }
        }
    }

    suspend fun findStablePageForHref(currentChapterAbsPath: String, href: String): Int? = withContext(Dispatchers.IO) {
        Timber.i("Navigating to href: '$href' from chapter: '$currentChapterAbsPath'")

        val (targetChapterPath, anchor) = resolveHref(currentChapterAbsPath, href)
        if (targetChapterPath == null) {
            Timber.w("Could not resolve href '$href' to a valid chapter path.")
            return@withContext null
        }

        val targetChapterIndex = chapters.indexOfFirst { it.absPath == targetChapterPath }
        if (targetChapterIndex == -1) {
            Timber.w("Could not find chapter for path: $targetChapterPath")
            return@withContext null
        }

        findStablePageForAnchor(targetChapterIndex, anchor)
    }

    suspend fun findStablePageForSearchResult(result: SearchResult): Int? = withContext(Dispatchers.IO) {
        val targetChapterIndex = result.locationInSource
        Timber.i("Finding page for search result: '${result.query}' in chapter $targetChapterIndex")

        val chapterPages = ensureChapterPaginated(targetChapterIndex)
        val chapterStartPage = ensureStableStartPageForChapter(targetChapterIndex)

        if (chapterPages == null || chapterStartPage == null) {
            Timber.e("Search result navigation failed: Could not stabilize target chapter $targetChapterIndex.")
            return@withContext null
        }

        var targetPageInChapter = 0
        var occurrenceCount = 0

        pageLoop@ for ((pageIndex, page) in chapterPages.withIndex()) {
            for (block in page.content) {
                val textToSearch = when (block) {
                    is ParagraphBlock -> block.content.text
                    is HeaderBlock -> block.content.text
                    is QuoteBlock -> block.content.text
                    is ListItemBlock -> block.content.text
                    else -> null
                }

                if (textToSearch != null) {
                    var lastIndex = -1
                    while (true) {
                        lastIndex = textToSearch.indexOf(result.query, startIndex = lastIndex + 1, ignoreCase = true)
                        if (lastIndex == -1) break

                        if (occurrenceCount == result.occurrenceIndexInLocation) {
                            targetPageInChapter = pageIndex
                            Timber.i("Found search result '${result.query}' at occurrence ${result.occurrenceIndexInLocation} on page $pageIndex of chapter $targetChapterIndex")
                            break@pageLoop
                        }
                        occurrenceCount++
                    }
                }
            }
        }
        val finalPageIndex = chapterStartPage + targetPageInChapter
        Timber.i("Search result found. Final page index: $finalPageIndex")
        finalPageIndex
    }

    override fun findPageForSearchResult(result: SearchResult, onResult: (pageIndex: Int) -> Unit) {
        coroutineScope.launch(Dispatchers.IO) {
            val page = findStablePageForSearchResult(result) ?: return@launch
            withContext(Dispatchers.Main) { onResult(page) }
        }
    }

    override fun findPageForCfiAndOffset(chapterIndex: Int, cfi: String, charOffset: Int): Int? {
        val index = chapterCharacterIndex[chapterIndex]
        if (index.isNullOrEmpty()) {
            Timber.tag("TTS_PAGE_JUMP_DIAG").w("Lookup failed: No character index for chapter $chapterIndex")
            return null
        }

        val targetPath = CfiUtils.getPath(cfi)

        val matches = index.filter { range ->
            val rangePath = CfiUtils.getPath(range.cfi)
            val pathMatches = targetPath == rangePath || targetPath.startsWith(rangePath)
            val offsetMatches = charOffset >= range.startOffset && charOffset < range.endOffset
            pathMatches && offsetMatches
        }

        val foundRange = matches.maxByOrNull { CfiUtils.getPath(it.cfi).length }

        return if (foundRange != null) {
            val chapterStartPage = chapterStartPageIndices[chapterIndex]
            if (chapterStartPage == null) {
                null
            } else {
                val absolutePage = chapterStartPage + foundRange.pageInChapter
                absolutePage
            }
        } else {
            null
        }
    }

    private fun findPageInChapterForLocator(locator: Locator, chapterPages: List<Page>): Int? {
        val targetChapterIndex = locator.chapterIndex
        chapterTextRangeIndex[targetChapterIndex]
            ?.firstOrNull { range ->
                range.blockIndex == locator.blockIndex &&
                    (locator.charOffset in range.startOffset..<range.endOffset ||
                        (range.startOffset == range.endOffset && locator.charOffset == range.startOffset))
            }
            ?.let { range ->
                Timber.tag("POS_DIAG").i("findPageInChapterForLocator: FOUND via runtime index on pageInChapter ${range.pageInChapter}")
                return range.pageInChapter
            }

        chapterPageNavigationIndex[targetChapterIndex]
            ?.firstOrNull { locator.blockIndex in it.firstBlockIndex..it.lastBlockIndex }
            ?.let { entry ->
                Timber.tag("POS_DIAG").w("findPageInChapterForLocator: Using block-range fallback pageInChapter ${entry.pageInChapter}")
                return entry.pageInChapter
            }

        var fallbackPageInChapter = -1

        for ((pageIndex, page) in chapterPages.withIndex()) {
            val allTextBlocks = getAllTextBlocks(page.content)
            if (allTextBlocks.any { it.blockIndex == locator.blockIndex }) {
                Timber.tag("POS_DIAG").d("findPageInChapterForLocator: Found target blockIndex ${locator.blockIndex} on PageInChapter $pageIndex")
            }
            for (textBlock in allTextBlocks) {
                if (textBlock.blockIndex == locator.blockIndex) {
                    if (fallbackPageInChapter == -1) {
                        fallbackPageInChapter = pageIndex
                    }
                    val startOffsetOnPage = textBlock.startCharOffsetInSource
                    val endOffsetOnPage = startOffsetOnPage + textBlock.content.length

                    Timber.tag("POS_DIAG").d(" -> Block Match: page=$pageIndex, targetOffset=${locator.charOffset}, blockRange=[$startOffsetOnPage, $endOffsetOnPage]")

                    val isInside = locator.charOffset in startOffsetOnPage..<endOffsetOnPage
                    if (isInside) {
                        Timber.tag("POS_DIAG").i("findPageInChapterForLocator: FOUND match on pageInChapter $pageIndex")
                        return pageIndex
                    }

                    if (textBlock.content.isEmpty() && locator.charOffset == startOffsetOnPage) {
                        Timber.tag("POS_DIAG").i("findPageInChapterForLocator: FOUND empty block match on pageInChapter $pageIndex")
                        return pageIndex
                    }
                }
            }

            if (fallbackPageInChapter == -1) {
                for (block in page.content) {
                    if (block.blockIndex == locator.blockIndex) {
                        fallbackPageInChapter = pageIndex
                        break
                    }
                }
            }
        }

        if (fallbackPageInChapter != -1) {
            Timber.tag("POS_DIAG").w("findPageInChapterForLocator: Exact offset not found, using block-start fallback pageInChapter $fallbackPageInChapter")
            return fallbackPageInChapter
        }

        return null
    }

    suspend fun findPageForLocator(locator: Locator): Int? {
        val targetChapterIndex = locator.chapterIndex
        Timber.tag("POS_DIAG").d("findPageForLocator: Searching for $locator")

        val chapterPages = ensureChapterPaginated(targetChapterIndex)
        val chapterStartPage = chapterStartPageIndices[targetChapterIndex] ?: 0

        Timber.tag("POS_DIAG").d("findPageForLocator: targetChapterIndex=$targetChapterIndex, chapterStartPage=$chapterStartPage, chapterPages.size=${chapterPages?.size}")

        if (chapterPages.isNullOrEmpty()) {
            Timber.e("Locator navigation failed: Could not paginate target chapter $targetChapterIndex.")
            return null
        }

        val pageInChapter = findPageInChapterForLocator(locator, chapterPages) ?: run {
            Timber.tag("POS_DIAG").e("findPageForLocator: FAILED to resolve locator in chapter $targetChapterIndex")
            return null
        }

        val finalPageIndex = chapterStartPage + pageInChapter
        Timber.tag("POS_DIAG").i("findPageForLocator: FOUND absolute page $finalPageIndex")
        return finalPageIndex
    }

    fun getLocatorForPage(pageIndex: Int): Locator? {
        val chapterIndex = findChapterIndexForPage(pageIndex) ?: return null
        val chStart = chapterStartPageIndices[chapterIndex] ?: 0

        Timber.tag("POS_DIAG").d("getLocatorForPage: Request pageIndex=$pageIndex. Resolved chapterIndex=$chapterIndex (starts at $chStart). PageInChapter=${pageIndex - chStart}")
        chapterPageNavigationIndex[chapterIndex]?.getOrNull(pageIndex - chStart)?.let { entry ->
            entry.firstTextBlockIndex?.let { blockIndex ->
                return Locator(
                    chapterIndex = chapterIndex,
                    blockIndex = blockIndex,
                    charOffset = entry.firstTextCharOffset
                )
            }
            if (entry.firstBlockIndex >= 0) {
                return Locator(
                    chapterIndex = chapterIndex,
                    blockIndex = entry.firstBlockIndex,
                    charOffset = 0
                )
            }
        }

        val pageContent = getPageContent(pageIndex) ?: return null

        Timber.tag("POS_DIAG").d("getLocatorForPage: Inspecting page $pageIndex (chapter=$chapterIndex). Total top-level blocks=${pageContent.content.size}")

        val allTextBlocks = getAllTextBlocks(pageContent.content)
        val firstTextBlock = allTextBlocks.firstOrNull { it.content.text.isNotBlank() } ?: allTextBlocks.firstOrNull()

        Timber.tag("POS_DIAG").d("getLocatorForPage: allTextBlocks count=${allTextBlocks.size}. Selected blockIndex=${firstTextBlock?.blockIndex}, charOffset=${firstTextBlock?.startCharOffsetInSource}, text snippet='${firstTextBlock?.content?.text?.take(20)?.replace("\n", " ")}'")

        if (firstTextBlock != null) {
            val locator = Locator(
                chapterIndex = chapterIndex,
                blockIndex = firstTextBlock.blockIndex,
                charOffset = firstTextBlock.startCharOffsetInSource
            )
            Timber.tag("POS_DIAG").d("getLocatorForPage: Generated $locator for absolute page $pageIndex")
            return locator
        } else {
            val firstBlock = pageContent.content.firstOrNull() ?: return null
            val locator = Locator(
                chapterIndex = chapterIndex,
                blockIndex = firstBlock.blockIndex,
                charOffset = 0
            )
            Timber.tag("POS_DIAG").d("getLocatorForPage: Generated fallback $locator for absolute page $pageIndex")
            return locator
        }
    }

    override fun findPageForCfi(chapterIndex: Int, cfi: String, onResult: (pageIndex: Int) -> Unit) {
        coroutineScope.launch(Dispatchers.IO) {
            Timber.i("findPageForCfi: Starting search for CFI: '$cfi' in chapter: '$chapterIndex'")

            val chapterPages = ensureChapterPaginated(chapterIndex)
            val chapterStartPage = ensureStableStartPageForChapter(chapterIndex)
            Timber.d("findPageForCfi: Chapter $chapterIndex starts at absolute page $chapterStartPage.")

            if (chapterPages == null || chapterStartPage == null) {
                Timber.e("CFI Navigation failed: Could not stabilize target chapter $chapterIndex.")
                return@launch
            }
            Timber.d("findPageForCfi: Chapter $chapterIndex has ${chapterPages.size} pages.")

            val (basePath, offset) = cfi.split(':').let {
                it[0] to (it.getOrNull(1)?.toIntOrNull() ?: 0)
            }

            val sanitizedBasePath = if (basePath.startsWith("/2/")) {
                "/" + basePath.substring(3)
            } else {
                basePath
            }

            Timber.d("findPageForCfi: Parsed CFI into sanitizedBasePath='$sanitizedBasePath' (from '$basePath') and offset=$offset.")

            var targetPageInChapter = 0
            var foundMatch = false

            pageLoop@ for ((pageIndex, page) in chapterPages.withIndex()) {
                for (block in page.content) {
                    val blockCfi = block.cfi ?: continue

                    if (blockCfi.endsWith(sanitizedBasePath)) {
                        val index = blockCfi.lastIndexOf(sanitizedBasePath)
                        val isGenuineSuffix = (index == 0) || (index > 0 && blockCfi[index - 1] == '/')
                        if (!isGenuineSuffix) continue

                        Timber.d("findPageForCfi: Potential CFI match on page $pageIndex. Input base: '$basePath', Block CFI: '$blockCfi'.")

                        val blockMatches = when (block) {
                            is TextContentBlock -> {
                                val start = block.startCharOffsetInSource
                                val end = block.endCharOffsetInSource
                                val contentLength = block.content.length
                                val finalEnd = if (end >= 0) end else (start + contentLength)
                                val match = offset in start..<finalEnd
                                Timber.d("findPageForCfi: TextBlock check: offset ($offset) in range [$start, $finalEnd)? -> $match")
                                match
                            }
                            else -> {
                                val match = offset == 0
                                Timber.d("findPageForCfi: Non-TextBlock check: offset is 0? -> $match")
                                match
                            }
                        }

                        if (blockMatches) {
                            targetPageInChapter = pageIndex
                            foundMatch = true
                            Timber.i("findPageForCfi: SUCCESS. Found precise match on page $pageIndex of chapter.")
                            break@pageLoop
                        }
                    }
                }
            }

            if (!foundMatch) {
                Timber.w("Could not find a precise character-offset match for CFI '$cfi'. Falling back to best-effort suffix match.")
                var bestMatchLength = -1
                chapterPages.forEachIndexed { pageIndex, page ->
                    for (block in page.content) {
                        val blockCfi = block.cfi ?: continue
                        if (blockCfi.endsWith(sanitizedBasePath)) {
                            val index = blockCfi.lastIndexOf(sanitizedBasePath)
                            val isGenuineSuffix = (index == 0) || (index > 0 && blockCfi[index - 1] == '/')
                            if (isGenuineSuffix && blockCfi.length > bestMatchLength) {
                                targetPageInChapter = pageIndex
                                bestMatchLength = blockCfi.length
                                Timber.d("findPageForCfi: Fallback found better prefix match on page $pageIndex. New best match length: $bestMatchLength (Block CFI: '$blockCfi')")
                            }
                        }
                    }
                }
            }

            val finalPageIndex = chapterStartPage + targetPageInChapter
            Timber.i("findPageForCfi: Search complete. finalPageIndex: $finalPageIndex (start: $chapterStartPage + pageInChapter: $targetPageInChapter)")
            withContext(Dispatchers.Main) { onResult(finalPageIndex) }
        }
    }

    private fun resolveHref(currentChapterPath: String, href: String): Pair<String?, String?> {
        try {
            val decodedHref = URLDecoder.decode(href, "UTF-8")
            val (pathPart, anchorPart) = decodedHref.split('#', limit = 2).let {
                if (it.size > 1) it[0] to it[1] else it[0] to null
            }

            if (pathPart.isBlank()) {
                Timber.d("Href path is blank, resolving to current chapter: $currentChapterPath")
                return currentChapterPath to anchorPart
            }

            val targetPath = URI(currentChapterPath).resolve(pathPart).normalize().path
            Timber.d("Resolved href '$href' to target path '$targetPath'")

            val targetChapter = chapters.find {
                try {
                    URI(it.absPath).normalize().path == targetPath
                } catch (_: Exception) {
                    false
                }
            }

            if (targetChapter == null) {
                Timber.w("Could not find a matching chapter for path: $targetPath")
            }

            return targetChapter?.absPath to anchorPart
        } catch (e: Exception) {
            Timber.e(e, "Failed to resolve href: $href")
            return null to null
        }
    }
}
