// UniversalDocument.kt
package com.aryan.reader.pdf

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.BitmapRegionDecoder
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import android.net.Uri
import android.os.Build
import com.aryan.reader.FileType
import com.aryan.reader.R
import com.aryan.reader.pptx.PptxDocumentWrapper
import io.legere.pdfiumandroid.api.Bookmark
import io.legere.pdfiumandroid.suspend.PdfDocumentKt
import io.legere.pdfiumandroid.suspend.PdfPageKt
import io.legere.pdfiumandroid.suspend.PdfTextPageKt
import io.legere.pdfiumandroid.suspend.PdfiumCoreKt
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import me.zhanghai.android.libarchive.Archive
import me.zhanghai.android.libarchive.ArchiveEntry
import me.zhanghai.android.libarchive.ArchiveException
import okhttp3.Request
import timber.log.Timber
import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
import java.util.zip.ZipFile
import androidx.core.graphics.createBitmap

interface ReaderDocument : AutoCloseable {
    suspend fun getPageCount(): Int
    suspend fun openPage(pageIndex: Int): ReaderPage?
    suspend fun getTableOfContents(): List<Bookmark>
}

interface ReaderPage : AutoCloseable {
    suspend fun getPageWidthPoint(): Int
    suspend fun getPageHeightPoint(): Int
    suspend fun getPageRotation(): Int
    suspend fun renderPageBitmap(bitmap: Bitmap, startX: Int, startY: Int, drawSizeX: Int, drawSizeY: Int, renderAnnot: Boolean)
    suspend fun mapRectToDevice(startX: Int, startY: Int, sizeX: Int, sizeY: Int, rotate: Int, coords: RectF): Rect
    suspend fun mapDeviceCoordsToPage(startX: Int, startY: Int, sizeX: Int, sizeY: Int, rotate: Int, deviceX: Int, deviceY: Int): PointF
    suspend fun openTextPage(): ReaderTextPage
    suspend fun getLinks(): List<ReaderLink>
    fun getNativePointer(): Long
}

interface ReaderTextPage : AutoCloseable {
    suspend fun textPageCountChars(): Int
    suspend fun textPageGetText(startIndex: Int, count: Int): String?
    suspend fun textPageGetRectsForRanges(ranges: IntArray): List<ReaderTextRect>?
    suspend fun textPageGetCharIndexAtPos(x: Double, y: Double, xTolerance: Double, yTolerance: Double): Int
    suspend fun textPageGetCharBox(index: Int): RectF?
    suspend fun textPageGetUnicode(index: Int): Int
    suspend fun loadWebLink(): ReaderWebLinks?
}

data class ReaderLink(val uri: String?, val destPageIdx: Int?, val bounds: RectF)
data class ReaderTextRect(val rect: RectF)

interface ReaderWebLinks : AutoCloseable {
    suspend fun countWebLinks(): Int
    suspend fun getURL(linkIndex: Int, maxLength: Int): String?
    suspend fun countRects(linkIndex: Int): Int
    suspend fun getRect(linkIndex: Int, rectIndex: Int): RectF
}

object DocumentFactory {
    suspend fun loadDocument(context: Context, uri: Uri, type: FileType, password: String?, pdfiumCore: PdfiumCoreKt): ReaderDocument {
        if (uri.scheme == "opds-pse") {
            val bookId = uri.getQueryParameter("id") ?: UUID.randomUUID().toString()
            val urlTemplate = uri.getQueryParameter("url") ?: ""
            val count = uri.getQueryParameter("count")?.toIntOrNull() ?: 0
            val catalogId = uri.getQueryParameter("catalogId")
            return OpdsStreamDocumentWrapper(context, bookId, urlTemplate, count, catalogId)
        }
        return if (type == FileType.PPTX) {
            val cacheFile = File(context.cacheDir, "temp_pptx_${System.currentTimeMillis()}.pptx")
            try {
                withContext(Dispatchers.IO) {
                    context.contentResolver.openInputStream(uri)?.use { input ->
                        cacheFile.outputStream().use { output -> input.copyTo(output) }
                    } ?: throw Exception("Failed to open PPTX")
                }
            } catch (e: Exception) {
                runCatching { cacheFile.delete() }
                throw e
            }
            PptxDocumentWrapper(cacheFile, deleteOnClose = true)
        } else if (type == FileType.CBZ || type == FileType.CBR || type == FileType.CB7) {
            val cacheFile = File(context.cacheDir, "temp_comic_${System.currentTimeMillis()}.${type.name.lowercase()}")
            withContext(Dispatchers.IO) {
                context.contentResolver.openInputStream(uri)?.use { input ->
                    cacheFile.outputStream().use { output -> input.copyTo(output) }
                }
            }
            ArchiveDocumentWrapper(cacheFile)
        } else {
            val pfd = context.contentResolver.openFileDescriptor(uri, "r") ?: throw Exception("Failed to open PDF")
            PdfDocumentWrapper(PdfiumEngineProvider.withPdfium { pdfiumCore.newDocument(pfd, password) })
        }
    }
}

