package com.syncflow.ui.browser import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.syncflow.data.providers.ProviderFactory import com.syncflow.data.repository.AccountRepository import com.syncflow.domain.model.RemoteFile import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject data class BrowserState( val accountId: Long = -1L, val currentPath: String = "/", val pathStack: List = listOf("/"), val entries: List = emptyList(), val isLoading: Boolean = false, val error: String? = null, ) @HiltViewModel class RemoteBrowserViewModel @Inject constructor( private val accountRepository: AccountRepository, private val providerFactory: ProviderFactory, ) : ViewModel() { private val _state = MutableStateFlow(BrowserState()) val state = _state.asStateFlow() private var loadJob: Job? = null fun init(accountId: Long, startPath: String = "/") { loadJob?.cancel() _state.value = BrowserState(accountId = accountId, currentPath = startPath, pathStack = listOf(startPath), isLoading = true) loadJob = loadPath(accountId, startPath) } fun navigateTo(path: String) { val accountId = _state.value.accountId loadJob?.cancel() _state.update { s -> s.copy(currentPath = path, pathStack = s.pathStack + path, isLoading = true, entries = emptyList(), error = null) } loadJob = loadPath(accountId, path) } fun navigateUp(): Boolean { val stack = _state.value.pathStack if (stack.size <= 1) return false val newStack = stack.dropLast(1) val parent = newStack.last() loadJob?.cancel() _state.update { it.copy(currentPath = parent, pathStack = newStack, isLoading = true, entries = emptyList(), error = null) } loadJob = loadPath(_state.value.accountId, parent) return true } fun navigateToBreadcrumb(path: String) { val stack = _state.value.pathStack val idx = stack.lastIndexOf(path) val newStack = if (idx >= 0) stack.take(idx + 1) else listOf(path) loadJob?.cancel() _state.update { it.copy(currentPath = path, pathStack = newStack, isLoading = true, entries = emptyList(), error = null) } loadJob = loadPath(_state.value.accountId, path) } fun createFolder(name: String) { val s = _state.value val newPath = if (s.currentPath.trimEnd('/') == "") "/$name" else "${s.currentPath.trimEnd('/')}/$name" viewModelScope.launch { val account = accountRepository.getAccount(s.accountId) ?: return@launch val provider = runCatching { providerFactory.create(account) }.getOrElse { return@launch } provider.createDirectory(newPath) .onSuccess { retry() } .onFailure { e -> _state.update { it.copy(error = "Could not create folder: ${e.message}") } } } } fun retry() { val s = _state.value if (s.accountId == -1L) return loadJob?.cancel() _state.update { it.copy(isLoading = true, error = null) } loadJob = loadPath(s.accountId, s.currentPath) } private fun loadPath(accountId: Long, path: String): Job = viewModelScope.launch { val account = accountRepository.getAccount(accountId) if (account == null) { _state.update { it.copy(isLoading = false, error = "Account not found") } return@launch } val provider = runCatching { providerFactory.create(account) }.getOrElse { e -> _state.update { it.copy(isLoading = false, error = e.message ?: "Failed to create provider") } return@launch } provider.listFiles(path) .onSuccess { files -> _state.update { it.copy(isLoading = false, entries = files.sortedWith(compareBy({ !it.isDirectory }, { it.name.lowercase() }))) } } .onFailure { e -> _state.update { it.copy(isLoading = false, error = e.message ?: "Failed to list files") } } } }