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

import android.annotation.SuppressLint
import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.Rect
import android.webkit.JavascriptInterface
import android.webkit.WebChromeClient
import android.webkit.WebResourceRequest
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
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.viewinterop.AndroidView
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupPositionProvider
import androidx.core.net.toUri
import com.aryan.reader.R
import com.aryan.reader.getReaderTextureDataUri
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.json.JSONObject
import timber.log.Timber
import java.io.BufferedReader
import java.io.InputStreamReader

private const val TAG_LINK_NAV = "LINK_NAV"
private val READER_WEB_VIEW_JS_INTERFACES = arrayOf(
    "PageInfoReporter",
    "ProgressReporter",
    "ContentBridge",
    "HighlightBridge",
    "AutoScrollBridge",
    "CfiBridge",
    "SnippetBridge",
    "TtsBridge",
    "AiBridge",
    "FootnoteBridge",
    "LinkNavBridge"
)

private fun WebView.releaseReaderResources() {
    try {
        stopLoading()
        READER_WEB_VIEW_JS_INTERFACES.forEach { removeJavascriptInterface(it) }
        webChromeClient = null
        webViewClient = WebViewClient()
        loadDataWithBaseURL(null, "", "text/html", "UTF-8", null)
        clearHistory()
        removeAllViews()
        destroy()
    } catch (e: Exception) {
        Timber.w(e, "Failed to fully release EPUB WebView resources")
    }
}

private fun getFontCssInjection(): String {
    return """
        @font-face { font-family: 'Merriweather'; src: url('file:///android_asset/fonts/merriweather.ttf'); }
        @font-face { font-family: 'Lato'; src: url('file:///android_asset/fonts/lato.ttf'); }
        @font-face { font-family: 'Lora'; src: url('file:///android_asset/fonts/lora.ttf'); }
        @font-face { font-family: 'Roboto Mono'; src: url('file:///android_asset/fonts/roboto_mono.ttf'); }
        @font-face { font-family: 'Lexend'; src: url('file:///android_asset/fonts/lexend.ttf'); }
    """.trimIndent()
}

private fun getJsToInject(context: Context): String {
    return try {
        context.assets.open("epub_reader.js").use { inputStream ->
            BufferedReader(InputStreamReader(inputStream)).use { reader ->
                reader.readText()
            }
        }
    } catch (e: Exception) {
        Timber.e(e, "Error reading epub_reader.js from assets")
        "" // Return empty string on error
    }
}

@Suppress("unused")
class AutoScrollJsBridge(
    private val callback: () -> Unit
) {
    @JavascriptInterface
    fun onChapterEnd() {
        Timber.d("Bridge: onChapterEnd called from JavaScript. Invoking callback.")
        callback()
    }
}

@Suppress("unused")
class TtsJsBridge(
    private val scope: CoroutineScope,
    private val ttsStructuredTextHandler: suspend (String) -> Unit
) {
    @JavascriptInterface
    fun onStructuredTextExtracted(json: String) {
        Timber.tag("TTS_CHAPTER_CHANGE_DIAG").d("JS Bridge received JSON. Length: ${json.length}")
        if (json.isNotBlank() && json != "[]") {
            scope.launch(kotlinx.coroutines.Dispatchers.Default) {
                ttsStructuredTextHandler(json)
            }
        } else {
            Timber.tag("TTS_CHAPTER_CHANGE_DIAG").w("JS Bridge received empty or blank JSON. This may trigger a chapter skip.")
            scope.launch(kotlinx.coroutines.Dispatchers.Default) {
                ttsStructuredTextHandler("[]")
            }
        }
    }
}

@Suppress("unused")
class HighlightJsBridge(
    private val onCreateCallback: (String, String, String) -> Unit, // Renamed to avoid recursion
    private val onClickCallback: ((String, String, Int, Int, Int, Int) -> Unit)? = null // Renamed
) {
    @JavascriptInterface
    fun onHighlightCreated(cfi: String, text: String, colorId: String) {
        onCreateCallback(cfi, text, colorId) // Calls the lambda property
    }

    @JavascriptInterface
    fun onHighlightClicked(
        cfi: String,
        text: String,
        left: Int,
        top: Int,
        right: Int,
        bottom: Int
    ) {
        onClickCallback?.invoke(cfi, text, left, top, right, bottom)
    }
}

@Suppress("unused")
class ContentBridge(
    private val onChunkRequested: (index: Int) -> Unit
) {
    @JavascriptInterface
    fun requestChunk(index: Int) {
        onChunkRequested(index)
    }
}

