package ru.forbion.f7cloud.feature.files

import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import ru.forbion.f7cloud.core.auth.AuthSession
import ru.forbion.f7cloud.core.network.UnauthorizedException

class FilesViewModel(
    private val repository: FilesRepository,
    private val downloadRepository: FileDownloadRepository,
    private val apiRepository: FilesApiRepository = FilesApiRepository(),
    private val templatesRepository: FilesTemplatesRepository = FilesTemplatesRepository(),
    private val officeFileOpener: OfficeFileOpener = OfficeFileOpener(),
) : ViewModel() {
    private val _state = MutableStateFlow(FilesUiState())
    val state: StateFlow<FilesUiState> = _state.asStateFlow()
    private var searchJob: Job? = null

    fun load(session: AuthSession, path: String = _state.value.currentPath) {
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(
                loading = true,
                error = null,
                currentPath = path,
                browseMode = FilesBrowseMode.AllFiles,
                selectedPaths = emptySet(),
            )
            runCatching { repository.listFolder(session, path) }
                .onSuccess { items ->
                    _state.value = _state.value.copy(
                        loading = false,
                        items = applySorting(items, _state.value),
                        currentPath = path,
                        error = null,
                    )
                }
                .onFailure { t ->
                    val cached = if (path.isBlank()) {
                        runCatching { repository.listCached(session) }.getOrDefault(emptyList())
                    } else {
                        emptyList()
                    }
                    _state.value = _state.value.copy(
                        loading = false,
                        items = applySorting(cached, _state.value),
                        currentPath = path,
                        error = t.message ?: "Не удалось загрузить файлы",
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun loadSidebarData(session: AuthSession) {
        viewModelScope.launch(Dispatchers.IO) {
            runCatching {
                val config = apiRepository.fetchUserConfig(session)
                val stats = apiRepository.fetchStorageStats(session)
                val tree = if (config.folderTree) {
                    apiRepository.fetchFolderTree(session, depth = 2)
                } else {
                    emptyList()
                }
                _state.value = _state.value.copy(
                    userConfig = config,
                    storageStats = stats,
                    folderTree = tree,
                    items = applySorting(_state.value.items, _state.value.copy(userConfig = config)),
                )
            }
        }
    }

    fun setSearchActive(active: Boolean) {
        _state.value = _state.value.copy(searchActive = active)
        if (!active) {
            _state.value = _state.value.copy(searchQuery = "", searchResults = emptyList())
        }
    }

    fun setSearchQuery(session: AuthSession, query: String) {
        _state.value = _state.value.copy(searchQuery = query)
        searchJob?.cancel()
        if (query.isBlank()) {
            _state.value = _state.value.copy(searchResults = emptyList())
            return
        }
        searchJob = viewModelScope.launch(Dispatchers.IO) {
            delay(300)
            runCatching { apiRepository.searchFiles(session, query) }
                .onSuccess { hits ->
                    if (_state.value.searchQuery == query) {
                        _state.value = _state.value.copy(searchResults = hits)
                    }
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        error = t.message,
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun navigateBrowseMode(session: AuthSession, mode: FilesBrowseMode) {
        when (mode) {
            FilesBrowseMode.AllFiles -> load(session, "")
            FilesBrowseMode.Personal -> load(session, "")
            FilesBrowseMode.Recent -> loadRecent(session)
            FilesBrowseMode.Favorites -> loadFavorites(session)
        }
    }

    private fun loadRecent(session: AuthSession) {
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(loading = true, error = null, selectedPaths = emptySet())
            runCatching { apiRepository.fetchRecentFiles(session) }
                .onSuccess { items ->
                    _state.value = _state.value.copy(
                        loading = false,
                        items = applySorting(items, _state.value),
                        currentPath = "",
                        browseMode = FilesBrowseMode.Recent,
                    )
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        loading = false,
                        error = t.message ?: "Не удалось загрузить недавние файлы",
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    private fun loadFavorites(session: AuthSession) {
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(loading = true, error = null, selectedPaths = emptySet())
            runCatching {
                repository.listFolder(session, "").filter { it.favorite }
            }.onSuccess { items ->
                _state.value = _state.value.copy(
                    loading = false,
                    items = applySorting(items, _state.value),
                    currentPath = "",
                    browseMode = FilesBrowseMode.Favorites,
                )
            }.onFailure { t ->
                _state.value = _state.value.copy(
                    loading = false,
                    error = t.message ?: "Не удалось загрузить избранное",
                    unauthorized = t is UnauthorizedException,
                )
            }
        }
    }

    fun navigateToPath(session: AuthSession, path: String) {
        load(session, path)
    }

    fun toggleSelection(path: String) {
        val selected = _state.value.selectedPaths.toMutableSet()
        if (path in selected) selected.remove(path) else selected.add(path)
        _state.value = _state.value.copy(selectedPaths = selected)
    }

    fun selectAllVisible() {
        val paths = displayItems(_state.value).map { it.relativePath }.toSet()
        _state.value = _state.value.copy(selectedPaths = paths)
    }

    fun clearSelection() {
        _state.value = _state.value.copy(selectedPaths = emptySet())
    }

    fun setSortColumn(column: FilesSortColumn) {
        val current = _state.value
        val direction = if (current.sortColumn == column && current.sortDirection == FilesSortDirection.Asc) {
            FilesSortDirection.Desc
        } else {
            FilesSortDirection.Asc
        }
        _state.value = current.copy(
            sortColumn = column,
            sortDirection = direction,
            items = applySorting(current.items, current.copy(sortColumn = column, sortDirection = direction)),
        )
    }

    fun toggleTreePath(path: String) {
        val expanded = _state.value.expandedTreePaths.toMutableSet()
        if (path in expanded) expanded.remove(path) else expanded.add(path)
        _state.value = _state.value.copy(expandedTreePaths = expanded)
    }

    fun toggleSidebarSection(sectionId: String) {
        val expanded = _state.value.expandedSidebarSections.toMutableSet()
        if (sectionId in expanded) expanded.remove(sectionId) else expanded.add(sectionId)
        _state.value = _state.value.copy(expandedSidebarSections = expanded)
    }

    fun refreshCurrent(session: AuthSession) {
        viewModelScope.launch(Dispatchers.IO) {
            loadSidebarData(session)
            reloadCurrent(session)
        }
    }

    fun showDetails(session: AuthSession, item: FileItem, tab: FileDetailsTab = FileDetailsTab.Sharing) {
        _state.value = _state.value.copy(
            detailsItem = item,
            detailsTab = tab,
            fileDetails = null,
            detailsLoading = true,
        )
        loadFileDetails(session, item)
    }

    fun setDetailsTab(tab: FileDetailsTab) {
        _state.value = _state.value.copy(detailsTab = tab)
    }

    fun hideDetails() {
        _state.value = _state.value.copy(
            detailsItem = null,
            fileDetails = null,
            detailsLoading = false,
        )
    }

    private fun loadFileDetails(session: AuthSession, item: FileItem) {
        viewModelScope.launch(Dispatchers.IO) {
            val details = runCatching {
                FileDetailsData(
                    shares = apiRepository.fetchShares(session, item.relativePath),
                    activities = item.fileId?.let { apiRepository.fetchFileActivity(session, it) }.orEmpty(),
                )
            }.getOrElse { FileDetailsData(error = it.message) }
            if (_state.value.detailsItem?.relativePath == item.relativePath) {
                _state.value = _state.value.copy(
                    fileDetails = details,
                    detailsLoading = false,
                )
            }
        }
    }

    fun showRename(item: FileItem) {
        _state.value = _state.value.copy(renameItem = item)
    }

    fun hideRename() {
        _state.value = _state.value.copy(renameItem = null)
    }

    fun renameItem(session: AuthSession, item: FileItem, newName: String) {
        val trimmed = newName.trim()
        if (trimmed.isBlank() || trimmed == item.name) {
            hideRename()
            return
        }
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(loading = true, renameItem = null)
            runCatching {
                repository.renameItem(session, item.relativePath, trimmed, item.isDirectory)
            }.onSuccess {
                reloadCurrent(session)
            }.onFailure { t ->
                _state.value = _state.value.copy(
                    loading = false,
                    error = t.message ?: "Не удалось переименовать",
                    unauthorized = t is UnauthorizedException,
                )
            }
        }
    }

    fun deleteItem(session: AuthSession, item: FileItem) {
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(loading = true)
            runCatching {
                repository.deleteItem(session, item.relativePath, item.isDirectory)
            }.onSuccess {
                reloadCurrent(session)
            }.onFailure { t ->
                _state.value = _state.value.copy(
                    loading = false,
                    error = t.message ?: "Не удалось удалить",
                    unauthorized = t is UnauthorizedException,
                )
            }
        }
    }

    fun deleteSelected(session: AuthSession) {
        val items = displayItems(_state.value).filter { it.relativePath in _state.value.selectedPaths }
        if (items.isEmpty()) return
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(loading = true)
            runCatching {
                items.forEach { item ->
                    repository.deleteItem(session, item.relativePath, item.isDirectory)
                }
            }.onSuccess {
                reloadCurrent(session)
            }.onFailure { t ->
                _state.value = _state.value.copy(
                    loading = false,
                    error = t.message ?: "Не удалось удалить выбранные элементы",
                    unauthorized = t is UnauthorizedException,
                )
            }
        }
    }

    fun toggleFavorite(session: AuthSession, item: FileItem) {
        viewModelScope.launch(Dispatchers.IO) {
            runCatching {
                repository.setFavorite(session, item.relativePath, !item.favorite, item.isDirectory)
            }.onSuccess {
                reloadCurrent(session)
            }.onFailure { t ->
                _state.value = _state.value.copy(
                    snackbar = t.message ?: "Не удалось изменить избранное",
                    unauthorized = t is UnauthorizedException,
                )
            }
        }
    }

    fun favoriteSelected(session: AuthSession) {
        val items = displayItems(_state.value).filter { it.relativePath in _state.value.selectedPaths }
        viewModelScope.launch(Dispatchers.IO) {
            runCatching {
                items.filter { !it.favorite }.forEach { item ->
                    repository.setFavorite(session, item.relativePath, true, item.isDirectory)
                }
            }.onSuccess {
                reloadCurrent(session)
            }.onFailure { t ->
                _state.value = _state.value.copy(
                    snackbar = t.message ?: "Не удалось добавить в избранное",
                    unauthorized = t is UnauthorizedException,
                )
            }
        }
    }

    fun downloadItem(session: AuthSession, item: FileItem, context: Context) {
        if (item.isDirectory) {
            _state.value = _state.value.copy(snackbar = "Скачивание папок пока доступно в веб-версии")
            return
        }
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(openingFile = item.name)
            runCatching { downloadRepository.download(session, item.relativePath, item.name) }
                .onSuccess { file ->
                    _state.value = _state.value.copy(
                        openingFile = null,
                        openAction = FileOpenAction.External(
                            path = file.absolutePath,
                            mimeType = item.mimeType ?: "application/octet-stream",
                            title = item.name,
                        ),
                    )
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        openingFile = null,
                        error = t.message ?: "Не удалось скачать файл",
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun updateUserConfig(session: AuthSession, key: String, value: String) {
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { apiRepository.saveUserConfig(session, key, value) }
                .onSuccess { loadSidebarData(session) }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        snackbar = t.message ?: "Не удалось сохранить настройку",
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun showWebOnlyMessage(message: String) {
        _state.value = _state.value.copy(snackbar = "$message — доступно в веб-версии")
    }

    fun clearSnackbar() {
        _state.value = _state.value.copy(snackbar = null)
    }

    fun displayItems(state: FilesUiState = _state.value): List<FileItem> {
        if (state.searchQuery.isNotBlank()) {
            return state.searchResults.map { hit ->
                FileItem(
                    name = hit.name,
                    isDirectory = hit.isDirectory,
                    relativePath = hit.path,
                    fileId = hit.fileId,
                )
            }
        }
        return state.items
    }

    private fun reloadCurrent(session: AuthSession) {
        when (_state.value.browseMode) {
            FilesBrowseMode.Recent -> loadRecent(session)
            FilesBrowseMode.Favorites -> loadFavorites(session)
            else -> load(session, _state.value.currentPath)
        }
    }

    private fun applySorting(items: List<FileItem>, state: FilesUiState): List<FileItem> {
        var filtered = items
        if (!state.userConfig.showHidden) {
            filtered = filtered.filter { !it.name.startsWith('.') }
        }
        val direction = state.sortDirection
        val sorted = when (state.sortColumn) {
            FilesSortColumn.Name -> filtered.sortedBy { it.name.lowercase() }
            FilesSortColumn.Type -> filtered.sortedBy { typeKey(it) }
            FilesSortColumn.Modified -> filtered.sortedBy { it.lastModified ?: 0L }
        }
        val withFolders = if (state.userConfig.sortFoldersFirst) {
            sorted.sortedByDescending { it.isDirectory }
        } else {
            sorted
        }
        val withFavorites = if (state.userConfig.sortFavoritesFirst) {
            withFolders.sortedByDescending { it.favorite }
        } else {
            withFolders
        }
        return if (direction == FilesSortDirection.Desc) withFavorites.reversed() else withFavorites
    }

    private fun typeKey(item: FileItem): String = when {
        item.isDirectory -> "0_folder"
        else -> item.mimeType ?: item.name.substringAfterLast('.', "file")
    }

    // --- existing methods below ---

    fun openFileById(session: AuthSession, fileId: Long, title: String = "Файл") {
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(openingFile = title, error = null)
            runCatching { officeFileOpener.prepareLaunch(session, fileId, title) }
                .onSuccess { launch ->
                    _state.value = _state.value.copy(openingFile = null, editorLaunch = launch)
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        openingFile = null,
                        error = t.message ?: "Не удалось открыть файл",
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun openFolder(session: AuthSession, item: FileItem) {
        if (!item.isDirectory) return
        load(session, item.relativePath)
    }

    fun openItem(session: AuthSession, item: FileItem) {
        if (item.isDirectory) return
        when {
            OfficeFiles.isOfficeFile(item.name) -> openOfficeFile(session, item)
            ArchiveFiles.isArchive(item.name) -> openArchiveFile(session, item)
            OpenableFiles.isImage(item.name) -> openImageFile(session, item)
            else -> _state.value = _state.value.copy(
                error = "Этот тип файла пока не поддерживается",
            )
        }
    }

    private fun openOfficeFile(session: AuthSession, item: FileItem) {
        val fileId = item.fileId ?: run {
            _state.value = _state.value.copy(error = "Не удалось определить ID файла")
            return
        }
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(openingFile = item.name, error = null)
            runCatching { officeFileOpener.prepareLaunch(session, fileId, item.name) }
                .onSuccess { launch ->
                    _state.value = _state.value.copy(openingFile = null, editorLaunch = launch)
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        openingFile = null,
                        error = t.message ?: "Не удалось открыть документ",
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    private fun openArchiveFile(session: AuthSession, item: FileItem) {
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(openingFile = item.name, error = null)
            runCatching { downloadRepository.download(session, item.relativePath, item.name) }
                .onSuccess { file ->
                    _state.value = _state.value.copy(
                        openingFile = null,
                        openAction = FileOpenAction.External(
                            path = file.absolutePath,
                            mimeType = ArchiveFiles.mimeType(item.name),
                            title = item.name,
                        ),
                    )
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        openingFile = null,
                        error = t.message ?: "Не удалось скачать архив",
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    private fun openImageFile(session: AuthSession, item: FileItem) {
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(openingFile = item.name, error = null)
            runCatching { downloadRepository.download(session, item.relativePath, item.name) }
                .onSuccess { file ->
                    _state.value = _state.value.copy(
                        openingFile = null,
                        openAction = FileOpenAction.Image(file.absolutePath, item.name),
                    )
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        openingFile = null,
                        error = t.message ?: "Не удалось открыть файл",
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun clearEditorLaunch() {
        _state.value = _state.value.copy(editorLaunch = null)
    }

    fun clearOpenAction() {
        _state.value = _state.value.copy(openAction = null)
    }

    fun uploadFromUris(context: Context, session: AuthSession, uris: List<Uri>) {
        if (uris.isEmpty()) return
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(loading = true, error = null)
            val path = _state.value.currentPath
            runCatching {
                uris.forEach { uri ->
                    val (name, bytes, mime) = readUriPayload(context, uri)
                    repository.uploadFile(session, path, name, bytes, mime)
                }
            }.onSuccess {
                load(session, path)
            }.onFailure { t ->
                _state.value = _state.value.copy(
                    loading = false,
                    error = t.message ?: "Не удалось загрузить файлы",
                    unauthorized = t is UnauthorizedException,
                )
            }
        }
    }

    fun uploadFolderTree(context: Context, session: AuthSession, treeUri: Uri) {
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(loading = true, error = null)
            val basePath = _state.value.currentPath
            runCatching {
                val root = DocumentFile.fromTreeUri(context, treeUri)
                    ?: error("Не удалось открыть папку")
                uploadDocumentNode(context, session, root, basePath, preserveRoot = true)
            }.onSuccess {
                load(session, basePath)
            }.onFailure { t ->
                _state.value = _state.value.copy(
                    loading = false,
                    error = t.message ?: "Не удалось загрузить папку",
                    unauthorized = t is UnauthorizedException,
                )
            }
        }
    }

    private suspend fun uploadDocumentNode(
        context: Context,
        session: AuthSession,
        node: DocumentFile,
        parentPath: String,
        preserveRoot: Boolean = false,
    ) {
        if (node.isDirectory) {
            val folderName = node.name?.takeIf { it.isNotBlank() } ?: return
            val nextPath = if (preserveRoot) {
                parentPath
            } else {
                val built = repository.buildRelativePath(parentPath, folderName)
                repository.ensureFolder(session, built)
                built
            }
            node.listFiles().forEach { child ->
                uploadDocumentNode(context, session, child, nextPath)
            }
            return
        }
        val name = node.name?.takeIf { it.isNotBlank() } ?: return
        val uri = node.uri
        val (resolvedName, bytes, mime) = readUriPayload(context, uri, fallbackName = name)
        repository.uploadFile(session, parentPath, resolvedName, bytes, mime)
    }

    fun createFolder(session: AuthSession, folderName: String) {
        val trimmed = folderName.trim()
        if (trimmed.isBlank()) {
            _state.value = _state.value.copy(error = "Введите имя папки")
            return
        }
        viewModelScope.launch(Dispatchers.IO) {
            val path = _state.value.currentPath
            _state.value = _state.value.copy(loading = true, error = null)
            runCatching {
                repository.createFolder(session, path, trimmed)
            }.onSuccess {
                load(session, path)
            }.onFailure { t ->
                _state.value = _state.value.copy(
                    loading = false,
                    error = t.message ?: "Не удалось создать папку",
                    unauthorized = t is UnauthorizedException,
                )
            }
        }
    }

    fun handleCreateAction(session: AuthSession, action: FilesCreateAction) {
        when (action) {
            FilesCreateAction.UploadFiles,
            FilesCreateAction.UploadFolders,
            FilesCreateAction.NewFolder,
            -> Unit
            FilesCreateAction.FileRequest -> {
                _state.value = _state.value.copy(
                    error = "Запрос на создание файла пока доступен в веб-версии",
                )
            }
            FilesCreateAction.NewDiagram -> createTemplateFile(session, "Новая диаграмма", ".odg", openAfterCreate = true)
            FilesCreateAction.NewBoard -> createTemplateFile(session, "Новая доска", ".whiteboard", openAfterCreate = false)
            FilesCreateAction.NewPresentation -> createTemplateFile(session, "Новая презентация", ".pptx", openAfterCreate = true)
            FilesCreateAction.NewSpreadsheet -> createTemplateFile(session, "Новая таблица", ".xlsx", openAfterCreate = true)
            FilesCreateAction.NewDocument -> createTemplateFile(session, "Новый документ", ".docx", openAfterCreate = true)
            FilesCreateAction.NewTextFile -> createTemplateFile(session, "Новый текстовый файл", ".txt", openAfterCreate = false)
            FilesCreateAction.TemplateFolder -> initializeTemplateFolder(session)
            FilesCreateAction.FolderDescription -> openFolderDescription(session)
        }
    }

    private fun createTemplateFile(
        session: AuthSession,
        baseName: String,
        extension: String,
        openAfterCreate: Boolean,
    ) {
        viewModelScope.launch(Dispatchers.IO) {
            val folderPath = _state.value.currentPath
            val existing = _state.value.items.map { it.name }.toSet()
            val fileName = repository.uniqueName(existing, baseName, extension)
            val relativeFilePath = repository.buildRelativePath(folderPath, fileName)
            _state.value = _state.value.copy(loading = true, error = null, openingFile = fileName)
            runCatching {
                templatesRepository.createFromTemplate(session, relativeFilePath)
            }.onSuccess { created ->
                load(session, folderPath)
                if (openAfterCreate && created.fileId > 0L) {
                    runCatching { officeFileOpener.prepareLaunch(session, created.fileId, created.name) }
                        .onSuccess { launch ->
                            _state.value = _state.value.copy(openingFile = null, editorLaunch = launch)
                        }
                        .onFailure { t ->
                            _state.value = _state.value.copy(
                                openingFile = null,
                                error = t.message ?: "Файл создан, но не удалось открыть редактор",
                            )
                        }
                } else {
                    _state.value = _state.value.copy(openingFile = null)
                }
            }.onFailure { t ->
                _state.value = _state.value.copy(
                    loading = false,
                    openingFile = null,
                    error = t.message ?: "Не удалось создать файл",
                    unauthorized = t is UnauthorizedException,
                )
            }
        }
    }

    private fun initializeTemplateFolder(session: AuthSession) {
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(loading = true, error = null)
            runCatching {
                templatesRepository.initializeTemplateDirectory(session)
            }.onSuccess {
                load(session, _state.value.currentPath)
            }.onFailure { t ->
                _state.value = _state.value.copy(
                    loading = false,
                    error = t.message ?: "Не удалось создать папку шаблонов",
                    unauthorized = t is UnauthorizedException,
                )
            }
        }
    }

    private fun openFolderDescription(session: AuthSession) {
        val folderPath = _state.value.currentPath
        if (folderPath.isBlank()) {
            _state.value = _state.value.copy(error = "Откройте папку, чтобы добавить описание")
            return
        }
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(openingFile = "Описание папки", error = null)
            runCatching {
                val url = templatesRepository.openFolderDescription(session, folderPath)
                val collaboraBaseUrl = OfficeWarmup.getCachedCollaboraUrl(session.serverUrl)
                    .ifBlank { RichdocumentsRepository().fetchCollaboraPublicUrl(session) }
                OfficeEditorLaunch(
                    url = url,
                    title = "Описание папки",
                    username = session.username,
                    password = session.appPassword,
                    trustAllCerts = session.trustAllCerts,
                    serverUrl = session.serverUrl,
                    collaboraBaseUrl = collaboraBaseUrl,
                )
            }.onSuccess { launch ->
                _state.value = _state.value.copy(openingFile = null, editorLaunch = launch)
            }.onFailure { t ->
                _state.value = _state.value.copy(
                    openingFile = null,
                    error = t.message ?: "Не удалось открыть описание папки",
                    unauthorized = t is UnauthorizedException,
                )
            }
        }
    }

    private fun readUriPayload(
        context: Context,
        uri: Uri,
        fallbackName: String = "upload.bin",
    ): Triple<String, ByteArray, String?> {
        val resolver = context.contentResolver
        var name = fallbackName
        resolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor ->
            if (cursor.moveToFirst()) {
                val idx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
                if (idx >= 0) {
                    name = cursor.getString(idx)?.takeIf { it.isNotBlank() } ?: name
                }
            }
        }
        val mime = resolver.getType(uri)
        val bytes = resolver.openInputStream(uri)?.use { it.readBytes() }
            ?: error("Не удалось прочитать файл")
        return Triple(name, bytes, mime)
    }

    fun goUp(session: AuthSession) {
        val path = _state.value.currentPath.trim('/')
        if (path.isEmpty()) return
        val parent = path.substringBeforeLast('/', missingDelimiterValue = "")
        load(session, parent)
    }

    class Factory(private val context: Context) : ViewModelProvider.Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            val appContext = context.applicationContext
            return FilesViewModel(
                FilesRepository(appContext),
                FileDownloadRepository(appContext),
            ) as T
        }
    }
}
