package net.sergeych.toread

import net.sergeych.toread.fb2.Fb2Book
import net.sergeych.toread.fb2.Fb2Format
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

internal sealed interface AppState {
    data object LoadingStartup : AppState
    data class Library(
        val items: List<LibraryItem>,
        val scanPath: String,
        val message: String? = null,
        val selectedFilter: LibraryFilter = LibraryFilter.MyLibrary,
    ) : AppState

    data class Scan(
        val items: List<LibraryItem>,
        val scanPath: String,
        val message: String? = null,
        val selectedFilter: LibraryFilter = LibraryFilter.MyLibrary,
    ) : AppState

    data class Reader(
        val fileId: String,
        val book: Fb2Book,
        val libraryItems: List<LibraryItem>,
        val scanPath: String,
        val message: String? = null,
        val selectedFilter: LibraryFilter = LibraryFilter.MyLibrary,
    ) : AppState

    data class BookInfo(
        val fileId: String,
        val book: Fb2Book,
        val libraryItems: List<LibraryItem>,
        val scanPath: String,
        val message: String? = null,
        val selectedFilter: LibraryFilter = LibraryFilter.MyLibrary,
    ) : AppState

    data class Error(val message: String) : AppState
}

internal suspend fun loadStartupState(): AppState {
    val scanPath = try {
        defaultLibraryScanPath().orEmpty()
    } catch (t: Throwable) {
        return AppState.Error(t.message ?: strings.couldNotOpenLibrary)
    }
    val libraryFilter = loadLibraryFilter()
    val platformRequest = runCatching { loadPlatformOpenBookRequest() }.getOrElse {
        return AppState.Library(emptyList(), scanPath, it.message ?: strings.couldNotOpenLibrary, libraryFilter)
    }
    platformRequest?.let { request ->
        return runCatching {
            val book = parseBookInBackground(request.bytes, request.displayName)
            saveActiveReadingFileId(request.id)
            AppState.Reader(
                fileId = request.id,
                book = book,
                libraryItems = emptyList(),
                scanPath = scanPath,
                selectedFilter = libraryFilter,
            )
    }.getOrElse {
            AppState.Library(emptyList(), scanPath, it.message ?: strings.couldNotOpen(request.displayName), libraryFilter)
        }
    }
    val activeFileId = loadActiveReadingFileId() ?: return AppState.Library(emptyList(), scanPath, selectedFilter = libraryFilter)
    val item = loadLibraryItem(activeFileId)
    if (item == null) {
        saveActiveReadingFileId(null)
        return AppState.Library(emptyList(), scanPath, selectedFilter = libraryFilter)
    }
    return runCatching {
        val bytes = openLibraryBook(activeFileId) ?: error(strings.bookFileNotAvailable)
        AppState.Reader(
            fileId = activeFileId,
            book = parseBookInBackground(bytes, item.storageUri ?: item.title),
            libraryItems = emptyList(),
            scanPath = scanPath,
            selectedFilter = libraryFilter,
        )
    }.getOrElse {
        saveActiveReadingFileId(null)
        AppState.Library(emptyList(), scanPath, it.message ?: strings.couldNotReopenLastBook(), libraryFilter)
    }
}

internal suspend fun loadLibraryState(
    message: String? = null,
    scanPath: String? = null,
    selectedFilter: LibraryFilter? = null,
): AppState =
    runCatching {
        AppState.Library(
            items = emptyList(),
            scanPath = scanPath ?: defaultLibraryScanPath().orEmpty(),
            message = message,
            selectedFilter = selectedFilter ?: loadLibraryFilter(),
        )
    }.getOrElse {
        AppState.Error(it.message ?: strings.couldNotOpenLibrary)
    }

internal suspend fun parseBookInBackground(input: ByteArray, fileName: String? = null): Fb2Book =
    withContext(Dispatchers.Default) {
        Fb2Format.parse(input, fileName)
    }