@Suppress("unused")
class CfiJsBridge(
    private val onCfiReady: (String) -> Unit,
    private val onCfiForBookmarkReady: (String) -> Unit,
    private val onScrollFinishedCallback: (Boolean) -> Unit
) {
    @JavascriptInterface
    fun onCfiExtracted(jsonResponse: String) {
        try {
            // --- ADDED LOG ---
            Timber.tag("PosSaveDiag")
                .d("CfiJsBridge.onCfiExtracted: Raw JSON received from JS: $jsonResponse")

            val json = JSONObject(jsonResponse)
            val cfi = json.optString("cfi", "/4")
            val logArray = json.optJSONArray("log")

            Timber.d("--- Start CFI Save Diagnostics ---")
            Timber.d("Received CFI for saving: $cfi")
            if (logArray != null) {
                for (i in 0 until logArray.length()) {
                    Timber.d(logArray.getString(i))
                }
            } else {
                Timber.d("No log array received. Raw response: $jsonResponse")
            }
            Timber.d("--- End CFI Save Diagnostics ---")

            if (cfi.isNotBlank()) {
                onCfiReady(cfi)
            }
        } catch (e: Exception) {
            Timber.tag("PosSaveDiag")
                .e(e, "CfiJsBridge.onCfiExtracted: Error parsing CFI JSON response: $jsonResponse")
            onCfiReady("/4")
        }
    }

    @JavascriptInterface
    fun onCfiForBookmarkExtracted(jsonResponse: String) {
        try {
            val json = JSONObject(jsonResponse)
            val cfi = json.optString("cfi")
            val logArray = json.optJSONArray("log")

            Timber.d("--- Start CFI Diagnostics (Bookmark) ---")
            Timber.d("Received CFI for bookmark: $cfi")
            if (logArray != null) {
                for (i in 0 until logArray.length()) {
                    Timber.d(logArray.getString(i))
                }
            } else {
                Timber.d("No log array received. Raw response: $jsonResponse")
            }
            Timber.d("--- End CFI Diagnostics (Bookmark) ---")

            if (cfi != null) {
                onCfiForBookmarkReady(cfi)
            }
        } catch (e: Exception) {
            Timber.e(e, "Error parsing CFI JSON for bookmark: $jsonResponse")
        }
    }

    @JavascriptInterface
    fun onScrollFinished(success: Boolean) {
        Timber.tag("BookmarkDiagnosis").d("JS reported scroll finished. Success: $success")
        onScrollFinishedCallback(success)
    }
}

@Suppress("unused")
class SnippetJsBridge(
    private val onSnippetReady: (String, String) -> Unit
) {
    @JavascriptInterface
    fun onSnippetExtracted(cfi: String, snippet: String) {
        Timber.d("SnippetJsBridge.onSnippetExtracted received. CFI: '$cfi', Snippet: '$snippet'")
        onSnippetReady(cfi, snippet)
    }
}

@Suppress("unused")
class ProgressJsBridge(
    private val onTopChunkUpdated: (Int) -> Unit
) {
    private var lastReportedChunk = -1

    @JavascriptInterface
    fun updateTopChunk(chunkIndex: Int) {
        if (chunkIndex != lastReportedChunk) {
            lastReportedChunk = chunkIndex
            onTopChunkUpdated(chunkIndex)
        }
    }
}

private data class CustomMenuState(
    val selectedText: String,
    val selectionBounds: Rect,
    val finishActionModeCallback: () -> Unit,
    val cfi: String? = null,
    val isExistingHighlight: Boolean = false,
    val note: String? = null,
    val selectedColor: HighlightColor? = null
)

@Suppress("unused")
class AiJsBridge(
    private val scope: CoroutineScope, private val onContentReady: suspend (String) -> Unit
) {
    @JavascriptInterface
    fun onContentExtractedForSummarization(text: String) {
        Timber.d("Content extracted for summarization, length: ${text.length}")
        if (text.isNotBlank()) {
            scope.launch {
                onContentReady(text)
            }
        }
    }
}

@Suppress("unused")
class FootnoteJsBridge(
    private val onFootnoteRequestCallback: (String) -> Unit
) {
    @JavascriptInterface
    fun onFootnoteRequested(htmlContent: String) {
        Timber.tag("FootnoteDiag").d("Kotlin Bridge received footnote content. Length: ${htmlContent.length}")
        onFootnoteRequestCallback(htmlContent)
    }
}

@Suppress("unused")
class LinkNavJsBridge(
    private val currentChapterTitle: String,
    private val onInternalLinkClick: (String) -> Unit
) {
    @JavascriptInterface
    fun onLinkClicked(href: String, epubType: String, linkText: String) {
        Timber.tag(TAG_LINK_NAV)
            .d("[JS-CLICK] href='$href', epub:type='$epubType', label='$linkText' | currentChapter='$currentChapterTitle'")
        onInternalLinkClick(href)
    }
}