// ================= PDF IMPLEMENTATION =================

private inline fun closePdfiumResource(tag: String, closeBlock: () -> Unit) {
    try {
        closeBlock()
    } catch (e: IllegalStateException) {
        if (e.message == "Already closed") {
            Timber.tag(tag).d(e, "Ignoring duplicate Pdfium close")
        } else {
            throw e
        }
    }
}

class PdfDocumentWrapper(val pdfDocument: PdfDocumentKt) : ReaderDocument {
    private val isClosed = AtomicBoolean(false)

    override suspend fun getPageCount() = PdfiumEngineProvider.withPdfium {
        pdfDocument.getPageCount()
    }

    override suspend fun openPage(pageIndex: Int): ReaderPage? {
        if (isClosed.get()) return null
        val page = PdfiumEngineProvider.withPdfium {
            if (isClosed.get()) null else pdfDocument.openPage(pageIndex)
        } ?: return null
        return PdfPageWrapper(page)
    }

    override suspend fun getTableOfContents() = PdfiumEngineProvider.withPdfium {
        pdfDocument.getFixedTableOfContents()
    }

    override fun close() {
        if (!isClosed.compareAndSet(false, true)) return
        PdfiumEngineProvider.withPdfiumBlocking {
            closePdfiumResource("PdfDocumentWrapper") { pdfDocument.close() }
        }
    }
}

class PdfPageWrapper(val pdfPage: PdfPageKt) : ReaderPage {
    private val isClosed = AtomicBoolean(false)

    override suspend fun getPageWidthPoint() = PdfiumEngineProvider.withPdfium {
        if (isClosed.get()) 0 else pdfPage.getPageWidthPoint()
    }

    override suspend fun getPageHeightPoint() = PdfiumEngineProvider.withPdfium {
        if (isClosed.get()) 0 else pdfPage.getPageHeightPoint()
    }

    override suspend fun getPageRotation() = PdfiumEngineProvider.withPdfium {
        if (isClosed.get()) 0 else pdfPage.getPageRotation()
    }

    override suspend fun renderPageBitmap(bitmap: Bitmap, startX: Int, startY: Int, drawSizeX: Int, drawSizeY: Int, renderAnnot: Boolean) {
        PdfiumEngineProvider.withPdfium {
            if (!isClosed.get()) {
                pdfPage.renderPageBitmap(bitmap, startX, startY, drawSizeX, drawSizeY, renderAnnot)
            }
        }
    }

    override suspend fun mapRectToDevice(startX: Int, startY: Int, sizeX: Int, sizeY: Int, rotate: Int, coords: RectF) =
        PdfiumEngineProvider.withPdfium {
            if (isClosed.get()) Rect() else pdfPage.mapRectToDevice(startX, startY, sizeX, sizeY, rotate, coords)
        }

    override suspend fun mapDeviceCoordsToPage(startX: Int, startY: Int, sizeX: Int, sizeY: Int, rotate: Int, deviceX: Int, deviceY: Int) =
        PdfiumEngineProvider.withPdfium {
            if (isClosed.get()) PointF() else pdfPage.mapDeviceCoordsToPage(startX, startY, sizeX, sizeY, rotate, deviceX, deviceY)
        }

