package com.aletheia.app.ui.reader

import org.json.JSONArray
import org.json.JSONObject

sealed interface ReaderEvent {
    data object ShellReady : ReaderEvent
    data object ToggleControls : ReaderEvent
    data object Navigation : ReaderEvent
    data class BookReady(val totalPages: Int) : ReaderEvent
    data class Progress(
        val progress: Double,
        val locator: String?,
        val chapter: String?,
        val currentPage: Int,
        val totalPages: Int,
        val chapterCurrentPage: Int,
        val chapterTotalPages: Int
    ) : ReaderEvent

    data class Toc(val chapters: List<ReaderTocEntry>) : ReaderEvent
    data class Selection(val snapshot: ReaderSelection?) : ReaderEvent
    data class Search(val total: Int, val currentIndex: Int, val query: String) : ReaderEvent
    data class ExternalLink(val url: String) : ReaderEvent
    data class BrightnessDelta(val delta: Double) : ReaderEvent
    data class PaginationCache(val locations: String) : ReaderEvent
    data class Error(
        val stage: String?,
        val code: String?,
        val message: String,
        val recoverable: Boolean = true
    ) : ReaderEvent

    companion object {
        fun parse(message: String): ReaderEvent? {
            val root = runCatching { JSONObject(message) }.getOrNull() ?: return null
            if (root.optInt("version", 2) != 2) return null
            val payload = root.optJSONObject("payload") ?: JSONObject()
            return when (root.optString("type")) {
                "shellReady" -> ShellReady
                "toggleControls" -> ToggleControls
                "navigation" -> Navigation
                "bookReady" -> BookReady(payload.optInt("totalPages", 1).coerceAtLeast(1))
                "progress" -> Progress(
                    progress = payload.optDouble("progress", 0.0).coerceIn(0.0, 1.0),
                    locator = payload.locatorString(),
                    chapter = payload.optString("chapter").takeIf(String::isNotBlank),
                    currentPage = payload.optInt("currentPage", 1).coerceAtLeast(1),
                    totalPages = payload.optInt("totalPages", 1).coerceAtLeast(1),
                    chapterCurrentPage = payload.optPositiveInt("chapterCurrentPage", "chapterPage"),
                    chapterTotalPages = payload.optPositiveInt("chapterTotalPages", "chapterTotal")
                )
                "toc" -> Toc(
                    parseToc(
                        payload.optJSONArray("items")
                            ?: payload.optJSONArray("chapters")
                            ?: JSONArray()
                    )
                )
                "selection" -> {
                    val text = payload.optString("text").trim()
                    Selection(
                        if (text.isBlank()) null else ReaderSelection(
                            text = text,
                            locator = payload.locatorString(),
                            progress = payload.optDouble("progress", 0.0).coerceIn(0.0, 1.0),
                            currentPage = payload.optInt("currentPage", 1).coerceAtLeast(1),
                            totalPages = payload.optInt("totalPages", 1).coerceAtLeast(1),
                            chapter = payload.optString("chapter").takeIf(String::isNotBlank)
                        )
                    )
                }
                "search" -> Search(
                    total = payload.optInt("total", 0).coerceAtLeast(0),
                    currentIndex = payload.optInt("currentIndex", -1),
                    query = payload.optString("query")
                )
                "externalLink" -> payload.optString("url").takeIf(String::isNotBlank)?.let(::ExternalLink)
                "brightnessDelta" -> BrightnessDelta(payload.optDouble("delta", 0.0))
                "paginationCache" -> payload.optString("locations")
                    .takeIf(String::isNotBlank)
                    ?.let(::PaginationCache)
                "error" -> Error(
                    stage = payload.optString("stage").takeIf(String::isNotBlank),
                    code = payload.optString("code").takeIf(String::isNotBlank),
                    message = payload.optString("message", "Не удалось открыть книгу"),
                    recoverable = payload.optBoolean("recoverable", true)
                )
                else -> null
            }
        }

        private fun parseToc(array: JSONArray): List<ReaderTocEntry> = buildList {
            for (index in 0 until array.length()) {
                val item = array.optJSONObject(index) ?: continue
                val href = item.optString("href")
                val label = item.optString("label").trim()
                if (href.isNotBlank() && label.isNotBlank()) {
                    add(
                        ReaderTocEntry(
                            label = label,
                            href = href,
                            depth = item.optInt("depth", 0).coerceAtLeast(0),
                            page = item.optInt("page", 0).coerceAtLeast(0)
                        )
                    )
                }
            }
        }

        private fun JSONObject.locatorString(): String? = when (val locator = opt("locator")) {
            is JSONObject -> locator.toString()
            is String -> locator.takeIf(String::isNotBlank)
            else -> null
        }

        private fun JSONObject.optPositiveInt(primaryName: String, fallbackName: String): Int {
            val primary = optInt(primaryName, 0)
            val value = if (primary > 0) primary else optInt(fallbackName, 1)
            return value.coerceAtLeast(1)
        }
    }
}

data class ReaderTocEntry(
    val label: String,
    val href: String,
    val depth: Int = 0,
    val page: Int = 0
)

data class ReaderSelection(
    val text: String,
    val locator: String?,
    val progress: Double,
    val currentPage: Int,
    val totalPages: Int,
    val chapter: String?
)
