package net.sergeych.toread

import androidx.compose.ui.graphics.ImageBitmap
import kotlinx.browser.window
import net.sergeych.toread.fb2.Fb2Binary
import net.sergeych.toread.storage.BookReadingStatus
import org.w3c.dom.events.Event

actual fun loadDefaultBookBytes(): ByteArray? = null

actual fun decodeBookImage(binary: Fb2Binary): ImageBitmap? = null

actual fun decodeImageBytes(bytes: ByteArray): ImageBitmap? = null

actual suspend fun copyImageToClipboard(bytes: ByteArray, mimeType: String, label: String): Boolean = false

actual fun defaultLibraryScanPath(): String? = null

actual suspend fun loadPlatformOpenBookRequest(): PlatformOpenBookRequest? = null

actual suspend fun chooseLibraryScanDirectory(): String? = null

actual suspend fun loadLibraryItems(): List<LibraryItem> = emptyList()

actual suspend fun loadLibraryItemsPage(limit: Int, offset: Int): List<LibraryItem> = emptyList()

actual suspend fun loadRecentlyAddedLibraryItems(sinceImportedAt: Long, limit: Int): List<LibraryItem> = emptyList()

actual suspend fun searchLibraryItems(query: String, limit: Int): List<LibraryItem> = emptyList()

actual fun currentLibraryTimeMillis(): Long = 0L

actual suspend fun loadLibraryItem(fileId: String): LibraryItem? = null

actual suspend fun loadLibraryItemCover(fileId: String): LibraryCover? = null

actual suspend fun scanLibrarySubtree(
    path: String,
    onProgress: (LibraryScanProgress) -> Unit,
): LibraryScanReport =
    LibraryScanReport(scannedFiles = 0, importedFiles = 0, skippedFiles = 0, failedFiles = 1)

actual suspend fun openLibraryBook(fileId: String): ByteArray? = null

actual suspend fun refreshLibraryItemFromParsedBook(fileId: String, book: net.sergeych.toread.fb2.Fb2Book): LibraryItem? = null

actual suspend fun rescanAllLibraryBooks(): LibraryRescanReport =
    LibraryRescanReport(scannedFiles = 0, updatedFiles = 0, failedFiles = 0)

actual suspend fun deleteLibraryItem(fileId: String): Boolean = false

actual suspend fun loadLibraryReadingPosition(fileId: String): ReadingPosition? = null

actual suspend fun saveLibraryReadingPosition(fileId: String, position: ReadingPosition) = Unit

actual suspend fun clearLibraryReadingPosition(fileId: String) = Unit

actual suspend fun markLibraryReadingStatus(fileId: String, status: BookReadingStatus): Boolean = false

actual suspend fun markLibraryFavorite(fileId: String, favorite: Boolean): Boolean = false

actual suspend fun shareLibraryBookFile(fileId: String): Boolean = false

actual suspend fun viewLibraryBookFile(folder: String, fileName: String): Boolean = false

actual fun openPlatformUrl(url: String): Boolean =
    window.open(url, target = "_blank") != null

actual suspend fun loadBookInfoExtras(fileId: String): BookInfoExtras = BookInfoExtras()

actual suspend fun loadActiveReadingFileId(): String? = null

actual suspend fun saveActiveReadingFileId(fileId: String?) = Unit

actual suspend fun loadThemeMode(): ThemeMode = ThemeMode.SYSTEM

actual suspend fun saveThemeMode(mode: ThemeMode) = Unit

actual suspend fun loadReaderFontSettings(): ReaderFontSettings =
    readerFontSettingsFromStorageValue(window.localStorage.getItem(ReaderFontSettingsStorageKey))

actual suspend fun saveReaderFontSettings(settings: ReaderFontSettings) {
    window.localStorage.setItem(ReaderFontSettingsStorageKey, settings.toStorageValue())
}

actual suspend fun loadReaderFullScreen(): Boolean =
    window.localStorage.getItem(ReaderFullScreenStorageKey)?.toBooleanStrictOrNull() ?: false