    override suspend fun openTextPage(): ReaderTextPage = PdfiumEngineProvider.withPdfium {
        if (isClosed.get()) DummyTextPage() else PdfTextPageWrapper(pdfPage.openTextPage())
    }

    override suspend fun getLinks(): List<ReaderLink> {
        return PdfiumEngineProvider.withPdfium {
            if (isClosed.get()) {
                emptyList()
            } else {
                pdfPage.getPageLinks().map { ReaderLink(it.uri, it.destPageIdx, it.bounds) }
            }
        }
    }

    override fun getNativePointer(): Long {
        return extractNativePointer(pdfPage)
    }

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

        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 = extractNativePointer(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
    }

    override fun close() {
        if (!isClosed.compareAndSet(false, true)) return
        PdfiumEngineProvider.withPdfiumBlocking {
            closePdfiumResource("PdfPageWrapper") { pdfPage.close() }
        }
    }
}

class PdfTextPageWrapper(private val textPage: PdfTextPageKt) : ReaderTextPage {
    private val isClosed = AtomicBoolean(false)

    override suspend fun textPageCountChars() = PdfiumEngineProvider.withPdfium {
        if (isClosed.get()) 0 else textPage.textPageCountChars()
    }

    override suspend fun textPageGetText(startIndex: Int, count: Int) = PdfiumEngineProvider.withPdfium {
        if (isClosed.get()) null else textPage.textPageGetText(startIndex, count)
    }

    override suspend fun textPageGetRectsForRanges(ranges: IntArray) = PdfiumEngineProvider.withPdfium {
        if (isClosed.get()) null else textPage.textPageGetRectsForRanges(ranges)?.map { ReaderTextRect(it.rect) }
    }

    override suspend fun textPageGetCharIndexAtPos(x: Double, y: Double, xTolerance: Double, yTolerance: Double) = PdfiumEngineProvider.withPdfium {
        if (isClosed.get()) -1 else textPage.textPageGetCharIndexAtPos(x, y, xTolerance, yTolerance)
    }

    override suspend fun textPageGetCharBox(index: Int) = PdfiumEngineProvider.withPdfium {
        if (isClosed.get()) null else textPage.textPageGetCharBox(index)
    }

    override suspend fun textPageGetUnicode(index: Int): Int {
        return PdfiumEngineProvider.withPdfium {
            if (isClosed.get()) 0 else textPage.textPageGetUnicode(index).code
        }
    }

    override suspend fun loadWebLink(): ReaderWebLinks? {
        val links = PdfiumEngineProvider.withPdfium {
            if (isClosed.get()) null else textPage.loadWebLink()
        } ?: return null
        return object : ReaderWebLinks {
            private val isClosed = AtomicBoolean(false)

            override suspend fun countWebLinks() = PdfiumEngineProvider.withPdfium {
                if (isClosed.get()) 0 else links.countWebLinks()
            }

            override suspend fun getURL(linkIndex: Int, maxLength: Int) = PdfiumEngineProvider.withPdfium {
                if (isClosed.get()) null else links.getURL(linkIndex, maxLength)
            }

            override suspend fun countRects(linkIndex: Int) = PdfiumEngineProvider.withPdfium {
                if (isClosed.get()) 0 else links.countRects(linkIndex)
            }

            override suspend fun getRect(linkIndex: Int, rectIndex: Int) = PdfiumEngineProvider.withPdfium {
                if (isClosed.get()) RectF() else links.getRect(linkIndex, rectIndex)
            }

            override fun close() {
                if (!isClosed.compareAndSet(false, true)) return
                PdfiumEngineProvider.withPdfiumBlocking {
                    closePdfiumResource("PdfWebLinksWrapper") { links.close() }
                }
            }
        }
    }
    override fun close() {
        if (!isClosed.compareAndSet(false, true)) return
        PdfiumEngineProvider.withPdfiumBlocking {
            closePdfiumResource("PdfTextPageWrapper") { textPage.close() }
        }
    }
}

// ================= CBZ, CBR, CB7 IMPLEMENTATION =================

