package com.ponzischeme89.memby.data

import android.content.Context
import coil.annotation.ExperimentalCoilApi
import coil.imageLoader
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

/**
 * What Memby is holding of the library's artwork on this television.
 *
 * Two caches, deliberately reported apart: the disk half is the 128MB of posters and
 * backdrops under `cacheDir/media_artwork` and survives a restart, while the memory half is
 * decoded bitmaps and is gone the moment Android reclaims the process. Adding them into one
 * figure would tell a viewer that emptying the cache frees more storage than it does.
 */
data class ImageCacheSize(
    val diskBytes: Long,
    val memoryBytes: Long,
) {
    val totalBytes: Long get() = diskBytes + memoryBytes

    companion object {
        val EMPTY = ImageCacheSize(0L, 0L)
    }
}

/**
 * A size a viewer reads on a television across the room, so it is one number and a unit —
 * never a byte count with six digits in it.
 *
 * Units are binary (a kilobyte is 1024 bytes), because that is what Coil's own budget is
 * measured in and a figure that disagreed with the cache's stated maximum would look wrong.
 * Below a megabyte nothing is worth a decimal point; above it one place is enough to show
 * the number moving.
 */
fun formatCacheSize(bytes: Long): String {
    if (bytes <= 0L) return "0 MB"
    val kb = 1024.0
    val mb = kb * 1024.0
    val gb = mb * 1024.0
    return when {
        bytes < kb -> "$bytes B"
        bytes < mb -> "${Math.round(bytes / kb)} KB"
        bytes < gb -> "${roundToOneDecimal(bytes / mb)} MB"
        else -> "${roundToOneDecimal(bytes / gb)} GB"
    }
}

private fun roundToOneDecimal(value: Double): String {
    val tenths = Math.round(value * 10.0)
    val whole = tenths / 10
    val remainder = tenths % 10
    return if (remainder == 0L) whole.toString() else "$whole.$remainder"
}

/**
 * Measuring and emptying Coil's caches.
 *
 * Both sides are off the main thread: reading the disk cache's size walks its journal, and
 * clearing it deletes up to 128MB of files — either one on the main thread is a settings
 * screen that stops answering the remote.
 */
@OptIn(ExperimentalCoilApi::class)
object ImageCacheMaintenance {

    suspend fun measure(context: Context): ImageCacheSize = withContext(Dispatchers.IO) {
        val loader = context.applicationContext.imageLoader
        ImageCacheSize(
            diskBytes = runCatching { loader.diskCache?.size ?: 0L }.getOrDefault(0L),
            memoryBytes = runCatching { loader.memoryCache?.size?.toLong() ?: 0L }.getOrDefault(0L),
        )
    }

    /**
     * Empties both halves and answers with what is left, which on a working set is nothing.
     * Memory goes first: a bitmap still held there would be re-written to disk by the very
     * next card that asked for it, and the figure reported back would not be zero.
     */
    suspend fun clear(context: Context): ImageCacheSize = withContext(Dispatchers.IO) {
        val loader = context.applicationContext.imageLoader
        runCatching { loader.memoryCache?.clear() }
        runCatching { loader.diskCache?.clear() }
        measure(context)
    }
}