actual suspend fun saveReaderFullScreen(fullScreen: Boolean) {
    window.localStorage.setItem(ReaderFullScreenStorageKey, fullScreen.toString())
}

actual suspend fun loadScanDownloadsAutomatically(): Boolean = true

actual suspend fun saveScanDownloadsAutomatically(enabled: Boolean) = Unit

internal actual suspend fun loadLibraryFilter(): LibraryFilter =
    when (val saved = window.localStorage.getItem(LibraryFilterStorageKey)) {
        null -> {
            warnLibraryFilterFallback("library filter was not saved")
            LibraryFilter.MyLibrary
        }
        else -> runCatching { LibraryFilter.valueOf(saved) }
            .getOrElse {
                warnLibraryFilterFallback("invalid saved library filter '$saved'")
                LibraryFilter.MyLibrary
            }
    }

internal actual suspend fun saveLibraryFilter(filter: LibraryFilter) {
    window.localStorage.setItem(LibraryFilterStorageKey, filter.name)
}

actual suspend fun loadAppLocaleTag(): String? =
    window.localStorage.getItem(AppLocaleStorageKey)?.takeIf { it.isNotBlank() }

actual suspend fun saveAppLocaleTag(localeTag: String?) {
    if (localeTag.isNullOrBlank()) {
        window.localStorage.removeItem(AppLocaleStorageKey)
    } else {
        window.localStorage.setItem(AppLocaleStorageKey, localeTag)
    }
}

actual suspend fun loadDownloadsWasScanned(): Boolean = false

actual suspend fun saveDownloadsWasScanned(scanned: Boolean) = Unit

actual fun downloadsScanPath(): String? = null

actual fun isDownloadsScanPath(path: String): Boolean = false

actual fun isPlatformDarkTheme(): Boolean =
    window.matchMedia("(prefers-color-scheme: dark)").matches

actual fun watchPlatformDarkTheme(onChange: (Boolean) -> Unit): () -> Unit {
    val query = window.matchMedia("(prefers-color-scheme: dark)")
    val listener: (Event) -> Unit = {
        onChange(query.matches)
    }
    query.addEventListener("change", listener)
    return { query.removeEventListener("change", listener) }
}

actual fun libraryLogPath(): String? = null

private fun warnLibraryFilterFallback(reason: String) {
    println("warning $reason; using ${LibraryFilter.MyLibrary.name}")
}

private const val AppLocaleStorageKey = "toread.appLocaleTag"
private const val LibraryFilterStorageKey = "toread.libraryFilter"
private const val ReaderFontSettingsStorageKey = "toread.readerFontSettings"
private const val ReaderFullScreenStorageKey = "toread.readerFullScreen"

actual fun formatLibraryLastReadTime(millis: Long): String {
    val totalMinutes = millis / 60_000L
    val minute = (totalMinutes % 60).toString().padStart(2, '0')
    val totalHours = totalMinutes / 60
    val hour = (totalHours % 24).toString().padStart(2, '0')
    val (yearValue, monthValue, dayValue) = civilDateFromEpochDay(totalHours / 24)
    val year = yearValue.toString()
    val month = monthValue.toString().padStart(2, '0')
    val day = dayValue.toString().padStart(2, '0')
    return "$year-$month-$day $hour:$minute"
}

private fun civilDateFromEpochDay(epochDay: Long): Triple<Int, Int, Int> {
    val shiftedDay = epochDay + 719_468
    val era = shiftedDay / 146_097
    val dayOfEra = shiftedDay - era * 146_097
    val yearOfEra = (dayOfEra - dayOfEra / 1_460 + dayOfEra / 36_524 - dayOfEra / 146_096) / 365
    var year = (yearOfEra + era * 400).toInt()
    val dayOfYear = dayOfEra - (365 * yearOfEra + yearOfEra / 4 - yearOfEra / 100)
    val monthPart = (5 * dayOfYear + 2) / 153
    val day = (dayOfYear - (153 * monthPart + 2) / 5 + 1).toInt()
    val month = (monthPart + if (monthPart < 10) 3 else -9).toInt()
    if (month <= 2) year += 1
    return Triple(year, month, day)
}