class DummyTextPage : ReaderTextPage {
    override suspend fun textPageCountChars() = 0
    override suspend fun textPageGetText(startIndex: Int, count: Int) = null
    override suspend fun textPageGetRectsForRanges(ranges: IntArray) = null
    override suspend fun textPageGetCharIndexAtPos(x: Double, y: Double, xTolerance: Double, yTolerance: Double) = -1
    override suspend fun textPageGetCharBox(index: Int) = null
    override suspend fun textPageGetUnicode(index: Int) = 0
    override suspend fun loadWebLink() = null
    override fun close() {}
}

class ArchiveDocumentWrapper(private val file: File) : ReaderDocument {
    private val imageEntries = mutableListOf<String>()
    private var zipFile: ZipFile? = null
    private var extractedDir: File? = null

    init {
        // Try reading as ZIP first for instant O(1) random access (Handles .cbz efficiently)
        try {
            val zf = ZipFile(file)
            val entries = zf.entries()
            while (entries.hasMoreElements()) {
                val entry = entries.nextElement()
                if (!entry.isDirectory && entry.name.matches(Regex(".*\\.(jpg|jpeg|png|webp|bmp)$", RegexOption.IGNORE_CASE))) {
                    imageEntries.add(entry.name)
                }
            }
            if (imageEntries.isNotEmpty()) {
                zipFile = zf
                imageEntries.sort()
            } else {
                zf.close()
            }
        } catch (_: Exception) {
            zipFile = null
        }

        if (zipFile == null) {
            imageEntries.clear()
            extractedDir = File(file.parentFile, "extracted_${file.name}_${System.currentTimeMillis()}")
            extractedDir?.mkdirs()

            var archive = 0L
            try {
                archive = Archive.readNew()
                Archive.readSupportFilterAll(archive)
                Archive.readSupportFormatAll(archive)
                Archive.readOpenFileName(archive, file.absolutePath.toByteArray(), 10240)

                val tempEntries = mutableListOf<Pair<String, File>>()

                while (true) {
                    val entry = try {
                        Archive.readNextHeader(archive)
                    } catch (e: ArchiveException) {
                        if (e.code == Archive.ERRNO_EOF) break
                        throw e
                    }
                    if (entry == 0L) break

                    val path = ArchiveEntry.pathnameUtf8(entry)
                    if (path != null && path.matches(Regex(".*\\.(jpg|jpeg|png|webp|bmp)$", RegexOption.IGNORE_CASE))) {
                        val extractedFile = File(extractedDir, UUID.randomUUID().toString() + ".img")
                        tempEntries.add(Pair(path, extractedFile))

                        var pfd: android.os.ParcelFileDescriptor? = null
                        @Suppress("ConvertTryFinallyToUseCall") try {
                            pfd = android.os.ParcelFileDescriptor.open(extractedFile, android.os.ParcelFileDescriptor.MODE_READ_WRITE or android.os.ParcelFileDescriptor.MODE_CREATE)
                            Archive.readDataIntoFd(archive, pfd.fd)
                        } finally {
                            pfd?.close()
                        }
                    } else {
                        Archive.readDataSkip(archive)
                    }
                }

                tempEntries.sortBy { it.first }
                tempEntries.forEach { imageEntries.add(it.second.absolutePath) }

            } catch (e: Exception) {
                Timber.e(e, "Failed to extract archive entries")
            } finally {
                if (archive != 0L) Archive.readFree(archive)
            }
        }
    }

    override suspend fun getPageCount() = imageEntries.size

    override suspend fun openPage(pageIndex: Int): ReaderPage? = withContext(Dispatchers.IO) {
        if (pageIndex !in imageEntries.indices) return@withContext null
        val targetPath = imageEntries[pageIndex]

        var imageBytes: ByteArray? = null

        if (zipFile != null) {
            try {
                val entry = zipFile!!.getEntry(targetPath)
                if (entry != null) {
                    zipFile!!.getInputStream(entry).use { imageBytes = it.readBytes() }
                }
            } catch (e: Exception) {
                Timber.e(e, "Failed to extract page from ZIP")
            }
        } else {
            try {
                val extractedFile = File(targetPath)
                if (extractedFile.exists()) {
                    imageBytes = extractedFile.readBytes()
                }
            } catch (e: Exception) {
                Timber.e(e, "Failed to read extracted page")
            }
        }

        if (imageBytes != null && imageBytes!!.isNotEmpty()) ArchivePageWrapper(imageBytes!!) else null
    }

