// PdfPageComposable
@file:Suppress(
    "RemoveRedundantQualifierName", "COMPOSE_APPLIER_CALL_MISMATCH", "UnusedVariable", "unused"
)
package com.aryan.reader.pdf

import android.graphics.Bitmap
import android.graphics.BitmapShader
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.Shader
import android.util.LruCache
import androidx.activity.compose.BackHandler
import androidx.compose.ui.graphics.drawscope.withTransform
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.animation.splineBasedDecay
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.calculateCentroid
import androidx.compose.foundation.gestures.calculatePan
import androidx.compose.foundation.gestures.calculateZoom
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.drag
import androidx.compose.foundation.gestures.waitForUpOrCancellation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.lerp
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.ImageShader
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.ShaderBrush
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.TileMode
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.clipRect
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.input.pointer.changedToUp
import androidx.compose.ui.input.pointer.isPrimaryPressed
import androidx.compose.ui.input.pointer.isSecondaryPressed
import androidx.compose.ui.input.pointer.isTertiaryPressed
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChanged
import androidx.compose.ui.input.pointer.util.VelocityTracker
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isSpecified
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.lerp
import androidx.compose.ui.window.PopupPositionProvider
import androidx.compose.ui.zIndex
import androidx.core.graphics.createBitmap
import androidx.core.graphics.scale
import androidx.core.graphics.set
import com.aryan.reader.R
import com.aryan.reader.SearchResult
import com.aryan.reader.isCanvasSafeBitmap
import com.aryan.reader.loadReaderTextureBitmap
import com.aryan.reader.ml.SpeechBubble
import com.aryan.reader.pdf.data.PdfAnnotation
import com.aryan.reader.pdf.data.PdfTextBox
import com.aryan.reader.pdf.data.VirtualPage
import com.aryan.reader.pdf.ocr.OcrElement
import com.aryan.reader.pdf.ocr.OcrResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import timber.log.Timber
import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.coroutines.cancellation.CancellationException
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.math.sin
import kotlin.math.sqrt
import android.graphics.Color as AndroidColor
import android.graphics.Paint as NativePaint

enum class Handle {
    START, END
}

enum class AnnotationType {
    INK, TEXT
}

enum class InkType {
    PEN, HIGHLIGHTER, HIGHLIGHTER_ROUND, ERASER, FOUNTAIN_PEN, PENCIL, TEXT
}

internal fun shouldReportPdfPageCamera(
    isZoomEnabled: Boolean,
    isVerticalScroll: Boolean,
    isScrollLocked: Boolean,
    lockedState: Triple<Float, Float, Float>?,
    hasAppliedLockedState: Boolean
): Boolean {
    return !isZoomEnabled ||
        isVerticalScroll ||
        !isScrollLocked ||
        lockedState == null ||
        hasAppliedLockedState
}

internal fun initialPdfPageCamera(
    isZoomEnabled: Boolean,
    isVerticalScroll: Boolean,
    isScrollLocked: Boolean,
    lockedState: Triple<Float, Float, Float>?
): Pair<Float, Offset> {
    return if (isZoomEnabled && !isVerticalScroll && isScrollLocked && lockedState != null) {
        lockedState.first to Offset(lockedState.second, lockedState.third)
    } else {
        1f to Offset.Zero
    }
}

internal fun shouldResetPdfZoomAfterBubbleZoomCleanup(
    isBubbleZoomModeActive: Boolean,
    scale: Float,
    isVerticalScroll: Boolean,
    isZoomEnabled: Boolean,
    isScrollLocked: Boolean
): Boolean {
    return !isBubbleZoomModeActive &&
        scale > 1f &&
        !isVerticalScroll &&
        isZoomEnabled &&
        !isScrollLocked
}

data class EmbeddedAnnotation(
    val index: Int,
    val subtype: Int,
    val rect: android.graphics.RectF,
    val contents: String?,
    val author: String?,
    val name: String?,
    val inReplyTo: String?,
    val replies: MutableList<EmbeddedAnnotation> = mutableListOf()
)

data class PdfPoint(val x: Float, val y: Float, val timestamp: Long = 0L)

data class PdfTile(val bitmap: Bitmap, val renderRect: Rect, val tileId: Int, val renderScale: Float = 1f)

internal fun pdfRenderPageId(documentKey: String, pageIndex: Int, virtualPage: VirtualPage?): String {
    val sourcePageId = when (virtualPage) {
        is VirtualPage.BlankPage -> "BLANK_${virtualPage.id}"
        is VirtualPage.PdfPage -> "PDF_${virtualPage.pdfIndex}"
        null -> "PDF_$pageIndex"
    }
    return "$documentKey:$sourcePageId"
}

private fun Throwable.readablePdfErrorDetail(): String {
    return localizedMessage?.takeIf { it.isNotBlank() }
        ?: javaClass.simpleName.takeIf { it.isNotBlank() }
        ?: "Unknown error"
}

private const val PDF_TILE_SIZE_DP = 256
private const val PDF_MAX_TILE_BITMAP_SIZE_PX = 3072
private const val PDF_MAX_DRAW_BITMAP_BYTES = 64L * 1024L * 1024L
private const val PDF_MAX_DRAW_BITMAP_DIMENSION_PX = 4096
private const val PDF_TILE_SCALE_TOLERANCE = 0.06f
private const val PDF_TILE_IDLE_RENDER_DELAY_MS = 90L
private const val PDF_PAGINATION_PAN_FLING_MIN_VELOCITY = 600f
private const val PDF_PAGINATION_PAN_FLING_MULTIPLIER = 0.72f

enum class LinkSource {
    ANNOTATION, TEXT_CONTENT
}

data class PageLink(
    val highlightBounds: Rect,
    val tapBounds: Rect,
    val url: String?,
    val destPageIdx: Int?,
    val source: LinkSource
)

private data class ExpandedBubbleRender(
    val bitmap: Bitmap,
    val zoomFactor: Float
)

private fun computeDynamicBubbleZoomFactor(
    bubbleBounds: RectF,
    viewportWidth: Float,
    viewportHeight: Float
): Float {
    if (bubbleBounds.width() <= 0f || bubbleBounds.height() <= 0f) return 1.5f
    val targetWidth = viewportWidth * 0.6f
    val targetHeight = viewportHeight * 0.32f
    return min(targetWidth / bubbleBounds.width(), targetHeight / bubbleBounds.height())
        .coerceIn(1.35f, 4.25f)
}

private fun isTapInsideBubble(
    bubble: SpeechBubble,
    tapX: Float,
    tapY: Float,
    hitSlopPx: Float
): Boolean {
    val expandedBounds = RectF(bubble.bounds)
    expandedBounds.inset(-hitSlopPx, -hitSlopPx)
    if (!expandedBounds.contains(tapX, tapY)) return false

    val mask = bubble.maskBitmap ?: return true
    if (!bubble.bounds.contains(tapX, tapY)) return true

    val normalizedX = ((tapX - bubble.bounds.left) / bubble.bounds.width()).coerceIn(0f, 0.999f)
    val normalizedY = ((tapY - bubble.bounds.top) / bubble.bounds.height()).coerceIn(0f, 0.999f)
    val maskX = (normalizedX * mask.width).toInt().coerceIn(0, mask.width - 1)
    val maskY = (normalizedY * mask.height).toInt().coerceIn(0, mask.height - 1)
    return AndroidColor.alpha(mask.getPixel(maskX, maskY)) > 24
}

private suspend fun renderExpandedBubbleBitmap(
    document: ReaderDocument,
    pageIndex: Int,
    bubbleBounds: RectF,
    pageWidth: Int,
    pageHeight: Int,
    renderScale: Float
): Bitmap? = withContext(Dispatchers.IO) {
    if (pageWidth <= 0 || pageHeight <= 0 || bubbleBounds.width() <= 0f || bubbleBounds.height() <= 0f) {
        return@withContext null
    }

    document.openPage(pageIndex)?.use { page ->
        val safeRenderScale = safePdfBitmapRenderScale(
            contentWidth = bubbleBounds.width(),
            contentHeight = bubbleBounds.height(),
            requestedScale = renderScale
        )
        val cropWidth = (bubbleBounds.width() * safeRenderScale).roundToInt().coerceAtLeast(1)
        val cropHeight = (bubbleBounds.height() * safeRenderScale).roundToInt().coerceAtLeast(1)
        val bitmap = createBitmap(cropWidth, cropHeight)

        try {
            page.renderPageBitmap(
                bitmap = bitmap,
                startX = (-bubbleBounds.left * safeRenderScale).roundToInt(),
                startY = (-bubbleBounds.top * safeRenderScale).roundToInt(),
                drawSizeX = (pageWidth * safeRenderScale).roundToInt().coerceAtLeast(cropWidth),
                drawSizeY = (pageHeight * safeRenderScale).roundToInt().coerceAtLeast(cropHeight),
                renderAnnot = true
            )
            bitmap
        } catch (t: Throwable) {
            bitmap.recycle()
            Timber.tag("BubbleZoom").w(t, "Failed to render expanded bubble bitmap for page $pageIndex")
            null
        }
    }
}

private fun safePdfBitmapRenderScale(
    contentWidth: Float,
    contentHeight: Float,
    requestedScale: Float
): Float {
    if (contentWidth <= 0f || contentHeight <= 0f || requestedScale <= 0f) return 1f

    val requestedWidth = contentWidth * requestedScale
    val requestedHeight = contentHeight * requestedScale
    val requestedBytes = requestedWidth.toDouble() * requestedHeight.toDouble() * 4.0
    val byteScale = sqrt(PDF_MAX_DRAW_BITMAP_BYTES.toDouble() / requestedBytes.coerceAtLeast(1.0))
    val dimensionScale = PDF_MAX_DRAW_BITMAP_DIMENSION_PX.toDouble() /
        max(requestedWidth, requestedHeight).toDouble().coerceAtLeast(1.0)
    val limiter = min(1.0, min(byteScale, dimensionScale)).coerceAtLeast(0.01)
    return (requestedScale.toDouble() * limiter).coerceAtLeast(0.01).toFloat()
}

object PdfInkGeometry {
    fun calculateFountainPenPoints(
        points: List<PdfPoint>, baseWidth: Float, pageWidth: Float, pageHeight: Float
    ): Pair<List<Offset>, List<Offset>> {
        if (points.size < 2) return Pair(emptyList(), emptyList())

        if (points.size % 50 == 0) {
            Timber.tag("FountainPenDebug").d(
                "Calculate Points: PWidth=$pageWidth, PHeight=$pageHeight, BaseW=$baseWidth, Pts=${points.size}"
            )
        }

        val leftSide = mutableListOf<Offset>()
        val rightSide = mutableListOf<Offset>()

        val computedWidths = FloatArray(points.size)
        computedWidths[0] = baseWidth

        val velocityFactor = 300f

        for (i in 1 until points.size) {
            val p1 = points[i - 1]
            val p2 = points[i]

            val dxNorm = p2.x - p1.x
            val dyNorm = p2.y - p1.y
            val aspect = if (pageWidth > 0 && pageHeight > 0) pageHeight / pageWidth else 1f
            val distNorm = sqrt(dxNorm * dxNorm + (dyNorm * aspect) * (dyNorm * aspect))

            val timeDelta = (p2.timestamp - p1.timestamp).coerceAtLeast(1)
            val velocityNorm = distNorm / timeDelta

            val targetWidth = (baseWidth * (1f / (1f + velocityNorm * velocityFactor))).coerceIn(
                baseWidth * 0.2f, baseWidth * 1.4f
            )

            computedWidths[i] = computedWidths[i - 1] * 0.6f + targetWidth * 0.4f

            if (i < 5) {
                Timber.tag("FountainPenDebug").v(
                    "Pt[$i]: dt=$timeDelta, velNorm=$velocityNorm, width=${computedWidths[i]} (base=$baseWidth)"
                )
            }
        }

        for (i in 0 until points.size - 1) {
            val pCurrent = points[i]
            val pNext = points[i + 1]

            val curX = pCurrent.x * pageWidth
            val curY = pCurrent.y * pageHeight
            val nextX = pNext.x * pageWidth
            val nextY = pNext.y * pageHeight

            val angle = atan2(nextY - curY, nextX - curX)
            val normalAngle = angle - (PI / 2f).toFloat()

            val w = computedWidths[i] / 2f

            leftSide.add(Offset((curX + cos(normalAngle) * w), (curY + sin(normalAngle) * w)))
            rightSide.add(Offset((curX - cos(normalAngle) * w), (curY - sin(normalAngle) * w)))
        }

        val lastIdx = points.lastIndex
        val lastP = points[lastIdx]
        val prevP = points[lastIdx - 1]

        val lastX = lastP.x * pageWidth
        val lastY = lastP.y * pageHeight
        val prevX = prevP.x * pageWidth
        val prevY = prevP.y * pageHeight

        val lastAngle = atan2(lastY - prevY, lastX - prevX)
        val lastNormal = lastAngle - (PI / 2f).toFloat()
        val lastW = computedWidths[lastIdx] / 2f

        leftSide.add(Offset((lastX + cos(lastNormal) * lastW), (lastY + sin(lastNormal) * lastW)))
        rightSide.add(Offset((lastX - cos(lastNormal) * lastW), (lastY - sin(lastNormal) * lastW)))

        return Pair(leftSide, rightSide)
    }
}

internal object PdfBitmapPool {
    private val pool = ConcurrentLinkedQueue<Bitmap>()
    private const val MAX_POOL_SIZE = 4

    fun get(width: Int, height: Int): Bitmap {
        val iterator = pool.iterator()
        while (iterator.hasNext()) {
            val b = iterator.next()
            if (b.width == width && b.height == height && !b.isRecycled) {
                iterator.remove()
                b.eraseColor(AndroidColor.TRANSPARENT)
                return b
            }
        }
        return createBitmap(width, height)
    }

    fun get(size: Int): Bitmap = get(size, size)

    fun recycle(bitmap: Bitmap) {
        // Overflow bitmaps are left for GC; HWUI may still reference recently drawn bitmaps.
        if (!bitmap.isRecycled && pool.size < MAX_POOL_SIZE) {
            pool.offer(bitmap)
        }
    }

    fun clear() {
        while (!pool.isEmpty()) {
            pool.poll()
        }
    }
}

internal object PdfThumbnailCache {
    private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
    private val cacheSize = maxMemory / 8

    private data class CacheEntry(val bitmap: Bitmap, val sizeKb: Int)

    private val memoryCache = object : LruCache<String, CacheEntry>(cacheSize) {
        override fun sizeOf(key: String, entry: CacheEntry): Int {
            return entry.sizeKb
        }
    }

    fun get(pageId: String): Bitmap? {
        return memoryCache.get(pageId)?.bitmap?.takeUnless { it.isRecycled }
    }

    fun put(pageId: String, bitmap: Bitmap) {
        if (get(pageId) == null) {
            val sizeKb = (bitmap.allocationByteCount / 1024).coerceAtLeast(1)
            memoryCache.put(pageId, CacheEntry(bitmap, sizeKb))
        }
    }

    fun clear() {
        memoryCache.evictAll()
    }
}

@Stable
data class StableHolder<T>(val item: T)

@Stable
data class PageStaticData(
    val bitmap: StableHolder<Bitmap?>,
    val tiles: StableHolder<List<PdfTile>>,
    val effectiveScale: Float,
    val centeringOffsetX: Float,
    val centeringOffsetY: Float,
    val canvasWidth: Float,
    val canvasHeight: Float,
    val targetWidth: Int,
    val targetHeight: Int,
    val colorFilter: StableHolder<ColorFilter?>,
    val isDarkMode: Boolean,
    val excludeImages: Boolean,
    val imageRects: StableHolder<List<android.graphics.Rect>>,
    val textureBitmap: StableHolder<ImageBitmap?>,
    val textureAlpha: Float,
    val textureBlendMode: BlendMode
)

@Stable
data class PageSelectionData(
    val pageLinks: StableHolder<List<PageLink>>,
    val showAllTextHighlights: Boolean,
    val actualBitmapWidthPx: Int,
    val actualBitmapHeightPx: Int,
    val mergedAllTextPageHighlightRects: StableHolder<List<Rect>>,
    val mergedTtsHighlightRects: StableHolder<List<Rect>>,
    val mergedSearchFocusedRects: StableHolder<List<Rect>>,
    val mergedSearchAllRects: StableHolder<List<Rect>>,
    val searchHighlightMode: SearchHighlightMode,
    val ocrHoverHighlights: StableHolder<List<RectF>>,
    val mergedSelectionRects: StableHolder<List<Rect>>,
    val centeringOffsetX: Float,
    val centeringOffsetY: Float,
    val linkHighlightColor: Color,
    val scrimColorForTextHighlight: Color,
    val allTextPageHighlightColor: Color,
    val ttsHighlightColor: Color,
    val selectionHighlightColor: Color,
    val pageIndex: Int,
    val userHighlightScreenRects: StableHolder<List<Pair<PdfUserHighlight, List<Rect>>>>,
    val customHighlightColors: StableHolder<Map<PdfHighlightColor, Color>>
)

