package com.github.nullptroma.wallenc.app.sync

import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncPaths
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
import com.github.nullptroma.wallenc.domain.tasks.StorageSyncTriggerReason
import com.github.nullptroma.wallenc.usecases.RunStorageSyncUseCase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
@OptIn(FlowPreview::class)
class StorageSyncBootstrap @Inject constructor(
    private val scheduler: StorageSyncScheduler,
    private val vaultsManager: IVaultsManager,
    private val syncRunner: RunStorageSyncUseCase,
) {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

    fun start() {
        scheduler.ensureScheduled()
        scope.launch {
            combine(
                vaultsManager.allStorages,
                vaultsManager.unlockManager.openedStorages,
            ) { rootStorages, opened ->
                (rootStorages + opened.values).distinctBy { it.uuid }
            }.collectLatest { storages ->
                if (storages.isEmpty()) {
                    return@collectLatest
                }
                val triggers = storages.flatMap { storage ->
                    listOf(
                        storage.accessor.filesUpdates
                            .filter { page ->
                                page.data.any { file ->
                                    StorageSyncPaths.isSyncableUserPath(file.metaInfo.path)
                                }
                            }
                            .map {},
                        storage.accessor.dirsUpdates
                            .filter { page ->
                                page.data.any { dir ->
                                    StorageSyncPaths.isSyncableUserPath(dir.metaInfo.path)
                                }
                            }
                            .map {},
                    )
                }
                merge(*triggers.toTypedArray())
                    .filter { shouldScheduleDebounceSync() }
                    .debounce(RunStorageSyncUseCase.DEBOUNCE_AFTER_CHANGE_MS)
                    .collect {
                        syncRunner.enqueue(StorageSyncTriggerReason.Debounce)
                    }
            }
        }
    }

    /**
     * Игнорировать события во время sync и сразу после него: запись файлов при sync
     * (в т.ч. через [EncryptedStorageAccessor]) иначе снова запускает debounce через 60 с.
     */
    private fun shouldScheduleDebounceSync(): Boolean {
        if (syncRunner.syncRunning.value) {
            return false
        }
        return System.currentTimeMillis() >= syncRunner.debounceSuppressUntilMs.value
    }
}