    override suspend fun getTableOfContents() = emptyList<Bookmark>()

    override fun close() {
        try { zipFile?.close() } catch (_: Exception) {}
        try { extractedDir?.deleteRecursively() } catch (_: Exception) {}
        try { file.delete() } catch (_: Exception) {}
    }
}

class ArchivePageWrapper(imageBytes: ByteArray) : ReaderPage {
    private val decoder = try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            BitmapRegionDecoder.newInstance(imageBytes, 0, imageBytes.size)
        } else {
            @Suppress("DEPRECATION")
            BitmapRegionDecoder.newInstance(imageBytes, 0, imageBytes.size, false)
        }
    } catch (_: Exception) {
        null
    }

    private val originalWidth = decoder?.width ?: 1
    private val originalHeight = decoder?.height ?: 1

    override suspend fun getPageWidthPoint() = originalWidth
    override suspend fun getPageHeightPoint() = originalHeight
    override suspend fun getPageRotation() = 0

    override suspend fun renderPageBitmap(bitmap: Bitmap, startX: Int, startY: Int, drawSizeX: Int, drawSizeY: Int, renderAnnot: Boolean) {
        if (decoder == null || decoder.isRecycled) return
        val scaleX = drawSizeX.toFloat() / originalWidth
        val scaleY = drawSizeY.toFloat() / originalHeight

        val pageOffsetX = -startX.toFloat()
        val pageOffsetY = -startY.toFloat()

        val exactSrcLeft = pageOffsetX / scaleX
        val exactSrcTop = pageOffsetY / scaleY
        val exactSrcRight = (pageOffsetX + bitmap.width) / scaleX
        val exactSrcBottom = (pageOffsetY + bitmap.height) / scaleY

        val srcLeft = kotlin.math.floor(exactSrcLeft).toInt().coerceAtLeast(0)
        val srcTop = kotlin.math.floor(exactSrcTop).toInt().coerceAtLeast(0)
        val srcRight = kotlin.math.ceil(exactSrcRight).toInt().coerceAtMost(originalWidth)
        val srcBottom = kotlin.math.ceil(exactSrcBottom).toInt().coerceAtMost(originalHeight)

        val rect = Rect(srcLeft, srcTop, srcRight, srcBottom)
        if (rect.width() <= 0 || rect.height() <= 0) return

        val options = BitmapFactory.Options().apply {
            inPreferredConfig = Bitmap.Config.ARGB_8888
            inScaled = false
            @Suppress("DEPRECATION")
            inDither = true

            var sampleSize = 1
            val srcWidth = rect.width()
            val srcHeight = rect.height()

            if (srcHeight > bitmap.height || srcWidth > bitmap.width) {
                val halfHeight = srcHeight / 2
                val halfWidth = srcWidth / 2
                while (halfHeight / sampleSize >= bitmap.height && halfWidth / sampleSize >= bitmap.width) {
                    sampleSize *= 2
                }
            }
            inSampleSize = sampleSize
        }

        val region = try {
            decoder.decodeRegion(rect, options)
        } catch (_: Exception) {
            null
        }

        if (region != null) {
            val canvas = Canvas(bitmap)

            val destLeft = (srcLeft * scaleX) - pageOffsetX
            val destTop = (srcTop * scaleY) - pageOffsetY
            val destRight = (srcRight * scaleX) - pageOffsetX
            val destBottom = (srcBottom * scaleY) - pageOffsetY

            val destRect = RectF(destLeft, destTop, destRight, destBottom)

            val paint = Paint(Paint.FILTER_BITMAP_FLAG or Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG)

            canvas.drawBitmap(region, null, destRect, paint)
            region.recycle()
        }
    }

    override suspend fun mapRectToDevice(startX: Int, startY: Int, sizeX: Int, sizeY: Int, rotate: Int, coords: RectF): Rect {
        val scaleX = sizeX.toFloat() / originalWidth
        val scaleY = sizeY.toFloat() / originalHeight
        return Rect(
            (startX + coords.left * scaleX).toInt(),
            (startY + coords.top * scaleY).toInt(),
            (startX + coords.right * scaleX).toInt(),
            (startY + coords.bottom * scaleY).toInt()
        )
    }

    override suspend fun mapDeviceCoordsToPage(startX: Int, startY: Int, sizeX: Int, sizeY: Int, rotate: Int, deviceX: Int, deviceY: Int): PointF {
        val scaleX = sizeX.toFloat() / originalWidth
        val scaleY = sizeY.toFloat() / originalHeight
        return PointF((deviceX - startX) / scaleX, (deviceY - startY) / scaleY)
    }

    override suspend fun openTextPage() = DummyTextPage()
    override suspend fun getLinks() = emptyList<ReaderLink>()
    override fun getNativePointer() = 0L

    override fun close() {
        decoder?.recycle()
    }
}

