package dev.inmo.micro_utils.repos

import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doForAllWithCurrentPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import kotlinx.coroutines.flow.Flow

interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
    suspend fun get(k: Key, pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
    suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
    suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
    suspend fun contains(k: Key): Boolean
    suspend fun contains(k: Key, v: Value): Boolean
    suspend fun count(k: Key): Long
    suspend fun count(): Long

    suspend fun getAll(k: Key, reversed: Boolean = false): List<Value> {
        val results = getAllWithNextPaging { get(k, it) }
        return if (reversed) {
            results.reversed()
        } else {
            results
        }
    }

    /**
     * WARNING!!! THIS METHOD PROBABLY IS NOT EFFICIENT, USE WITH CAUTION
     */
    suspend fun getAll(reverseLists: Boolean = false): Map<Key, List<Value>> = mutableMapOf<Key, List<Value>>().also { map ->
        doWithPagination {
            keys(it).also { paginationResult ->
                paginationResult.results.forEach { k ->
                    map[k] = getAll(k, reverseLists)
                }
            }.nextPageIfNotEmpty()
        }
    }
}
typealias ReadKeyValuesRepo<Key,Value> = ReadOneToManyKeyValueRepo<Key, Value>

interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
    val onNewValue: Flow<Pair<Key, Value>>
    val onValueRemoved: Flow<Pair<Key, Value>>
    val onDataCleared: Flow<Key>

    suspend fun add(toAdd: Map<Key, List<Value>>)

    suspend fun remove(toRemove: Map<Key, List<Value>>)

    suspend fun clear(k: Key)
    suspend fun clearWithValue(v: Value)

    suspend fun set(toSet: Map<Key, List<Value>>) {
        toSet.keys.forEach { key -> clear(key) }
        add(toSet)
    }
}
typealias WriteKeyValuesRepo<Key,Value> = WriteOneToManyKeyValueRepo<Key, Value>

suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
    keysAndValues: List<Pair<Key, List<Value>>>
) = add(keysAndValues.toMap())

suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
    vararg keysAndValues: Pair<Key, List<Value>>
) = add(keysAndValues.toMap())

suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
    k: Key, v: List<Value>
) = add(mapOf(k to v))

suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
    k: Key, vararg v: Value
) = add(k, v.toList())

suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
    keysAndValues: List<Pair<Key, List<Value>>>
) = set(keysAndValues.toMap())

suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
    vararg keysAndValues: Pair<Key, List<Value>>
) = set(keysAndValues.toMap())

suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
    k: Key, v: List<Value>
) = set(mapOf(k to v))

suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
    k: Key, vararg v: Value
) = set(k, v.toList())

interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value> {
    override suspend fun clearWithValue(v: Value) {
        doWithPagination {
            val keysResult = keys(v, it)

            if (keysResult.results.isNotEmpty()) {
                remove(keysResult.results.map { it to listOf(v) })
            }

            keysResult.currentPageIfNotEmpty()
        }
    }
}
typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>

suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
    keysAndValues: List<Pair<Key, List<Value>>>
) = remove(keysAndValues.toMap())

suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
    vararg keysAndValues: Pair<Key, List<Value>>
) = remove(keysAndValues.toMap())

suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
    k: Key,
    v: List<Value>
) = remove(mapOf(k to v))

suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
    k: Key,
    vararg v: Value
) = remove(k, v.toList())