@OptIn(FlowPreview::class)
@Suppress("unused")
@Composable
internal fun PdfPageComposable(
    pdfDocument: StableHolder<ReaderDocument>,
    documentKey: String,
    pageIndex: Int,
    totalPages: Int,
    modifier: Modifier = Modifier,
    virtualPage: VirtualPage? = null,
    onScaleChanged: (Float) -> Unit,
    externalScale: Float = 1f,
    showAllTextHighlights: Boolean,
    onHighlightLoading: (Boolean) -> Unit,
    searchQuery: String = "",
    searchHighlightMode: SearchHighlightMode = SearchHighlightMode.ALL,
    searchResultToHighlight: SearchResult?,
    ocrHoverHighlights: StableHolder<List<RectF>> = StableHolder(emptyList()),
    onPreSingleTap: ((Offset) -> Boolean)? = null,
    onSingleTap: (Offset?) -> Unit,
    isProUser: Boolean,
    onShowDictionaryUpsellDialog: () -> Unit,
    onWordSelectedForAiDefinition: (String) -> Unit,
    onTranslateText: (String) -> Unit,
    onSearchText: (String) -> Unit,
    ttsHighlightData: TtsHighlightData?,
    onLinkClicked: (String) -> Unit,
    onInternalLinkClicked: (Int) -> Unit,
    isBookmarked: Boolean,
    onOcrStateChange: (Boolean) -> Unit,
    onBookmarkClick: () -> Unit,
    onOcrModelDownloading: () -> Unit = {},
    onTwoFingerSwipe: (direction: Int) -> Unit = {},
    placeholderBitmap: Bitmap? = null,
    isZoomEnabled: Boolean = true,
    isScrolling: Boolean = false,
    lazyListState: LazyListState? = null,
    isVerticalScroll: Boolean = false,
    showPageNumberOverlay: Boolean = true,
    visualScaleProvider: () -> Float = { 1f },
    clearSelectionTrigger: Long = 0L,
    resetZoomTrigger: Long = 0L,
    onTtsHighlightCenterCalculated: ((Float) -> Unit)? = null,
    onSearchHighlightCenterCalculated: ((Float) -> Unit)? = null,
    activeTheme: com.aryan.reader.ReaderTheme = com.aryan.reader.ReaderTheme("no_theme", "No Theme", Color.Unspecified, Color.Unspecified, false),
    activeTextureAlpha: Float = 0.55f,
    excludeImages: Boolean = false,
    onDoubleTap: ((Offset) -> Unit)? = null,
    isEditMode: Boolean = false,
    drawingState: PdfDrawingState? = null,
    pageAnnotations: () -> List<PdfAnnotation> = { emptyList() },
    onDrawStart: (PdfPoint, Boolean) -> Unit = { _, _ -> },
    onDraw: (PdfPoint, Boolean) -> Unit = { _, _ -> },
    onDrawEnd: () -> Unit = {},
    visibleScreenRect: () -> IntRect? = { null },
    selectedTool: InkType = InkType.PEN,
    richTextController: RichTextController? = null,
    textBoxes: List<PdfTextBox> = emptyList(),
    selectedTextBoxId: String? = null,
    onTextBoxChange: (PdfTextBox) -> Unit = {},
    onTextBoxSelect: (String) -> Unit = {},
    onTextBoxDragStart: (PdfTextBox, Offset, Offset) -> Unit = { _, _, _ -> },
    onTextBoxDrag: (Offset) -> Unit = {},
    onTextBoxDragEnd: () -> Unit = {},
    onDragPageTurn: (Int) -> Unit = {},
    draggingBoxId: String? = null,
    isScrollLocked: Boolean = false,
    isVisible: Boolean = true,
    isActivePage: Boolean = true,
    isBubbleZoomModeActive: Boolean = false,
    isStylusOnlyMode: Boolean = false,
    stylusButtonHovering: Boolean = false,
    isAutoScrollPlaying: Boolean = false,
    isHighlighterSnapEnabled: Boolean = false,
    userHighlights: List<PdfUserHighlight> = emptyList(),
    onHighlightAdd: (Int, Pair<Int, Int>, String, PdfHighlightColor) -> Unit = { _,_,_,_ -> },
    onHighlightUpdate: (String, PdfHighlightColor) -> Unit = { _,_ -> },
    onHighlightDelete: (String) -> Unit = {},
    onNoteRequested: (String?) -> Unit = {},
    onTts: (Int, Int) -> Unit = { _, _ -> },
    activeToolThickness: Float = 0f,
    eraserToolThickness: Float = 0f,
    customHighlightColors: Map<PdfHighlightColor, Color> = emptyMap(),
    onPaletteClick: (() -> Unit)? = null,
    lockedState: Triple<Float, Float, Float>? = null,
    onZoomAndPanChanged: ((Float, Offset) -> Unit)? = null,
    onDetectBubbles: suspend (Int, Bitmap) -> List<SpeechBubble> = { _, _ -> emptyList() },
    onShowPanelPopup: (Bitmap) -> Unit = {}
) {
    val pdfDocumentItem = pdfDocument.item
    val targetPageId = remember(documentKey, virtualPage, pageIndex) {
        pdfRenderPageId(documentKey, pageIndex, virtualPage)
    }
    var bitmapState by remember(targetPageId) { mutableStateOf(PdfThumbnailCache.get(targetPageId)) }
    var currentRenderedPageId by remember(targetPageId) { mutableStateOf<String?>(null) }
    var isLoadingPage by remember(targetPageId) { mutableStateOf(true) }
    var pageErrorMessage by remember(targetPageId) { mutableStateOf<String?>(null) }
    val density = LocalDensity.current
    val context = LocalContext.current
    val viewConfiguration = LocalViewConfiguration.current
    val coroutineScope = rememberCoroutineScope()
    var isStylusEraserOverride by remember { mutableStateOf(false) }

    var layoutCoordinates by remember { mutableStateOf<LayoutCoordinates?>(null) }

    var ocrRipplePosition by remember { mutableStateOf<Offset?>(null) }

    var isTransforming by remember { mutableStateOf(false) }
    val initialCamera = initialPdfPageCamera(
        isZoomEnabled = isZoomEnabled,
        isVerticalScroll = isVerticalScroll,
        isScrollLocked = isScrollLocked,
        lockedState = lockedState
    )
    var scale by remember(targetPageId) { mutableFloatStateOf(initialCamera.first) }
    var offset by remember(targetPageId) { mutableStateOf(initialCamera.second) }
    var paginationPanFlingJob by remember { mutableStateOf<Job?>(null) }
    var hasAppliedLockedPaginationState by remember(targetPageId) {
        mutableStateOf(initialCamera.second != Offset.Zero || initialCamera.first != 1f)
    }
    val shouldReportCamera = shouldReportPdfPageCamera(
        isZoomEnabled = isZoomEnabled,
        isVerticalScroll = isVerticalScroll,
        isScrollLocked = isScrollLocked,
        lockedState = lockedState,
        hasAppliedLockedState = hasAppliedLockedPaginationState
    )

    LaunchedEffect(scale, offset, shouldReportCamera) {
        if (shouldReportCamera) {
            onZoomAndPanChanged?.invoke(scale, offset)
        }
    }

    val currentOnSingleTap by rememberUpdatedState(onSingleTap)
    val currentOnPreSingleTap by rememberUpdatedState(onPreSingleTap)
    val currentOnDoubleTap by rememberUpdatedState(onDoubleTap)

    val effectiveScale = if (isZoomEnabled && !isVerticalScroll) scale else externalScale
    val effectiveOffset = if (isZoomEnabled && !isVerticalScroll) offset else Offset.Zero

    var eraserPosition by remember { mutableStateOf<Offset?>(null) }

    SideEffect {
        if (drawingState?.currentAnnotation?.pageIndex == pageIndex) {
            Timber.tag("PdfDrawPerf").v(
                "PAGE EFFECTIVE SCALE: Page $pageIndex = $effectiveScale (ZoomEnabled=$isZoomEnabled)"
            )
        }
    }

    val isPdfPage = virtualPage == null || virtualPage is VirtualPage.PdfPage
    val pdfPageIndex = (virtualPage as? VirtualPage.PdfPage)?.pdfIndex ?: pageIndex

    var tiles by remember(targetPageId) { mutableStateOf<List<PdfTile>>(emptyList()) }
    val tileSizeDp = PDF_TILE_SIZE_DP.dp
    val tileSizePx = with(LocalDensity.current) { tileSizeDp.toPx().toInt() }
    val latestEffectiveScale by rememberUpdatedState(effectiveScale)
    val latestEffectiveOffset by rememberUpdatedState(effectiveOffset)

    SideEffect {
        Timber.tag("PdfDrawPerf")
            .v("PAGE RECOMPOSE: Page $pageIndex (EffectiveScale: $effectiveScale)")
    }

    val teardropWidthDp = 24.dp
    val teardropHeightDp = 24.dp
    val teardropWidthPxState = remember(density) {
        derivedStateOf { with(density) { teardropWidthDp.toPx() / visualScaleProvider() } }
    }
    val teardropHeightPxState = remember(density) {
        derivedStateOf { with(density) { teardropHeightDp.toPx() / visualScaleProvider() } }
    }

    val handleTouchExpansionDp = 8.dp
    val handleTouchWidthPxState = remember(density) {
        derivedStateOf {
            with(density) {
                (teardropWidthDp + handleTouchExpansionDp).toPx() / visualScaleProvider()
            }
        }
    }
    val handleTouchHeightPxState = remember(density) {
        derivedStateOf {
            with(density) {
                (teardropHeightDp + handleTouchExpansionDp).toPx() / visualScaleProvider()
            }
        }
    }

    val selectionCharRange = remember(targetPageId) { mutableStateOf<Pair<Int, Int>?>(null) }
    var activeDraggingHandle by remember(targetPageId) { mutableStateOf<Handle?>(null) }
    var selectedWordScreenRects by remember(targetPageId) { mutableStateOf<List<Rect>>(emptyList()) }
    val startHandleContentPosition = remember(targetPageId) { mutableStateOf<Offset?>(null) }
    val endHandleContentPosition = remember(targetPageId) { mutableStateOf<Offset?>(null) }

    var actualBitmapWidthPx by remember(targetPageId) { mutableIntStateOf(0) }
    var actualBitmapHeightPx by remember(targetPageId) { mutableIntStateOf(0) }
    var currentPageRotation by remember(targetPageId) { mutableIntStateOf(0) }

    val needsTilingNow = (effectiveScale > 1f || actualBitmapWidthPx > 3000 || actualBitmapHeightPx > 3000) && (isVerticalScroll || isActivePage)

    val canvasWidthPx = remember { mutableFloatStateOf(0f) }
    val canvasHeightPx = remember { mutableFloatStateOf(0f) }

    val isDarkMode = activeTheme.isDark || activeTheme.id == "reverse"

    val colorFilter = remember(activeTheme) {
        when (activeTheme.id) {
            "no_theme", "system" -> null
            "reverse" -> {
                val colorMatrix = floatArrayOf(
                    -1f,  0f,  0f,  0f, 255f,
                    0f, -1f,  0f,  0f, 255f,
                    0f,  0f, -1f,  0f, 255f,
                    0f,  0f,  0f,  1f,   0f
                )
                ColorFilter.colorMatrix(ColorMatrix(colorMatrix))
            }
            else -> {
                val bgR = activeTheme.backgroundColor.red * 255f
                val bgG = activeTheme.backgroundColor.green * 255f
                val bgB = activeTheme.backgroundColor.blue * 255f

                val fgR = activeTheme.textColor.red * 255f
                val fgG = activeTheme.textColor.green * 255f
                val fgB = activeTheme.textColor.blue * 255f

                val dr = (bgR - fgR) / 255f
                val dg = (bgG - fgG) / 255f
                val db = (bgB - fgB) / 255f

                val lumR = 0.2126f
                val lumG = 0.7152f
                val lumB = 0.0722f

                val colorMatrix = floatArrayOf(
                    dr * lumR, dr * lumG, dr * lumB, 0f, fgR,
                    dg * lumR, dg * lumG, dg * lumB, 0f, fgG,
                    db * lumR, db * lumG, db * lumB, 0f, fgB,
                    0f, 0f, 0f, 1f, 0f
                )
                ColorFilter.colorMatrix(ColorMatrix(colorMatrix))
            }
        }
    }

    val backgroundColor = remember(activeTheme, isVerticalScroll) {
        if (activeTheme.id == "no_theme" || activeTheme.id == "system") {
            if (isVerticalScroll) Color.White else Color.Black
        } else if (activeTheme.id == "reverse") {
            if (isVerticalScroll) Color.Black else Color.White
        } else {
            activeTheme.backgroundColor
        }
    }
    val textureBitmap = remember(activeTheme.textureId) {
        loadReaderTextureBitmap(context, activeTheme.textureId)
    }
    val effectiveTextureAlpha = remember(activeTheme.textureId, activeTextureAlpha) {
        if (activeTheme.textureId == null) 0f else activeTextureAlpha.coerceIn(0f, 1f)
    }
    val textureBlendMode = remember(activeTheme.textureId, activeTheme.isDark, activeTheme.id) {
        if (activeTheme.isDark || activeTheme.id == "reverse") BlendMode.Screen else BlendMode.Multiply
    }

    val centeringOffsetX by remember(canvasWidthPx.floatValue, actualBitmapWidthPx) {
        derivedStateOf { (canvasWidthPx.floatValue - actualBitmapWidthPx) / 2f }
    }
    val centeringOffsetY by remember(canvasHeightPx.floatValue, actualBitmapHeightPx) {
        derivedStateOf { (canvasHeightPx.floatValue - actualBitmapHeightPx) / 2f }
    }

    LaunchedEffect(centeringOffsetX, centeringOffsetY, pageIndex) {

    }

    var showMagnifier by remember { mutableStateOf(false) }
    var magnifierBitmapCenterTarget by remember { mutableStateOf(Offset.Zero) }
    val magnifierZoomFactor = 2.0f

    var customMenuState by remember(targetPageId) { mutableStateOf<CustomPdfMenuState?>(null) }

    val inputScale = if (isZoomEnabled && !isVerticalScroll) scale else 1f
    val inputOffset = if (isZoomEnabled && !isVerticalScroll) offset else Offset.Zero

    val screenToContentCoordinates: (Offset) -> Offset = { screenOffset ->
        val screenCenter = Offset(canvasWidthPx.floatValue / 2f, canvasHeightPx.floatValue / 2f)
        val pCanvas = ((screenOffset - inputOffset - screenCenter) / inputScale) + screenCenter
        val contentOffset = pCanvas - Offset(centeringOffsetX, centeringOffsetY)
        contentOffset
    }

    val contentToScreenCoordinates: (Offset) -> Offset = { contentOffset ->
        val pCanvas = contentOffset + Offset(centeringOffsetX, centeringOffsetY)
        val screenCenter = Offset(canvasWidthPx.floatValue / 2f, canvasHeightPx.floatValue / 2f)
        val screenOffset = (pCanvas - screenCenter) * inputScale + screenCenter + inputOffset
        screenOffset
    }

    var detectedBubbles by remember(targetPageId) { mutableStateOf<List<SpeechBubble>>(emptyList()) }
    var expandedBubbleIndex by remember(targetPageId) { mutableIntStateOf(-1) }
    var animatingBubbleIndex by remember(targetPageId) { mutableIntStateOf(-1) }
    val bubbleExpansionProgress = remember(targetPageId) { Animatable(0f) }
    var isDetectingBubbles by remember(targetPageId) { mutableStateOf(false) }
    var expandedBubbleRender by remember(targetPageId) { mutableStateOf<ExpandedBubbleRender?>(null) }
    val currentDetectedBubbles by rememberUpdatedState(detectedBubbles)
    val currentExpandedBubbleIndex by rememberUpdatedState(expandedBubbleIndex)
    val currentBubbleZoomModeActive by rememberUpdatedState(isBubbleZoomModeActive)
    val bubbleTapSlopPx = with(density) { 18.dp.toPx() }

    LaunchedEffect(expandedBubbleIndex) {
        if (expandedBubbleIndex != -1) {
            if (animatingBubbleIndex != -1 && animatingBubbleIndex != expandedBubbleIndex) {
                bubbleExpansionProgress.animateTo(0f, tween(150))
            }
            animatingBubbleIndex = expandedBubbleIndex
            bubbleExpansionProgress.animateTo(1f, tween(250, easing = androidx.compose.animation.core.FastOutSlowInEasing))
        } else {
            bubbleExpansionProgress.animateTo(0f, tween(200, easing = androidx.compose.animation.core.FastOutLinearInEasing))
            animatingBubbleIndex = -1
        }
    }

    LaunchedEffect(
        isBubbleZoomModeActive,
        isActivePage,
        isPdfPage,
        pdfPageIndex,
        bitmapState,
        actualBitmapWidthPx,
        actualBitmapHeightPx
    ) {
        Timber.tag("BubbleZoom").d("LaunchedEffect triggered. modeActive=$isBubbleZoomModeActive, activePage=$isActivePage, hasBitmap=${bitmapState != null}, dims=${actualBitmapWidthPx}x${actualBitmapHeightPx}")

        if (isBubbleZoomModeActive && isActivePage && isPdfPage && bitmapState != null && actualBitmapWidthPx > 0 && actualBitmapHeightPx > 0) {
            Timber.tag("BubbleZoom").d("Conditions met. Starting detection...")
            isDetectingBubbles = true
            try {
                val rawBubbles = onDetectBubbles(pdfPageIndex, bitmapState!!)
                Timber.tag("BubbleZoom").d("Detection complete. Found ${rawBubbles.size} raw bubbles.")

                // NEW: Scale bubbles down from render bitmap space to logical screen space
                val scaleX = actualBitmapWidthPx.toFloat() / bitmapState!!.width.toFloat()
                val scaleY = actualBitmapHeightPx.toFloat() / bitmapState!!.height.toFloat()

                val logicalBubbles = rawBubbles.map { b ->
                    b.copy(bounds = android.graphics.RectF(
                        b.bounds.left * scaleX,
                        b.bounds.top * scaleY,
                        b.bounds.right * scaleX,
                        b.bounds.bottom * scaleY
                    ))
                }

                val rowHeight = actualBitmapHeightPx * 0.1f
                detectedBubbles = logicalBubbles.sortedWith(compareBy<SpeechBubble> { (it.bounds.centerY() / rowHeight).roundToInt() }.thenBy { it.bounds.centerX() })
                expandedBubbleIndex = -1

                Timber.tag("BubbleZoom").d("Sorted logical bubbles count: ${detectedBubbles.size}")
            } catch (e: Exception) {
                Timber.tag("BubbleZoom").e(e, "Bubble detection failed with exception")
            } finally {
                isDetectingBubbles = false
            }
        } else {
            Timber.tag("BubbleZoom").d("Conditions NOT met or mode disabled. Clearing bubbles.")
            detectedBubbles = emptyList()
            expandedBubbleIndex = -1
            expandedBubbleRender?.bitmap?.takeUnless { it.isRecycled }?.recycle()
            expandedBubbleRender = null
            if (
                shouldResetPdfZoomAfterBubbleZoomCleanup(
                    isBubbleZoomModeActive = isBubbleZoomModeActive,
                    scale = scale,
                    isVerticalScroll = isVerticalScroll,
                    isZoomEnabled = isZoomEnabled,
                    isScrollLocked = isScrollLocked
                )
            ) {
                coroutineScope.launch {
                    Animatable(scale).animateTo(1f, tween(300)) {
                        scale = this.value
                        offset = Offset.Zero
                        onScaleChanged(scale)
                    }
                }
            }
        }
    }

    LaunchedEffect(
        animatingBubbleIndex,
        detectedBubbles,
        actualBitmapWidthPx,
        actualBitmapHeightPx,
        canvasWidthPx.floatValue,
        canvasHeightPx.floatValue,
        isBubbleZoomModeActive,
        isPdfPage,
        pdfPageIndex
    ) {
        val previousRender = expandedBubbleRender
        expandedBubbleRender = null
        previousRender?.bitmap?.takeUnless { it.isRecycled }?.recycle()

        if (!isBubbleZoomModeActive || !isPdfPage || animatingBubbleIndex !in detectedBubbles.indices) {
            return@LaunchedEffect
        }

        val bubble = detectedBubbles[animatingBubbleIndex]
        val zoomFactor = computeDynamicBubbleZoomFactor(
            bubbleBounds = bubble.bounds,
            viewportWidth = canvasWidthPx.floatValue.coerceAtLeast(actualBitmapWidthPx.toFloat()),
            viewportHeight = canvasHeightPx.floatValue.coerceAtLeast(actualBitmapHeightPx.toFloat())
        )
        val renderScale = (zoomFactor * 1.2f).coerceAtLeast(1.6f)
        val renderedBubble = renderExpandedBubbleBitmap(
            document = pdfDocumentItem,
            pageIndex = pdfPageIndex,
            bubbleBounds = bubble.bounds,
            pageWidth = actualBitmapWidthPx,
            pageHeight = actualBitmapHeightPx,
            renderScale = renderScale
        )

        if (renderedBubble != null) {
            expandedBubbleRender = ExpandedBubbleRender(
                bitmap = renderedBubble,
                zoomFactor = zoomFactor
            )
        }
    }

    DisposableEffect(targetPageId) {
        onDispose {
            val currentBitmap = bitmapState
            val cachedBitmap = PdfThumbnailCache.get(targetPageId)
            if (currentBitmap != null && !currentBitmap.isRecycled && currentBitmap !== cachedBitmap) {
                currentBitmap.recycle()
            }
            expandedBubbleRender?.bitmap?.takeUnless { it.isRecycled }?.recycle()
        }
    }

    @Suppress("DEPRECATION") val clipboardManager = LocalClipboardManager.current

    // OCR
    var ocrVisionTextForSelection by remember(targetPageId) { mutableStateOf<OcrResult?>(null) }
    var isPerformingOcrForSelection by remember { mutableStateOf(false) }
    var selectionMethodUsed by remember { mutableStateOf(PdfSelectionMethod.PDFIUM) }
    var ocrSelectionSymbolIndices by remember(targetPageId) { mutableStateOf<Pair<Int, Int>?>(null) }
    var allOcrSymbolsForSelection by remember(targetPageId) { mutableStateOf<List<OcrSymbolInfo>>(emptyList()) }

    var highlightedTextScreenRects by remember(targetPageId) { mutableStateOf<List<Rect>>(emptyList()) }
    val ttsHighlightColor = Color(0xFFFFECB3).copy(alpha = 0.4f)

    var allTextPageHighlightRects by remember(targetPageId) { mutableStateOf<List<Rect>>(emptyList()) }

    var accumulatedKeyboardOffset by remember { mutableFloatStateOf(0f) }

    val allTextPageHighlightColor = if (isDarkMode) {
        Color(0xFFFFEB3B).copy(alpha = 0.4f)
    } else {
        MaterialTheme.colorScheme.primary.copy(alpha = 0f)
    }

    val scrimColorForTextHighlight = if (isDarkMode) {
        Color.Transparent
    } else {
        Color.Black.copy(alpha = 0.4f)
    }

    val selectionHighlightColor = Color(0x6633B5E5)
    val mergedSelectionRects =
        remember(selectedWordScreenRects) { mergeRectsIntoLines(selectedWordScreenRects) }
    val mergedTtsHighlightRects =
        remember(highlightedTextScreenRects, centeringOffsetX, centeringOffsetY) {
            mergeRectsIntoLines(highlightedTextScreenRects)
        }
    val mergedAllTextPageHighlightRects =
        remember(allTextPageHighlightRects, centeringOffsetX, centeringOffsetY) {
            mergeRectsIntoLines(allTextPageHighlightRects)
        }

    var searchHighlightRects by remember { mutableStateOf<List<Rect>>(emptyList()) }
    val searchHighlightColor = Color(0xFFFFAB00).copy(alpha = 0.5f)
    val mergedSearchHighlightRects =
        remember(searchHighlightRects) { mergeRectsIntoLines(searchHighlightRects) }

    var pageLinks by remember(targetPageId) { mutableStateOf<List<PageLink>>(emptyList()) }
    val linkHighlightColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f)
    val linkVerticalPaddingPx = remember(density) { with(density) { 10.dp.toPx().toInt() } }

    var userHighlightScreenRects by remember { mutableStateOf<List<Pair<PdfUserHighlight, List<Rect>>>>(emptyList()) }

    LaunchedEffect(userHighlights, actualBitmapWidthPx, actualBitmapHeightPx, currentPageRotation, virtualPage) {
        if (!isPdfPage || actualBitmapWidthPx == 0 || actualBitmapHeightPx == 0 || userHighlights.isEmpty()) {
            userHighlightScreenRects = emptyList()
            return@LaunchedEffect
        }
        withContext(Dispatchers.IO) {
            try {
                pdfDocumentItem.openPage(pdfPageIndex)?.use { page ->
                    val mapped = userHighlights.map { highlight ->
                        val screenRects = highlight.bounds.mapNotNull { pdfRectF ->
                            page.mapRectToDevice(
                                startX = 0,
                                startY = 0,
                                sizeX = actualBitmapWidthPx,
                                sizeY = actualBitmapHeightPx,
                                rotate = currentPageRotation,
                                coords = pdfRectF
                            ).takeIf { it.width() > 0 && it.height() > 0 }
                        }
                        highlight to screenRects
                    }
                    withContext(Dispatchers.Main) {
                        userHighlightScreenRects = mapped
                    }
                }
            } catch (e: Exception) {
                Timber.e(e, "Failed to map user highlights to screen rects")
            }
        }
    }

    LaunchedEffect(isPerformingOcrForSelection) { onOcrStateChange(isPerformingOcrForSelection) }

    LaunchedEffect(
        showAllTextHighlights, pageIndex, pdfDocumentItem, actualBitmapWidthPx, scale, isScrolling, virtualPage
    ) {
        if (!isPdfPage) {
            if (allTextPageHighlightRects.isNotEmpty()) {
                allTextPageHighlightRects = emptyList()
            }
            return@LaunchedEffect
        }

        if (isScrolling) {
            return@LaunchedEffect
        }

        if (effectiveScale > 1f) {
            if (allTextPageHighlightRects.isNotEmpty()) {
                allTextPageHighlightRects = emptyList()
            }
            return@LaunchedEffect
        }

        if (!showAllTextHighlights) {
            if (allTextPageHighlightRects.isNotEmpty()) {
                allTextPageHighlightRects = emptyList()
            }
            return@LaunchedEffect
        }

        if (actualBitmapWidthPx == 0 || actualBitmapHeightPx == 0) {
            return@LaunchedEffect
        }

        onHighlightLoading(true)

        var rects: List<Rect> = emptyList()
        var pdfiumSucceeded = false
        var tempPage: ReaderPage? = null

        try {
            withContext(Dispatchers.IO) {
                tempPage = pdfDocumentItem.openPage(pdfPageIndex)
                tempPage?.openTextPage()?.use { textPage ->
                    val charCount = textPage.textPageCountChars()
                    if (charCount > 0) {
                        val pdfRectsF =
                            textPage.textPageGetRectsForRanges(intArrayOf(0, charCount))?.map {
                                it.rect
                            } ?: emptyList()

                        if (pdfRectsF.isNotEmpty()) {
                            val mappedScreenRects = pdfRectsF.mapNotNull { pdfRectF ->
                                tempPage?.mapRectToDevice(
                                    startX = 0,
                                    startY = 0,
                                    sizeX = actualBitmapWidthPx,
                                    sizeY = actualBitmapHeightPx,
                                    rotate = currentPageRotation,
                                    coords = pdfRectF
                                )?.takeIf { it.width() > 0 && it.height() > 0 }
                            }
                            if (mappedScreenRects.isNotEmpty()) {
                                rects = mappedScreenRects
                                pdfiumSucceeded = true
                                Timber.d(
                                    "Found ${rects.size} rects for all-text highlight via PDFium."
                                )
                            }
                        } else {
                            Timber.d(
                                "PDFium's textPageGetRectsForRanges returned no rects for page $pageIndex."
                            )
                        }
                    } else {
                        Timber.d("PDFium found 0 characters on page $pageIndex.")
                    }
                }
            }
        } catch (e: Exception) {
            Timber.e(e, "Error getting all-text highlights via PDFium")
        } finally {
            withContext(NonCancellable) {
                withContext(Dispatchers.IO) {
                    try {
                        tempPage?.close()
                    } catch (e: Exception) {
                        Timber.w("Error closing tempPage in highlights: ${e.message}")
                    }
                }
            }
        }

        if (!pdfiumSucceeded && bitmapState != null) {
            Timber.d("PDFium failed for all-text highlight, trying OCR.")
            try {
                val visionText =
                    OcrHelper.extractTextFromBitmap(bitmapState!!, onOcrModelDownloading)
                if (visionText != null) {
                    val ocrRects = visionText.textBlocks.flatMap { block ->
                        block.lines.asSequence().mapNotNull { it.boundingBox }
                    }.toList()
                    if (ocrRects.isNotEmpty()) {
                        rects = ocrRects
                        Timber.d("Found ${rects.size} rects for all-text highlight via OCR.")
                    } else {
                        Timber.d("OCR ran but found no text lines for page $pageIndex.")
                    }
                }
            } catch (e: Exception) {
                Timber.e(e, "Error getting all-text highlights via OCR")
            }
        }
        allTextPageHighlightRects = rects
        onHighlightLoading(false)
    }

    @Suppress("VariableNeverRead") var embeddedAnnotations by remember(targetPageId) { mutableStateOf<List<EmbeddedAnnotation>>(emptyList()) }
    var standardAnnotScreenRects by remember(targetPageId) { mutableStateOf<List<Pair<EmbeddedAnnotation, Rect>>>(emptyList()) }
    var imageScreenRects by remember(targetPageId) { mutableStateOf<List<android.graphics.Rect>>(emptyList()) }

    LaunchedEffect(pageIndex, pdfDocumentItem, actualBitmapWidthPx, actualBitmapHeightPx, virtualPage) {
        if (!isPdfPage || actualBitmapWidthPx == 0 || actualBitmapHeightPx == 0) {
            if (pageLinks.isNotEmpty()) pageLinks = emptyList()
            if (standardAnnotScreenRects.isNotEmpty()) standardAnnotScreenRects = emptyList()
            if (imageScreenRects.isNotEmpty()) imageScreenRects = emptyList()
            return@LaunchedEffect
        }

        withContext(Dispatchers.IO) {
            val allLinks = mutableListOf<PageLink>()
            var finalDisplayList = emptyList<EmbeddedAnnotation>()
            var mappedAnnots = emptyList<Pair<EmbeddedAnnotation, Rect>>()
            var mappedImageRects = emptyList<android.graphics.Rect>()
            val annotLink = 2

            try {
                pdfDocumentItem.openPage(pdfPageIndex)?.use { pageWrapper ->

                    // 1. Extract Links (Method 1: Annotations)
                    try {
                        val annotationLinks = pageWrapper.getLinks()
                        if (annotationLinks.isNotEmpty()) {
                            val mappedAnnotationLinks = annotationLinks.mapNotNull { link ->
                                val uri = link.uri
                                val destPageIdx = link.destPageIdx
                                val bounds = link.bounds

                                if (uri != null || (destPageIdx != null && destPageIdx >= 0)) {
                                    val deviceRect = pageWrapper.mapRectToDevice(
                                        startX = 0, startY = 0,
                                        sizeX = actualBitmapWidthPx, sizeY = actualBitmapHeightPx,
                                        rotate = currentPageRotation, coords = bounds
                                    )
                                    if (deviceRect.width() > 0 && deviceRect.height() > 0) {
                                        val tapRect = Rect(
                                            deviceRect.left, deviceRect.top - linkVerticalPaddingPx,
                                            deviceRect.right, deviceRect.bottom + linkVerticalPaddingPx
                                        )
                                        PageLink(deviceRect, tapRect, uri, destPageIdx, LinkSource.ANNOTATION)
                                    } else null
                                } else null
                            }
                            allLinks.addAll(mappedAnnotationLinks)
                        }
                    } catch (e: Exception) {
                        Timber.e(e, "Error fetching annotation links")
                    }

                    // 2. Extract Links (Method 2: Text)
                    try {
                        pageWrapper.openTextPage().use { textPage ->
                            textPage.loadWebLink()?.use { webLinks ->
                                val webLinkCount = webLinks.countWebLinks()
                                for (linkIndex in 0 until webLinkCount) {
                                    val rawUrl = webLinks.getURL(linkIndex, 2048)
                                    val url = rawUrl?.substringBefore('\u0000')
                                    if (url.isNullOrBlank()) continue

                                    val rectCount = webLinks.countRects(linkIndex)
                                    for (rectIndex in 0 until rectCount) {
                                        val pdfRect = webLinks.getRect(linkIndex, rectIndex)
                                        val deviceRect = pageWrapper.mapRectToDevice(
                                            0, 0, actualBitmapWidthPx, actualBitmapHeightPx,
                                            currentPageRotation, pdfRect
                                        )
                                        if (deviceRect.width() > 0 && deviceRect.height() > 0) {
                                            val tapRect = Rect(
                                                deviceRect.left, deviceRect.top - linkVerticalPaddingPx,
                                                deviceRect.right, deviceRect.bottom + linkVerticalPaddingPx
                                            )
                                            allLinks.add(PageLink(deviceRect, tapRect, url, null, LinkSource.TEXT_CONTENT))
                                        }
                                    }
                                }
                            }
                        }
                    } catch (e: Exception) {
                        Timber.e(e, "Error fetching web links")
                    }

                    // --- Extract Image Bounds ---
                    try {
                        val pagePtr = pageWrapper.getNativePointer()
                        if (pagePtr != 0L) {
                            val objCount = PdfiumEngineProvider.bridge.getPageObjectCount(pagePtr)
                            val imgRects = mutableListOf<android.graphics.Rect>()
                            val outRect = FloatArray(4)

                            for (i in 0 until objCount) {
                                if (PdfiumEngineProvider.bridge.getPageObjectType(pagePtr, i) == 3) { // 3 = FPDF_PAGEOBJ_IMAGE
                                    if (PdfiumEngineProvider.bridge.getPageObjectBoundingBox(pagePtr, i, outRect)) {
                                        val pdfRectF = android.graphics.RectF(
                                            min(outRect[0], outRect[2]),
                                            max(outRect[1], outRect[3]),
                                            max(outRect[0], outRect[2]),
                                            min(outRect[1], outRect[3])
                                        )
                                        val deviceRect = pageWrapper.mapRectToDevice(
                                            0, 0, actualBitmapWidthPx, actualBitmapHeightPx,
                                            currentPageRotation, pdfRectF
                                        )
                                        if (deviceRect.width() > 0 && deviceRect.height() > 0) {
                                            imgRects.add(android.graphics.Rect(deviceRect.left, deviceRect.top, deviceRect.right, deviceRect.bottom))
                                        }
                                    }
                                }
                            }
                            mappedImageRects = imgRects
                        }
                    } catch (e: Exception) {
                        Timber.tag("PdfImageDebug").e(e, "Error extracting image rects")
                    }

                    // 3. Extract Embedded Annotations
                    try {
                        val pagePtr = pageWrapper.getNativePointer()

                        if (pagePtr != 0L) {
                            val count = PdfiumEngineProvider.bridge.getAnnotCount(pagePtr)
                            Timber.tag("PdfCommentDebug").d("Page $pageIndex: Total Annotations found = $count")
                            if (count > 0) {
                                val allAnnots = (0 until count).mapNotNull { i ->
                                    val subtype = PdfiumEngineProvider.bridge.getAnnotSubtype(pagePtr, i)
                                    if (subtype == annotLink) return@mapNotNull null

                                    var contents = PdfiumEngineProvider.bridge.getAnnotString(pagePtr, i, "Contents")
                                    if (contents.isNullOrBlank()) {
                                        contents = PdfiumEngineProvider.bridge.getAnnotString(pagePtr, i, "RC")
                                    }

                                    val name = PdfiumEngineProvider.bridge.getAnnotString(pagePtr, i, "NM")
                                    val irt = PdfiumEngineProvider.bridge.getAnnotString(pagePtr, i, "IRT")
                                    val author = PdfiumEngineProvider.bridge.getAnnotString(pagePtr, i, "T")

                                    val pdfRectArray = PdfiumEngineProvider.bridge.getAnnotRect(pagePtr, i)
                                    val pdfRectF = if (pdfRectArray != null) {
                                        android.graphics.RectF(
                                            min(pdfRectArray[0], pdfRectArray[2]),
                                            max(pdfRectArray[1], pdfRectArray[3]),
                                            max(pdfRectArray[0], pdfRectArray[2]),
                                            min(pdfRectArray[1], pdfRectArray[3])
                                        )
                                    } else android.graphics.RectF()

                                    EmbeddedAnnotation(i, subtype, pdfRectF, contents, author, name, irt)
                                }

                                val annotMap = allAnnots.associateBy { it.name }
                                val orphans = mutableListOf<EmbeddedAnnotation>()

                                allAnnots.forEach { annot ->
                                    if (!annot.inReplyTo.isNullOrBlank() && annotMap.containsKey(annot.inReplyTo)) {
                                        Timber.tag("PdfCommentDebug").i("Linking: ${annot.name} is a reply to ${annot.inReplyTo}")
                                        annotMap[annot.inReplyTo]?.replies?.add(annot)
                                    } else {
                                        orphans.add(annot)
                                    }
                                }

                                Timber.tag("PdfCommentDebug").d("After ID linking: Orphans count = ${orphans.size}")

                                val groupedRoots = mutableListOf<MutableList<EmbeddedAnnotation>>()
                                orphans.forEach { annot ->
                                    val match = groupedRoots.find { group ->
                                        val root = group.first()
                                        val inflatedRoot = android.graphics.RectF(root.rect).apply { inset(-10f, -10f) }
                                        android.graphics.RectF.intersects(inflatedRoot, annot.rect)
                                    }
                                    if (match != null) {
                                        Timber.tag("PdfCommentDebug").w("Geometric grouping triggered for ${annot.name} with ${match.first().name}. This might flatten nested replies!")
                                        match.add(annot)
                                    } else {
                                        groupedRoots.add(mutableListOf(annot))
                                    }
                                }

                                val rootsWithReplies = groupedRoots.map { group ->
                                    val root = group.first()
                                    if (group.size > 1) {
                                        root.replies.addAll(group.drop(1))
                                    }
                                    root
                                }

                                finalDisplayList = rootsWithReplies.filter {
                                    !it.contents.isNullOrBlank() || it.replies.any { r -> !r.contents.isNullOrBlank() }
                                }

                                mappedAnnots = finalDisplayList.map { annot ->
                                    val screenRect = pageWrapper.mapRectToDevice(
                                        0, 0, actualBitmapWidthPx, actualBitmapHeightPx,
                                        currentPageRotation, annot.rect
                                    )
                                    annot to screenRect
                                }
                            }
                        } else {
                            Timber.tag("PdfCommentDebug").w("Page $pageIndex: Failed to resolve native page pointer.")
                        }
                    } catch (e: Exception) {
                        Timber.tag("PdfCommentDebug").e(e, "Error extracting annotations")
                    }
                }
            } catch (e: Exception) {
                if (e !is kotlinx.coroutines.CancellationException) {
                    Timber.e(e, "Failed to load links and annotations for page $pdfPageIndex")
                }
            }

            withContext(Dispatchers.Main) {
                pageLinks = allLinks
                embeddedAnnotations = finalDisplayList
                standardAnnotScreenRects = mappedAnnots
                imageScreenRects = mappedImageRects
            }
        }
    }

    LaunchedEffect(isVisible, pageIndex) {
        if (!isVisible && !isVerticalScroll) {
            if (bitmapState != null) {
                Timber.d("Page $pageIndex hidden. Releasing bitmap to save memory.")
                val old = bitmapState
                bitmapState = null
                @Suppress("ControlFlowWithEmptyBody") if (old != null && old !== PdfThumbnailCache.get(targetPageId)) { }
            }
        }
    }

    SideEffect {
        if (effectiveScale > 1f) {
            Timber.tag("PdfZoomDiagnostics").v(
                """
            Page $pageIndex Stats:
            - Internal Scale: $scale | Effective Scale: $effectiveScale
            - Offset: $offset
            - Bitmap Dims: ${actualBitmapWidthPx}x${actualBitmapHeightPx}
            - Canvas Dims: ${canvasWidthPx.floatValue}x${canvasHeightPx.floatValue}
            - Centering: X=$centeringOffsetX, Y=$centeringOffsetY
            """.trimIndent()
            )
        }
    }

    LaunchedEffect(
        needsTilingNow,
        actualBitmapWidthPx,
        actualBitmapHeightPx,
        canvasWidthPx.floatValue,
        canvasHeightPx.floatValue,
        isVerticalScroll,
        isScrolling,
        isAutoScrollPlaying,
        virtualPage,
        isActivePage
    ) {
        if (!needsTilingNow) {
            if (tiles.isNotEmpty()) {
                val oldTiles = tiles
                tiles = emptyList()
                withContext(Dispatchers.IO) {
                    oldTiles.forEach { PdfBitmapPool.recycle(it.bitmap) }
                }
            }
            return@LaunchedEffect
        }

        val screenWidth = canvasWidthPx.floatValue
        val screenHeight = canvasHeightPx.floatValue

        if (actualBitmapWidthPx == 0 || actualBitmapHeightPx == 0 || screenWidth == 0f || screenHeight == 0f) return@LaunchedEffect

        var page: ReaderPage? = null

        if (!isPdfPage) {
            if (tiles.isNotEmpty()) {
                val oldTiles = tiles
                tiles = emptyList()
                withContext(Dispatchers.IO) {
                    oldTiles.forEach { PdfBitmapPool.recycle(it.bitmap) }
                }
            }
            return@LaunchedEffect
        }

        try {
            page = withContext(Dispatchers.IO) { pdfDocumentItem.openPage(pdfPageIndex) }

            snapshotFlow {
                val rect = visibleScreenRect()
                val observedScale = latestEffectiveScale
                if (isVerticalScroll && rect != null) {
                    val qTop = rect.top / (tileSizePx / 2)
                    val qLeft = rect.left / (tileSizePx / 2)
                    val qBottom = rect.bottom / (tileSizePx / 2)
                    val qRight = rect.right / (tileSizePx / 2)
                    listOf(qTop, qLeft, qBottom, qRight, (observedScale * 10f).roundToInt())
                } else if (!isVerticalScroll) {
                    val observedOffset = latestEffectiveOffset
                    val pivotX = screenWidth / 2f
                    val pivotY = screenHeight / 2f
                    val pxTl = (((0 - observedOffset.x) - pivotX) / observedScale + pivotX) - centeringOffsetX
                    val pyTl = (((0 - observedOffset.y) - pivotY) / observedScale + pivotY) - centeringOffsetY
                    val pxBr = (((screenWidth - observedOffset.x) - pivotX) / observedScale + pivotX) - centeringOffsetX
                    val pyBr = (((screenHeight - observedOffset.y) - pivotY) / observedScale + pivotY) - centeringOffsetY

                    val qTop = pyTl.toInt() / (tileSizePx / 2)
                    val qLeft = pxTl.toInt() / (tileSizePx / 2)
                    val qBottom = pyBr.toInt() / (tileSizePx / 2)
                    val qRight = pxBr.toInt() / (tileSizePx / 2)
                    listOf(qTop, qLeft, qBottom, qRight, (observedScale * 10f).roundToInt())
                } else {
                    null
                }
            }.conflate().collectLatest { _ ->

                val tileCalcStart = System.nanoTime()
                if (!isActive) return@collectLatest

                val renderScale = latestEffectiveScale
                val renderOffset = latestEffectiveOffset

                val currentVisibleRect = visibleScreenRect()

                val pxTl: Float
                val pxBr: Float
                val pyTl: Float
                val pyBr: Float

                if (isVerticalScroll) {
                    if (currentVisibleRect != null) {
                        pxTl = currentVisibleRect.left.toFloat()
                        pyTl = currentVisibleRect.top.toFloat()
                        pxBr = currentVisibleRect.right.toFloat()
                        pyBr = currentVisibleRect.bottom.toFloat()
                    } else {
                        if (tiles.isNotEmpty()) {
                            val oldTiles = tiles
                            tiles = emptyList()
                            withContext(Dispatchers.IO) {
                                oldTiles.forEach { PdfBitmapPool.recycle(it.bitmap) }
                            }
                        }
                        return@collectLatest
                    }
                } else {
                    val pivotX = screenWidth / 2f
                    val pivotY = screenHeight / 2f

                    pxTl = (((0 - renderOffset.x) - pivotX) / renderScale + pivotX) - centeringOffsetX
                    pyTl = (((0 - renderOffset.y) - pivotY) / renderScale + pivotY) - centeringOffsetY
                    pxBr = (((screenWidth - renderOffset.x) - pivotX) / renderScale + pivotX) - centeringOffsetX
                    pyBr = (((screenHeight - renderOffset.y) - pivotY) / renderScale + pivotY) - centeringOffsetY
                }

                val visibleBitmapRect = Rect(pxTl.toInt(), pyTl.toInt(), pxBr.toInt(), pyBr.toInt())
                val inset = if (renderScale > 2f) 0 else -tileSizePx
                visibleBitmapRect.inset(inset, inset)

                val requiredTileIds = mutableSetOf<Int>()
                val cols = (actualBitmapWidthPx + tileSizePx - 1) / tileSizePx
                val startCol = (visibleBitmapRect.left / tileSizePx).coerceAtLeast(0)
                val endCol = ((visibleBitmapRect.right + tileSizePx - 1) / tileSizePx).coerceAtMost(cols)
                val startRow = (visibleBitmapRect.top / tileSizePx).coerceAtLeast(0)
                val endRow = ((visibleBitmapRect.bottom + tileSizePx - 1) / tileSizePx).coerceAtMost(
                    (actualBitmapHeightPx + tileSizePx - 1) / tileSizePx
                )

                for (row in startRow until endRow) {
                    for (col in startCol until endCol) {
                        requiredTileIds.add(row * cols + col)
                    }
                }

                val currentTileIds = tiles.map { it.tileId }.toSet()

                val scaleTolerance = PDF_TILE_SCALE_TOLERANCE
                val validCurrentTileIds = tiles.filter { abs(it.renderScale - renderScale) <= scaleTolerance }.map { it.tileId }.toSet()
                val tilesToRenderIds = requiredTileIds - validCurrentTileIds
                val tilesToRecycleIds = currentTileIds - requiredTileIds

                val duration = (System.nanoTime() - tileCalcStart) / 1_000_000f
                if (duration > 2f) {
                    Timber.tag("PdfPerformance").d(
                        "Page $pageIndex | Tile Calc took ${duration}ms | Tiles Needed: ${requiredTileIds.size}"
                    )
                }

                if (tilesToRecycleIds.isNotEmpty()) {
                    val (tilesToRecycle, tilesToKeep) = tiles.partition { it.tileId in tilesToRecycleIds }
                    tiles = tilesToKeep
                    withContext(Dispatchers.IO) {
                        tilesToRecycle.forEach { PdfBitmapPool.recycle(it.bitmap) }
                    }
                }

                if (isScrolling && renderScale > 1f) {
                    return@collectLatest
                }

                if (requiredTileIds != validCurrentTileIds) {
                    if (tilesToRenderIds.isNotEmpty()) {
                        delay(PDF_TILE_IDLE_RENDER_DELAY_MS)
                        if (!isActive) return@collectLatest
                        if (isScrolling && latestEffectiveScale > 1f) return@collectLatest

                        val renderedTiles = withContext(Dispatchers.IO) {
                            val newTiles = mutableListOf<PdfTile>()
                            tilesToRenderIds.forEach { tileId ->
                                if (!isActive) return@forEach

                                yield()

                                val row = tileId / cols
                                val col = tileId % cols
                                val tileRect = Rect(
                                    col * tileSizePx,
                                    row * tileSizePx,
                                    (col + 1) * tileSizePx,
                                    (row + 1) * tileSizePx
                                )
                                val tileRenderScale = min(
                                    renderScale,
                                    PDF_MAX_TILE_BITMAP_SIZE_PX.toFloat() / tileSizePx.toFloat()
                                )
                                val tileRenderSize = (tileSizePx * tileRenderScale).toInt().coerceAtLeast(1)

                                val tileBitmap = PdfBitmapPool.get(tileRenderSize)

                                val fullPageRenderWidth = (actualBitmapWidthPx * tileRenderScale).toInt()
                                val fullPageRenderHeight = (actualBitmapHeightPx * tileRenderScale).toInt()
                                val tileRenderX = (col * tileSizePx * tileRenderScale).toInt()
                                val tileRenderY = (row * tileSizePx * tileRenderScale).toInt()

                                page?.renderPageBitmap(
                                    bitmap = tileBitmap,
                                    startX = -tileRenderX,
                                    startY = -tileRenderY,
                                    drawSizeX = fullPageRenderWidth,
                                    drawSizeY = fullPageRenderHeight,
                                    renderAnnot = true
                                )

                                newTiles += PdfTile(tileBitmap, tileRect, tileId, renderScale)
                            }
                            newTiles
                        }

                        if (!isActive) {
                            withContext(Dispatchers.IO) {
                                renderedTiles.forEach { PdfBitmapPool.recycle(it.bitmap) }
                            }
                            return@collectLatest
                        }

                        if (renderedTiles.isNotEmpty()) {
                            val renderedIds = renderedTiles.map { it.tileId }.toSet()
                            val replacedTiles = tiles.filter { it.tileId in renderedIds }
                            tiles = tiles.filterNot { it.tileId in renderedIds } + renderedTiles

                            if (replacedTiles.isNotEmpty()) {
                                coroutineScope.launch(Dispatchers.IO) {
                                    replacedTiles.forEach { PdfBitmapPool.recycle(it.bitmap) }
                                }
                            }
                        }
                    }
                }
            }
        } catch (e: Exception) {
            if (e is CancellationException) throw e
            Timber.e(e, "Error during tiling process for page $pageIndex")
        } finally {
            withContext(NonCancellable) {
                withContext(Dispatchers.IO) {
                    try {
                        page?.close()
                    } catch (e: Exception) {
                        Timber.w("Error closing tile page: ${e.message}")
                    }
                }
            }
        }
    }

    var searchFocusedRects by remember(targetPageId) { mutableStateOf<List<Rect>>(emptyList()) }
    var searchAllRects by remember(targetPageId) { mutableStateOf<List<Rect>>(emptyList()) }

    var keyboardAdjustmentOriginalOffset by remember(targetPageId) { mutableStateOf<Float?>(null) }

    val mergedSearchFocusedRects = remember(searchFocusedRects) { searchFocusedRects }
    val mergedSearchAllRects = remember(searchAllRects) { searchAllRects }

    // Colors
    val searchFocusedColor = Color(0xFFFF6D00).copy(alpha = 0.5f) // Bold Orange
    val searchAllColor = Color(0xFFFFEB3B).copy(alpha = 0.4f) // Standard Yellow

    LaunchedEffect(
        searchQuery, searchResultToHighlight, actualBitmapWidthPx, actualBitmapHeightPx, virtualPage
    ) {
        if (!isPdfPage) {
            searchFocusedRects = emptyList()
            searchAllRects = emptyList()
            return@LaunchedEffect
        }

        if (actualBitmapWidthPx == 0 || searchQuery.isBlank()) {
            searchFocusedRects = emptyList()
            searchAllRects = emptyList()
            return@LaunchedEffect
        }

        val isTargetOnPage = searchResultToHighlight?.locationInSource == pageIndex
        var foundAll: List<Rect> = emptyList()
        var foundFocused: List<Rect> = emptyList()

        withContext(Dispatchers.IO) {
            try {
                pdfDocumentItem.openPage(pdfPageIndex)?.use { page ->
                    page.openTextPage().use { textPage ->
                        val charCount = textPage.textPageCountChars()
                        if (charCount > 0) {
                            val fullText = textPage.textPageGetText(0, charCount)
                            if (!fullText.isNullOrBlank()) {
                                val occurrences = mutableListOf<Int>()
                                try {
                                    val regex = Regex("(?i)\\b${Regex.escape(searchQuery)}")
                                    val matches = regex.findAll(fullText)
                                    matches.forEach { matchResult ->
                                        occurrences.add(matchResult.range.first)
                                    }
                                } catch (_: Exception) {
                                    var lastIndex = -1
                                    while (true) {
                                        lastIndex = fullText.indexOf(
                                            searchQuery, lastIndex + 1, ignoreCase = true
                                        )
                                        if (lastIndex == -1) break
                                        occurrences.add(lastIndex)
                                    }
                                }

                                if (occurrences.isNotEmpty()) {
                                    val queryLen = searchQuery.length
                                    val allRectsRaw = occurrences.flatMap { startIndex ->
                                        textPage.textPageGetRectsForRanges(
                                            intArrayOf(startIndex, queryLen)
                                        )?.map { it.rect } ?: emptyList()
                                    }

                                    foundAll = allRectsRaw.mapNotNull { pdfRectF ->
                                        page.mapRectToDevice(
                                            startX = 0,
                                            startY = 0,
                                            sizeX = actualBitmapWidthPx,
                                            sizeY = actualBitmapHeightPx,
                                            rotate = currentPageRotation,
                                            coords = pdfRectF
                                        ).takeIf {
                                            it.width() > 0 && it.height() > 0
                                        }
                                    }

                                    if (isTargetOnPage) {
                                        val targetIdx =
                                            searchResultToHighlight.occurrenceIndexInLocation
                                        if (targetIdx >= 0 && targetIdx < occurrences.size) {
                                            val startIndex = occurrences[targetIdx]
                                            val focusedRectsRaw =
                                                textPage.textPageGetRectsForRanges(
                                                    intArrayOf(startIndex, queryLen)
                                                )?.map { it.rect } ?: emptyList()

                                            foundFocused = focusedRectsRaw.mapNotNull { pdfRectF ->
                                                page.mapRectToDevice(
                                                    startX = 0,
                                                    startY = 0,
                                                    sizeX = actualBitmapWidthPx,
                                                    sizeY = actualBitmapHeightPx,
                                                    rotate = currentPageRotation,
                                                    coords = pdfRectF
                                                ).takeIf {
                                                    it.width() > 0 && it.height() > 0
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (e: Exception) {
                Timber.e(e, "Search Highlight: Failed to get rects for page $pageIndex")
            }
        }

        searchAllRects = foundAll
        searchFocusedRects = foundFocused

        if (foundFocused.isNotEmpty() && onSearchHighlightCenterCalculated != null) {
            val unionRect = RectF()
            if (foundFocused.isNotEmpty()) {
                unionRect.set(foundFocused[0])
                for (i in 1 until foundFocused.size) {
                    unionRect.union(RectF(foundFocused[i]))
                }
                onSearchHighlightCenterCalculated(unionRect.centerY())
            }
        }
    }

    fun findClosestOcrSymbolIndex(symbols: List<OcrSymbolInfo>, x: Float, y: Float): Int {
        if (symbols.isEmpty()) return -1

        val containingSymbolIndex = symbols.indexOfFirst {
            it.symbol.boundingBox?.contains(x.toInt(), y.toInt()) == true
        }
        if (containingSymbolIndex != -1) return containingSymbolIndex

        var minDistanceSq = Float.MAX_VALUE
        var closestSymbolIndex = -1

        symbols.forEachIndexed { index, info ->
            info.symbol.boundingBox?.let { box ->
                val distSq = (x - box.exactCenterX()).pow(2) + (y - box.exactCenterY()).pow(2)
                if (distSq < minDistanceSq) {
                    minDistanceSq = distSq
                    closestSymbolIndex = index
                }
            }
        }
        return closestSymbolIndex
    }

    fun updateOcrSymbolSelectionRectsAndHandles(indices: Pair<Int, Int>?) {
        if (indices == null || allOcrSymbolsForSelection.isEmpty() || indices.first >= indices.second) {
            selectedWordScreenRects = emptyList()
            startHandleContentPosition.value = null
            endHandleContentPosition.value = null
            return
        }

        val selectedSymbols = allOcrSymbolsForSelection.subList(indices.first, indices.second)
        if (selectedSymbols.isEmpty()) {
            selectedWordScreenRects = emptyList()
            startHandleContentPosition.value = null
            endHandleContentPosition.value = null
            return
        }

        selectedWordScreenRects = selectedSymbols.mapNotNull { it.symbol.boundingBox }

        val firstRect = selectedSymbols.first().symbol.boundingBox!!
        val lastRect = selectedSymbols.last().symbol.boundingBox!!

        startHandleContentPosition.value =
            Offset(firstRect.left.toFloat(), firstRect.bottom.toFloat())
        endHandleContentPosition.value = Offset(lastRect.right.toFloat(), lastRect.bottom.toFloat())
    }

    suspend fun updateSelectionVisuals(
        doc: ReaderDocument,
        pageIdx: Int,
        charRange: Pair<Int, Int>?,
        currentBitmapWidth: Int,
        currentBitmapHeight: Int,
        rotation: Int,
        providedPage: ReaderPage? = null,
        providedTextPage: ReaderTextPage? = null
    ) {
        if (charRange == null || currentBitmapWidth == 0 || currentBitmapHeight == 0) {
            withContext(Dispatchers.Main) {
                selectedWordScreenRects = emptyList()
                startHandleContentPosition.value = null
                endHandleContentPosition.value = null
            }
            return
        }

        withContext(Dispatchers.IO) {
            var localPage: ReaderPage? = null
            var localTextPage: ReaderTextPage? = null

            try {
                val pageToUse: ReaderPage
                val textPageToUse: ReaderTextPage

                if (providedPage != null && providedTextPage != null) {
                    pageToUse = providedPage
                    textPageToUse = providedTextPage
                } else {
                    localPage = doc.openPage(pageIdx)
                    localTextPage = localPage?.openTextPage()
                    if (localPage == null || localTextPage == null) {
                        withContext(Dispatchers.Main) {
                            selectedWordScreenRects = emptyList()
                            startHandleContentPosition.value = null
                            endHandleContentPosition.value = null
                        }
                        return@withContext
                    }
                    pageToUse = localPage
                    textPageToUse = localTextPage
                }

                val (startIndex, endIndex) = charRange
                if (startIndex >= endIndex) {
                    withContext(Dispatchers.Main) {
                        selectedWordScreenRects = emptyList()
                        startHandleContentPosition.value = null
                        endHandleContentPosition.value = null
                    }
                    return@withContext
                }

                val length = endIndex - startIndex
                val wordPdfRectsF =
                    textPageToUse.textPageGetRectsForRanges(intArrayOf(startIndex, length))?.map {
                        it.rect
                    } ?: emptyList()

                if (wordPdfRectsF.isNotEmpty()) {
                    val mappedScreenRects = wordPdfRectsF.mapNotNull { pdfRectF ->
                        val screenRect = pageToUse.mapRectToDevice(
                            startX = 0,
                            startY = 0,
                            sizeX = currentBitmapWidth,
                            sizeY = currentBitmapHeight,
                            rotate = rotation,
                            coords = pdfRectF
                        )
                        if (screenRect.width() > 0 && screenRect.height() > 0) screenRect
                        else {
                            Timber.d(
                                "updateSelectionVisuals: Filtering out invalid screen rect: $screenRect"
                            )
                            null
                        }
                    }

                    withContext(Dispatchers.Main) {
                        selectedWordScreenRects = mappedScreenRects

                        if (mappedScreenRects.isNotEmpty()) {
                            val firstRect = mappedScreenRects.first()
                            val lastRect = mappedScreenRects.last()

                            startHandleContentPosition.value =
                                Offset(firstRect.left.toFloat(), firstRect.bottom.toFloat())
                            endHandleContentPosition.value =
                                Offset(lastRect.right.toFloat(), lastRect.bottom.toFloat())
                        } else {
                            selectedWordScreenRects = emptyList()
                            startHandleContentPosition.value = null
                            endHandleContentPosition.value = null
                        }
                    }
                } else {
                    withContext(Dispatchers.Main) {
                        selectedWordScreenRects = emptyList()
                        startHandleContentPosition.value = null
                        endHandleContentPosition.value = null
                    }
                }
            } catch (e: Exception) {
                Timber.e(e, "Error updating selection visuals for page $pageIdx, range $charRange: $e")
                withContext(Dispatchers.Main) {
                    selectedWordScreenRects = emptyList()
                    startHandleContentPosition.value = null
                    endHandleContentPosition.value = null
                }
            } finally {
                if (providedPage == null && providedTextPage == null) {
                    withContext(NonCancellable) {
                        try {
                            localTextPage?.close()
                        } catch (_: Exception) {
                        }
                        try {
                            localPage?.close()
                        } catch (_: Exception) {
                        }
                    }
                }
            }
        }
    }

    LaunchedEffect(clearSelectionTrigger) {
        if (clearSelectionTrigger != 0L) {
            if (customMenuState != null || selectionCharRange.value != null || ocrSelectionSymbolIndices != null) {
                customMenuState = null
                selectionCharRange.value = null
                ocrSelectionSymbolIndices = null
                updateSelectionVisuals(
                    pdfDocumentItem,
                    pdfPageIndex,
                    null,
                    actualBitmapWidthPx,
                    actualBitmapHeightPx,
                    currentPageRotation
                )
            }
        }
    }

    LaunchedEffect(resetZoomTrigger) {
        if (resetZoomTrigger != 0L && scale > 1f && isZoomEnabled && !isVerticalScroll && !isScrollLocked) {
            coroutineScope.launch {
                val startScale = scale
                val startOffset = offset
                Animatable(0f).animateTo(
                    1f, animationSpec = tween(durationMillis = 300)
                ) {
                    val progress = value
                    scale = androidx.compose.ui.util.lerp(
                        startScale, 1f, progress
                    )
                    offset = androidx.compose.ui.geometry.lerp(
                        startOffset, Offset.Zero, progress
                    )
                    onScaleChanged(scale)
                }
                if (scale <= 1.05f) {
                    scale = 1f
                    offset = Offset.Zero
                    onScaleChanged(scale)
                }
            }
        }
    }

    BoxWithConstraints(
        modifier = modifier
            .onGloballyPositioned { layoutCoordinates = it }
            .pointerInput(
                pdfDocumentItem,
                pageIndex,
                actualBitmapWidthPx,
                actualBitmapHeightPx,
                currentPageRotation,
                centeringOffsetX,
                centeringOffsetY,
                scale,
                offset,
                isTransforming,
                isVerticalScroll,
                inputScale,
                inputOffset,
                isEditMode
            ) {
                if (isTransforming) return@pointerInput
                if (isEditMode) return@pointerInput

                awaitEachGesture {
                    val down = awaitFirstDown(requireUnconsumed = false)
                    var dragStartedOnHandle = false
                    val shp = startHandleContentPosition.value
                    val ehp = endHandleContentPosition.value
                    val handleTouchWidthPx = handleTouchWidthPxState.value
                    val handleTouchHeightPx = handleTouchHeightPxState.value

                    if (shp != null) {
                        val handleScreenPos = contentToScreenCoordinates(shp)
                        val handleRect = Rect(
                            (handleScreenPos.x - handleTouchWidthPx / 2).toInt(),
                            handleScreenPos.y.toInt(),
                            (handleScreenPos.x + handleTouchWidthPx / 2).toInt(),
                            (handleScreenPos.y + handleTouchHeightPx).toInt()
                        )
                        if (handleRect.contains(
                                down.position.x.toInt(), down.position.y.toInt()
                            )
                        ) {
                            activeDraggingHandle = Handle.START
                            dragStartedOnHandle = true
                            down.consume()
                            customMenuState = null
                            Timber.d("PointerInput: Press on START teardrop handle")
                        }
                    }

                    if (!dragStartedOnHandle && ehp != null) {
                        val handleScreenPos = contentToScreenCoordinates(ehp)
                        val handleRect = Rect(
                            (handleScreenPos.x - handleTouchWidthPx / 2).toInt(),
                            handleScreenPos.y.toInt(),
                            (handleScreenPos.x + handleTouchWidthPx / 2).toInt(),
                            (handleScreenPos.y + handleTouchHeightPx).toInt()
                        )
                        if (handleRect.contains(
                                down.position.x.toInt(), down.position.y.toInt()
                            )
                        ) {
                            activeDraggingHandle = Handle.END
                            dragStartedOnHandle = true
                            down.consume()
                            customMenuState = null
                            Timber.d("PointerInput: Press on END teardrop handle")
                        }
                    }

                    if (dragStartedOnHandle) {
                        Timber.d(
                            "PointerInput: Drag started on handle $activeDraggingHandle"
                        )
                        showMagnifier = true
                        customMenuState = null

                        val rects = selectedWordScreenRects
                        // (keep the magnifier target logic here as
                        // is)
                        if (rects.isNotEmpty()) {
                            val relevantRect = when (activeDraggingHandle) {
                                Handle.START -> rects.first()
                                Handle.END -> rects.last()
                                else -> null
                            }
                            val handlePos = when (activeDraggingHandle) {
                                Handle.START -> startHandleContentPosition.value
                                Handle.END -> endHandleContentPosition.value
                                else -> null
                            }
                            if (relevantRect != null && handlePos != null) {
                                magnifierBitmapCenterTarget = Offset(
                                    x = handlePos.x, y = relevantRect.exactCenterY()
                                )
                            }
                        } else {
                            val initialHandlePos = when (activeDraggingHandle) {
                                Handle.START -> startHandleContentPosition.value
                                Handle.END -> endHandleContentPosition.value
                                else -> null
                            }
                            initialHandlePos?.let { contentPos ->
                                magnifierBitmapCenterTarget = contentPos
                            }
                        }

                        val dragEventChannel = Channel<Offset>(Channel.CONFLATED)

                        coroutineScope.launch(Dispatchers.IO) {
                            var pageForDrag: ReaderPage? = null
                            var textPageForDrag: ReaderTextPage? = null

                            try {
                                if (selectionMethodUsed == PdfSelectionMethod.PDFIUM) {
                                    if (isPdfPage) {
                                        pageForDrag = pdfDocumentItem.openPage(
                                            pdfPageIndex
                                        )
                                        textPageForDrag = pageForDrag?.openTextPage()
                                    }
                                }

                                for (dragPosition in dragEventChannel) {
                                    if (activeDraggingHandle != null) {
                                        if (selectionMethodUsed == PdfSelectionMethod.OCR) {
                                            if (allOcrSymbolsForSelection.isNotEmpty()) {
                                                val touchInContentCoords =
                                                    screenToContentCoordinates(
                                                        dragPosition
                                                    )
                                                val touchXInBitmap = touchInContentCoords.x
                                                val touchYInBitmap = touchInContentCoords.y

                                                val targetSymbolIndex = findClosestOcrSymbolIndex(
                                                    allOcrSymbolsForSelection,
                                                    touchXInBitmap,
                                                    touchYInBitmap
                                                )

                                                if (targetSymbolIndex != -1) {
                                                    ocrSelectionSymbolIndices?.let { currentRange ->
                                                        val (start, end) = currentRange
                                                        when (activeDraggingHandle) {
                                                            Handle.START -> {
                                                                if (targetSymbolIndex >= end - 1) {
                                                                    activeDraggingHandle =
                                                                        Handle.END
                                                                    ocrSelectionSymbolIndices =
                                                                        Pair(
                                                                            end - 1,
                                                                            targetSymbolIndex + 1
                                                                        )
                                                                } else {
                                                                    ocrSelectionSymbolIndices =
                                                                        Pair(
                                                                            targetSymbolIndex, end
                                                                        )
                                                                }
                                                            }

                                                            Handle.END -> {
                                                                if (targetSymbolIndex + 1 <= start + 1) {
                                                                    activeDraggingHandle =
                                                                        Handle.START
                                                                    ocrSelectionSymbolIndices =
                                                                        Pair(
                                                                            targetSymbolIndex,
                                                                            start + 1
                                                                        )
                                                                } else {
                                                                    ocrSelectionSymbolIndices =
                                                                        Pair(
                                                                            start,
                                                                            targetSymbolIndex + 1
                                                                        )
                                                                }
                                                            }

                                                            else -> {}
                                                        }
                                                        withContext(
                                                            Dispatchers.Main
                                                        ) {
                                                            updateOcrSymbolSelectionRectsAndHandles(
                                                                ocrSelectionSymbolIndices
                                                            )
                                                        }
                                                    }
                                                }
                                                withContext(Dispatchers.Main) {
                                                    val currentActiveHandle = activeDraggingHandle
                                                    val currentRects = selectedWordScreenRects
                                                    if (currentRects.isNotEmpty()) {
                                                        val relevantRect =
                                                            when (currentActiveHandle) {
                                                                Handle.START -> currentRects.first()
                                                                Handle.END -> currentRects.last()
                                                                null -> null
                                                            }
                                                        val handleContentPos =
                                                            when (currentActiveHandle) {
                                                                Handle.START -> startHandleContentPosition.value
                                                                Handle.END -> endHandleContentPosition.value
                                                                null -> null
                                                            }
                                                        if (relevantRect != null && handleContentPos != null) {
                                                            magnifierBitmapCenterTarget = Offset(
                                                                x = handleContentPos.x,
                                                                y = relevantRect.exactCenterY()
                                                            )
                                                        }
                                                    }
                                                }
                                            }
                                        } else {
                                            // PDFIUM Logic
                                            val pDrag = pageForDrag
                                            val tDrag = textPageForDrag
                                            if (pDrag != null && tDrag != null) {
                                                val touchInContentCoords =
                                                    screenToContentCoordinates(
                                                        dragPosition
                                                    )

                                                val pdfCoords = pDrag.mapDeviceCoordsToPage(
                                                    startX = 0,
                                                    startY = 0,
                                                    sizeX = actualBitmapWidthPx,
                                                    sizeY = actualBitmapHeightPx,
                                                    rotate = currentPageRotation,
                                                    deviceX = touchInContentCoords.x.toInt(),
                                                    deviceY = touchInContentCoords.y.toInt()
                                                )
                                                val charTolerance = 10.0

                                                var charIndexForUpdate =
                                                    tDrag.textPageGetCharIndexAtPos(
                                                        x = pdfCoords.x.toDouble(),
                                                        y = pdfCoords.y.toDouble(),
                                                        xTolerance = charTolerance,
                                                        yTolerance = charTolerance
                                                    )

                                                if (charIndexForUpdate == -1 && activeDraggingHandle != null) {
                                                    val pageWidthPdfUnits =
                                                        pDrag.getPageWidthPoint()
                                                    val wideSearchXTolerance =
                                                        pageWidthPdfUnits.toDouble()
                                                    var ySearchCoordinate = pdfCoords.y.toDouble()

                                                    val currentRange = selectionCharRange.value
                                                    if (currentRange != null) {
                                                        val pageTotalChars =
                                                            tDrag.textPageCountChars()
                                                        if (pageTotalChars > 0) {
                                                            val anchorCharIndex =
                                                                if (activeDraggingHandle == Handle.START) {
                                                                    currentRange.first
                                                                } else {
                                                                    (currentRange.second - 1).coerceAtLeast(
                                                                        0
                                                                    )
                                                                }
                                                            if (anchorCharIndex in 0..<pageTotalChars) {
                                                                val anchorCharBox =
                                                                    tDrag.textPageGetCharBox(
                                                                        anchorCharIndex
                                                                    )
                                                                if (anchorCharBox != null) {
                                                                    ySearchCoordinate =
                                                                        ((anchorCharBox.top + anchorCharBox.bottom) / 2.0)
                                                                }
                                                            }
                                                        }
                                                    }

                                                    val wideSearchYTolerance = charTolerance * 1.5
                                                    if (wideSearchXTolerance > 0) {
                                                        charIndexForUpdate =
                                                            tDrag.textPageGetCharIndexAtPos(
                                                                x = pdfCoords.x.toDouble(),
                                                                y = ySearchCoordinate,
                                                                xTolerance = wideSearchXTolerance,
                                                                yTolerance = wideSearchYTolerance
                                                            )
                                                    }
                                                }

                                                if (charIndexForUpdate != -1) {
                                                    val pageCharCount =
                                                        tDrag.textPageCountChars()

                                                    val currentRange = selectionCharRange.value
                                                    if (currentRange != null) {
                                                        val (currentStart, currentEnd) = currentRange
                                                        var newRange: Pair<Int, Int>? = null
                                                        var newHandle = activeDraggingHandle

                                                        when (activeDraggingHandle) {
                                                            Handle.START -> {
                                                                val newStart =
                                                                    charIndexForUpdate.coerceIn(
                                                                        0, pageCharCount - 1
                                                                    )
                                                                if (newStart >= currentEnd - 1 && pageCharCount > 0) {
                                                                    val tempOldEndCharIndex =
                                                                        (currentEnd - 1).coerceAtLeast(
                                                                            0
                                                                        )
                                                                    newHandle = Handle.END
                                                                    newRange = Pair(
                                                                        tempOldEndCharIndex,
                                                                        (newStart + 1).coerceAtMost(
                                                                            pageCharCount
                                                                        )
                                                                    )
                                                                } else {
                                                                    newRange = Pair(
                                                                        newStart, currentEnd
                                                                    )
                                                                }
                                                            }

                                                            Handle.END -> {
                                                                val newEnd =
                                                                    (charIndexForUpdate + 1).coerceIn(
                                                                        1, pageCharCount
                                                                    )
                                                                if (newEnd <= currentStart + 1 && pageCharCount > 0) {
                                                                    newHandle = Handle.START
                                                                    newRange = Pair(
                                                                        (newEnd - 1).coerceAtLeast(
                                                                            0
                                                                        ),
                                                                        (currentStart + 1).coerceAtMost(
                                                                            pageCharCount
                                                                        )
                                                                    )
                                                                } else {
                                                                    newRange = Pair(
                                                                        currentStart, newEnd
                                                                    )
                                                                }
                                                            }

                                                            else -> {}
                                                        }

                                                        if (newRange != null) {
                                                            if (newRange.first >= newRange.second) {
                                                                if (pageCharCount > 0) {
                                                                    val fixStart = min(
                                                                        newRange.first,
                                                                        newRange.second - 1
                                                                    ).coerceIn(
                                                                        0, pageCharCount - 1
                                                                    )
                                                                    val fixEnd =
                                                                        (fixStart + 1).coerceAtMost(
                                                                            pageCharCount
                                                                        )
                                                                    newRange =
                                                                        if (fixStart < fixEnd) Pair(
                                                                            fixStart, fixEnd
                                                                        )
                                                                        else null
                                                                } else {
                                                                    newRange = null
                                                                }
                                                            }
                                                        }

                                                        withContext(
                                                            Dispatchers.Main
                                                        ) {
                                                            if (newHandle != null) activeDraggingHandle =
                                                                newHandle
                                                            if (newRange != null) selectionCharRange.value =
                                                                newRange

                                                            updateSelectionVisuals(
                                                                pdfDocumentItem,
                                                                pageIndex,
                                                                selectionCharRange.value,
                                                                actualBitmapWidthPx,
                                                                actualBitmapHeightPx,
                                                                currentPageRotation,
                                                                providedPage = pageForDrag,
                                                                providedTextPage = textPageForDrag
                                                            )

                                                            // Magnifier update
                                                            val currentActiveHandleForMagnifier =
                                                                activeDraggingHandle
                                                            val rects = selectedWordScreenRects
                                                            if (rects.isNotEmpty()) {
                                                                val relevantRect =
                                                                    when (currentActiveHandleForMagnifier) {
                                                                        Handle.START -> rects.first()
                                                                        Handle.END -> rects.last()
                                                                        null -> null
                                                                    }
                                                                val handleContentPosToFollow =
                                                                    when (currentActiveHandleForMagnifier) {
                                                                        Handle.START -> startHandleContentPosition.value
                                                                        Handle.END -> endHandleContentPosition.value
                                                                        null -> null
                                                                    }

                                                                if (relevantRect != null && handleContentPosToFollow != null) {
                                                                    magnifierBitmapCenterTarget =
                                                                        Offset(
                                                                            x = handleContentPosToFollow.x,
                                                                            y = relevantRect.exactCenterY()
                                                                        )
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            } catch (e: Exception) {
                                Timber.e(e, "Error during handle drag worker")
                            } finally {
                                withContext(Dispatchers.IO) {
                                    textPageForDrag?.close()
                                    pageForDrag?.close()
                                }
                            }
                        }

                        try {
                            drag(down.id) { change ->
                                dragEventChannel.trySend(change.position)
                                change.consume()
                            }
                        } finally {
                            dragEventChannel.close()
                        }

                        if (selectionMethodUsed == PdfSelectionMethod.PDFIUM) {
                            if (selectionCharRange.value != null && selectedWordScreenRects.isNotEmpty()) {
                                val currentRange = selectionCharRange.value!!
                                coroutineScope.launch {
                                    var pageForMenu: ReaderPage? = null
                                    var textPageForMenu: ReaderTextPage? = null
                                    try {
                                        val text = withContext(Dispatchers.IO) {
                                            pageForMenu = pdfDocumentItem.openPage(pdfPageIndex)
                                            textPageForMenu = pageForMenu?.openTextPage()
                                            textPageForMenu?.textPageGetText(
                                                currentRange.first,
                                                currentRange.second - currentRange.first
                                            )
                                        }
                                        if (!text.isNullOrBlank()) {
                                            val combinedRect = Rect(selectedWordScreenRects.first())
                                            selectedWordScreenRects.forEach { combinedRect.union(it) }

                                            customMenuState = CustomPdfMenuState(
                                                selectedText = text,
                                                anchorRect = combinedRect,
                                                charRange = currentRange
                                            )
                                            Timber.d(
                                                "Menu shown after drag. Anchor: ${customMenuState?.anchorRect}"
                                            )
                                        } else {
                                            customMenuState = null
                                        }
                                    } catch (e: Exception) {
                                        Timber.e(
                                            e, "Error fetching text for menu after drag"
                                        )
                                        customMenuState = null
                                    } finally {
                                        withContext(NonCancellable) {
                                            withContext(Dispatchers.IO) {
                                                try {
                                                    textPageForMenu?.close()
                                                } catch (_: Exception) {
                                                }
                                                try {
                                                    pageForMenu?.close()
                                                } catch (_: Exception) {
                                                }
                                            }
                                        }
                                    }
                                }
                            } else {
                                customMenuState = null
                            }
                        } else {
                            if (ocrSelectionSymbolIndices != null && selectedWordScreenRects.isNotEmpty()) {
                                val indices = ocrSelectionSymbolIndices!!
                                val selectedSymbolInfos = allOcrSymbolsForSelection.subList(
                                    indices.first, indices.second
                                )
                                if (selectedSymbolInfos.isNotEmpty()) {
                                    val selectedText = buildString {
                                        selectedSymbolInfos.forEachIndexed { index, info ->
                                            append(info.symbol.text)

                                            if (index < selectedSymbolInfos.size - 1) {
                                                val nextInfo = selectedSymbolInfos[index + 1]

                                                if (info.parentLine !== nextInfo.parentLine) {
                                                    append('\n')
                                                } else if (info.parentElement !== nextInfo.parentElement) {
                                                    append(' ')
                                                }
                                            }
                                        }
                                    }
                                    val firstRect = selectedSymbolInfos.first().symbol.boundingBox!!
                                    val combinedRect = Rect(firstRect)
                                    selectedSymbolInfos.forEach { info -> info.symbol.boundingBox?.let { combinedRect.union(it) } }

                                    customMenuState = CustomPdfMenuState(
                                        selectedText = selectedText,
                                        anchorRect = combinedRect,
                                        charRange = Pair(indices.first, indices.second)
                                    )
                                    Timber.d(
                                        "Menu shown after OCR drag. Anchor: ${customMenuState?.anchorRect}"
                                    )
                                } else {
                                    customMenuState = null
                                }
                            } else {
                                customMenuState = null
                            }
                        }
                        activeDraggingHandle = null
                        showMagnifier = false
                        Timber.d(
                            "PointerInput: Drag on handle completed/cancelled. Menu state: $customMenuState"
                        )
                    } else {
                        val longPressTimeout = viewConfiguration.longPressTimeoutMillis
                        try {
                            withTimeout(longPressTimeout) {
                                waitForUpOrCancellation()
                            }
                        } catch (_: PointerEventTimeoutCancellationException) {
                            down.consume()
                            Timber.d(
                                "PointerInput: Long press detected at screen position ${down.position}"
                            )
                            selectionCharRange.value = null
                            ocrSelectionSymbolIndices = null
                            customMenuState = null
                            showMagnifier = false

                            coroutineScope.launch {
                                var tempPage: ReaderPage? = null
                                var tempTextPage: ReaderTextPage? = null
                                var ocrAttemptedForThisPress = false
                                try {
                                    if (!isPdfPage) return@launch

                                    val touchInContentCoords = screenToContentCoordinates(down.position)
                                    Timber.d("Long press: initial touch in content coords: $touchInContentCoords")

                                    if (touchInContentCoords.x < 0 || touchInContentCoords.x > actualBitmapWidthPx || touchInContentCoords.y < 0 || touchInContentCoords.y > actualBitmapHeightPx) {
                                        Timber.d("Long press: Touch point outside bitmap bounds.")
                                        return@launch
                                    }

                                    var pdfiumSelectionSuccessful = false
                                    withContext(Dispatchers.IO) {
                                        tempPage = pdfDocumentItem.openPage(pdfPageIndex)
                                        tempTextPage = tempPage?.openTextPage()

                                        val tPage = tempPage
                                        val tTextPage = tempTextPage
                                        if (tPage != null && tTextPage != null) {
                                            val pdfCoords = tPage.mapDeviceCoordsToPage(
                                                startX = 0,
                                                startY = 0,
                                                sizeX = actualBitmapWidthPx,
                                                sizeY = actualBitmapHeightPx,
                                                rotate = currentPageRotation,
                                                deviceX = touchInContentCoords.x.toInt(),
                                                deviceY = touchInContentCoords.y.toInt()
                                            )
                                            val charTolerance = 5.0
                                            val charIndex = tTextPage.textPageGetCharIndexAtPos(
                                                x = pdfCoords.x.toDouble(),
                                                y = pdfCoords.y.toDouble(),
                                                xTolerance = charTolerance,
                                                yTolerance = charTolerance
                                            )

                                            if (charIndex != -1) {
                                                val pageCharCount = tTextPage.textPageCountChars()
                                                val wordBoundaries = findWordBoundaries(
                                                    tTextPage, charIndex, pageCharCount
                                                )

                                                if (wordBoundaries != null) {
                                                    withContext(Dispatchers.Main) {
                                                        selectionMethodUsed = PdfSelectionMethod.PDFIUM
                                                        selectionCharRange.value = wordBoundaries
                                                    }
                                                    updateSelectionVisuals(
                                                        pdfDocumentItem,
                                                        pdfPageIndex,
                                                        wordBoundaries,
                                                        actualBitmapWidthPx,
                                                        actualBitmapHeightPx,
                                                        currentPageRotation,
                                                        providedPage = tPage,
                                                        providedTextPage = tTextPage
                                                    )
                                                    withContext(Dispatchers.Main) {
                                                        if (selectionCharRange.value != null && selectedWordScreenRects.isNotEmpty()) {
                                                            val currentRange = selectionCharRange.value!!
                                                            val text = withContext(Dispatchers.IO) {
                                                                tTextPage.textPageGetText(
                                                                    currentRange.first,
                                                                    currentRange.second - currentRange.first
                                                                )
                                                            }
                                                            if (!text.isNullOrBlank()) {
                                                                val combinedRect = Rect(selectedWordScreenRects.first())
                                                                selectedWordScreenRects.forEach { combinedRect.union(it) }

                                                                customMenuState = CustomPdfMenuState(
                                                                    selectedText = text,
                                                                    anchorRect = combinedRect,
                                                                    charRange = currentRange
                                                                )
                                                                pdfiumSelectionSuccessful = true
                                                                Timber.d(
                                                                    "Long press: PDFIUM selection successful. Menu: ${customMenuState?.anchorRect}"
                                                                )
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }

                                    if (!pdfiumSelectionSuccessful && bitmapState != null) {
                                        Timber.d(
                                            "Long press: PDFIUM selection failed or incomplete. Attempting OCR."
                                        )
                                        ocrRipplePosition = down.position
                                        ocrAttemptedForThisPress = true
                                        isPerformingOcrForSelection = true
                                        customMenuState = null
                                        selectionCharRange.value = null
                                        selectedWordScreenRects = emptyList()

                                        try {
                                            val visionText = OcrHelper.extractTextFromBitmap(
                                                bitmapState!!, onOcrModelDownloading
                                            )
                                            ocrVisionTextForSelection = visionText

                                            if (visionText != null && visionText.text.isNotBlank()) {
                                                val symbolInfoList = mutableListOf<OcrSymbolInfo>()
                                                val allElements = mutableListOf<OcrElement>()
                                                visionText.textBlocks.forEach { block ->
                                                    block.lines.forEach { line ->
                                                        line.elements.forEach { element ->
                                                            allElements.add(element)
                                                            element.symbols.forEach { symbol ->
                                                                symbolInfoList.add(
                                                                    OcrSymbolInfo(
                                                                        symbol, element, line
                                                                    )
                                                                )
                                                            }
                                                        }
                                                    }
                                                }
                                                allOcrSymbolsForSelection = symbolInfoList

                                                var foundElement: OcrElement? = null
                                                val touchInContentCoords =
                                                    screenToContentCoordinates(
                                                        down.position
                                                    )
                                                val touchXInBitmap = touchInContentCoords.x
                                                val touchYInBitmap = touchInContentCoords.y
                                                for (element in allElements) {
                                                    val box = element.boundingBox
                                                    if (box != null) {
                                                        if (touchXInBitmap >= box.left && touchXInBitmap <= box.right && touchYInBitmap >= box.top && touchYInBitmap <= box.bottom) {
                                                            foundElement = element
                                                            break
                                                        }
                                                    }
                                                }

                                                if (foundElement != null) {
                                                    var symbolStartIndex = 0
                                                    var elementFoundInList = false
                                                    for (element in allElements) {
                                                        if (element === foundElement) {
                                                            elementFoundInList = true
                                                            break
                                                        }
                                                        symbolStartIndex += element.symbols.size
                                                    }

                                                    if (elementFoundInList && foundElement.symbols.isNotEmpty()) {
                                                        val symbolEndIndex =
                                                            symbolStartIndex + foundElement.symbols.size

                                                        selectionMethodUsed = PdfSelectionMethod.OCR
                                                        ocrSelectionSymbolIndices = Pair(
                                                            symbolStartIndex, symbolEndIndex
                                                        )
                                                        updateOcrSymbolSelectionRectsAndHandles(
                                                            ocrSelectionSymbolIndices
                                                        )

                                                        val combinedRect = Rect(selectedWordScreenRects.first())
                                                        selectedWordScreenRects.forEach { combinedRect.union(it) }

                                                        customMenuState = CustomPdfMenuState(
                                                            selectedText = foundElement.text,
                                                            anchorRect = combinedRect,
                                                            charRange = Pair(
                                                                symbolStartIndex, symbolEndIndex
                                                            )
                                                        )
                                                        Timber.d(
                                                            "Long press: OCR selection successful. Menu: ${customMenuState?.anchorRect}"
                                                        )
                                                    } else {
                                                        Timber.d(
                                                            "Long press: OCR word found, but couldn't calculate its symbol indices. Found in list: $elementFoundInList"
                                                        )
                                                        customMenuState = null
                                                        ocrSelectionSymbolIndices = null
                                                        allOcrSymbolsForSelection = emptyList()
                                                        selectedWordScreenRects = emptyList()
                                                        startHandleContentPosition.value = null
                                                        endHandleContentPosition.value = null
                                                    }
                                                } else {
                                                    Timber.d(
                                                        "Long press: OCR successful but no text element at touch point."
                                                    )
                                                    customMenuState = null
                                                    ocrSelectionSymbolIndices = null
                                                    allOcrSymbolsForSelection = emptyList()
                                                    selectedWordScreenRects = emptyList()
                                                    startHandleContentPosition.value = null
                                                    endHandleContentPosition.value = null
                                                }
                                            } else {
                                                Timber.d(
                                                    "Long press: OCR returned no text."
                                                )
                                            }
                                        } catch (e: Exception) {
                                            Timber.e(
                                                e, "Long press: Error during OCR text selection"
                                            )
                                            pageErrorMessage = context.getString(
                                                R.string.error_ocr_selection,
                                                e.readablePdfErrorDetail()
                                            )
                                        } finally {
                                            isPerformingOcrForSelection = false
                                            ocrRipplePosition = null
                                        }
                                    } else if (!pdfiumSelectionSuccessful) {
                                        Timber.d(
                                            "Long press: PDFIUM selection failed and no bitmap for OCR."
                                        )
                                    }

                                    if (!pdfiumSelectionSuccessful && !ocrAttemptedForThisPress) {
                                        customMenuState = null
                                        selectionCharRange.value = null
                                        selectedWordScreenRects = emptyList()
                                    }
                                } catch (e: Exception) {
                                    Timber.e(
                                        e,
                                        "Error during long press text selection on page $pageIndex"
                                    )
                                    pageErrorMessage = context.getString(
                                        R.string.error_selection,
                                        e.readablePdfErrorDetail()
                                    )
                                    customMenuState = null
                                    selectionCharRange.value = null
                                    selectedWordScreenRects = emptyList()
                                } finally {
                                    withContext(NonCancellable) {
                                        withContext(Dispatchers.IO) {
                                            try {
                                                tempTextPage?.close()
                                            } catch (_: Exception) {
                                            }
                                            try {
                                                tempPage?.close()
                                            } catch (_: Exception) {
                                            }
                                        }
                                    }
                                    if (isPerformingOcrForSelection && !ocrAttemptedForThisPress) isPerformingOcrForSelection =
                                        false
                                }
                            }
                            val longPressInteractionEndEvent = waitForUpOrCancellation()
                            longPressInteractionEndEvent?.consume()
                            Timber.d(
                                "PointerInput: Long press interaction's 'up' event consumed."
                            )
                        }
                    }
                }
            }

            .pointerInput(
                actualBitmapWidthPx,
                actualBitmapHeightPx,
                scale,
                offset,
                customMenuState,
                selectionCharRange.value,
                pageLinks,
                centeringOffsetX,
                centeringOffsetY,
                isZoomEnabled,
                isVerticalScroll,
                isEditMode,
                selectedTool,
                isStylusOnlyMode,
                userHighlightScreenRects,
                bubbleTapSlopPx,
                isScrollLocked
            ) {
                val isTapDetectionAllowed = !isEditMode ||
                        selectedTool == InkType.TEXT ||
                        isStylusOnlyMode

                if (!isTapDetectionAllowed) return@pointerInput

                detectTapGestures(onTap = { tapOffset ->
                    if (currentOnPreSingleTap?.invoke(tapOffset) == true) {
                        return@detectTapGestures
                    }

                    val tapInContentCoords = screenToContentCoordinates(tapOffset)
                    val tapXInBitmap = tapInContentCoords.x
                    val tapYInBitmap = tapInContentCoords.y
                    val isWithinContentBounds =
                        tapXInBitmap in 0f..actualBitmapWidthPx.toFloat() &&
                            tapYInBitmap in 0f..actualBitmapHeightPx.toFloat()

                    if (!isWithinContentBounds) {
                        currentOnSingleTap(tapOffset)
                        return@detectTapGestures
                    }

                    Timber.tag("BubbleZoom").d("Tap inside bounds. modeActive=$currentBubbleZoomModeActive, detectedBubbles=${currentDetectedBubbles.size}, tapPos=($tapXInBitmap, $tapYInBitmap)")

                    if (currentBubbleZoomModeActive && currentDetectedBubbles.isNotEmpty()) {
                        val tappedBubbleIndex = currentDetectedBubbles.indexOfFirst { bubble ->
                            isTapInsideBubble(
                                bubble = bubble,
                                tapX = tapXInBitmap,
                                tapY = tapYInBitmap,
                                hitSlopPx = bubbleTapSlopPx
                            )
                        }

                        Timber.tag("BubbleZoom").d("Tapped bubble index: $tappedBubbleIndex (expandedIndex=$currentExpandedBubbleIndex)")

                        if (tappedBubbleIndex != -1) {
                            expandedBubbleIndex = if (currentExpandedBubbleIndex == tappedBubbleIndex) {
                                -1
                            } else {
                                tappedBubbleIndex
                            }
                            return@detectTapGestures
                        } else if (currentExpandedBubbleIndex != -1) {
                            expandedBubbleIndex = -1
                            return@detectTapGestures
                        }
                    }

                    coroutineScope.launch {
                        val nativeResult = withContext(Dispatchers.IO) {
                            try {
                                pdfDocumentItem.openPage(pdfPageIndex)?.use { page ->
                                    val pagePtr = page.getNativePointer()

                                    if (pagePtr == 0L) {
                                        Timber.tag("PdfInteraction").e("Could not find native pointer for page $pdfPageIndex")
                                        return@withContext 0
                                    }

                                    val pdfCoords = page.mapDeviceCoordsToPage(
                                        0, 0, actualBitmapWidthPx, actualBitmapHeightPx,
                                        currentPageRotation, tapInContentCoords.x.toInt(), tapInContentCoords.y.toInt()
                                    )

                                    val docPtr = try {
                                        val pdfDocKt = (pdfDocumentItem as? PdfDocumentWrapper)?.pdfDocument
                                        if (pdfDocKt != null) {
                                            val documentField = pdfDocKt.javaClass.getDeclaredField("document").apply { isAccessible = true }
                                            val docUInstance = documentField.get(pdfDocKt)
                                            if (docUInstance != null) {
                                                val ptrField = docUInstance.javaClass.getDeclaredField("mNativeDocPtr").apply { isAccessible = true }
                                                ptrField.get(docUInstance) as Long
                                            } else 0L
                                        } else 0L
                                    } catch (e: Exception) { 0L }

                                    Timber.tag("PdfLinkDiagnostic").i("Extracted docPtr: $docPtr | pagePtr: $pagePtr")

                                    val linkInfo = PdfiumEngineProvider.bridge.getLinkInfoAtPoint(
                                        docPtr, pagePtr, pdfCoords.x.toDouble(), pdfCoords.y.toDouble()
                                    )

                                    if (linkInfo != null) {
                                        Timber.tag("PdfLinkDiagnostic").i(">>> Native Link Info Extracted: $linkInfo")
                                        if (linkInfo.startsWith("URI:")) {
                                            val url = linkInfo.substringAfter("URI:")
                                            withContext(Dispatchers.Main) { onLinkClicked(url) }
                                            return@withContext 1
                                        } else if (linkInfo.startsWith("PAGE:")) {
                                            val targetPage = linkInfo.substringAfter("PAGE:").toIntOrNull()
                                            if (targetPage != null && targetPage >= 0) {
                                                withContext(Dispatchers.Main) { onInternalLinkClicked(targetPage) }
                                                return@withContext 1
                                            }
                                        }
                                    }

                                    val clickHandled = PdfiumEngineProvider.bridge.performClick(pagePtr, pdfCoords.x.toDouble(), pdfCoords.y.toDouble())
                                    if (clickHandled) {
                                        return@withContext 2
                                    }
                                    return@withContext 0
                                } ?: 0
                            } catch (e: Exception) {
                                Timber.tag("PdfInteraction").e(e, "Interaction error")
                                0
                            }
                        }

                        if (nativeResult == 2) {
                            Timber.tag("PdfInteraction").i("Action detected. Refreshing page.")
                            tiles = emptyList()
                            bitmapState = null
                            isLoadingPage = true
                            currentRenderedPageId = "ACTION_${System.currentTimeMillis()}"
                            return@launch
                        } else if (nativeResult == 1) {
                            return@launch
                        }

                        val annotHitTolerance = with(density) { 24.dp.toPx() } / inputScale
                        val hitTolerance = with(density) { 16.dp.toPx() } / inputScale

                        Timber.d("detectTapGestures: Tap at bitmap coords (${tapXInBitmap.toInt()}, ${tapYInBitmap.toInt()})")

                        var tappedRect: Rect? = null
                        val hitHighlightPair = userHighlightScreenRects.findLast { pair ->
                            val hit = pair.second.find { r ->
                                val hitLeft = r.left - hitTolerance
                                val hitTop = r.top - hitTolerance
                                val hitRight = r.right + hitTolerance
                                val hitBottom = r.bottom + hitTolerance

                                tapXInBitmap in hitLeft..hitRight && tapYInBitmap >= hitTop && tapYInBitmap <= hitBottom
                            }
                            if (hit != null) {
                                tappedRect = hit
                                true
                            } else false
                        }

                        val standardHit = standardAnnotScreenRects.findLast { (annot, screenRect) ->
                            if (annot.subtype == 2) return@findLast false

                            val left = min(screenRect.left, screenRect.right)
                            val right = max(screenRect.left, screenRect.right)
                            val top = min(screenRect.top, screenRect.bottom)
                            val bottom = max(screenRect.top, screenRect.bottom)

                            val inflatedHitBox = Rect(
                                (left - annotHitTolerance).toInt(),
                                (top - annotHitTolerance).toInt(),
                                (right + annotHitTolerance).toInt(),
                                (bottom + annotHitTolerance).toInt()
                            )

                            inflatedHitBox.contains(tapInContentCoords.x.toInt(), tapInContentCoords.y.toInt())
                        }

                        if (standardHit != null) {
                            val (annot, screenRect) = standardHit
                            customMenuState = CustomPdfMenuState(
                                selectedText = annot.contents ?: "No comment",
                                anchorRect = screenRect,
                                charRange = Pair(-1, -1),
                                isComment = true,
                                author = annot.author,
                                annotation = annot
                            )
                            return@launch
                        }

                        if (hitHighlightPair != null && tappedRect != null) {
                            val hitHighlight = hitHighlightPair.first
                            onNoteRequested(hitHighlight.id)
                            return@launch
                        }

                        val clickedLink = pageLinks.firstOrNull { link ->
                            link.tapBounds.contains(tapXInBitmap.toInt(), tapYInBitmap.toInt())
                        }

                        if (clickedLink != null) {
                            Timber.d("PdfPageComposable: Fallback pageLinks intercepted click.")
                            if (clickedLink.destPageIdx != null && clickedLink.destPageIdx >= 0) {
                                onInternalLinkClicked(clickedLink.destPageIdx)
                            } else if (clickedLink.url != null) {
                                onLinkClicked(clickedLink.url)
                            }
                            return@launch
                        }

                        val wasMenuVisible = customMenuState != null
                        val wasSelectionVisible = selectionCharRange.value != null || ocrSelectionSymbolIndices != null

                        if (wasMenuVisible || wasSelectionVisible) {
                            customMenuState = null
                            selectionCharRange.value = null
                            ocrSelectionSymbolIndices = null
                            updateSelectionVisuals(
                                pdfDocumentItem,
                                pdfPageIndex,
                                null,
                                actualBitmapWidthPx,
                                actualBitmapHeightPx,
                                currentPageRotation,
                            )
                        } else {
                            currentOnSingleTap(tapOffset)
                        }
                    }
                }, onDoubleTap = { tapOffset ->
                    if (isZoomEnabled && !isVerticalScroll && !isScrollLocked) {
                        if (actualBitmapWidthPx == 0) return@detectTapGestures
                        coroutineScope.launch {
                            val startScale = scale
                            val targetScale = if (startScale > 1.1f) 1f else 2.5f

                            val startOffset = offset
                            val targetOffsetUnbounded = if (targetScale <= 1.1f) {
                                Offset.Zero
                            } else {
                                val ratio = targetScale / startScale
                                val screenCenter = Offset(
                                    size.width / 2f, size.height / 2f
                                )
                                startOffset * ratio + (tapOffset - screenCenter) * (1 - ratio)
                            }

                            val contentWidth = actualBitmapWidthPx * targetScale
                            val contentHeight = actualBitmapHeightPx * targetScale
                            val maxOffsetX = (contentWidth - size.width).coerceAtLeast(0f) / 2f
                            val maxOffsetY = (contentHeight - size.height).coerceAtLeast(0f) / 2f

                            val targetOffset = Offset(
                                x = targetOffsetUnbounded.x.coerceIn(
                                    -maxOffsetX, maxOffsetX
                                ), y = targetOffsetUnbounded.y.coerceIn(
                                    -maxOffsetY, maxOffsetY
                                )
                            )

                            Animatable(0f).animateTo(
                                1f, animationSpec = tween(
                                    durationMillis = 300
                                )
                            ) {
                                val progress = value
                                scale = androidx.compose.ui.util.lerp(
                                    startScale, targetScale, progress
                                )
                                offset = androidx.compose.ui.geometry.lerp(
                                    startOffset, targetOffset, progress
                                )
                                onScaleChanged(scale)
                            }
                            if (scale <= 1.05f) {
                                scale = 1f
                                offset = Offset.Zero
                                onScaleChanged(scale)
                            }
                        }
                    } else if (isVerticalScroll && !isScrollLocked && currentOnDoubleTap != null) {
                        currentOnDoubleTap!!(tapOffset)
                    }
                })
            }
            .pointerInput(
                actualBitmapWidthPx,
                actualBitmapHeightPx,
                activeDraggingHandle,
                isZoomEnabled,
                isVerticalScroll,
                isEditMode,
                onTwoFingerSwipe,
                isScrollLocked
            ) {
                if (!isZoomEnabled || isVerticalScroll || actualBitmapWidthPx == 0 || activeDraggingHandle != null) return@pointerInput

                val decay = splineBasedDecay<Float>(this)
                val velocityTracker = VelocityTracker()
                val touchSlop = viewConfiguration.touchSlop

                awaitEachGesture {
                    @Suppress("UnusedVariable", "Unused") val down =
                        awaitFirstDown(requireUnconsumed = false)
                    paginationPanFlingJob?.cancel()
                    paginationPanFlingJob = null
                    velocityTracker.resetTracking()

                    var mode = 0
                    var accumulatedZoom = 1f
                    var accumulatedPan = Offset.Zero
                    var swipeAccumulatorX = 0f
                    var velocityAccumulator = Offset.Zero

                    do {
                        val event = awaitPointerEvent()
                        val canceled = event.changes.any { it.isConsumed }
                        val pointerCount = event.changes.size

                        if (!canceled) {
                            val rawPanChange = event.calculatePan()
                            val panChange = if (isScrollLocked && pointerCount == 1) {
                                if (isVerticalScroll) Offset(0f, rawPanChange.y) else Offset.Zero
                            } else {
                                rawPanChange
                            }
                            val zoomChange = event.calculateZoom()

                            if (scale > 1f) {
                                if (mode == 0) {
                                    if (pointerCount == 1) {
                                        accumulatedPan += panChange
                                        if (accumulatedPan.getDistance() > touchSlop) {
                                            mode = 1
                                            Timber.tag("PdfZoomDebug").d("Mode Change: PAN (Single Pointer)")
                                        }
                                    } else if (pointerCount > 1) {
                                        accumulatedZoom *= zoomChange
                                        accumulatedPan += panChange

                                        val zoomDiff = abs(accumulatedZoom - 1f)
                                        val panDist = accumulatedPan.getDistance()

                                        if (zoomDiff > 0.05f) {
                                            mode = 2
                                            Timber.tag("PdfZoomDebug").d("Mode Change: ZOOM (Multi Pointer)")
                                        } else if (panDist > touchSlop) {
                                            mode = 1
                                            Timber.tag("PdfZoomDebug").d("Mode Change: PAN (Multi Pointer)")
                                        }
                                    }
                                }

                                if (mode == 1) {
                                    val contentWidth = actualBitmapWidthPx * scale
                                    val contentHeight = actualBitmapHeightPx * scale
                                    val maxOffsetX =
                                        (contentWidth - size.width).coerceAtLeast(0f) / 2f
                                    val maxOffsetY =
                                        (contentHeight - size.height).coerceAtLeast(0f) / 2f

                                    val newX = (offset.x + panChange.x).coerceIn(
                                        -maxOffsetX, maxOffsetX
                                    )
                                    val newY = (offset.y + panChange.y).coerceIn(
                                        -maxOffsetY, maxOffsetY
                                    )
                                    Timber.tag("PdfZoomDebug").v("Panning: Offset $offset -> $newX, $newY (Max: $maxOffsetX, $maxOffsetY)")
                                    offset = Offset(newX, newY)

                                    if (event.changes.isNotEmpty() && panChange != Offset.Zero) {
                                        velocityAccumulator += panChange
                                        velocityTracker.addPosition(
                                            event.changes[0].uptimeMillis,
                                            velocityAccumulator
                                        )
                                    }

                                    event.changes.forEach {
                                        if (it.positionChanged()) it.consume()
                                    }
                                } else if (mode == 2 && pointerCount > 1) {
                                    val oldScale = scale
                                    val newScale = (scale * zoomChange).coerceIn(1f, 4f)
                                    Timber.tag("PdfZoomIssue").v("Gesture Scaling: old=$oldScale, new=$newScale, zoomChange=$zoomChange")

                                    val previousCentroid = event.calculateCentroid(useCurrent = false)
                                    if (previousCentroid != Offset.Unspecified) {
                                        val ratio = newScale / oldScale
                                        val screenCenter = Offset(size.width / 2f, size.height / 2f)
                                        val newOffset = offset * ratio + (previousCentroid - screenCenter) * (1 - ratio) + panChange

                                        val contentWidth = actualBitmapWidthPx * newScale
                                        val contentHeight = actualBitmapHeightPx * newScale
                                        val maxOffsetX = (contentWidth - size.width).coerceAtLeast(0f) / 2f
                                        val maxOffsetY = (contentHeight - size.height).coerceAtLeast(0f) / 2f

                                        Timber.tag("PdfZoomDebug").v("Scaling: RawZoom=$zoomChange, Scale=$oldScale->$newScale, Offset=$offset->$newOffset (Clamped Max: $maxOffsetX, $maxOffsetY)")

                                        offset = Offset(
                                            x = newOffset.x.coerceIn(-maxOffsetX, maxOffsetX),
                                            y = newOffset.y.coerceIn(-maxOffsetY, maxOffsetY)
                                        )
                                        scale = newScale
                                        onScaleChanged(scale)
                                    }
                                    event.changes.forEach {
                                        if (it.positionChanged()) it.consume()
                                    }
                                }
                            } else {
                                if (pointerCount > 1) {
                                    if (mode == 0) {
                                        accumulatedZoom *= zoomChange
                                        accumulatedPan += panChange

                                        if (abs(accumulatedZoom - 1f) > 0.05f) {
                                            mode = 2
                                        } else if (accumulatedPan.getDistance() > touchSlop) {
                                            if (abs(accumulatedPan.x) > abs(accumulatedPan.y) * 1.5f) {
                                                mode = 3
                                            }
                                        }
                                    }

                                    if (mode == 2) {
                                        val oldScale = scale
                                        val newScale = (scale * zoomChange).coerceIn(
                                            1f, 4f
                                        )
                                        val previousCentroid = event.calculateCentroid(
                                            useCurrent = false
                                        )

                                        if (previousCentroid != Offset.Unspecified) {
                                            val ratio = newScale / oldScale
                                            val screenCenter = Offset(
                                                size.width / 2f, size.height / 2f
                                            )
                                            val newOffset =
                                                offset * ratio + (previousCentroid - screenCenter) * (1 - ratio) + panChange

                                            val contentWidth = actualBitmapWidthPx * newScale
                                            val contentHeight = actualBitmapHeightPx * newScale
                                            val maxOffsetX =
                                                (contentWidth - size.width).coerceAtLeast(0f) / 2f
                                            val maxOffsetY =
                                                (contentHeight - size.height).coerceAtLeast(0f) / 2f

                                            offset = Offset(
                                                x = newOffset.x.coerceIn(
                                                    -maxOffsetX, maxOffsetX
                                                ), y = newOffset.y.coerceIn(
                                                    -maxOffsetY, maxOffsetY
                                                )
                                            )
                                            scale = newScale
                                            onScaleChanged(scale)
                                        }
                                        event.changes.forEach {
                                            if (it.positionChanged()) it.consume()
                                        }
                                    } else if (mode == 3) {
                                        swipeAccumulatorX += panChange.x
                                        if (abs(swipeAccumulatorX) > 70f) {
                                            val direction = if (swipeAccumulatorX > 0) -1
                                            else 1
                                            onTwoFingerSwipe(direction)
                                            mode = 4
                                        }
                                        event.changes.forEach {
                                            if (it.positionChanged()) it.consume()
                                        }
                                    }
                                }
                            }
                        }
                    } while (!canceled && event.changes.any { it.pressed })

                    if (scale > 1f && scale < 1.05f) {
                        coroutineScope.launch {
                            val startScale = scale
                            val startOffset = offset
                            Animatable(0f).animateTo(1f) {
                                scale = lerp(startScale, 1f, value)
                                offset = lerp(startOffset, Offset.Zero, value)
                                onScaleChanged(scale)
                            }
                        }
                    } else if (mode == 1 && scale > 1f) {
                        val contentWidth = actualBitmapWidthPx * scale
                        val contentHeight = actualBitmapHeightPx * scale
                        val maxOffsetX = (contentWidth - size.width).coerceAtLeast(0f) / 2f
                        val maxOffsetY = (contentHeight - size.height).coerceAtLeast(0f) / 2f

                        val velocity = velocityTracker.calculateVelocity()
                        val flingX = if (!isScrollLocked && abs(velocity.x) > PDF_PAGINATION_PAN_FLING_MIN_VELOCITY) {
                            velocity.x * PDF_PAGINATION_PAN_FLING_MULTIPLIER
                        } else {
                            0f
                        }
                        val flingY = if (abs(velocity.y) > PDF_PAGINATION_PAN_FLING_MIN_VELOCITY) {
                            velocity.y * PDF_PAGINATION_PAN_FLING_MULTIPLIER
                        } else {
                            0f
                        }

                        if (flingX == 0f && flingY == 0f) {
                            offset = Offset(
                                x = offset.x.coerceIn(-maxOffsetX, maxOffsetX),
                                y = offset.y.coerceIn(-maxOffsetY, maxOffsetY)
                            )
                        } else {
                            val startOffset = offset
                            paginationPanFlingJob = coroutineScope.launch {
                                try {
                                    kotlinx.coroutines.coroutineScope {
                                        launch {
                                            if (flingX != 0f) {
                                                Animatable(startOffset.x).animateDecay(flingX, decay) {
                                                    offset = offset.copy(
                                                        x = value.coerceIn(-maxOffsetX, maxOffsetX)
                                                    )
                                                }
                                            }
                                        }
                                        launch {
                                            if (flingY != 0f) {
                                                Animatable(startOffset.y).animateDecay(flingY, decay) {
                                                    offset = offset.copy(
                                                        y = value.coerceIn(-maxOffsetY, maxOffsetY)
                                                    )
                                                }
                                            }
                                        }
                                    }
                                } finally {
                                    paginationPanFlingJob = null
                                }
                            }
                        }
                    }
                }
            }
            .pointerInput(
                isEditMode,
                actualBitmapWidthPx,
                actualBitmapHeightPx,
                scale,
                offset,
                isScrolling,
                isVerticalScroll,
                selectedTool,
                isStylusOnlyMode,
                isHighlighterSnapEnabled
            ) {
                val canDraw = isEditMode && selectedTool != InkType.TEXT && !isScrolling && !isVerticalScroll && actualBitmapWidthPx > 0 && actualBitmapHeightPx > 0

                if (!canDraw) {
                    return@pointerInput
                }

                try {
                    awaitEachGesture {
                        val down = awaitFirstDown(requireUnconsumed = false)

                        Timber.tag("PointerTypeDebug").d("Page $pageIndex: Input Type detected: ${down.type}")

                        if (isStylusOnlyMode && down.type == PointerType.Touch) {
                            return@awaitEachGesture
                        }

                        val buttons = currentEvent.buttons
                        Timber.tag("StylusDebug").d(
                            "Page $pageIndex | Type: ${down.type} | isPrimary: ${buttons.isPrimaryPressed} | isSecondary: ${buttons.isSecondaryPressed} | isTertiary: ${buttons.isTertiaryPressed} | buttonsString: $buttons"
                        )

                        val isEraserOverride = down.type == PointerType.Eraser || (down.type == PointerType.Stylus && (currentEvent.buttons.isSecondaryPressed || currentEvent.buttons.isPrimaryPressed || stylusButtonHovering))
                        isStylusEraserOverride = isEraserOverride

                        val dragPointerId = down.id
                        val startPos = down.position
                        var dragStarted = false
                        val touchSlop = viewConfiguration.touchSlop

                        if (selectedTool == InkType.ERASER || isEraserOverride) {
                            eraserPosition = down.position
                        }

                        while (true) {
                            val event = awaitPointerEvent()

                            if (event.changes.size > 1) {
                                if (dragStarted) {
                                    drawingState?.onDrawCancel()
                                }
                                eraserPosition = null
                                isStylusEraserOverride = false
                                return@awaitEachGesture
                            }

                            val change = event.changes.firstOrNull {
                                it.id == dragPointerId
                            }
                            if (change == null) return@awaitEachGesture

                            if (change.changedToUp()) {
                                change.consume()
                                if (!dragStarted) {
                                    val contentPos = screenToContentCoordinates(startPos)
                                    val normX =
                                        (contentPos.x / actualBitmapWidthPx).coerceIn(0f, 1f)
                                    val normY =
                                        (contentPos.y / actualBitmapHeightPx).coerceIn(0f, 1f)

                                    onDrawStart(PdfPoint(normX, normY), isEraserOverride)
                                    onDrawEnd()
                                } else {
                                    onDrawEnd()
                                }
                                eraserPosition = null
                                isStylusEraserOverride = false
                                return@awaitEachGesture
                            }

                            if (change.positionChanged()) {
                                val dist = (change.position - startPos).getDistance()

                                if (!dragStarted) {
                                    if (dist > touchSlop) {
                                        dragStarted = true

                                        val startContentPos = screenToContentCoordinates(startPos)
                                        val startNormX =
                                            (startContentPos.x / actualBitmapWidthPx).coerceIn(
                                                0f, 1f
                                            )
                                        val startNormY =
                                            (startContentPos.y / actualBitmapHeightPx).coerceIn(
                                                0f, 1f
                                            )
                                        onDrawStart(
                                            PdfPoint(startNormX, startNormY), isEraserOverride
                                        )

                                        val currContentPos = screenToContentCoordinates(
                                            change.position
                                        )
                                        val currNormX =
                                            (currContentPos.x / actualBitmapWidthPx).coerceIn(
                                                0f, 1f
                                            )
                                        val currNormY =
                                            (currContentPos.y / actualBitmapHeightPx).coerceIn(
                                                0f, 1f
                                            )
                                        onDraw(PdfPoint(currNormX, currNormY), isEraserOverride)

                                        if (selectedTool == InkType.ERASER || isEraserOverride) {
                                            eraserPosition = change.position
                                        }
                                        change.consume()
                                    }
                                } else {
                                    val currContentPos = screenToContentCoordinates(
                                        change.position
                                    )
                                    val currNormX =
                                        (currContentPos.x / actualBitmapWidthPx).coerceIn(0f, 1f)
                                    val currNormY =
                                        (currContentPos.y / actualBitmapHeightPx).coerceIn(0f, 1f)
                                    onDraw(PdfPoint(currNormX, currNormY), isEraserOverride)

                                    if (selectedTool == InkType.ERASER || isEraserOverride) {
                                        eraserPosition = change.position
                                    }
                                    change.consume()
                                }
                            }
                        }
                    }
                } finally {
                    eraserPosition = null
                    isStylusEraserOverride = false
                }
            }, contentAlignment = Alignment.Center
    ) {
        SideEffect {
            if (effectiveScale > 1f) {
                Timber.tag("PdfZoomDiagnostics").d(
                    "BoxWithConstraints Page $pageIndex: MaxW=$maxWidth, MaxH=$maxHeight"
                )
            }
        }
        val imeInsets = WindowInsets.ime
        val screenHeight = constraints.maxHeight.toFloat()

        val imeBottom = imeInsets.getBottom(density)

        LaunchedEffect(
            richTextController?.cursorPageIndex,
            richTextController?.cursorRectInPage,
            imeBottom,
            pageIndex,
            screenHeight,
            isEditMode,
            selectedTool,
            density
        ) {
            val controller = richTextController ?: return@LaunchedEffect

            Timber.tag("KeyboardAdjust").v(
                "Check: Page=$pageIndex, IME=$imeBottom, EditMode=$isEditMode, OffsetY=${offset.y}"
            )

            if (imeBottom == 0 || !isEditMode || selectedTool != InkType.TEXT) {
                if (keyboardAdjustmentOriginalOffset != null) {
                    Timber.tag("KeyboardAdjust")
                        .i("Restoring original offset: $keyboardAdjustmentOriginalOffset")
                    if (isZoomEnabled && !isVerticalScroll) {
                        offset = offset.copy(y = keyboardAdjustmentOriginalOffset!!)
                    }
                    keyboardAdjustmentOriginalOffset = null
                }
                return@LaunchedEffect
            }

            val cursorPage = controller.cursorPageIndex
            val cursorRect = controller.cursorRectInPage

            if (cursorPage == pageIndex && cursorRect != null) {
                if (keyboardAdjustmentOriginalOffset == null) {
                    keyboardAdjustmentOriginalOffset = offset.y
                    Timber.tag("KeyboardAdjust").i("Snapshot original offset: ${offset.y}")
                }

                val baseOffsetY = keyboardAdjustmentOriginalOffset!!

                val currentScale = if (isZoomEnabled && !isVerticalScroll) scale else externalScale
                val currentOffset =
                    if (isZoomEnabled && !isVerticalScroll) offset.copy(y = baseOffsetY)
                    else Offset.Zero

                val contentHeight = actualBitmapHeightPx * currentScale
                val centeringY = (screenHeight - contentHeight) / 2f

                val cursorTopInPage = cursorRect.top * currentScale
                val cursorBottomInPage = cursorRect.bottom * currentScale

                val cursorScreenTop = centeringY + currentOffset.y + cursorTopInPage
                val cursorScreenBottom = centeringY + currentOffset.y + cursorBottomInPage

                val topSafeBuffer = with(density) { 80.dp.toPx() }

                val visibleBottom = screenHeight - imeBottom

                var targetAdjustment = 0f

                if (cursorScreenBottom > visibleBottom) {
                    targetAdjustment = visibleBottom - cursorScreenBottom
                } else if (cursorScreenTop < topSafeBuffer) {
                    targetAdjustment = topSafeBuffer - cursorScreenTop
                }

                Timber.tag("KeyboardAdjust").v(
                    "CursorBottom=$cursorScreenBottom, VisibleBottom=$visibleBottom, TargetAdj=$targetAdjustment"
                )

                if (kotlin.math.abs(targetAdjustment) > 1f && isZoomEnabled && !isVerticalScroll) {
                    val finalTargetY = baseOffsetY + targetAdjustment

                    if (offset.y != finalTargetY) {
                        Timber.tag("KeyboardAdjust").i("Applying adjustment. NewY=$finalTargetY")
                        offset = offset.copy(y = finalTargetY)
                    }
                } else if (kotlin.math.abs(targetAdjustment) <= 1f && offset.y != baseOffsetY) {
                    Timber.tag("KeyboardAdjust")
                        .i("Cursor visible, restoring base. NewY=$baseOffsetY")
                    offset = offset.copy(y = baseOffsetY)
                }
            }
        }

        var previousLockedViewportSize by remember { mutableStateOf<Pair<Dp, Dp>?>(null) }

        LaunchedEffect(
            pageIndex, this@BoxWithConstraints.maxWidth, this@BoxWithConstraints.maxHeight,
            isScrollLocked, lockedState
        ) {
            val currentViewportSize = this@BoxWithConstraints.maxWidth to this@BoxWithConstraints.maxHeight
            val previousViewportSize = previousLockedViewportSize
            val orientationChanged = previousViewportSize != null &&
                (previousViewportSize.first > previousViewportSize.second) !=
                (currentViewportSize.first > currentViewportSize.second)
            previousLockedViewportSize = currentViewportSize

            if (isScrollLocked && !isVerticalScroll) {
                if (orientationChanged) {
                    scale = 1f
                    offset = Offset.Zero
                    hasAppliedLockedPaginationState = true
                    Timber.tag("PdfLockDiagnostic").i(
                        "Orientation changed while locked; reset paginated zoom to fit on page $pageIndex"
                    )
                } else if (lockedState != null) {
                    scale = lockedState.first
                    offset = Offset(lockedState.second, lockedState.third)
                    hasAppliedLockedPaginationState = true
                } else {
                    hasAppliedLockedPaginationState = true
                }
                onScaleChanged(scale)
            } else if (!isScrollLocked && !isVerticalScroll) {
                hasAppliedLockedPaginationState = false
                scale = 1f
                offset = Offset.Zero
                onScaleChanged(1f)
            }
        }

        LaunchedEffect(
            pdfDocumentItem,
            pageIndex,
            ttsHighlightData,
            actualBitmapWidthPx,
            actualBitmapHeightPx,
            currentPageRotation,
            lazyListState,
            isVerticalScroll,
            virtualPage
        ) {
            if (scale > 1f) {
                if (highlightedTextScreenRects.isNotEmpty()) highlightedTextScreenRects =
                    emptyList()
                return@LaunchedEffect
            }

            if (ttsHighlightData == null || actualBitmapWidthPx == 0) {
                if (highlightedTextScreenRects.isNotEmpty()) {
                    Timber.d("Highlighting: Clearing highlights for page $pageIndex.")
                    highlightedTextScreenRects = emptyList()
                }
                return@LaunchedEffect
            }

            if (!isPdfPage) {
                if (highlightedTextScreenRects.isNotEmpty()) highlightedTextScreenRects =
                    emptyList()
                return@LaunchedEffect
            }

            try {
                var rects: List<Rect> = emptyList()

                when (ttsHighlightData) {
                    is TtsHighlightData.Pdfium -> {
                        Timber.d(
                            "Highlighting (Pdfium): page $pageIndex, index: ${ttsHighlightData.startIndex}, len: ${ttsHighlightData.length}"
                        )
                        rects = withContext(Dispatchers.IO) {
                            pdfDocumentItem.openPage(pdfPageIndex)?.use { page ->
                                page.openTextPage().use { textPage ->
                                    val pdfRectsF = textPage.textPageGetRectsForRanges(
                                        intArrayOf(
                                            ttsHighlightData.startIndex, ttsHighlightData.length
                                        )
                                    )?.map { it.rect } ?: emptyList()

                                    if (pdfRectsF.isNotEmpty()) {
                                        pdfRectsF.mapNotNull { pdfRectF ->
                                            page.mapRectToDevice(
                                                startX = 0,
                                                startY = 0,
                                                sizeX = actualBitmapWidthPx,
                                                sizeY = actualBitmapHeightPx,
                                                rotate = currentPageRotation,
                                                coords = pdfRectF
                                            ).takeIf {
                                                it.width() > 0 && it.height() > 0
                                            }
                                        }
                                    } else {
                                        emptyList()
                                    }
                                }
                            } ?: emptyList()
                        }
                    }

                    is TtsHighlightData.Ocr -> {
                        val textToHighlight = ttsHighlightData.text
                        Timber.d(
                            "Highlighting (OCR): page $pageIndex, text: \"${textToHighlight.take(50)}...\""
                        )
                        val ocrTextToUse = ocrVisionTextForSelection ?: try {
                            bitmapState?.let {
                                OcrHelper.extractTextFromBitmap(
                                    it, onOcrModelDownloading
                                )
                            }
                        } catch (e: Exception) {
                            Timber.e(
                                e, "Highlighting: On-demand OCR for TTS failed."
                            )
                            null
                        }

                        rects = if (ocrTextToUse != null) {
                            findRectsForTextChunkInOcrVisual(ocrTextToUse, textToHighlight)
                        } else {
                            emptyList()
                        }
                    }
                }

                highlightedTextScreenRects = rects

                if (rects.isNotEmpty() && onTtsHighlightCenterCalculated != null) {
                    val unionRect = RectF()
                    unionRect.set(
                        rects[0].left.toFloat(),
                        rects[0].top.toFloat(),
                        rects[0].right.toFloat(),
                        rects[0].bottom.toFloat()
                    )
                    for (i in 1 until rects.size) {
                        val r = rects[i]
                        unionRect.union(
                            r.left.toFloat(), r.top.toFloat(), r.right.toFloat(), r.bottom.toFloat()
                        )
                    }
                    onTtsHighlightCenterCalculated(unionRect.centerY())
                }
            } catch (e: Exception) {
                Timber.e(e, "Highlighting: Error calculating highlights for page $pageIndex: $e")
                highlightedTextScreenRects = emptyList()
            }
        }

        Box(modifier = Modifier
            .fillMaxSize()
            .clipToBounds()
            .background(backgroundColor)) {
            val currentContainerMaxWidth = this@BoxWithConstraints.maxWidth
            val currentContainerMaxHeight = this@BoxWithConstraints.maxHeight

            val containerWidthPx = with(density) { this@BoxWithConstraints.maxWidth.toPx() }
            val containerHeightPx = with(density) { this@BoxWithConstraints.maxHeight.toPx() }

            val centeringOffsetX = (containerWidthPx - actualBitmapWidthPx) / 2f
            val centeringOffsetY = (containerHeightPx - actualBitmapHeightPx) / 2f

            LaunchedEffect(
                pdfDocumentItem,
                pageIndex,
                isPdfPage,
                pdfPageIndex,
                currentContainerMaxWidth,
                currentContainerMaxHeight,
                density,
                virtualPage,
                targetPageId,
                isVisible,
                currentRenderedPageId
            ) {
                if (!isVisible && !isVerticalScroll) return@LaunchedEffect
                pageErrorMessage = null

                val viewContainerWidthPx = with(density) { currentContainerMaxWidth.toPx().toInt() }
                val viewContainerHeightPx =
                    with(density) { currentContainerMaxHeight.toPx().toInt() }

                if (viewContainerWidthPx <= 0 || viewContainerHeightPx <= 0) {
                    if (bitmapState == null) isLoadingPage = true
                    return@LaunchedEffect
                }

                if (!isPdfPage) {
                    val blankPage = virtualPage as? VirtualPage.BlankPage
                    val pageAspect = if (blankPage != null && blankPage.width > 0 && blankPage.height > 0) {
                        blankPage.width.toFloat() / blankPage.height.toFloat()
                    } else {
                        1f / 1.414f
                    }

                    val (scaledWidth, scaledHeight) = if (isVerticalScroll) {
                        viewContainerWidthPx to viewContainerHeightPx
                    } else {
                        var fittedWidth = viewContainerWidthPx
                        var fittedHeight = (fittedWidth / pageAspect).toInt()

                        if (fittedHeight > viewContainerHeightPx) {
                            fittedHeight = viewContainerHeightPx
                            fittedWidth = (fittedHeight * pageAspect).toInt()
                        }

                        fittedWidth to fittedHeight
                    }

                    if (scaledWidth == actualBitmapWidthPx &&
                        scaledHeight == actualBitmapHeightPx &&
                        bitmapState != null &&
                        currentRenderedPageId == targetPageId
                    ) {
                        isLoadingPage = false
                        return@LaunchedEffect
                    }

                    actualBitmapWidthPx = scaledWidth
                    actualBitmapHeightPx = scaledHeight
                    currentPageRotation = 0

                    val MAX_BASE_DIMEN = 3000

                    val baseRenderScale = 1.5f

                    var baseW = (scaledWidth * baseRenderScale).toInt()
                    var baseH = (scaledHeight * baseRenderScale).toInt()

                    if (baseW > MAX_BASE_DIMEN || baseH > MAX_BASE_DIMEN) {
                        val downScale = MAX_BASE_DIMEN.toFloat() / maxOf(baseW, baseH)
                        baseW = (baseW * downScale).toInt().coerceAtLeast(1)
                        baseH = (baseH * downScale).toInt().coerceAtLeast(1)
                    }

                    val finalBitmap = PdfBitmapPool.get(baseW, baseH)
                    finalBitmap.eraseColor(android.graphics.Color.WHITE)

                    val old = bitmapState
                    if (old != null && old !== finalBitmap) {
                        if (old !== PdfThumbnailCache.get(targetPageId)) {
                            old.recycle()
                        }
                    }
                    bitmapState = finalBitmap
                    currentRenderedPageId = targetPageId

                    isLoadingPage = false
                    return@LaunchedEffect
                }

                kotlinx.coroutines.coroutineScope {
                    var localBitmap: Bitmap? = null
                    try {
                        val renderResult = withContext(Dispatchers.IO) {
                            val rawPageCount = pdfDocumentItem.getPageCount()
                            if (pdfPageIndex >= rawPageCount) {
                                Timber.w(
                                    "PdfPageComposable: Index $pdfPageIndex out of bounds (count $rawPageCount). Waiting for layout update."
                                )
                                return@withContext null
                            }
                            val page = pdfDocumentItem.openPage(pdfPageIndex) ?: return@withContext null
                            try {
                                val rotation = page.getPageRotation()
                                val originalWidthPdfUnits = page.getPageWidthPoint()
                                val originalHeightPdfUnits = page.getPageHeightPoint()

                                if (originalWidthPdfUnits <= 0 || originalHeightPdfUnits <= 0) {
                                    throw Exception("Invalid page dimensions")
                                }

                                val aspectRatio =
                                    originalWidthPdfUnits.toFloat() / originalHeightPdfUnits.toFloat()
                                val (scaledWidth, scaledHeight) = if (isVerticalScroll) {
                                    viewContainerWidthPx to viewContainerHeightPx
                                } else {
                                    var fittedWidth = viewContainerWidthPx
                                    var fittedHeight = (fittedWidth / aspectRatio).toInt()

                                    if (fittedHeight > viewContainerHeightPx) {
                                        fittedHeight = viewContainerHeightPx
                                        fittedWidth = (fittedHeight * aspectRatio).toInt()
                                    }

                                    fittedWidth to fittedHeight
                                }

                                if (scaledWidth == actualBitmapWidthPx &&
                                    scaledHeight == actualBitmapHeightPx &&
                                    bitmapState != null &&
                                    currentRenderedPageId == targetPageId
                                ) {
                                    return@withContext null
                                }

                                val MAX_BASE_DIMEN = 3000

                                val baseRenderScale = 1.5f

                                var baseW = (scaledWidth * baseRenderScale).toInt()
                                var baseH = (scaledHeight * baseRenderScale).toInt()

                                if (baseW > MAX_BASE_DIMEN || baseH > MAX_BASE_DIMEN) {
                                    val downScale = MAX_BASE_DIMEN.toFloat() / maxOf(baseW, baseH)
                                    baseW = (baseW * downScale).toInt().coerceAtLeast(1)
                                    baseH = (baseH * downScale).toInt().coerceAtLeast(1)
                                }

                                Timber.d(
                                    "Rendering page $pageIndex at ${baseW}x${baseH} (logical: ${scaledWidth}x${scaledHeight})"
                                )
                                val newBitmap = createBitmap(baseW, baseH)
                                localBitmap = newBitmap
                                page.renderPageBitmap(
                                    newBitmap,
                                    0, 0,
                                    baseW, baseH,
                                    true
                                )

                                Triple(newBitmap, rotation, Pair(scaledWidth, scaledHeight))
                            } finally {
                                page.close()
                            }
                        }

                        if (renderResult != null) {
                            val (newBitmap, rotation, dims) = renderResult

                            actualBitmapWidthPx = dims.first
                            actualBitmapHeightPx = dims.second
                            currentPageRotation = rotation

                            val old = bitmapState

                            bitmapState = newBitmap
                            localBitmap = null // Handed over successfully
                            currentRenderedPageId = targetPageId

                            withContext(Dispatchers.IO) {
                                if (old != null && old !== newBitmap && !old.isRecycled) {
                                    val cached = PdfThumbnailCache.get(targetPageId)
                                    if (old !== cached) {
                                        old.recycle()
                                    }
                                }

                                val thumbWidth = newBitmap.width / 2
                                val thumbHeight = newBitmap.height / 2
                                if (thumbWidth > 0 && thumbHeight > 0) {
                                    PdfThumbnailCache.put(
                                        targetPageId, newBitmap.scale(thumbWidth, thumbHeight)
                                    )
                                }
                            }
                        }
                    } catch (e: Exception) {
                        if (e is CancellationException) throw e
                        pageErrorMessage = context.getString(
                            R.string.error_processing_page,
                            e.readablePdfErrorDetail()
                        )
                    } finally {
                        isLoadingPage = false
                        localBitmap?.recycle()
                    }
                }
            }

            when {
                isLoadingPage -> {
                    if (placeholderBitmap != null && !placeholderBitmap.isRecycled) {
                        Canvas(modifier = Modifier.fillMaxSize()) {
                            if (!placeholderBitmap.isRecycled) {
                                val canvasWidth = size.width
                                val canvasHeight = size.height
                                val bitmapWidth = placeholderBitmap.width
                                val bitmapHeight = placeholderBitmap.height

                                if (bitmapWidth <= 0 || bitmapHeight <= 0) return@Canvas

                                val canvasAspectRatio = canvasWidth / canvasHeight
                                val bitmapAspectRatio =
                                    bitmapWidth.toFloat() / bitmapHeight.toFloat()

                                val dstWidth: Float
                                val dstHeight: Float

                                if (bitmapAspectRatio > canvasAspectRatio) {
                                    dstWidth = canvasWidth
                                    dstHeight = dstWidth / bitmapAspectRatio
                                } else {
                                    dstHeight = canvasHeight
                                    dstWidth = dstHeight * bitmapAspectRatio
                                }

                                val dstOffset = Offset(
                                    x = (canvasWidth - dstWidth) / 2f,
                                    y = (canvasHeight - dstHeight) / 2f
                                )

                                drawImage(
                                    image = placeholderBitmap.asImageBitmap(),
                                    dstOffset = IntOffset(
                                        dstOffset.x.roundToInt(), dstOffset.y.roundToInt()
                                    ),
                                    dstSize = IntSize(
                                        dstWidth.roundToInt(), dstHeight.roundToInt()
                                    )
                                )
                            }
                        }
                    } else {
                        // Blank page
                    }
                }

                pageErrorMessage != null -> {
                    Text(
                        text = pageErrorMessage!!,
                        color = MaterialTheme.colorScheme.error,
                        modifier = Modifier
                            .padding(16.dp)
                            .align(Alignment.Center)
                    )
                }

                bitmapState != null && actualBitmapWidthPx > 0 && actualBitmapHeightPx > 0 -> {
                    val stableBitmapState = remember(bitmapState) { StableHolder(bitmapState) }
                    val stableTiles = remember(tiles) { StableHolder(tiles) }
                    val stableColorFilter = remember(colorFilter) { StableHolder(colorFilter) }
                    val stableImageRects = remember(imageScreenRects) { StableHolder(imageScreenRects) }

                    val staticData = remember(
                        stableBitmapState,
                        stableTiles,
                        effectiveScale,
                        centeringOffsetX,
                        centeringOffsetY,
                        canvasWidthPx.floatValue,
                        canvasHeightPx.floatValue,
                        actualBitmapWidthPx,
                        actualBitmapHeightPx,
                        stableColorFilter,
                        isDarkMode,
                        excludeImages,
                        stableImageRects,
                        textureBitmap,
                        effectiveTextureAlpha,
                        textureBlendMode
                    ) {
                        Timber.tag("PdfDrawPerf").v(
                            "STATIC DATA GENERATED: Scale=$effectiveScale, Tiles=${stableTiles.item.size}"
                        )
                        PageStaticData(
                            bitmap = stableBitmapState,
                            tiles = stableTiles,
                            effectiveScale = effectiveScale,
                            centeringOffsetX = centeringOffsetX,
                            centeringOffsetY = centeringOffsetY,
                            canvasWidth = canvasWidthPx.floatValue,
                            canvasHeight = canvasHeightPx.floatValue,
                            targetWidth = actualBitmapWidthPx,
                            targetHeight = actualBitmapHeightPx,
                            colorFilter = stableColorFilter,
                            isDarkMode = isDarkMode,
                            excludeImages = excludeImages,
                            imageRects = stableImageRects,
                            textureBitmap = StableHolder(textureBitmap),
                            textureAlpha = effectiveTextureAlpha,
                            textureBlendMode = textureBlendMode
                        )
                    }

                    val selectionData = remember(
                        pageLinks,
                        showAllTextHighlights,
                        actualBitmapWidthPx,
                        actualBitmapHeightPx,
                        mergedAllTextPageHighlightRects,
                        mergedTtsHighlightRects,
                        mergedSearchHighlightRects,
                        ocrHoverHighlights,
                        mergedSelectionRects,
                        centeringOffsetX,
                        centeringOffsetY,
                        linkHighlightColor,
                        scrimColorForTextHighlight,
                        allTextPageHighlightColor,
                        ttsHighlightColor,
                        searchHighlightColor,
                        selectionHighlightColor,
                        pageIndex,
                        mergedSearchFocusedRects,
                        mergedSearchAllRects,
                        searchHighlightMode,
                        searchFocusedColor,
                        searchAllColor,
                        userHighlightScreenRects,
                        customHighlightColors
                    ) {
                        PageSelectionData(
                            pageLinks = StableHolder(pageLinks),
                            showAllTextHighlights = showAllTextHighlights,
                            actualBitmapWidthPx = actualBitmapWidthPx,
                            actualBitmapHeightPx = actualBitmapHeightPx,
                            mergedAllTextPageHighlightRects = StableHolder(
                                mergedAllTextPageHighlightRects
                            ),
                            mergedTtsHighlightRects = StableHolder(mergedTtsHighlightRects),
                            ocrHoverHighlights = ocrHoverHighlights,
                            mergedSelectionRects = StableHolder(mergedSelectionRects),
                            centeringOffsetX = centeringOffsetX,
                            centeringOffsetY = centeringOffsetY,
                            linkHighlightColor = linkHighlightColor,
                            scrimColorForTextHighlight = scrimColorForTextHighlight,
                            allTextPageHighlightColor = allTextPageHighlightColor,
                            ttsHighlightColor = ttsHighlightColor,
                            selectionHighlightColor = selectionHighlightColor,
                            pageIndex = pageIndex,
                            mergedSearchFocusedRects = StableHolder(mergedSearchFocusedRects),
                            mergedSearchAllRects = StableHolder(mergedSearchAllRects),
                            searchHighlightMode = searchHighlightMode,
                            userHighlightScreenRects = StableHolder(userHighlightScreenRects),
                            customHighlightColors = StableHolder(customHighlightColors)
                        )
                    }

                    val centeringPaddingEnd =
                        with(density) { centeringOffsetX.coerceAtLeast(0f).toDp() }
                    val centeringPaddingTop =
                        with(density) { centeringOffsetY.coerceAtLeast(0f).toDp() }

                    PdfPageRenderer(
                        staticData = staticData,
                        selectionData = selectionData,
                        totalPages = totalPages,
                        annotationsProvider = pageAnnotations,
                        drawingState = drawingState,
                        onCanvasSizeChanged = { w, h ->
                            if (canvasWidthPx.floatValue != w || canvasHeightPx.floatValue != h) {
                                canvasWidthPx.floatValue = w
                                canvasHeightPx.floatValue = h
                            }
                        },
                        scale = scale,
                        uiScale = effectiveScale,
                        offset = offset,
                        startHandlePos = startHandleContentPosition.value,
                        endHandlePos = endHandleContentPosition.value,
                        teardropWidthPx = teardropWidthPxState.value,
                        onHighlightAdd = onHighlightAdd,
                        onHighlightUpdate = onHighlightUpdate,
                        onHighlightDelete = onHighlightDelete,
                        onTts = onTts,
                        onNote = onNoteRequested,
                        teardropHeightPx = teardropHeightPxState.value,
                        activeDraggingHandle = activeDraggingHandle,
                        showMagnifier = showMagnifier,
                        magnifierCenterTarget = magnifierBitmapCenterTarget,
                        magnifierZoomFactor = magnifierZoomFactor,
                        menuState = customMenuState,
                        onMenuDismiss = {
                            customMenuState = null
                            selectionCharRange.value = null
                            coroutineScope.launch {
                                updateSelectionVisuals(
                                    pdfDocumentItem,
                                    pdfPageIndex,
                                    null,
                                    actualBitmapWidthPx,
                                    actualBitmapHeightPx,
                                    currentPageRotation,
                                )
                            }
                        },
                        onCopy = { textToCopy ->
                            clipboardManager.setText(AnnotatedString(textToCopy))
                            customMenuState = null
                            selectionCharRange.value = null
                            coroutineScope.launch {
                                updateSelectionVisuals(
                                    pdfDocumentItem,
                                    pdfPageIndex,
                                    null,
                                    actualBitmapWidthPx,
                                    actualBitmapHeightPx,
                                    currentPageRotation
                                )
                            }
                        },
                        onAiDefine = { textToDefine ->
                            onWordSelectedForAiDefinition(textToDefine.trim())
                            customMenuState = null
                            selectionCharRange.value = null
                            coroutineScope.launch {
                                updateSelectionVisuals(
                                    pdfDocumentItem,
                                    pdfPageIndex,
                                    null,
                                    actualBitmapWidthPx,
                                    actualBitmapHeightPx,
                                    currentPageRotation
                                )
                            }
                        },
                        onTranslate = { textToTranslate ->
                            onTranslateText(textToTranslate.trim())
                            customMenuState = null
                            selectionCharRange.value = null
                            coroutineScope.launch {
                                updateSelectionVisuals(
                                    pdfDocumentItem,
                                    pdfPageIndex,
                                    null,
                                    actualBitmapWidthPx,
                                    actualBitmapHeightPx,
                                    currentPageRotation
                                )
                            }
                        },
                        onSearch = { textToSearch ->
                            onSearchText(textToSearch.trim())
                            customMenuState = null
                            selectionCharRange.value = null
                            coroutineScope.launch {
                                updateSelectionVisuals(
                                    pdfDocumentItem,
                                    pdfPageIndex,
                                    null,
                                    actualBitmapWidthPx,
                                    actualBitmapHeightPx,
                                    currentPageRotation
                                )
                            }
                        },
                        onSelectAll = {
                            customMenuState = null
                            coroutineScope.launch {
                                if (!isPdfPage) return@launch

                                if (selectionMethodUsed == PdfSelectionMethod.PDFIUM) {
                                    var page: ReaderPage? = null
                                    var textPage: ReaderTextPage? = null
                                    try {
                                        val charCount = withContext(Dispatchers.IO) {
                                            page = pdfDocumentItem.openPage(pdfPageIndex)
                                            textPage = page?.openTextPage()
                                            textPage?.textPageCountChars() ?: 0
                                        }

                                        if (charCount > 0) {
                                            selectionCharRange.value = Pair(0, charCount)
                                            val tPage = page
                                            val tTextPage = textPage
                                            updateSelectionVisuals(
                                                pdfDocumentItem,
                                                pdfPageIndex,
                                                selectionCharRange.value,
                                                actualBitmapWidthPx,
                                                actualBitmapHeightPx,
                                                currentPageRotation,
                                                providedPage = tPage,
                                                providedTextPage = tTextPage
                                            )
                                            if (selectedWordScreenRects.isNotEmpty()) {
                                                val fullText = withContext(Dispatchers.IO) {
                                                    tTextPage?.textPageGetText(0, charCount)
                                                }
                                                if (!fullText.isNullOrBlank()) {
                                                    val combinedRect = Rect(selectedWordScreenRects.first())
                                                    selectedWordScreenRects.forEach { combinedRect.union(it) }
                                                    customMenuState = CustomPdfMenuState(
                                                        selectedText = fullText,
                                                        anchorRect = combinedRect,
                                                        charRange = selectionCharRange.value!!
                                                    )
                                                }
                                            }
                                        }
                                    } catch (e: Exception) {
                                        Timber.e(e, "Failed to select all")
                                    } finally {
                                        withContext(NonCancellable) {
                                            withContext(Dispatchers.IO) {
                                                textPage?.close()
                                                page?.close()
                                            }
                                        }
                                    }
                                } else {
                                    if (allOcrSymbolsForSelection.isNotEmpty()) {
                                        ocrSelectionSymbolIndices =
                                            Pair(0, allOcrSymbolsForSelection.size)
                                        updateOcrSymbolSelectionRectsAndHandles(
                                            ocrSelectionSymbolIndices
                                        )
                                        if (selectedWordScreenRects.isNotEmpty()) {
                                            val fullText = buildString {
                                                allOcrSymbolsForSelection.forEachIndexed { index, info ->
                                                    append(info.symbol.text)
                                                    if (index < allOcrSymbolsForSelection.size - 1) {
                                                        val nextInfo =
                                                            allOcrSymbolsForSelection[index + 1]
                                                        if (info.parentLine !== nextInfo.parentLine) append(
                                                            '\n'
                                                        )
                                                        else if (info.parentElement !== nextInfo.parentElement) append(
                                                            ' '
                                                        )
                                                    }
                                                }
                                            }
                                            if (fullText.isNotBlank()) {
                                                val combinedRect = Rect(selectedWordScreenRects.first())
                                                selectedWordScreenRects.forEach { combinedRect.union(it) }
                                                customMenuState = CustomPdfMenuState(
                                                    selectedText = fullText,
                                                    anchorRect = combinedRect,
                                                    charRange = Pair(-1, -1)
                                                )
                                            }
                                        }
                                    }
                                }
                            }
                        },
                        onShowUpsellDialog = {
                            onShowDictionaryUpsellDialog()
                            customMenuState = null
                            selectionCharRange.value = null
                            coroutineScope.launch {
                                updateSelectionVisuals(
                                    pdfDocumentItem,
                                    pdfPageIndex,
                                    null,
                                    actualBitmapWidthPx,
                                    actualBitmapHeightPx,
                                    currentPageRotation
                                )
                            }
                        },
                        isProUser = isProUser,
                        isBookmarked = isBookmarked,
                        onBookmarkClick = onBookmarkClick,
                        centeringPaddingTop = centeringPaddingTop,
                        centeringPaddingEnd = centeringPaddingEnd,
                        isPerformingOcr = isPerformingOcrForSelection,
                        ocrRipplePos = ocrRipplePosition,
                        layoutCoordinates = layoutCoordinates,
                        contentToScreenCoordinates = contentToScreenCoordinates,
                        density = density,
                        isVerticalScroll = isVerticalScroll,
                        showPageNumberOverlay = showPageNumberOverlay,
                        isScrolling = isScrolling,
                        isEditMode = isEditMode,
                        selectedTool = selectedTool,
                        eraserPosition = eraserPosition,
                        isStylusEraserOverride = isStylusEraserOverride,
                        activeToolThickness = activeToolThickness,
                        eraserToolThickness = eraserToolThickness,
                        richTextController = richTextController,
                        textBoxes = textBoxes,
                        selectedTextBoxId = selectedTextBoxId,
                        onTextBoxChange = onTextBoxChange,
                        onTextBoxSelect = onTextBoxSelect,
                        onTextBoxDragStart = onTextBoxDragStart,
                        onTextBoxDrag = onTextBoxDrag,
                        onTextBoxDragEnd = onTextBoxDragEnd,
                        onDragPageTurn = onDragPageTurn,
                        draggingBoxId = draggingBoxId,
                        customHighlightColors = customHighlightColors,
                        onPaletteClick = onPaletteClick,
                        isBubbleZoomModeActive = isBubbleZoomModeActive,
                        isActivePage = isActivePage,
                        isDetectingBubbles = isDetectingBubbles,
                        detectedBubbles = detectedBubbles,
                        animatingBubbleIndex = animatingBubbleIndex,
                        bubbleExpansionProgress = bubbleExpansionProgress.value,
                        expandedBubbleRender = expandedBubbleRender
                    )
                }

                else -> {
                    Text(
                        text = stringResource(R.string.error_unable_to_display_page, pageIndex + 1),
                        modifier = Modifier
                            .padding(16.dp)
                            .align(Alignment.Center)
                    )
                }
            }
        }
    }
}

@Composable
private fun OcrProcessingIndicator(position: Offset) {
    val infiniteTransition = rememberInfiniteTransition(label = "ocr_indicator_transition")
    val animatedRadius by infiniteTransition.animateFloat(
        initialValue = 20f, targetValue = 120f, animationSpec = infiniteRepeatable(
            animation = tween(1200), repeatMode = RepeatMode.Restart
        ), label = "ocr_radius"
    )
    val animatedAlpha by infiniteTransition.animateFloat(
        initialValue = 0.8f, targetValue = 0f, animationSpec = infiniteRepeatable(
            animation = tween(1200), repeatMode = RepeatMode.Restart
        ), label = "ocr_alpha"
    )

    val color = MaterialTheme.colorScheme.primary

    Canvas(modifier = Modifier.fillMaxSize()) {
        drawCircle(
            color = color.copy(alpha = animatedAlpha),
            radius = animatedRadius,
            center = position,
            style = Stroke(width = (4.dp * animatedAlpha).toPx())
        )
    }
}

@Composable
private fun PdfBitmapLayer(
    bitmapState: Bitmap?,
    tiles: List<PdfTile>,
    effectiveScale: Float,
    centeringOffsetX: Float,
    centeringOffsetY: Float,
    @Suppress("unused") canvasWidth: Float,
    @Suppress("unused") canvasHeight: Float,
    targetWidth: Int,
    targetHeight: Int,
    colorFilter: ColorFilter? = null,
    isDarkMode: Boolean = false,
    excludeImages: Boolean = false,
    imageRects: List<android.graphics.Rect> = emptyList(),
    textureBitmap: ImageBitmap? = null,
    textureAlpha: Float = 0f,
    textureBlendMode: BlendMode = BlendMode.Multiply
) {
    Canvas(modifier = Modifier.fillMaxSize().graphicsLayer()) {
        translate(left = centeringOffsetX, top = centeringOffsetY) {
            clipRect(left = 0f, top = 0f, right = targetWidth.toFloat(), bottom = targetHeight.toFloat()) {
                if (
                    bitmapState != null &&
                    bitmapState.isCanvasSafeBitmap(
                        maxBytes = PDF_MAX_DRAW_BITMAP_BYTES,
                        maxDimension = PDF_MAX_DRAW_BITMAP_DIMENSION_PX
                    )
                ) {
                    val dstW = if (targetWidth > 0) targetWidth else bitmapState.width
                    val dstH = if (targetHeight > 0) targetHeight else bitmapState.height
                    val srcSize = IntSize(bitmapState.width, bitmapState.height)
                    val dstSize = IntSize(dstW, dstH)

                    drawImage(
                        image = bitmapState.asImageBitmap(),
                        srcOffset = IntOffset.Zero,
                        srcSize = srcSize,
                        dstOffset = IntOffset.Zero,
                        dstSize = dstSize,
                        colorFilter = colorFilter,
                        filterQuality = androidx.compose.ui.graphics.FilterQuality.High
                    )

                    if (excludeImages && colorFilter != null && imageRects.isNotEmpty()) {
                        imageRects.forEach { rect ->
                            val scaleX = bitmapState.width.toFloat() / dstW.toFloat()
                            val scaleY = bitmapState.height.toFloat() / dstH.toFloat()

                            val srcRectLeft = (rect.left * scaleX).roundToInt().coerceAtLeast(0)
                            val srcRectTop = (rect.top * scaleY).roundToInt().coerceAtLeast(0)
                            val srcRectRight = (rect.right * scaleX).roundToInt().coerceAtMost(bitmapState.width)
                            val srcRectBottom = (rect.bottom * scaleY).roundToInt().coerceAtMost(bitmapState.height)

                            val w = srcRectRight - srcRectLeft
                            val h = srcRectBottom - srcRectTop
                            if (w > 0 && h > 0) {
                                drawImage(
                                    image = bitmapState.asImageBitmap(),
                                    srcOffset = IntOffset(srcRectLeft, srcRectTop),
                                    srcSize = IntSize(w, h),
                                    dstOffset = IntOffset(rect.left, rect.top),
                                    dstSize = IntSize(rect.width(), rect.height()),
                                    colorFilter = null,
                                    filterQuality = androidx.compose.ui.graphics.FilterQuality.High
                                )
                            }
                        }
                    }

                    val needsTiling = effectiveScale > 1f || targetWidth > 3000 || targetHeight > 3000
                    if (needsTiling) {
                        tiles.forEach { tile ->
                            if (
                                tile.bitmap.isCanvasSafeBitmap(
                                    maxBytes = PDF_MAX_DRAW_BITMAP_BYTES,
                                    maxDimension = PDF_MAX_DRAW_BITMAP_DIMENSION_PX
                                )
                            ) {
                                drawImage(
                                    image = tile.bitmap.asImageBitmap(),
                                    srcOffset = IntOffset.Zero,
                                    srcSize = IntSize(tile.bitmap.width, tile.bitmap.height),
                                    dstOffset = IntOffset(tile.renderRect.left, tile.renderRect.top),
                                    dstSize = IntSize(tile.renderRect.width(), tile.renderRect.height()),
                                    colorFilter = colorFilter,
                                    filterQuality = androidx.compose.ui.graphics.FilterQuality.High
                                )

                                if (excludeImages && colorFilter != null && imageRects.isNotEmpty()) {
                                    imageRects.forEach { imgRect ->
                                        val intersectLeft = max(imgRect.left, tile.renderRect.left)
                                        val intersectTop = max(imgRect.top, tile.renderRect.top)
                                        val intersectRight = min(imgRect.right, tile.renderRect.right)
                                        val intersectBottom = min(imgRect.bottom, tile.renderRect.bottom)

                                        val iw = intersectRight - intersectLeft
                                        val ih = intersectBottom - intersectTop

                                        if (iw > 0 && ih > 0) {
                                            val scaleXBmp = tile.bitmap.width.toFloat() / tile.renderRect.width()
                                            val scaleYBmp = tile.bitmap.height.toFloat() / tile.renderRect.height()

                                            val srcLeft = ((intersectLeft - tile.renderRect.left) * scaleXBmp).roundToInt()
                                            val srcTop = ((intersectTop - tile.renderRect.top) * scaleYBmp).roundToInt()
                                            val srcRight = ((intersectRight - tile.renderRect.left) * scaleXBmp).roundToInt()
                                            val srcBottom = ((intersectBottom - tile.renderRect.top) * scaleYBmp).roundToInt()

                                            val srcW = srcRight - srcLeft
                                            val srcH = srcBottom - srcTop

                                            if (srcW > 0 && srcH > 0) {
                                                drawImage(
                                                    image = tile.bitmap.asImageBitmap(),
                                                    srcOffset = IntOffset(srcLeft, srcTop),
                                                    srcSize = IntSize(srcW, srcH),
                                                    dstOffset = IntOffset(intersectLeft, intersectTop),
                                                    dstSize = IntSize(iw, ih),
                                                    colorFilter = null,
                                                    filterQuality = androidx.compose.ui.graphics.FilterQuality.High
                                                )
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    if (textureBitmap != null && textureAlpha > 0f) {
                        drawRect(
                            brush = ShaderBrush(ImageShader(textureBitmap, TileMode.Repeated, TileMode.Repeated)),
                            size = Size(dstW.toFloat(), dstH.toFloat()),
                            blendMode = textureBlendMode,
                            alpha = textureAlpha
                        )
                    }
                }
            }
        }
    }
}

@Composable
private fun PdfHighlightsLayer(
    pageLinks: List<PageLink>,
    showAllTextHighlights: Boolean,
    actualBitmapWidthPx: Int,
    actualBitmapHeightPx: Int,
    mergedAllTextPageHighlightRects: List<Rect>,
    mergedTtsHighlightRects: List<Rect>,
    mergedSearchFocusedRects: List<Rect>,
    mergedSearchAllRects: List<Rect>,
    searchHighlightMode: SearchHighlightMode,
    ocrHoverHighlights: List<RectF>,
    mergedSelectionRects: List<Rect>,
    userHighlightScreenRects: List<Pair<PdfUserHighlight, List<Rect>>>,
    centeringOffsetX: Float,
    centeringOffsetY: Float,
    linkHighlightColor: Color,
    scrimColorForTextHighlight: Color,
    allTextPageHighlightColor: Color,
    ttsHighlightColor: Color,
    selectionHighlightColor: Color,
    customHighlightColors: Map<PdfHighlightColor, Color> = emptyMap()
) {
    Canvas(modifier = Modifier
        .fillMaxSize()
        .graphicsLayer()) {
        translate(left = centeringOffsetX, top = centeringOffsetY) {
            fun isVisible(r: Rect): Boolean {
                val left = r.left + centeringOffsetX
                val right = r.right + centeringOffsetX
                val top = r.top + centeringOffsetY
                val bottom = r.bottom + centeringOffsetY
                return left < size.width && right > 0 && top < size.height && bottom > 0
            }

            // 1. Page Links
            pageLinks.forEach { link ->
                if (isVisible(link.highlightBounds)) {
                    drawRect(
                        color = linkHighlightColor, topLeft = Offset(
                            link.highlightBounds.left.toFloat(), link.highlightBounds.top.toFloat()
                        ), size = Size(
                            link.highlightBounds.width().toFloat(),
                            link.highlightBounds.height().toFloat()
                        )
                    )
                }
            }

            // 2. Search Results - BACKGROUND (Yellow)
            if (searchHighlightMode == SearchHighlightMode.ALL) {
                val yellowColor = Color(0xFFFFEB3B).copy(alpha = 0.4f)
                mergedSearchAllRects.forEach { rect ->
                    if (rect.width() > 0 && rect.height() > 0 && isVisible(rect)) {
                        val inflated = RectF(rect)
                        inflated.inset(-3f, -3f) // padding
                        drawRect(
                            color = yellowColor,
                            topLeft = Offset(inflated.left, inflated.top),
                            size = Size(inflated.width(), inflated.height())
                        )
                    }
                }
            }

            // 3. Search Results - FOCUSED (Orange + Border)
            val focusedColor = Color(0xFFFF6D00).copy(alpha = 0.4f)
            val focusedStroke = Color(0xFFFF6D00).copy(alpha = 0.9f)
            mergedSearchFocusedRects.forEach { rect ->
                if (rect.width() > 0 && rect.height() > 0 && isVisible(rect)) {
                    val inflated = RectF(rect)
                    inflated.inset(-5f, -5f) // Extra padding for focus

                    // Fill
                    drawRect(
                        color = focusedColor,
                        topLeft = Offset(inflated.left, inflated.top),
                        size = Size(inflated.width(), inflated.height())
                    )
                    // Border
                    drawRect(
                        color = focusedStroke,
                        topLeft = Offset(inflated.left, inflated.top),
                        size = Size(inflated.width(), inflated.height()),
                        style = Stroke(width = 3.dp.toPx())
                    )
                }
            }

            // 4. Scrim for Text Highlights
            if (showAllTextHighlights && actualBitmapWidthPx > 0 && actualBitmapHeightPx > 0 && scrimColorForTextHighlight.alpha > 0f) {
                with(drawContext.canvas.nativeCanvas) {
                    val checkPoint = saveLayer(null, null)
                    drawRect(
                        color = scrimColorForTextHighlight, topLeft = Offset.Zero, size = Size(
                            actualBitmapWidthPx.toFloat(), actualBitmapHeightPx.toFloat()
                        )
                    )
                    mergedAllTextPageHighlightRects.forEach { rect ->
                        if (rect.width() > 0 && rect.height() > 0) {
                            drawRect(
                                color = Color.Transparent,
                                topLeft = Offset(rect.left.toFloat(), rect.top.toFloat()),
                                size = Size(rect.width().toFloat(), rect.height().toFloat()),
                                blendMode = BlendMode.Clear
                            )
                        }
                    }
                    restoreToCount(checkPoint)
                }
            }

            // 5. All Text Highlights (Overlay)
            mergedAllTextPageHighlightRects.forEach { rect ->
                if (rect.width() > 0 && rect.height() > 0 && isVisible(rect)) {
                    drawRect(
                        color = allTextPageHighlightColor,
                        topLeft = Offset(rect.left.toFloat(), rect.top.toFloat()),
                        size = Size(rect.width().toFloat(), rect.height().toFloat())
                    )
                }
            }

            // 6. TTS Highlights
            mergedTtsHighlightRects.forEach { rect ->
                if (rect.width() > 0 && rect.height() > 0 && isVisible(rect)) {
                    drawRect(
                        color = ttsHighlightColor,
                        topLeft = Offset(rect.left.toFloat(), rect.top.toFloat()),
                        size = Size(rect.width().toFloat(), rect.height().toFloat())
                    )
                }
            }

            // 7. OCR Hover
            ocrHoverHighlights.forEach { rectF ->
                val left = rectF.left * actualBitmapWidthPx
                val top = rectF.top * actualBitmapHeightPx
                val width = rectF.width() * actualBitmapWidthPx
                val height = rectF.height() * actualBitmapHeightPx
                val absLeft = left + centeringOffsetX
                val absTop = top + centeringOffsetY
                if (absLeft < size.width && absTop < size.height && (absLeft + width) > 0 && (absTop + height) > 0) {
                    drawRect(
                        color = Color(0xFFFFAB00).copy(alpha = 0.5f), // Generic highlight color
                        topLeft = Offset(left, top), size = Size(width, height)
                    )
                }
            }

            // 8. User Selection
            mergedSelectionRects.forEach { lineRect ->
                if (lineRect.width() > 0 && lineRect.height() > 0 && isVisible(lineRect)) {
                    drawRect(
                        color = selectionHighlightColor,
                        topLeft = Offset(lineRect.left.toFloat(), lineRect.top.toFloat()),
                        size = Size(lineRect.width().toFloat(), lineRect.height().toFloat())
                    )
                }
            }

            // 9. Persistent User Highlights
            userHighlightScreenRects.forEach { (highlight, screenRects) ->
                val displayColor = customHighlightColors[highlight.color] ?: highlight.color.color
                screenRects.forEach { r ->
                    if (isVisible(r)) {
                        drawRect(
                            color = displayColor.copy(alpha = 0.4f),
                            topLeft = Offset(r.left.toFloat(), r.top.toFloat()),
                            size = Size(r.width().toFloat(), r.height().toFloat())
                        )
                    }
                }
            }
        }
    }
}

internal object PdfTextureGenerator {
    private var noiseBitmap: Bitmap? = null

    fun getNoiseTexture(): Bitmap {
        if (noiseBitmap == null) {
            val size = 256
            val bitmap = createBitmap(size, size, Bitmap.Config.ARGB_8888)
            for (x in 0 until size) {
                for (y in 0 until size) {
                    val isGrain = Math.random() > 0.4
                    if (isGrain) {
                        val alpha = (Math.random() * 100 + 100).toInt()
                        bitmap[x, y] = AndroidColor.argb(alpha, 0, 0, 0)
                    } else {
                        bitmap[x, y] = AndroidColor.TRANSPARENT
                    }
                }
            }
            noiseBitmap = bitmap
        }
        return noiseBitmap!!
    }
}

internal sealed interface AnnotationRenderData {
    data class Standard(
        val path: Path,
        val color: Color,
        val strokeWidth: Float,
        val cap: StrokeCap,
        val blendMode: BlendMode
    ) : AnnotationRenderData

    data class Fountain(val path: Path, val color: Color) : AnnotationRenderData

    data class Pencil(
        val path: android.graphics.Path,
        val color: Color,
        val strokeWidth: Float,
        val velocityAlpha: Float
    ) : AnnotationRenderData
}

internal object PdfAnnotationRenderHelper {
    fun createRenderData(annot: PdfAnnotation, widthPx: Int, heightPx: Int): AnnotationRenderData? {
        val startTime = System.nanoTime()
        if (annot.points.isEmpty()) return null

        if (annot.points.size == 1) {
            val p = annot.points[0]
            val x = p.x * widthPx
            val y = p.y * heightPx

            val path = if (annot.inkType == InkType.PENCIL) android.graphics.Path() else Path()

            if (path is android.graphics.Path) {
                path.moveTo(x, y)
                path.lineTo(x, y)
                return AnnotationRenderData.Pencil(
                    path = path,
                    color = annot.color,
                    strokeWidth = annot.strokeWidth * widthPx,
                    velocityAlpha = 1.0f
                )
            } else if (path is Path) {
                if (annot.inkType == InkType.FOUNTAIN_PEN) {
                    val radius = (annot.strokeWidth * widthPx) / 2f
                    path.addOval(
                        androidx.compose.ui.geometry.Rect(
                            center = Offset(x, y), radius = radius
                        )
                    )
                    return AnnotationRenderData.Fountain(path = path, color = annot.color)
                }

                path.moveTo(x, y)
                path.lineTo(x, y)

                val cap = when (annot.inkType) {
                    InkType.HIGHLIGHTER -> StrokeCap.Butt
                    InkType.HIGHLIGHTER_ROUND -> StrokeCap.Round
                    else -> StrokeCap.Round
                }

                val blendMode = BlendMode.SrcOver

                return AnnotationRenderData.Standard(
                    path = path,
                    color = annot.color,
                    strokeWidth = annot.strokeWidth * widthPx,
                    cap = cap,
                    blendMode = blendMode
                )
            }
        }

        val result = when (annot.inkType) {
            InkType.PENCIL -> {
                val path = android.graphics.Path()
                val first = annot.points[0]
                path.moveTo(first.x * widthPx, first.y * heightPx)
                var totalDist = 0f
                for (i in 1 until annot.points.size) {
                    val p0 = annot.points[i - 1]
                    val p1 = annot.points[i]
                    val p0x = p0.x * widthPx
                    val p0y = p0.y * heightPx
                    val p1x = p1.x * widthPx
                    val p1y = p1.y * heightPx
                    val midX = (p0x + p1x) / 2f
                    val midY = (p0y + p1y) / 2f
                    val dx = p1x - p0x
                    val dy = p1y - p0y
                    totalDist += sqrt(dx * dx + dy * dy)

                    if (i == 1) path.lineTo(midX, midY)
                    else path.quadTo(p0x, p0y, midX, midY)
                }
                val last = annot.points.last()
                path.lineTo(last.x * widthPx, last.y * heightPx)

                val duration =
                    (annot.points.last().timestamp - annot.points.first().timestamp).coerceAtLeast(1)
                val velocity = totalDist / duration
                val velocityAlphaFactor = (1f - (velocity - 0.2f) / 1.8f).coerceIn(0.4f, 1.0f)

                AnnotationRenderData.Pencil(
                    path = path,
                    color = annot.color,
                    strokeWidth = annot.strokeWidth * widthPx,
                    velocityAlpha = velocityAlphaFactor
                )
            }

            InkType.FOUNTAIN_PEN -> {
                val baseStrokeWidth = annot.strokeWidth * widthPx
                val path = Path()

                val (leftSide, rightSide) = PdfInkGeometry.calculateFountainPenPoints(
                    annot.points, baseStrokeWidth, widthPx.toFloat(), heightPx.toFloat()
                )

                if (leftSide.isNotEmpty()) {
                    path.moveTo(leftSide[0].x, leftSide[0].y)

                    for (i in 1 until leftSide.size) {
                        path.lineTo(leftSide[i].x, leftSide[i].y)
                    }

                    for (i in rightSide.size - 1 downTo 0) {
                        path.lineTo(rightSide[i].x, rightSide[i].y)
                    }

                    path.close()
                }

                AnnotationRenderData.Fountain(path = path, color = annot.color)
            }

            else -> {
                val path = Path()
                val first = annot.points[0]
                path.moveTo(first.x * widthPx, first.y * heightPx)
                for (i in 1 until annot.points.size) {
                    val p0 = annot.points[i - 1]
                    val p1 = annot.points[i]
                    val p0x = p0.x * widthPx
                    val p0y = p0.y * heightPx
                    val p1x = p1.x * widthPx
                    val p1y = p1.y * heightPx
                    val midX = (p0x + p1x) / 2f
                    val midY = (p0y + p1y) / 2f
                    if (i == 1) path.lineTo(midX, midY)
                    else path.quadraticTo(p0x, p0y, midX, midY)
                }
                val last = annot.points.last()
                path.lineTo(last.x * widthPx, last.y * heightPx)

                val blendMode = when (annot.inkType) {
                    InkType.HIGHLIGHTER, InkType.HIGHLIGHTER_ROUND -> BlendMode.Multiply
                    else -> BlendMode.SrcOver
                }

                val cap = when (annot.inkType) {
                    InkType.HIGHLIGHTER -> StrokeCap.Butt
                    InkType.HIGHLIGHTER_ROUND -> StrokeCap.Round
                    else -> StrokeCap.Round
                }

                AnnotationRenderData.Standard(
                    path = path,
                    color = annot.color,
                    strokeWidth = annot.strokeWidth * widthPx,
                    cap = cap,
                    blendMode = blendMode
                )
            }
        }
        val duration = (System.nanoTime() - startTime) / 1_000_000f
        if (duration > 1f) {
            Timber.tag("PdfPerf").v("Path Gen: Type=${annot.inkType}, Pts=${annot.points.size}, Time=${duration}ms")
        }
        return result
    }
}

@Suppress("SameParameterValue")
@Composable
private fun PdfAnnotationLayer(
    actualBitmapWidthPx: Int,
    actualBitmapHeightPx: Int,
    annotationsProvider: () -> List<PdfAnnotation>,
    drawingState: PdfDrawingState?,
    centeringOffsetX: Float,
    centeringOffsetY: Float,
    pageIndex: Int
) {
    SideEffect { Timber.tag("PdfPerf").v("ANNOT_LAYER: Recomposing Page $pageIndex") }

    val staticAnnotations = annotationsProvider()

    val staticRenderData = remember(staticAnnotations, actualBitmapWidthPx, actualBitmapHeightPx) {
        val startTime = System.nanoTime()
        val data = staticAnnotations.mapNotNull { annot ->
            PdfAnnotationRenderHelper.createRenderData(
                annot, actualBitmapWidthPx, actualBitmapHeightPx
            )
        }
        val duration = (System.nanoTime() - startTime) / 1_000_000f
        Timber.tag("PdfPerf").d("ANNOT_LAYER: Processed ${staticAnnotations.size} static annots in ${duration}ms")
        data
    }
    val currentAnnotation = remember(drawingState, pageIndex) {
        derivedStateOf {
            val annot = drawingState?.currentAnnotation
            val result = if (annot?.pageIndex == pageIndex) annot else null
            if (drawingState != null) {
                Timber.tag("PdfDrawPerf").v(
                    "DerivedState Calc Page $pageIndex: Global=${annot?.pageIndex} -> Result=${result != null}"
                )
            }
            result
        }
    }.value

    SideEffect {
        Timber.tag("PdfDrawPerf").v(
            "ANNOT LAYER: State Check Page $pageIndex | AnnotHash: ${currentAnnotation?.hashCode()} | AnnotPoints: ${currentAnnotation?.points?.size}"
        )
    }

    val activeRenderData = remember(
        currentAnnotation,
        currentAnnotation?.points?.size,
        actualBitmapWidthPx,
        actualBitmapHeightPx
    ) {
        val startTime = System.nanoTime()
        val res = currentAnnotation?.let { annot ->
            PdfAnnotationRenderHelper.createRenderData(annot, actualBitmapWidthPx, actualBitmapHeightPx)
        }
        val duration = (System.nanoTime() - startTime) / 1_000_000f
        if (duration > 0.5f) {
            Timber.tag("PdfPerf").v("ANNOT_LAYER: Active path gen took ${duration}ms")
        }
        res
    }

    Canvas(modifier = Modifier.fillMaxSize()) {
        val drawStart = System.nanoTime()
        translate(left = centeringOffsetX, top = centeringOffsetY) {
            fun drawData(data: AnnotationRenderData) {
                when (data) {
                    is AnnotationRenderData.Standard -> {
                        drawPath(
                            path = data.path, color = data.color, style = Stroke(
                                width = data.strokeWidth, cap = data.cap, join = StrokeJoin.Round
                            ), blendMode = data.blendMode
                        )
                    }

                    is AnnotationRenderData.Fountain -> {
                        drawPath(
                            path = data.path,
                            color = data.color,
                            style = androidx.compose.ui.graphics.drawscope.Fill
                        )
                    }

                    is AnnotationRenderData.Pencil -> {
                        val texture = PdfTextureGenerator.getNoiseTexture()
                        drawIntoCanvas { canvas ->
                            val paint = NativePaint().apply {
                                isAntiAlias = true
                                style = NativePaint.Style.STROKE
                                strokeCap = NativePaint.Cap.ROUND
                                strokeJoin = NativePaint.Join.ROUND
                                strokeWidth = data.strokeWidth
                                shader = BitmapShader(
                                    texture, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT
                                )
                                colorFilter = PorterDuffColorFilter(
                                    data.color.toArgb(), PorterDuff.Mode.SRC_IN
                                )
                                alpha = (data.color.alpha * data.velocityAlpha * 255).toInt()
                            }
                            canvas.nativeCanvas.drawPath(data.path, paint)
                        }
                    }
                }
            }

            staticRenderData.forEach { drawData(it) }
            activeRenderData?.let { drawData(it) }
        }
        val drawDuration = (System.nanoTime() - drawStart) / 1_000_000f
        if (drawDuration > 2f) {
            Timber.tag("PdfPerf").v("ANNOT_DRAW: Canvas draw took ${drawDuration}ms (Page $pageIndex)")
        }
    }
}

@Composable
private fun PdfPageStaticLayer(data: PageStaticData) {
    PdfBitmapLayer(
        bitmapState = data.bitmap.item,
        tiles = data.tiles.item,
        effectiveScale = data.effectiveScale,
        centeringOffsetX = data.centeringOffsetX,
        centeringOffsetY = data.centeringOffsetY,
        canvasWidth = data.canvasWidth,
        canvasHeight = data.canvasHeight,
        targetWidth = data.targetWidth,
        targetHeight = data.targetHeight,
        colorFilter = data.colorFilter.item,
        isDarkMode = data.isDarkMode,
        excludeImages = data.excludeImages,
        imageRects = data.imageRects.item,
        textureBitmap = data.textureBitmap.item,
        textureAlpha = data.textureAlpha,
        textureBlendMode = data.textureBlendMode
    )
}

@Composable
private fun PdfPageSelectionsLayer(
    pageLinks: List<PageLink>,
    showAllTextHighlights: Boolean,
    actualBitmapWidthPx: Int,
    actualBitmapHeightPx: Int,
    mergedAllTextPageHighlightRects: List<Rect>,
    mergedTtsHighlightRects: List<Rect>,
    mergedSearchFocusedRects: List<Rect>,
    mergedSearchAllRects: List<Rect>,
    searchHighlightMode: SearchHighlightMode,
    ocrHoverHighlights: List<RectF>,
    mergedSelectionRects: List<Rect>,
    userHighlightScreenRects: List<Pair<PdfUserHighlight, List<Rect>>>,
    centeringOffsetX: Float,
    centeringOffsetY: Float,
    linkHighlightColor: Color,
    scrimColorForTextHighlight: Color,
    allTextPageHighlightColor: Color,
    ttsHighlightColor: Color,
    selectionHighlightColor: Color,
    customHighlightColors: Map<PdfHighlightColor, Color> = emptyMap()
) {
    SideEffect {
        Timber.tag("PdfDrawPerf").v("SELECTIONS LAYER: Recomposing")
        Timber.tag("PdfHighlightDebug").v("PdfPageSelectionsLayer Recomposing. userHighlights count: ${userHighlightScreenRects.size}")
    }
    val highlightStart = System.nanoTime()

    PdfHighlightsLayer(
        pageLinks = pageLinks,
        showAllTextHighlights = showAllTextHighlights,
        actualBitmapWidthPx = actualBitmapWidthPx,
        actualBitmapHeightPx = actualBitmapHeightPx,
        mergedAllTextPageHighlightRects = mergedAllTextPageHighlightRects,
        mergedTtsHighlightRects = mergedTtsHighlightRects,
        mergedSearchFocusedRects = mergedSearchFocusedRects,
        mergedSearchAllRects = mergedSearchAllRects,
        searchHighlightMode = searchHighlightMode,
        ocrHoverHighlights = ocrHoverHighlights,
        mergedSelectionRects = mergedSelectionRects,
        userHighlightScreenRects = userHighlightScreenRects,
        centeringOffsetX = centeringOffsetX,
        centeringOffsetY = centeringOffsetY,
        linkHighlightColor = linkHighlightColor,
        scrimColorForTextHighlight = scrimColorForTextHighlight,
        allTextPageHighlightColor = allTextPageHighlightColor,
        ttsHighlightColor = ttsHighlightColor,
        selectionHighlightColor = selectionHighlightColor,
        customHighlightColors = customHighlightColors
    )

    val highlightTime = (System.nanoTime() - highlightStart) / 1_000_000f
    if (highlightTime > 1f) {
        SideEffect {
            Timber.tag("PdfPerformance")
                .v("PdfHighlightsLayer composition/draw took ${highlightTime}ms")
        }
    }
}

@Composable
private fun PdfPageRenderer(
    staticData: PageStaticData,
    selectionData: PageSelectionData,
    totalPages: Int,
    annotationsProvider: () -> List<PdfAnnotation>,
    drawingState: PdfDrawingState?,
    onCanvasSizeChanged: (Float, Float) -> Unit,
    scale: Float,
    uiScale: Float,
    offset: Offset,
    startHandlePos: Offset?,
    endHandlePos: Offset?,
    teardropWidthPx: Float,
    teardropHeightPx: Float,
    activeDraggingHandle: Handle?,
    showMagnifier: Boolean,
    magnifierCenterTarget: Offset,
    magnifierZoomFactor: Float,
    menuState: CustomPdfMenuState?,
    onMenuDismiss: () -> Unit,
    onCopy: (String) -> Unit,
    onAiDefine: (String) -> Unit,
    onTranslate: (String) -> Unit,
    onSearch: (String) -> Unit,
    onSelectAll: () -> Unit,
    onShowUpsellDialog: () -> Unit,
    isProUser: Boolean,
    isBookmarked: Boolean,
    onBookmarkClick: () -> Unit,
    centeringPaddingTop: Dp,
    centeringPaddingEnd: Dp,
    isPerformingOcr: Boolean,
    ocrRipplePos: Offset?,
    layoutCoordinates: LayoutCoordinates?,
    contentToScreenCoordinates: (Offset) -> Offset,
    density: Density,
    isVerticalScroll: Boolean,
    showPageNumberOverlay: Boolean,
    isScrolling: Boolean,
    isEditMode: Boolean,
    selectedTool: InkType,
    eraserPosition: Offset?,
    isStylusEraserOverride: Boolean,
    richTextController: RichTextController?,
    textBoxes: List<PdfTextBox>,
    selectedTextBoxId: String?,
    onTextBoxChange: (PdfTextBox) -> Unit,
    onTextBoxSelect: (String) -> Unit,
    onTextBoxDragStart: (PdfTextBox, Offset, Offset) -> Unit,
    onTextBoxDrag: (Offset) -> Unit,
    onTextBoxDragEnd: () -> Unit,
    onDragPageTurn: (Int) -> Unit,
    draggingBoxId: String? = null,
    customHighlightColors: Map<PdfHighlightColor, Color> = emptyMap(),
    onPaletteClick: (() -> Unit)? = null,
    onHighlightAdd: (Int, Pair<Int, Int>, String, PdfHighlightColor) -> Unit,
    onHighlightUpdate: (String, PdfHighlightColor) -> Unit,
    onHighlightDelete: (String) -> Unit,
    onTts: (Int, Int) -> Unit,
    activeToolThickness: Float,
    eraserToolThickness: Float,
    onNote: (String?) -> Unit,
    isBubbleZoomModeActive: Boolean = false,
    isActivePage: Boolean = true,
    isDetectingBubbles: Boolean = false,
    detectedBubbles: List<SpeechBubble> = emptyList(),
    animatingBubbleIndex: Int = -1,
    bubbleExpansionProgress: Float = 0f,
    expandedBubbleRender: ExpandedBubbleRender? = null
) {
    Box(modifier = Modifier.fillMaxSize()) {
        Box(
            modifier = Modifier
                .fillMaxSize()
                .graphicsLayer {
                    scaleX = scale
                    scaleY = scale
                    translationX = offset.x
                    translationY = offset.y
                }) {

            // Layer 1: The Heavy Bitmap
            Box(modifier = Modifier.fillMaxSize().graphicsLayer()) {
                PdfPageStaticLayer(data = staticData)
            }

            // Layer 2: The Lightweight Highlights
            PdfPageSelectionsLayer(
                pageLinks = selectionData.pageLinks.item,
                showAllTextHighlights = selectionData.showAllTextHighlights,
                actualBitmapWidthPx = selectionData.actualBitmapWidthPx,
                actualBitmapHeightPx = selectionData.actualBitmapHeightPx,
                mergedAllTextPageHighlightRects = selectionData.mergedAllTextPageHighlightRects.item,
                mergedTtsHighlightRects = selectionData.mergedTtsHighlightRects.item,
                mergedSearchFocusedRects = selectionData.mergedSearchFocusedRects.item,
                mergedSearchAllRects = selectionData.mergedSearchAllRects.item,
                searchHighlightMode = selectionData.searchHighlightMode,
                ocrHoverHighlights = selectionData.ocrHoverHighlights.item,
                mergedSelectionRects = selectionData.mergedSelectionRects.item,
                userHighlightScreenRects = selectionData.userHighlightScreenRects.item,
                centeringOffsetX = selectionData.centeringOffsetX,
                centeringOffsetY = selectionData.centeringOffsetY,
                linkHighlightColor = selectionData.linkHighlightColor,
                scrimColorForTextHighlight = selectionData.scrimColorForTextHighlight,
                allTextPageHighlightColor = selectionData.allTextPageHighlightColor,
                ttsHighlightColor = selectionData.ttsHighlightColor,
                selectionHighlightColor = selectionData.selectionHighlightColor,
                customHighlightColors = selectionData.customHighlightColors.item
            )

            // Layer 3: Annotations & Text
            if (staticData.targetWidth > 0 && staticData.targetHeight > 0) {
                Box(modifier = Modifier.fillMaxSize().graphicsLayer()) {
                    PdfAnnotationLayer(
                        actualBitmapWidthPx = staticData.targetWidth,
                        actualBitmapHeightPx = staticData.targetHeight,
                        annotationsProvider = annotationsProvider,
                        drawingState = drawingState,
                        centeringOffsetX = staticData.centeringOffsetX,
                        centeringOffsetY = staticData.centeringOffsetY,
                        pageIndex = selectionData.pageIndex
                    )
                }

                if (richTextController != null) {
                    val isEditable = isEditMode && selectedTool == InkType.TEXT
                    val hasContent = richTextController.pageLayouts.any {
                        it.pageIndex == selectionData.pageIndex
                    } || richTextController.hasRenderableText

                    if (isEditable || hasContent) {
                        PdfRichTextLayer(
                            pageIndex = selectionData.pageIndex,
                            controller = richTextController,
                            pageWidth = staticData.targetWidth.toFloat(),
                            pageHeight = staticData.targetHeight.toFloat(),
                            isTextEditingEnabled = isEditable && selectedTextBoxId == null,
                            centeringOffsetX = staticData.centeringOffsetX,
                            centeringOffsetY = staticData.centeringOffsetY,
                            isDarkMode = staticData.isDarkMode,
                            isScrolling = isScrolling
                        )
                    }
                }

                if (textBoxes.isNotEmpty()) {
                    androidx.compose.runtime.SideEffect {
                        Timber.tag("PdfTextBoxDebug").d("PdfPageRenderer parent graphicsLayer applied | scale=$scale | offset=$offset | Centering: X=${staticData.centeringOffsetX}, Y=${staticData.centeringOffsetY}")
                    }
                }

                textBoxes.forEach { box ->
                    val isDraggingThisBox = (box.id == draggingBoxId)
                    val boxAlpha = if (isDraggingThisBox) 0f else 1f

                    key(box.id) {
                        ResizableTextBox(
                            box = box,
                            isSelected = (box.id == selectedTextBoxId),
                            isEditMode = isEditMode,
                            isDarkMode = staticData.isDarkMode,
                            scale = uiScale,
                            pageWidthPx = staticData.targetWidth.toFloat(),
                            pageHeightPx = staticData.targetHeight.toFloat(),
                            handlePosition = HandlePosition.AUTO,
                            onBoundsChanged = { newBounds ->
                                Timber.tag("PdfTextBoxDebug").v("PdfPageRenderer onBoundsChanged [ID: ${box.id}] bounds=$newBounds draggingBoxId=$draggingBoxId")
                                if (draggingBoxId != box.id) {
                                    onTextBoxChange(box.copy(relativeBounds = newBounds))
                                } else {
                                    Timber.tag("PdfTextBoxDebug").d("PdfPageRenderer onBoundsChanged IGNORED because box[ID: ${box.id}] is being dragged globally")
                                }
                            },
                            onTextChanged = { newText ->
                                onTextBoxChange(box.copy(text = newText))
                            },
                            onSelect = {
                                Timber.tag("PdfTextBoxDebug").d("PdfPageRenderer onSelect propagated[ID: ${box.id}]")
                                onTextBoxSelect(box.id)
                            },
                            onDragStart = { touchOffset ->
                                Timber.tag("PdfTextBoxDebug").d("PdfPageRenderer onDragStart[ID: ${box.id}] isVerticalScroll=$isVerticalScroll | offset=$touchOffset")
                                if (isVerticalScroll) {
                                    val topLeft = Offset(
                                        box.relativeBounds.left * staticData.targetWidth,
                                        box.relativeBounds.top * staticData.targetHeight
                                    )
                                    onTextBoxDragStart(box, topLeft, touchOffset)
                                } else {
                                    onTextBoxDragStart(box, Offset.Zero, touchOffset)
                                }
                            },
                            onDrag = { delta, currentBounds ->
                                Timber.tag("PdfTextBoxDebug").v("PdfPageRenderer onDrag [ID: ${box.id}] delta=$delta currentBounds=$currentBounds scale=$scale")
                                if (isVerticalScroll) {
                                    onTextBoxDrag(delta)
                                } else {
                                    val scaledDelta = delta * scale
                                    onTextBoxDrag(scaledDelta)

                                    val width = staticData.targetWidth
                                    val edgeThreshold = 60f
                                    if (width > 0) {
                                        if (currentBounds.left < edgeThreshold && delta.x < 0) {
                                            onDragPageTurn(-1)
                                        } else if (currentBounds.right > width - edgeThreshold && delta.x > 0) {
                                            onDragPageTurn(1)
                                        }
                                    }
                                }
                            },
                            onDragEnd = {
                                Timber.tag("PdfTextBoxDebug").d("PdfPageRenderer onDragEnd[ID: ${box.id}]")
                                onTextBoxDragEnd()
                            },
                            onDragCancel = {
                                Timber.tag("PdfTextBoxDebug").d("PdfPageRenderer onDragCancel [ID: ${box.id}]")
                                onTextBoxDragEnd()
                            },
                            modifier = Modifier
                                .zIndex(10f)
                                .offset {
                                    IntOffset(
                                        staticData.centeringOffsetX.roundToInt(),
                                        staticData.centeringOffsetY.roundToInt()
                                    )
                                }
                                .alpha(boxAlpha)
                        )
                    }
                }
            }

            // Layer 4: Page Number Indicator
            if (showPageNumberOverlay && totalPages > 0) {
                val pageNumColor = if (staticData.isDarkMode) {
                    Color.White
                } else {
                    Color.Black
                }

                Box(modifier = Modifier
                    .offset {
                        IntOffset(
                            x = staticData.centeringOffsetX.toInt(),
                            y = staticData.centeringOffsetY.toInt()
                        )
                    }
                    .size(width = with(density) {
                        staticData.targetWidth.toDp()
                    }, height = with(density) {
                        staticData.targetHeight.toDp()
                    })) {
                    Text(
                        text = "${selectionData.pageIndex + 1}/$totalPages",
                        color = pageNumColor.copy(alpha = 0.5f),
                        style = MaterialTheme.typography.labelSmall.copy(
                            fontSize = 12.sp, fontWeight = FontWeight.Bold
                        ),
                        modifier = Modifier
                            .align(Alignment.BottomEnd)
                            .padding(end = 12.dp, bottom = 12.dp)
                    )
                }
            }
        }

        // Capture size for coordinate conversions
        Canvas(modifier = Modifier.fillMaxSize()) {
            if (staticData.canvasWidth != size.width || staticData.canvasHeight != size.height) {
                onCanvasSizeChanged(size.width, size.height)
            }
        }

        val teardropPainter = painterResource(id = R.drawable.teardrop)

        if (isEditMode && (selectedTool == InkType.ERASER || isStylusEraserOverride) && eraserPosition != null) {
            Canvas(modifier = Modifier.fillMaxSize()) {
                val eraserStrokeWidth = resolveEraserStrokeWidth(
                    isStylusEraserOverride,
                    activeToolThickness,
                    eraserToolThickness
                )
                val radiusPx = if (eraserStrokeWidth > 0f && staticData.targetWidth > 0) {
                    eraserStrokeWidth * staticData.targetWidth * scale
                } else {
                    8.dp.toPx()
                }

                drawCircle(
                    color = Color.White.copy(alpha = 0.3f),
                    radius = radiusPx,
                    center = eraserPosition
                )

                drawCircle(
                    color = Color.Black,
                    radius = radiusPx,
                    center = eraserPosition,
                    style = Stroke(width = 1.dp.toPx())
                )
            }
        }

        Canvas(modifier = Modifier.fillMaxSize()) {
            val handleColor = Color.Blue
            val tiltAngleDegrees = 30f

            startHandlePos?.let { contentPos ->
                val position = contentToScreenCoordinates(contentPos)
                translate(left = position.x - teardropWidthPx / 2, top = position.y) {
                    rotate(degrees = tiltAngleDegrees, pivot = Offset(teardropWidthPx / 2f, 0f)) {
                        with(teardropPainter) {
                            draw(
                                size = Size(teardropWidthPx, teardropHeightPx),
                                colorFilter = ColorFilter.tint(handleColor)
                            )
                        }
                    }
                }
            }
            endHandlePos?.let { contentPos ->
                val position = contentToScreenCoordinates(contentPos)
                translate(left = position.x - teardropWidthPx / 2, top = position.y) {
                    rotate(degrees = -tiltAngleDegrees, pivot = Offset(teardropWidthPx / 2f, 0f)) {
                        with(teardropPainter) {
                            draw(
                                size = Size(teardropWidthPx, teardropHeightPx),
                                colorFilter = ColorFilter.tint(handleColor)
                            )
                        }
                    }
                }
            }
        }

        if (showMagnifier && activeDraggingHandle != null && staticData.bitmap.item != null) {
            val handleContentPos = when (activeDraggingHandle) {
                Handle.START -> startHandlePos
                Handle.END -> endHandlePos
            }

            handleContentPos?.let { contentPos ->
                val pos = contentToScreenCoordinates(contentPos)

                val magnifierWidth = 120.dp
                val magnifierHeight = 60.dp
                val magnifierOffsetAboveHandle = 24.dp
                val effectiveScale = staticData.effectiveScale

                val effectiveZoomFactor = if (isVerticalScroll && effectiveScale > 1f) {
                    effectiveScale * 1.25f
                } else {
                    magnifierZoomFactor
                }

                val popupPositionProvider = remember(pos, layoutCoordinates, density) {
                    object : androidx.compose.ui.window.PopupPositionProvider {
                        override fun calculatePosition(
                            anchorBounds: androidx.compose.ui.unit.IntRect,
                            windowSize: androidx.compose.ui.unit.IntSize,
                            layoutDirection: androidx.compose.ui.unit.LayoutDirection,
                            popupContentSize: androidx.compose.ui.unit.IntSize
                        ): androidx.compose.ui.unit.IntOffset {
                            val coords = layoutCoordinates ?: return androidx.compose.ui.unit.IntOffset.Zero

                            val windowPos = coords.localToWindow(pos)
                            val offsetPx = with(density) { magnifierOffsetAboveHandle.toPx() }

                            val x = (windowPos.x - popupContentSize.width / 2).toInt()
                            val y = (windowPos.y - popupContentSize.height - offsetPx).toInt()

                            return androidx.compose.ui.unit.IntOffset(x, y)
                        }
                    }
                }

                androidx.compose.ui.window.Popup(
                    popupPositionProvider = popupPositionProvider,
                    properties = androidx.compose.ui.window.PopupProperties(
                        focusable = false,
                        dismissOnClickOutside = false,
                        dismissOnBackPress = false,
                        usePlatformDefaultWidth = false
                    )
                ) {
                    MagnifierComposable(
                        sourceBitmap = staticData.bitmap.item.asImageBitmap(),
                        tiles = if (effectiveScale > 1f) staticData.tiles.item else emptyList(),
                        currentScale = effectiveScale,
                        magnifierCenterOnBitmap = magnifierCenterTarget,
                        contentWidthPx = staticData.targetWidth,
                        contentHeightPx = staticData.targetHeight,
                        magnifierWidth = magnifierWidth,
                        magnifierHeight = magnifierHeight,
                        zoomFactor = effectiveZoomFactor,
                        selectionRectsInContentCoords = selectionData.mergedSelectionRects.item,
                        highlightColor = Color(0x6633B5E5),
                        colorFilter = staticData.colorFilter.item,
                        modifier = Modifier
                    )
                }
            }
        }

        if (menuState != null) {
            BackHandler(enabled = true, onBack = onMenuDismiss)
        }

        if (menuState != null && !isScrolling && draggingBoxId == null && activeDraggingHandle == null) {
            if (menuState.anchorRect.width() > 0 || menuState.anchorRect.height() > 0) {
                val popupPositionProvider = remember(menuState.anchorRect, density, offset, scale, layoutCoordinates) {
                    object : PopupPositionProvider {
                        override fun calculatePosition(
                            anchorBounds: IntRect,
                            windowSize: IntSize,
                            layoutDirection: LayoutDirection,
                            popupContentSize: IntSize
                        ): IntOffset {
                            val coords = layoutCoordinates ?: return IntOffset.Zero

                            val topLeftLocal = contentToScreenCoordinates(Offset(
                                menuState.anchorRect.left.toFloat(),
                                menuState.anchorRect.top.toFloat()))
                            val bottomRightLocal = contentToScreenCoordinates(Offset(
                                menuState.anchorRect.right.toFloat(),
                                menuState.anchorRect.bottom.toFloat()))

                            val topLeftWindow = coords.localToWindow(topLeftLocal)
                            val bottomRightWindow = coords.localToWindow(bottomRightLocal)

                            val windowCenterX = (topLeftWindow.x + bottomRightWindow.x) / 2
                            val gapPx = with(density) { 16.dp.toPx() }

                            var yInWindow = (topLeftWindow.y - popupContentSize.height - gapPx).toInt()

                            if (yInWindow < 0) {
                                yInWindow = (bottomRightWindow.y + gapPx).toInt()
                                if (yInWindow + popupContentSize.height > windowSize.height) {
                                    yInWindow = windowSize.height - popupContentSize.height - gapPx.toInt()
                                }
                            }

                            val xInWindow = (windowCenterX - popupContentSize.width / 2).toInt()
                                .coerceIn(0, windowSize.width - popupContentSize.width)

                            return IntOffset(xInWindow, yInWindow)
                        }
                    }
                }

                PdfSelectionMenuPopup(
                    menuState = menuState,
                    popupPositionProvider = popupPositionProvider,
                    onDismiss = onMenuDismiss,
                    onCopy = onCopy,
                    onAiDefine = onAiDefine,
                    onTranslate = onTranslate,
                    onSearch = onSearch,
                    onSelectAll = onSelectAll,
                    onColorSelected = { color ->
                        if (menuState.isExistingHighlight && menuState.highlightId != null) {
                            onHighlightUpdate(menuState.highlightId, color)
                        } else {
                            onHighlightAdd(
                                selectionData.pageIndex, menuState.charRange, menuState.selectedText,
                                color
                            )
                        }
                        onMenuDismiss()
                    },
                    onDelete = {
                        if (menuState.isExistingHighlight && menuState.highlightId != null) {
                            onHighlightDelete(menuState.highlightId)
                        }
                        onMenuDismiss()
                    },
                    onTts = {
                        onTts(selectionData.pageIndex, menuState.charRange.first)
                        onMenuDismiss()
                    },
                    onNote = {
                        if (menuState.isExistingHighlight && menuState.highlightId != null) {
                            onNote(menuState.highlightId)
                        } else {
                            onNote(null)
                            onHighlightAdd(
                                selectionData.pageIndex, menuState.charRange, menuState.selectedText,
                                PdfHighlightColor.YELLOW
                            )
                        }
                        onMenuDismiss()
                    },
                    customHighlightColors = selectionData.customHighlightColors.item,
                    onPaletteClick = onPaletteClick
                )
            }
        }

        BookmarkButton(
            isBookmarked = isBookmarked,
            onClick = onBookmarkClick,
            modifier = Modifier
                .align(Alignment.TopEnd)
                .padding(top = centeringPaddingTop, end = centeringPaddingEnd)
        )

        if (isPerformingOcr && ocrRipplePos != null) {
            OcrProcessingIndicator(position = ocrRipplePos)
        }

        if (isBubbleZoomModeActive && isActivePage) {
            if (isDetectingBubbles) {
                androidx.compose.material3.CircularProgressIndicator(
                    modifier = Modifier.align(Alignment.Center)
                )
            } else if (detectedBubbles.isNotEmpty()) {
                Canvas(modifier = Modifier.fillMaxSize().zIndex(20f)) {
                    // Draw shadow-like hints for unexpanded bubbles
                    detectedBubbles.forEachIndexed { index, bubble ->
                        val hintAlpha = if (index == animatingBubbleIndex) 0.35f * (1f - bubbleExpansionProgress) else 0.35f
                        if (hintAlpha > 0f) {
                            val left = bubble.bounds.left + staticData.centeringOffsetX
                            val top = bubble.bounds.top + staticData.centeringOffsetY
                            val width = bubble.bounds.width()
                            val height = bubble.bounds.height()

                            if (bubble.maskBitmap != null) {
                                drawImage(
                                    image = bubble.maskBitmap.asImageBitmap(),
                                    dstOffset = IntOffset(left.toInt(), top.toInt()),
                                    dstSize = IntSize(width.toInt(), height.toInt()),
                                    colorFilter = ColorFilter.tint(Color.Black.copy(alpha = hintAlpha)),
                                    filterQuality = androidx.compose.ui.graphics.FilterQuality.High
                                )
                            } else {
                                drawRoundRect(
                                    color = Color.Black.copy(alpha = hintAlpha),
                                    topLeft = Offset(left, top),
                                    size = Size(width, height),
                                    cornerRadius = androidx.compose.ui.geometry.CornerRadius(24f, 24f)
                                )
                            }
                        }
                    }

                    if (animatingBubbleIndex in detectedBubbles.indices && staticData.bitmap.item != null && bubbleExpansionProgress > 0f) {
                        val baseBitmap = staticData.bitmap.item ?: return@Canvas
                        if (
                            !baseBitmap.isCanvasSafeBitmap(
                                maxBytes = PDF_MAX_DRAW_BITMAP_BYTES,
                                maxDimension = PDF_MAX_DRAW_BITMAP_DIMENSION_PX
                            )
                        ) {
                            return@Canvas
                        }
                        val safeExpandedBubbleRender = expandedBubbleRender?.takeIf {
                            it.bitmap.isCanvasSafeBitmap(
                                maxBytes = PDF_MAX_DRAW_BITMAP_BYTES,
                                maxDimension = PDF_MAX_DRAW_BITMAP_DIMENSION_PX
                            )
                        }
                        val bubble = detectedBubbles[animatingBubbleIndex]
                        val left = bubble.bounds.left + staticData.centeringOffsetX
                        val top = bubble.bounds.top + staticData.centeringOffsetY
                        val logicalWidth = bubble.bounds.width()
                        val logicalHeight = bubble.bounds.height()
                        val pivotX = left + logicalWidth / 2f
                        val pivotY = top + logicalHeight / 2f
                        val targetZoomFactor = safeExpandedBubbleRender?.zoomFactor ?: computeDynamicBubbleZoomFactor(
                            bubbleBounds = bubble.bounds,
                            viewportWidth = staticData.canvasWidth,
                            viewportHeight = staticData.canvasHeight
                        )
                        val zoomFactor = androidx.compose.ui.util.lerp(1f, targetZoomFactor, bubbleExpansionProgress)

                        withTransform({
                            scale(zoomFactor, zoomFactor, Offset(pivotX, pivotY))
                        }) {
                            val dstOffset = IntOffset(left.toInt(), top.toInt())
                            val dstSize = IntSize(logicalWidth.toInt(), logicalHeight.toInt())

                            val renderScaleX = baseBitmap.width.toFloat() / staticData.targetWidth.toFloat()
                            val renderScaleY = baseBitmap.height.toFloat() / staticData.targetHeight.toFloat()

                            val srcOffset = IntOffset(
                                (bubble.bounds.left * renderScaleX).toInt(),
                                (bubble.bounds.top * renderScaleY).toInt()
                            )
                            val srcSize = IntSize(
                                (logicalWidth * renderScaleX).toInt(),
                                (logicalHeight * renderScaleY).toInt()
                            )

                            if (bubble.maskBitmap != null) {
                                drawImage(
                                    image = bubble.maskBitmap.asImageBitmap(),
                                    dstOffset = IntOffset(left.toInt() + 12, top.toInt() + 12),
                                    dstSize = dstSize,
                                    colorFilter = ColorFilter.tint(Color.Black.copy(alpha = 0.5f * bubbleExpansionProgress)),
                                    filterQuality = androidx.compose.ui.graphics.FilterQuality.High
                                )
                            } else {
                                drawRoundRect(
                                    color = Color.Black.copy(alpha = 0.5f * bubbleExpansionProgress),
                                    topLeft = Offset(left + 12f, top + 12f),
                                    size = Size(logicalWidth, logicalHeight),
                                    cornerRadius = androidx.compose.ui.geometry.CornerRadius(24f, 24f)
                                )
                            }

                            if (bubble.maskBitmap != null) {
                                val rect = androidx.compose.ui.geometry.Rect(
                                    dstOffset.x.toFloat(),
                                    dstOffset.y.toFloat(),
                                    dstOffset.x.toFloat() + dstSize.width,
                                    dstOffset.y.toFloat() + dstSize.height
                                )
                                drawContext.canvas.saveLayer(rect, androidx.compose.ui.graphics.Paint())
                                drawImage(
                                    image = (safeExpandedBubbleRender?.bitmap ?: baseBitmap).asImageBitmap(),
                                    srcOffset = if (safeExpandedBubbleRender != null) IntOffset.Zero else srcOffset,
                                    srcSize = if (safeExpandedBubbleRender != null) {
                                        IntSize(
                                            safeExpandedBubbleRender.bitmap.width,
                                            safeExpandedBubbleRender.bitmap.height)
                                    } else {
                                        srcSize
                                    },
                                    dstOffset = dstOffset,
                                    dstSize = dstSize,
                                    filterQuality = androidx.compose.ui.graphics.FilterQuality.High
                                )
                                drawImage(
                                    image = bubble.maskBitmap.asImageBitmap(),
                                    dstOffset = dstOffset,
                                    dstSize = dstSize,
                                    blendMode = BlendMode.DstIn,
                                    filterQuality = androidx.compose.ui.graphics.FilterQuality.High
                                )
                                drawContext.canvas.restore()
                            } else {
                                clipRect(left, top, left + logicalWidth, top + logicalHeight) {
                                    drawImage(
                                        image = (safeExpandedBubbleRender?.bitmap ?: baseBitmap).asImageBitmap(),
                                        srcOffset = if (safeExpandedBubbleRender != null) IntOffset.Zero else srcOffset,
                                        srcSize = if (safeExpandedBubbleRender != null) {
                                            IntSize(
                                                safeExpandedBubbleRender.bitmap.width,
                                                safeExpandedBubbleRender.bitmap.height)
                                        } else {
                                            srcSize
                                        },
                                        dstOffset = dstOffset,
                                        dstSize = dstSize
                                    )
                                }
                                drawRect(
                                    color = Color.White.copy(alpha = 0.5f * bubbleExpansionProgress),
                                    topLeft = Offset(left, top),
                                    size = Size(logicalWidth, logicalHeight),
                                    style = Stroke(width = 4f)
                                )
                            }
                        }
                    }
                }
            }
        }
    }
}

@Stable
class PdfDrawingState {
    var currentAnnotation by mutableStateOf<PdfAnnotation?>(null)
        private set
    private val currentPoints = mutableListOf<PdfPoint>()

    fun onDrawStart(pageIndex: Int, point: PdfPoint, type: InkType, color: Color, width: Float) {
        currentPoints.clear()
        currentPoints.add(point)
        currentAnnotation = PdfAnnotation(
            type = AnnotationType.INK,
            inkType = type,
            pageIndex = pageIndex,
            points = currentPoints.toList(),
            color = color,
            strokeWidth = width
        )
    }

    fun onDraw(point: PdfPoint) {
        currentPoints.add(point)
        currentAnnotation = currentAnnotation?.copy(points = currentPoints.toList())
    }

    fun onDrawCancel() {
        currentAnnotation = null
        currentPoints.clear()
    }

    fun onDrawEnd(): PdfAnnotation? {
        val finalAnnot = currentAnnotation
        currentAnnotation = null
        currentPoints.clear()
        return finalAnnot
    }

    fun updateDrag(point: PdfPoint) {
        if (currentPoints.isNotEmpty()) {
            val start = currentPoints.first()
            currentPoints.clear()
            currentPoints.add(start)
            currentPoints.add(point)
            currentAnnotation = currentAnnotation?.copy(points = currentPoints.toList())
        }
    }
}

@Composable
fun PdfRichTextLayer(
    pageIndex: Int,
    controller: RichTextController,
    pageWidth: Float,
    pageHeight: Float,
    isTextEditingEnabled: Boolean,
    centeringOffsetX: Float,
    centeringOffsetY: Float,
    isDarkMode: Boolean,
    isScrolling: Boolean
) {
    val density = LocalDensity.current
    val textMeasurer = androidx.compose.ui.text.rememberTextMeasurer()

    LaunchedEffect(pageWidth, pageHeight, density) {
        if (pageWidth > 0 && pageHeight > 0) {
            controller.updateLayoutConfig(pageWidth, pageHeight, density, textMeasurer)
        }
    }

    val pageLayout = remember(controller.pageLayouts, pageIndex) {
        controller.pageLayouts.find { it.pageIndex == pageIndex }
    }

    val marginX = pageWidth * 0.1f
    val marginY = pageHeight * 0.08f
    val editorWidth = pageWidth - (marginX * 2)
    val editorHeight = pageHeight - (marginY * 2)

    val editorWidthDp = with(density) { editorWidth.toDp() }
    val editorHeightDp = with(density) { editorHeight.toDp() }

    Box(modifier = Modifier
        .offset {
            IntOffset(
                (centeringOffsetX + marginX).roundToInt(), (centeringOffsetY + marginY).roundToInt()
            )
        }
        .size(editorWidthDp, editorHeightDp)
        .graphicsLayer()
        .then(
            if (isTextEditingEnabled) {
                Modifier.pointerInput(pageIndex) {
                    detectTapGestures { tapOffset ->
                        controller.handleTapOnPage(pageIndex, tapOffset)
                    }
                }
            } else {
                Modifier
            }
        )
    ) {
        val textToRender = if (controller.activePageIndex == pageIndex) {
            controller.localTextFieldValue.annotatedString
        } else {
            pageLayout?.visibleText?.withoutTrailingPdfPageBreakForRender()
        }

        if (textToRender != null) {
            val measureResult = remember(textToRender, editorWidth, density) {
                textMeasurer.measure(
                    text = textToRender,
                    style = TextStyle(fontSize = 16.sp),
                    constraints = Constraints(maxWidth = editorWidth.toInt()),
                    density = density
                )
            }

            Canvas(modifier = Modifier.fillMaxSize()) {
                measureResult.multiParagraph.paint(drawContext.canvas)
            }

            if (isTextEditingEnabled) {
                val tfv = controller.editingValue
                val selection = tfv.selection

                @Suppress("ControlFlowWithEmptyBody") if (controller.activePageIndex == pageIndex) {
                    val localStart = selection.start.coerceIn(0, textToRender.length)
                    val localEnd = selection.end.coerceIn(0, textToRender.length)

                    if (localStart != localEnd) {
                        val selectionPath = measureResult.getPathForRange(localStart, localEnd)
                        Canvas(modifier = Modifier.fillMaxSize()) {
                            drawPath(selectionPath, Color(0xFFB3D7FF).copy(alpha = 0.5f))
                        }
                    }

                    if (selection.collapsed && controller.isCursorVisible) {
                        val alpha = if (isScrolling) {
                            1f
                        } else {
                            val infiniteTransition = rememberInfiniteTransition(label = "cursor")
                            infiniteTransition.animateFloat(
                                initialValue = 1f,
                                targetValue = 0f,
                                animationSpec = infiniteRepeatable(tween(500), RepeatMode.Reverse),
                                label = "cursorAlpha"
                            ).value
                        }

                        val cursorRect = measureResult.getCursorRect(localStart)
                        val cursorColor = if (isDarkMode) Color.White else Color.Black
                        val styleFontSize = controller.currentStyle.fontSize
                        val cursorHeight = if (styleFontSize.isSpecified) {
                            with(density) { styleFontSize.toPx() } * 1.2f
                        } else {
                            cursorRect.height
                        }

                        val centerY = cursorRect.center.y
                        val newTop = centerY - (cursorHeight / 2f)
                        val newBottom = centerY + (cursorHeight / 2f)

                        Canvas(modifier = Modifier.fillMaxSize()) {
                            drawLine(
                                color = cursorColor.copy(alpha = alpha),
                                start = Offset(cursorRect.left, newTop),
                                end = Offset(cursorRect.left, newBottom),
                                strokeWidth = 2.dp.toPx()
                            )
                        }
                    }
                } else if (pageLayout != null) { }
            }
        }
    }
}

private fun AnnotatedString.withoutTrailingPdfPageBreakForRender(): AnnotatedString {
    return if (text.lastOrNull() == PAGE_BREAK_CHAR) {
        subSequence(0, length - 1)
    } else {
        this
    }
}

private fun getNativePointer(obj: Any): Long {
    val priorityFields = listOf("pagePtr", "mNativePage", "page")

    for (name in priorityFields) {
        try {
            val field = obj.javaClass.getDeclaredField(name)
            field.isAccessible = true
            val value = field.get(obj)
            if (value is Long && value != 0L) return value
            if (value != null && value !is Long) {
                val nestedPtr = getNativePointer(value)
                if (nestedPtr != 0L) return nestedPtr
            }
        } catch (_: Exception) {}
    }

    try {
        for (field in obj.javaClass.declaredFields) {
            if (field.type == Long::class.java || field.type == Long::class.javaPrimitiveType) {
                field.isAccessible = true
                val value = field.get(obj) as Long
                if (value > 0xFFFFFFFFL) return value
            }
        }
    } catch (_: Exception) {}

    return 0L
}