class OpdsStreamDocumentWrapper(
    private val context: Context,
    private val bookId: String,
    private val urlTemplate: String,
    private val pageCount: Int,
    private val catalogId: String?
) : ReaderDocument {
    private val cacheDir = File(context.cacheDir, "opds_stream_${bookId.hashCode()}").apply { mkdirs() }

    private val catalog = catalogId?.let {
        com.aryan.reader.opds.OpdsRepository(context).getCatalogs().find { c -> c.id == it }
    }

    private val client = com.aryan.reader.opds.OpdsRepository.sharedHttpClient.newBuilder()
        .apply {
            val streamCatalog = catalog
            val username = streamCatalog?.username
            val password = streamCatalog?.password
            if (!username.isNullOrBlank() && !password.isNullOrBlank()) {
                authenticator(com.aryan.reader.opds.OpdsRepository.OpdsAuthenticator(username, password))
            }
        }
        .build()

    private fun createErrorPageBytes(): ByteArray {
        val bitmap = createBitmap(800, 1200)
        val canvas = Canvas(bitmap)
        canvas.drawColor(android.graphics.Color.DKGRAY)
        val paint = Paint().apply {
            color = android.graphics.Color.WHITE
            textSize = 40f
            textAlign = Paint.Align.CENTER
        }
        canvas.drawText(context.getString(R.string.msg_page_unavailable), 400f, 600f, paint)
        val stream = java.io.ByteArrayOutputStream()
        bitmap.compress(Bitmap.CompressFormat.JPEG, 80, stream)
        return stream.toByteArray()
    }

    override suspend fun getPageCount() = pageCount

    override suspend fun openPage(pageIndex: Int): ReaderPage? = withContext(Dispatchers.IO) {
        if (pageIndex !in 0 until pageCount) return@withContext null

        val cachedFile = File(cacheDir, "page_$pageIndex.jpg")
        if (cachedFile.exists() && cachedFile.length() > 0) {
            try {
                return@withContext ArchivePageWrapper(cachedFile.readBytes())
            } catch (e: Exception) {
                Timber.e(e, "Failed to read cached stream page")
            }
        }

        val streamCatalog = catalog
        val finalUrlTemplate = if (streamCatalog != null && urlTemplate.startsWith("http")) {
            try {
                val oldUrl = java.net.URL(urlTemplate)
                val newUrl = java.net.URL(streamCatalog.url)
                val oldBase = "${oldUrl.protocol}://${oldUrl.authority}"
                val newBase = "${newUrl.protocol}://${newUrl.authority}"
                urlTemplate.replace(oldBase, newBase)
            } catch (_: Exception) {
                urlTemplate
            }
        } else urlTemplate

        val url = finalUrlTemplate.replace("{pageNumber}", pageIndex.toString())
            .replace("{maxWidth}", "1600")

        val request = Request.Builder().url(url).build()
        try {
            val response = client.newCall(request).execute()
            if (response.isSuccessful) {
                val bytes = response.body?.bytes()
                if (bytes != null && bytes.isNotEmpty()) {
                    cachedFile.writeBytes(bytes)
                    return@withContext ArchivePageWrapper(bytes)
                }
            } else {
                Timber.e("Stream page failed with HTTP ${response.code}")
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to fetch stream page $pageIndex")
        }

        return@withContext ArchivePageWrapper(createErrorPageBytes())
    }

    override suspend fun getTableOfContents() = emptyList<Bookmark>()

    override fun close() {}
}
