package com.swordfish.lemuroid.common.coroutines

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import timber.log.Timber

suspend fun <T> Flow<T>.safeCollect(block: suspend (T) -> Unit) {
    this.safeCollect(Timber::e, block)
}

suspend fun <T> Flow<T>.safeCollect(
    onError: suspend (e: Throwable) -> Unit,
    block: suspend (T) -> Unit,
) {
    this.catch { Timber.e(it) }
        .collect {
            try {
                block(it)
            } catch (e: Throwable) {
                onError(e)
            }
        }
}

fun <T> Flow<T>.batchWithTime(maxMillis: Int) = batchWithSizeAndTime(Int.MAX_VALUE, maxMillis)

fun <T> Flow<T>.batchWithSizeAndTime(
    maxSize: Int,
    maxMillis: Int,
): Flow<List<T>> =
    flow {
        val batch = mutableListOf<T>()
        var lastEmission = System.currentTimeMillis()

        collect { value ->
            batch.add(value)
            if (batch.size >= maxSize || System.currentTimeMillis() > lastEmission + maxMillis) {
                emit(batch.toList())
                batch.clear()
                lastEmission = System.currentTimeMillis()
            }
        }

        if (batch.isNotEmpty()) emit(batch)
    }

fun <T1, T2, T3, T4, T5, T6, R> combine(
    flow: Flow<T1>,
    flow2: Flow<T2>,
    flow3: Flow<T3>,
    flow4: Flow<T4>,
    flow5: Flow<T5>,
    flow6: Flow<T6>,
    transform: suspend (T1, T2, T3, T4, T5, T6) -> R,
): Flow<R> =
    kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*> ->
        transform(
            args[0] as T1,
            args[1] as T2,
            args[2] as T3,
            args[3] as T4,
            args[4] as T5,
            args[5] as T6,
        )
    }

fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
    flow: Flow<T1>,
    flow2: Flow<T2>,
    flow3: Flow<T3>,
    flow4: Flow<T4>,
    flow5: Flow<T5>,
    flow6: Flow<T6>,
    flow7: Flow<T7>,
    transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R,
): Flow<R> =
    kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { args: Array<*> ->
        transform(
            args[0] as T1,
            args[1] as T2,
            args[2] as T3,
            args[3] as T4,
            args[4] as T5,
            args[5] as T6,
            args[6] as T7,
        )
    }