@SuppressLint("SetJavaScriptEnabled")
@Composable
fun ChapterWebView(
    key: Any,
    initialHtmlContent: String,
    baseUrl: String,
    totalChunks: Int,
    userHighlights: List<UserHighlight>,
    onHighlightCreated: (String, String, String) -> Unit,
    onHighlightDeleted: (String) -> Unit,
    onChunkRequested: (Int) -> Unit,
    chapterTitle: String,
    isDarkTheme: Boolean,
    effectiveBg: androidx.compose.ui.graphics.Color,
    effectiveText: androidx.compose.ui.graphics.Color,
    initialScrollTarget: ChapterScrollPosition?,
    initialPageScrollY: Int?,
    initialCfi: String?,
    initialChunkIndex: Int,
    onTopChunkUpdated: (Int) -> Unit,
    currentFontSize: Float,
    currentLineHeight: Float,
    currentParagraphGap: Float,
    currentImageSize: Float,
    currentHorizontalMargin: Float,
    currentVerticalMargin: Float,
    onChapterInitiallyScrolled: () -> Unit,
    modifier: Modifier = Modifier,
    onTap: () -> Unit,
    onPotentialScroll: () -> Unit,
    onOverScrollTop: (dragAmount: Float) -> Unit,
    onOverScrollBottom: (dragAmount: Float) -> Unit,
    onReleaseOverScrollTop: () -> Unit,
    onReleaseOverScrollBottom: () -> Unit,
    onScrollStateUpdate: (scrollY: Int, scrollHeight: Int, clientHeight: Int, activeFragmentId: String?) -> Unit,
    onWebViewInstanceCreated: (WebView) -> Unit,
    onCfiGenerated: (cfi: String) -> Unit,
    onBookmarkCfiGenerated: (cfi: String) -> Unit,
    onSnippetForBookmarkReady: (cfi: String, snippet: String) -> Unit,
    onScrollFinished: (Boolean) -> Unit = {},
    ttsScope: CoroutineScope,
    tocFragments: List<String>,
    initialFragmentId: String? = null,
    onTtsTextReady: suspend (String) -> Unit,
    isProUser: Boolean,
    isOss: Boolean = false,
    onShowDictionaryUpsellDialog: () -> Unit,
    onWordSelectedForAiDefinition: (String) -> Unit,
    onTranslate: (String) -> Unit,
    onSearch: (String) -> Unit,
    onNoteRequested: (String?) -> Unit,
    onContentReadyForSummarization: suspend (String) -> Unit,
    onFootnoteRequested: (String) -> Unit,
    currentFontFamily: ReaderFont,
    customFontPath: String? = null,
    currentTextAlign: ReaderTextAlign,
    onHighlightClicked: () -> Unit,
    onAutoScrollChapterEnd: () -> Unit = {},
    activeHighlightPalette: List<HighlightColor>,
    onUpdatePalette: (Int, HighlightColor) -> Unit,
    onInternalLinkClick: (String) -> Unit,
    onWebViewDisposed: (WebView) -> Unit = {},
    activeTextureId: String? = null,
    activeTextureAlpha: Float = 0.55f
) {
    Timber.d(
        "RenderChapterViaWebView for '$chapterTitle', Key: $key, isDarkTheme: $isDarkTheme, initialScrollTarget: $initialScrollTarget"
    )

    var showExternalLinkDialog by remember { mutableStateOf<String?>(null) }
    val context = LocalContext.current
    val density = LocalDensity.current
    var localWebViewRef by remember { mutableStateOf<WebView?>(null) }

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

    val jsToInject = remember(context) { getJsToInject(context) }

    var showPaletteManager by remember { mutableStateOf(false) }
    val latestUserHighlights by rememberUpdatedState(userHighlights)

    val textureBase64 by remember(activeTextureId) {
        mutableStateOf(
            getReaderTextureDataUri(context, activeTextureId)
        )
    }

    val currentOnSnippetForBookmarkReady by rememberUpdatedState(onSnippetForBookmarkReady)
    val currentOnCfiGenerated by rememberUpdatedState(onCfiGenerated)
    val currentOnBookmarkCfiGenerated by rememberUpdatedState(onBookmarkCfiGenerated)
    val currentOnScrollFinished by rememberUpdatedState(onScrollFinished)

    LaunchedEffect(currentFontSize, currentLineHeight) {
        localWebViewRef?.evaluateJavascript(
            "javascript:if(window.getSelection) window.getSelection().removeAllRanges();", null
        )
    }

    val highlightsJson = remember(userHighlights) {
        val jsonArray = org.json.JSONArray()
        userHighlights.forEach { h ->
            val obj = JSONObject()
            obj.put("cfi", h.cfi)
            obj.put("text", h.text)
            obj.put("cssClass", h.color.cssClass)
            jsonArray.put(obj)
        }
        jsonArray.toString()
    }

    if (showExternalLinkDialog != null) {
        val urlToShow = showExternalLinkDialog!!
        AlertDialog(
            onDismissRequest = { showExternalLinkDialog = null },
            title = { Text(stringResource(R.string.dialog_external_link_title)) },
            text = { Text(stringResource(R.string.dialog_external_link_desc, urlToShow)) },
            confirmButton = {
                Row(horizontalArrangement = Arrangement.End) {
                    TextButton(onClick = {
                        val intent = Intent(Intent.ACTION_VIEW, urlToShow.toUri())
                        try {
                            context.startActivity(intent)
                        } catch (e: ActivityNotFoundException) {
                            Timber.e(e, "No activity found to handle intent for URL: $urlToShow")
                            Toast.makeText(context, context.getString(R.string.error_no_browser), Toast.LENGTH_LONG).show()
                        }
                        showExternalLinkDialog = null
                    }) { Text(stringResource(R.string.action_open)) }
                    TextButton(onClick = {
                        val clipboard =
                            context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
                        val clip = ClipData.newPlainText(context.getString(R.string.clip_label_copied_link), urlToShow)
                        clipboard.setPrimaryClip(clip)
                        showExternalLinkDialog = null
                    }) { Text(stringResource(R.string.action_copy)) }
                }
            },
            dismissButton = {
                TextButton(onClick = { showExternalLinkDialog = null }) { Text(stringResource(R.string.action_cancel)) }
            })
    }

    Box(modifier = modifier.fillMaxSize()) {

        LaunchedEffect(isDarkTheme, effectiveBg, effectiveText, textureBase64, activeTextureAlpha) {
            val bgHex = String.format("#%06X", (0xFFFFFF and effectiveBg.toArgb()))
            val textHex = String.format("#%06X", (0xFFFFFF and effectiveText.toArgb()))
            localWebViewRef?.evaluateJavascript("javascript:window.applyReaderTheme($isDarkTheme, '$bgHex', '$textHex', ${textureBase64?.let { "'$it'" } ?: "null"}, ${activeTextureAlpha.coerceIn(0f, 1f)});", null)
        }

        key(
            key,
            currentFontSize,
            currentLineHeight,
            currentParagraphGap,
            currentImageSize,
            currentHorizontalMargin,
            currentVerticalMargin,
            currentFontFamily,
            currentTextAlign
        ) {
            AndroidView(
                factory = { ctx ->
                Timber.d(
                    "InteractiveWebView factory for $chapterTitle (Key: $key), isDarkTheme: $isDarkTheme, initialScroll: $initialScrollTarget"
                )
                @Suppress("unused") val webView = InteractiveWebView(
                    context = ctx,
                    onSingleTap = onTap,
                    onPotentialScroll = onPotentialScroll,
                    onOverScrollTop = onOverScrollTop,
                    onOverScrollBottom = onOverScrollBottom,
                    onReleaseOverScrollTop = onReleaseOverScrollTop,
                    onReleaseOverScrollBottom = onReleaseOverScrollBottom,
                    onShowCustomSelectionMenu = { text, bounds, finishCallback ->
                        if (text.isNotBlank() && !bounds.isEmpty) {
                            customMenuState = CustomMenuState(
                                selectedText = text,
                                selectionBounds = Rect(bounds),
                                finishActionModeCallback = finishCallback,
                                isExistingHighlight = false
                            )
                        } else {
                            customMenuState = null
                            finishCallback()
                        }
                    },
                    onHideCustomSelectionMenu = {
                        if (customMenuState?.isExistingHighlight != true) {
                            customMenuState = null
                        }
                    }).apply {
                    localWebViewRef = this
                    onWebViewInstanceCreated(this)
                    addJavascriptInterface(
                        PageInfoBridge { scrollY, scrollHeight, clientHeight, activeFragmentId ->
                            this.post { onScrollStateUpdate(scrollY, scrollHeight, clientHeight, activeFragmentId) }
                        }, "PageInfoReporter"
                    )
                    addJavascriptInterface(
                        ProgressJsBridge { chunkIndex ->
                            this.post { onTopChunkUpdated(chunkIndex) }
                        }, "ProgressReporter"
                    )
                    addJavascriptInterface(ContentBridge { index ->
                        this.post { onChunkRequested(index) }
                    }, "ContentBridge")

                    addJavascriptInterface(
                        HighlightJsBridge(
                            onCreateCallback = { cfi, text, colorId ->
                                this.post { onHighlightCreated(cfi, text, colorId) }
                            },
                            onClickCallback = { cfi, text, left, top, right, bottom ->
                                this.post {
                                    onHighlightClicked()
                                    localWebViewRef?.evaluateJavascript(
                                        "javascript:if(window.getSelection) window.getSelection().removeAllRanges();",
                                        null
                                    )
                                    onNoteRequested(cfi)
                                }
                            }
                        ), "HighlightBridge"
                    )

                    addJavascriptInterface(
                        AutoScrollJsBridge {
                            this.post { onAutoScrollChapterEnd() }
                        }, "AutoScrollBridge"
                    )

                    webChromeClient = object : WebChromeClient() {
                        override fun onConsoleMessage(consoleMessage: android.webkit.ConsoleMessage?): Boolean {
                            consoleMessage?.let {
                                val message = it.message()
                                when {
                                    message.startsWith("LINK_NAV:") -> {
                                        Timber.tag(TAG_LINK_NAV)
                                            .d("JS -> ${message.substringAfter("LINK_NAV: ")}")
                                    }

                                    message.startsWith("FootnoteDiag:") -> {
                                        Timber.tag("FootnoteDiag")
                                            .d("JS -> ${message.substringAfter("FootnoteDiag: ")}")
                                    }

                                    message.startsWith("TTS_CHAPTER_CHANGE_DIAG:") -> {
                                        Timber.tag("TTS_CHAPTER_CHANGE_DIAG").d("JS -> ${message.substringAfter("TTS_CHAPTER_CHANGE_DIAG: ")}")
                                    }

                                    message.startsWith("BookmarkDiagnosis") -> {
                                        Timber.tag("BookmarkDiagnosis")
                                            .d("JS -> ${message.substringAfter("BookmarkDiagnosis: ")}")
                                    }

                                    message.startsWith("TTS_LIST_DIAG:") -> {
                                        Timber.tag("TTS_LIST_DIAG").d("JS -> ${message.substringAfter("TTS_LIST_DIAG: ")}")
                                    }

                                    message.startsWith("CFI_DIAGNOSIS:") -> {
                                        Timber.d(
                                            "JS -> ${message.substringAfter("CFI_DIAGNOSIS: ")}"
                                        )
                                    }

                                    message.startsWith("ImageDiagnosis") -> {
                                        Timber.d("JS -> $message")
                                    }

                                    message.startsWith("TTS_HIGHLIGHT_DIAGNOSIS:") -> {
                                        Timber.d(
                                            "JS -> ${message.substringAfter("TTS_HIGHLIGHT_DIAGNOSIS: ")}"
                                        )
                                    }

                                    message.startsWith("PosSaveDiag:") -> {
                                        Timber.tag("PosSaveDiag")
                                            .d("JS -> ${message.substringAfter("PosSaveDiag: ")}")
                                    }

                                    message.startsWith("HIGHLIGHT_DEBUG:") -> {
                                        Timber.d(
                                            "JS -> ${message.substringAfter("HIGHLIGHT_DEBUG: ")}"
                                        )
                                    }

                                    message.startsWith("ReaderFontDiagnosis") -> {
                                        Timber.d(
                                            "JS -> ${message.substringAfter("ReaderFontDiagnosis: ")}"
                                        )
                                    }

                                    message.startsWith("NavDiag:") -> {
                                        Timber.tag("NavDiag")
                                            .d("JS -> ${message.substringAfter("NavDiag: ")}")
                                    }

                                    message.startsWith("AutoScrollDiagnosis") -> {
                                        Timber.d(
                                            "JS -> ${message.substringAfter("AutoScrollDiagnosis: ")}"
                                        )
                                    }

                                    message.startsWith("FRAG_NAV_DEBUG") -> {
                                        Timber.tag("FRAG_NAV_DEBUG")
                                            .d("JS -> ${message.substringAfter("FRAG_NAV_DEBUG: ")}")
                                    }

                                    else -> {
                                        Timber.d(
                                            "[${it.sourceId()}:${it.lineNumber()}] ${it.message()}"
                                        )
                                    }
                                }
                            }
                            return true
                        }
                    }
                    addJavascriptInterface(
                        CfiJsBridge(
                            onCfiReady = { cfi -> this.post { currentOnCfiGenerated(cfi) } },
                            onCfiForBookmarkReady = { cfi -> this.post { currentOnBookmarkCfiGenerated(cfi) } },
                            onScrollFinishedCallback = { success ->
                                this.post { currentOnScrollFinished(success) }
                            }
                        ), "CfiBridge"
                    )

                    addJavascriptInterface(
                        SnippetJsBridge { cfi, snippet ->
                            this.post { currentOnSnippetForBookmarkReady(cfi, snippet) }
                        }, "SnippetBridge"
                    )
                    addJavascriptInterface(TtsJsBridge(ttsScope, onTtsTextReady), "TtsBridge")
                    addJavascriptInterface(
                        AiJsBridge(ttsScope, onContentReadyForSummarization), "AiBridge"
                    )

                    addJavascriptInterface(
                        FootnoteJsBridge { html ->
                            this.post { onFootnoteRequested(html) }
                        }, "FootnoteBridge"
                    )

                    addJavascriptInterface(
                        LinkNavJsBridge(chapterTitle) { href ->
                            this.post { onInternalLinkClick(href) }
                        }, "LinkNavBridge"
                    )

                    webViewClient = object : WebViewClient() {
                        override fun shouldOverrideUrlLoading(
                            view: WebView?, request: WebResourceRequest?
                        ): Boolean {
                            val url = request?.url?.toString()
                            if (url != null && (url.startsWith("http://") || url.startsWith("https://"))) {
                                Timber.tag(TAG_LINK_NAV)
                                    .d("[EXTERNAL-INTERCEPT] url='$url' from chapter '$chapterTitle'")
                                showExternalLinkDialog = url
                                return true
                            }
                            if (url != null && url.startsWith("file://")) {
                                Timber.tag(TAG_LINK_NAV)
                                    .d("[INTERNAL-LINK-INTERCEPTED] url='$url' from chapter '$chapterTitle'")
                                onInternalLinkClick(url)
                                return true
                            }
                            if (url != null) {
                                Timber.tag(TAG_LINK_NAV)
                                    .d("[INTERNAL-LINK-PASSED] url='$url' from chapter '$chapterTitle' — allowing WebView to handle")
                            }
                            return false
                        }

                        override fun onLoadResource(view: WebView?, url: String?) {
                            super.onLoadResource(view, url)
                            if (url?.contains(".jpg", true) == true || url?.contains(
                                    ".jpeg",
                                    true
                                ) == true || url?.contains(
                                    ".png",
                                    true
                                ) == true || url?.contains(
                                    ".gif",
                                    true
                                ) == true || url?.contains(
                                    ".svg",
                                    true
                                ) == true || url?.contains("image", true) == true
                            ) {
                                Timber.d(
                                    "WebView is attempting to load resource: $url"
                                )
                            }
                        }

                        override fun onPageFinished(view: WebView?, url: String?) {
                            super.onPageFinished(view, url)
                            Timber.d(
                                "onPageFinished. Injecting CSS and Font: ${currentFontFamily.fontFamilyName}"
                            )

                            view?.evaluateJavascript(jsToInject, null)

                            val bgHex = String.format("#%06X", (0xFFFFFF and effectiveBg.toArgb()))
                            val textHex =
                                String.format("#%06X", (0xFFFFFF and effectiveText.toArgb()))
                            view?.evaluateJavascript("javascript:window.applyReaderTheme($isDarkTheme, '$bgHex', '$textHex', ${textureBase64?.let { "'$it'" } ?: "null"}, ${activeTextureAlpha.coerceIn(0f, 1f)});",
                                null)

                            val fragmentsJson = org.json.JSONArray(tocFragments).toString()
                            Timber.tag("FRAG_NAV_DEBUG")
                                .d("onPageFinished: Re-injecting TOC_FRAGMENTS: $fragmentsJson")
                            view?.evaluateJavascript(
                                "javascript:window.TOC_FRAGMENTS = $fragmentsJson;",
                                null
                            )

                            view?.evaluateJavascript(
                                "javascript:setTimeout(window.auditTocFragments, 500);",
                                null
                            )

                            view?.evaluateJavascript(
                                """
                                    javascript:(function() {
                                        if (window.__readerInternalLinkBridgeInstalled) return;
                                        window.__readerInternalLinkBridgeInstalled = true;
                                        document.addEventListener('click', function(event) {
                                            var target = event.target;
                                            var anchor = target && target.closest ? target.closest('a[href]') : null;
                                            if (!anchor && target && target.parentElement && target.parentElement.closest) {
                                                anchor = target.parentElement.closest('a[href]');
                                            }
                                            if (!anchor) return;
                                            var rawHref = anchor.getAttribute('href') || '';
                                            if (!rawHref) return;
                                            if (/^(https?:|mailto:|tel:|javascript:)/i.test(rawHref)) return;
                                            if (/^\/\//.test(rawHref)) return;
                                            event.preventDefault();
                                            var resolvedHref = anchor.href || rawHref;
                                            if (window.LinkNavBridge && window.LinkNavBridge.onLinkClicked) {
                                                window.LinkNavBridge.onLinkClicked(
                                                    resolvedHref,
                                                    anchor.getAttribute('epub:type') || '',
                                                    anchor.textContent || ''
                                                );
                                            }
                                        }, true);
                                    })();
                                """.trimIndent(),
                                null
                            )

                            view?.evaluateJavascript(
                                "javascript:window.HighlightBridgeHelper.restoreHighlights('${
                                    escapeJsString(
                                        highlightsJson
                                    )
                                }');", null
                            )

                            val fontCss = getFontCssInjection().replace("\n", " ")
                            val customFontCss = if (customFontPath != null) {
                                "@font-face { font-family: 'CustomFont'; src: url('file://$customFontPath'); }"
                            } else ""
                            val combinedCss = "$fontCss $customFontCss"

                            val injectFontJs =
                                "var style = document.createElement('style'); style.id='injectedFonts'; style.innerHTML = \"$combinedCss\"; document.head.appendChild(style);"
                            view?.evaluateJavascript("javascript:$injectFontJs") {
                                Timber.d("CSS Injection result: $it")
                            }

                            val fontNameForJs = if (customFontPath != null) {
                                "CustomFont"
                            } else if (currentFontFamily == ReaderFont.ORIGINAL) {
                                ""
                            } else {
                                currentFontFamily.fontFamilyName
                            }

                            view?.evaluateJavascript(
                                "javascript:window.updateReaderStyles($currentFontSize, $currentLineHeight, '$fontNameForJs', '${currentTextAlign.cssValue}', $currentParagraphGap, $currentImageSize, $currentHorizontalMargin, $currentVerticalMargin);",
                                null
                            )

                            view?.evaluateJavascript(
                                "javascript:window.checkImagesForDiagnosis();", null
                            )

                            view?.evaluateJavascript(
                                "javascript:window.virtualization.init($initialChunkIndex, $totalChunks);",
                                null
                            )

                            @Suppress("VariableNeverRead") var scrollActionTaken = false

                            if (!initialCfi.isNullOrBlank()) {
                                val cfiJsCommand = "javascript:window.scrollToCfi('$initialCfi');"
                                Timber.tag("NavDiag").d("WebView onPageFinished: Triggering scroll to initialCfi: $initialCfi")
                                view?.evaluateJavascript(cfiJsCommand) {
                                    onChapterInitiallyScrolled()
                                    scrollActionTaken = true
                                }
                            } else if (!initialFragmentId.isNullOrBlank()) {
                                Timber.tag("NavDiag").d("WebView onPageFinished: Scrolling to Element ID: $initialFragmentId")
                                val js = """
                                    (function() {
                                        var targetId = '$initialFragmentId';
                                        var el = document.getElementById(targetId) || document.querySelector('[name="' + targetId + '"]');
                                        if (el) {
                                            var targetScrollY = window.scrollY + el.getBoundingClientRect().top - (window.VIEWPORT_PADDING_TOP + 10);
                                            window.scrollTo({ top: targetScrollY, behavior: 'auto' });
                                            return -2;
                                        }
                                        if (window.virtualization && window.virtualization.chunksData) {
                                            for (var i = 0; i < window.virtualization.chunksData.length; i++) {
                                                var chunkHtml = window.virtualization.chunksData[i];
                                                if (chunkHtml && (chunkHtml.indexOf('id="' + targetId + '"') !== -1 || chunkHtml.indexOf('name="' + targetId + '"') !== -1 || chunkHtml.indexOf("id='" + targetId + "'") !== -1 || chunkHtml.indexOf("name='" + targetId + "'") !== -1)) {
                                                    return i;
                                                }
                                            }
                                        }
                                        return -1;
                                    })()
                                """.trimIndent()

                                view?.evaluateJavascript(js) { result ->
                                    val chunkIdx = result?.toIntOrNull() ?: -1
                                    if (chunkIdx >= 0) {
                                        for (i in 0..chunkIdx) {
                                            onChunkRequested(i)
                                        }
                                        val scrollJs = """
                                            (function() {
                                                var chunkIndex = $chunkIdx;
                                                var fragmentId = '$initialFragmentId';
                                                setTimeout(function() {
                                                    var chunkDiv = document.querySelector('.chunk-container[data-chunk-index="' + chunkIndex + '"]');
                                                    if (chunkDiv && chunkDiv.innerHTML === "" && window.virtualization && window.virtualization.chunksData[chunkIndex]) {
                                                        chunkDiv.innerHTML = window.virtualization.chunksData[chunkIndex];
                                                        chunkDiv.style.height = "";
                                                    }
                                                    setTimeout(function() {
                                                        var el = document.getElementById(fragmentId) || document.querySelector('[name="' + fragmentId + '"]');
                                                        if (el) {
                                                            var targetScrollY = window.scrollY + el.getBoundingClientRect().top - (window.VIEWPORT_PADDING_TOP + 10);
                                                            window.scrollTo({ top: targetScrollY, behavior: 'auto' });
                                                        } else if (chunkDiv) {
                                                            var targetScrollY = window.scrollY + chunkDiv.getBoundingClientRect().top - window.VIEWPORT_PADDING_TOP;
                                                            window.scrollTo({ top: targetScrollY, behavior: 'auto' });
                                                        }
                                                    }, 50);
                                                }, 200);
                                            })()
                                        """.trimIndent()
                                        view.evaluateJavascript(scrollJs, null)
                                    }
                                }
                                onChapterInitiallyScrolled()
                                scrollActionTaken = true
                            } else if (initialScrollTarget != null) {
                                val scrollJsCommand = when (initialScrollTarget) {
                                    ChapterScrollPosition.END -> "javascript:window.scrollToChapterEnd();"
                                    else -> "javascript:window.scrollToChapterStart();"
                                }
                                Timber.tag("NavDiag").d("WebView onPageFinished: Executing initial scroll to target: $initialScrollTarget")
                                view?.evaluateJavascript(scrollJsCommand) {
                                    onChapterInitiallyScrolled()
                                    scrollActionTaken = true
                                }
                            } else if (initialPageScrollY != null && initialPageScrollY > 0) {
                                val scrollJsCommand =
                                    "javascript:window.scrollToSpecificY($initialPageScrollY);"
                                Timber.tag("NavDiag").d("WebView onPageFinished: Executing initial scroll to Y: $initialPageScrollY")
                                view?.evaluateJavascript(scrollJsCommand) {
                                    onChapterInitiallyScrolled()
                                    scrollActionTaken = true
                                }
                            } else {
                                Timber.tag("NavDiag").d("WebView onPageFinished: No specific scroll, defaulting to start.")
                                view?.evaluateJavascript("javascript:window.scrollToChapterStart();") {
                                    onChapterInitiallyScrolled()
                                    scrollActionTaken = true
                                }
                            }

                            view?.clearFocus()
                            view?.evaluateJavascript(
                                "javascript:if(window.getSelection) window.getSelection().removeAllRanges();",
                                null
                            )
                        }
                    }
                    settings.apply {
                        javaScriptEnabled = true
                        allowFileAccess = true
                        allowContentAccess = true
                        domStorageEnabled = true
                        layoutAlgorithm = WebSettings.LayoutAlgorithm.NORMAL
                        setNeedInitialFocus(false)
                        setSupportZoom(false)
                        builtInZoomControls = false
                        displayZoomControls = false
                        useWideViewPort = true
                        loadWithOverviewMode = true
                    }
                    isVerticalScrollBarEnabled = false
                    isHorizontalScrollBarEnabled = false
                    this.setBackgroundColor(Color.TRANSPARENT)
                    Timber.d(
                        "WebView loading initial data with base URL: $baseUrl (Key: $key)"
                    )
                    loadDataWithBaseURL(baseUrl, initialHtmlContent, "text/html", "UTF-8", null)
                }
                webView
            },
            modifier = Modifier.fillMaxSize(),
            onRelease = { releasedWebView ->
                if (localWebViewRef === releasedWebView) {
                    localWebViewRef = null
                }
                customMenuState?.finishActionModeCallback?.invoke()
                customMenuState = null
                onWebViewDisposed(releasedWebView)
                releasedWebView.releaseReaderResources()
            },
            update = { webView ->
                Timber.d("WebView update. Setting Font: ${currentFontFamily.fontFamilyName}")
                localWebViewRef = webView
                onWebViewInstanceCreated(webView)
                val fontCss = getFontCssInjection().replace("\n", " ")
                val customFontCss = if (customFontPath != null) {
                    "@font-face { font-family: 'CustomFont'; src: url('file://$customFontPath'); }"
                } else ""
                val combinedCss = "$fontCss $customFontCss"
                val injectFontJs =
                    "var style = document.getElementById('injectedFonts'); if(!style) { style = document.createElement('style'); style.id='injectedFonts'; document.head.appendChild(style); } style.innerHTML = \"$combinedCss\";"
                webView.evaluateJavascript("javascript:$injectFontJs", null)
                val fontNameForJs = if (customFontPath != null) {
                    "CustomFont"
                } else if (currentFontFamily == ReaderFont.ORIGINAL) {
                    ""
                } else {
                    currentFontFamily.fontFamilyName
                }
                val fragmentsJson = org.json.JSONArray(tocFragments).toString()
                Timber.tag("FRAG_NAV_DEBUG").d("Injecting TOC_FRAGMENTS via setter: $fragmentsJson")

                webView.evaluateJavascript(
                    "javascript:window.setTocFragments($fragmentsJson);",
                    null
                )

                webView.evaluateJavascript(
                    "javascript:window.updateReaderStyles($currentFontSize, $currentLineHeight, '$fontNameForJs', '${currentTextAlign.cssValue}', $currentParagraphGap, $currentImageSize, $currentHorizontalMargin, $currentVerticalMargin);",
                    null
                )

                val escapedHighlights = escapeJsString(highlightsJson)
                webView.evaluateJavascript(
                    "javascript:window.CURRENT_HIGHLIGHTS = '${escapedHighlights}'; window.HighlightBridgeHelper.restoreHighlights(window.CURRENT_HIGHLIGHTS);",
                    null
                )
            }
            )
        }

        // Custom Selection Menu Popup
        customMenuState?.let { state ->
            val popupPositionProvider =
                remember(state.selectionBounds, density, state.isExistingHighlight) {
                    object : PopupPositionProvider {
                        override fun calculatePosition(
                            anchorBounds: IntRect,
                            windowSize: IntSize,
                            layoutDirection: LayoutDirection,
                            popupContentSize: IntSize
                        ): IntOffset {
                            val topMargin = with(density) { 16.dp.toPx() }.toInt()
                            val bottomMargin = with(density) {
                                if (state.isExistingHighlight) 16.dp.toPx() else 60.dp.toPx()
                            }.toInt()

                            var x = state.selectionBounds.centerX() - popupContentSize.width / 2

                            var y = state.selectionBounds.top - popupContentSize.height - topMargin
                            if (y < with(density) { 24.dp.toPx() }.toInt()) {
                                y = state.selectionBounds.bottom + bottomMargin
                            }
                            if (x < 0) x = 0
                            if (x + popupContentSize.width > windowSize.width) {
                                x = windowSize.width - popupContentSize.width
                            }
                            if (y + popupContentSize.height > windowSize.height) {
                                y = windowSize.height - popupContentSize.height
                            }
                            if (y < 0) y = 0

                            return IntOffset(
                                x.coerceIn(0, windowSize.width - popupContentSize.width),
                                y.coerceIn(0, windowSize.height - popupContentSize.height)
                            )
                        }
                    }
                }

            Popup(
                popupPositionProvider = popupPositionProvider, onDismissRequest = {
                    state.finishActionModeCallback()
                    customMenuState = null
                }) {
                Surface(
                    shape = RoundedCornerShape(12.dp),
                    shadowElevation = 6.dp,
                    color = MaterialTheme.colorScheme.surface,
                    border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant)
                ) {
                    Column(
                        modifier = Modifier.width(IntrinsicSize.Max)
                    ) {
                        Row(
                            modifier = Modifier
                                .padding(vertical = 12.dp, horizontal = 12.dp)
                                .fillMaxWidth(),
                            horizontalArrangement = Arrangement.Center,
                            verticalAlignment = Alignment.CenterVertically
                        ) {
                            activeHighlightPalette.forEachIndexed { index, colorEnum ->
                                Box(
                                    modifier = Modifier
                                        .padding(horizontal = 6.dp)
                                        .size(32.dp)
                                        .background(colorEnum.color, CircleShape)
                                        .pointerInput(colorEnum) {
                                            detectTapGestures(onTap = {
                                                Timber.d("Kotlin: Color clicked. Existing? ${state.isExistingHighlight}")
                                                if (state.isExistingHighlight && state.cfi != null) {
                                                    localWebViewRef?.evaluateJavascript(
                                                        "javascript:window.HighlightBridgeHelper.updateHighlightStyle('${state.cfi}', '${colorEnum.cssClass}', '${colorEnum.id}');",
                                                        null
                                                    )
                                                } else {
                                                    localWebViewRef?.evaluateJavascript(
                                                        "javascript:window.HighlightBridgeHelper.createUserHighlight('${colorEnum.cssClass}', '${colorEnum.id}');",
                                                        null
                                                    )
                                                }
                                                state.finishActionModeCallback()
                                                localWebViewRef?.clearFocus()
                                                customMenuState = null
                                            }, onLongPress = {
                                                showPaletteManager = true
                                            })
                                        })
                            }

                            Spacer(modifier = Modifier.width(8.dp))
                            SpectrumButton(
                                onClick = { showPaletteManager = true }, size = 32.dp
                            )
                        }

                        HorizontalDivider()
                        Row(
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(horizontal = 8.dp, vertical = 8.dp),
                            horizontalArrangement = Arrangement.SpaceEvenly,
                            verticalAlignment = Alignment.CenterVertically
                        ) {
                            PaginatedTextSelectionMenu(
                                onCopy = {
                                val clipboard =
                                    context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
                                val clip = ClipData.newPlainText(context.getString(R.string.clip_label_copied_text), state.selectedText)
                                clipboard.setPrimaryClip(clip)
                                state.finishActionModeCallback()
                                localWebViewRef?.clearFocus()
                                localWebViewRef?.evaluateJavascript(
                                    "javascript:if(window.getSelection) window.getSelection().removeAllRanges();",
                                    null
                                )
                                customMenuState = null
                            },
                                onSelectAll = null,
                                onDictionary = {
                                    val textToDefine = state.selectedText
                                    if (textToDefine.isNotBlank()) {
                                        onWordSelectedForAiDefinition(textToDefine)
                                    }
                                    customMenuState = null
                                },
                                onTranslate = {
                                    val textToDefine = state.selectedText
                                    if (textToDefine.isNotBlank()) {
                                        onTranslate(textToDefine)
                                    }
                                    customMenuState = null
                                },
                                onSearch = {
                                    val textToDefine = state.selectedText
                                    if (textToDefine.isNotBlank()) {
                                        onSearch(textToDefine)
                                    }
                                    customMenuState = null
                                },
                                onNote = {
                                    if (state.isExistingHighlight && state.cfi != null) {
                                        onNoteRequested(state.cfi)
                                    } else {
                                        onNoteRequested(null)
                                        localWebViewRef?.evaluateJavascript(
                                            "javascript:window.HighlightBridgeHelper.createUserHighlight('${HighlightColor.YELLOW.cssClass}', '${HighlightColor.YELLOW.id}');", null
                                        )
                                    }
                                    state.finishActionModeCallback()
                                    customMenuState = null
                                },
                                onHighlight = null,
                                onTts = {
                                    localWebViewRef?.evaluateJavascript(
                                        "javascript:window.TtsBridgeHelper.extractAndRelayTextFromSelection();",
                                        null
                                    )
                                    state.finishActionModeCallback()
                                    localWebViewRef?.clearFocus()
                                    localWebViewRef?.evaluateJavascript(
                                        "javascript:if(window.getSelection) window.getSelection().removeAllRanges();",
                                        null
                                    )
                                    customMenuState = null
                                },
                                onDelete = if (state.isExistingHighlight && state.cfi != null) {
                                    {
                                        val highlightToDelete = userHighlights.find { h ->
                                            h.cfi == state.cfi || h.cfi.split("|").contains(state.cfi)
                                        }
                                        if (highlightToDelete != null) {
                                            val cssClassToDelete = highlightToDelete.color.cssClass
                                            localWebViewRef?.evaluateJavascript(
                                                "javascript:window.HighlightBridgeHelper.removeHighlightByCfi('${escapeJsString(highlightToDelete.cfi)}', '$cssClassToDelete');", null
                                            )
                                            onHighlightDeleted(highlightToDelete.cfi)
                                        }
                                        state.finishActionModeCallback()
                                        customMenuState = null
                                    }
                                } else null,
                                isProUser = isProUser,
                                isOss = isOss,
                                existingNote = state.note,
                                selectedColor = state.selectedColor
                            )
                        }
                    }
                }
            }
        }
        if (showPaletteManager) {
            PaletteManagerDialog(
                currentPalette = activeHighlightPalette,
                onDismiss = { showPaletteManager = false },
                onSave = { newPalette ->
                    newPalette.forEachIndexed { index, color ->
                        onUpdatePalette(index, color)
                    }
                    showPaletteManager = false
                })
        }
    }
}
