/**
 * qsfera Android client application
 *
 * @author Fernando Sanz Velasco
 * @author Jose Antonio Barros Ramos
 * @author Juan Carlos Garrote Gascón
 * @author Manuel Plazas Palacio
 * @author Jorge Aguado Recio
 * @author Aitor Ballesteros Pavón
 *
 * Copyright (C) 2024 ownCloud GmbH.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package eu.qsfera.android.presentation.files.filelist

import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import coil.load
import com.bumptech.glide.Glide
import com.getbase.floatingactionbutton.AddFloatingActionButton
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import eu.qsfera.android.R
import eu.qsfera.android.databinding.MainFileListFragmentBinding

import eu.qsfera.android.domain.appregistry.model.AppRegistryMimeType
import eu.qsfera.android.domain.exceptions.InstanceNotConfiguredException
import eu.qsfera.android.domain.exceptions.TooEarlyException
import eu.qsfera.android.domain.files.model.FileListOption
import eu.qsfera.android.domain.files.model.FileMenuOption
import eu.qsfera.android.domain.files.model.OCFile
import eu.qsfera.android.domain.files.model.OCFile.Companion.ROOT_PATH
import eu.qsfera.android.domain.files.model.OCFileSyncInfo
import eu.qsfera.android.domain.files.model.OCFileWithSyncInfo
import eu.qsfera.android.domain.spaces.model.OCSpace
import eu.qsfera.android.domain.transfers.model.OCTransfer
import eu.qsfera.android.domain.transfers.model.TransferStatus
import eu.qsfera.android.domain.utils.Event
import eu.qsfera.android.extensions.addOpenInWebMenuOptions
import eu.qsfera.android.extensions.collectLatestLifecycleFlow
import eu.qsfera.android.extensions.filterMenuOptions
import eu.qsfera.android.extensions.parseError
import eu.qsfera.android.extensions.sendDownloadedFilesByShareSheet
import eu.qsfera.android.extensions.showErrorInSnackbar
import eu.qsfera.android.extensions.showMessageInSnackbar
import eu.qsfera.android.extensions.toDrawableRes
import eu.qsfera.android.extensions.toDrawableResId
import eu.qsfera.android.extensions.toResId
import eu.qsfera.android.extensions.toStringResId
import eu.qsfera.android.extensions.toSubtitleStringRes
import eu.qsfera.android.extensions.toTitleStringRes
import eu.qsfera.android.presentation.authentication.AccountUtils
import eu.qsfera.android.presentation.capabilities.CapabilityViewModel
import eu.qsfera.android.presentation.common.BottomSheetFragmentItemView
import eu.qsfera.android.presentation.common.UIResult
import eu.qsfera.android.presentation.files.SortBottomSheetFragment
import eu.qsfera.android.presentation.files.SortBottomSheetFragment.Companion.newInstance
import eu.qsfera.android.presentation.files.SortBottomSheetFragment.SortDialogListener
import eu.qsfera.android.presentation.files.SortOptionsView
import eu.qsfera.android.presentation.files.SortOrder
import eu.qsfera.android.presentation.files.SortType
import eu.qsfera.android.presentation.files.ViewType
import eu.qsfera.android.presentation.files.createfolder.CreateFolderDialogFragment
import eu.qsfera.android.presentation.files.createshortcut.CreateShortcutDialogFragment
import eu.qsfera.android.presentation.files.operations.FileOperation
import eu.qsfera.android.presentation.files.operations.FileOperationsViewModel
import eu.qsfera.android.presentation.files.removefile.RemoveFilesDialogFragment
import eu.qsfera.android.presentation.files.removefile.RemoveFilesDialogFragment.Companion.TAG_REMOVE_FILES_DIALOG_FRAGMENT
import eu.qsfera.android.presentation.files.renamefile.RenameFileDialogFragment
import eu.qsfera.android.presentation.files.renamefile.RenameFileDialogFragment.Companion.FRAGMENT_TAG_RENAME_FILE
import eu.qsfera.android.presentation.spaces.SpacesListViewModel
import eu.qsfera.android.presentation.thumbnails.ThumbnailsRequester
import eu.qsfera.android.presentation.transfers.TransfersViewModel
import eu.qsfera.android.ui.activity.FileActivity
import eu.qsfera.android.ui.activity.FileDisplayActivity
import eu.qsfera.android.ui.activity.FolderPickerActivity
import eu.qsfera.android.utils.DisplayUtils
import eu.qsfera.android.utils.MimetypeIconUtil
import eu.qsfera.android.utils.PreferenceUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okio.Path.Companion.toPath
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import timber.log.Timber
import java.io.File

class MainFileListFragment : Fragment(),
    CreateFolderDialogFragment.CreateFolderListener,
    FileListAdapter.FileListAdapterListener,
    SearchView.OnQueryTextListener,
    SortDialogListener,
    SortOptionsView.CreateFolderListener,
    SortOptionsView.SortOptionsListener,
    CreateShortcutDialogFragment.CreateShortcutListener {

    private val mainFileListViewModel by viewModel<MainFileListViewModel> {
        parametersOf(
            requireArguments().getParcelable(ARG_INITIAL_FOLDER_TO_DISPLAY),
            requireArguments().getParcelable(ARG_FILE_LIST_OPTION),
        )
    }
    private val fileOperationsViewModel by sharedViewModel<FileOperationsViewModel>()
    private val transfersViewModel by viewModel<TransfersViewModel>()
    private val spacesListViewModel: SpacesListViewModel by viewModel {
        parametersOf(
            requireArguments().getString(ARG_ACCOUNT_NAME),
            false,
        )
    }
    private val capabilityViewModel: CapabilityViewModel by viewModel {
        parametersOf(
            requireArguments().getString(ARG_ACCOUNT_NAME),
        )
    }

    private var _binding: MainFileListFragmentBinding? = null
    private val binding get() = _binding!!

    private lateinit var layoutManager: StaggeredGridLayoutManager
    private lateinit var fileListAdapter: FileListAdapter
    private lateinit var viewType: ViewType

    var actionMode: ActionMode? = null

    private var statusBarColorActionMode: Int? = null
    private var statusBarColor: Int? = null

    var fileActions: FileActions? = null
    var uploadActions: UploadActions? = null

    private var currentDefaultApplication: String? = null
    private var browserOpened = false

    private var openInWebProviders: Map<String, Int> = hashMapOf()

    private var isMultiPersonal = false

    private var menu: Menu? = null
    private var checkedFiles: List<OCFile> = emptyList()
    private var filesToRemove: List<OCFile> = emptyList()
    private var fileSingleFile: OCFile? = null
    private var fileOptionsBottomSheetSingleFileLayout: LinearLayout? = null
    private var succeededTransfers: List<OCTransfer>? = null
    private var numberOfUploadsRefreshed: Int = 0

    private val actionModeCallback: ActionMode.Callback = object : ActionMode.Callback {

        override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
            setDrawerStatus(enabled = false)
            actionMode = mode

            requireActivity().findViewById<View>(R.id.qsfera_app_bar).isFocusableInTouchMode = false

            val inflater = requireActivity().menuInflater
            inflater.inflate(R.menu.file_actions_menu, menu)
            this@MainFileListFragment.menu = menu

            mode?.invalidate()

            // Set gray color
            val window = activity?.window
            statusBarColor = window?.statusBarColor ?: -1

            // Hide FAB in multi selection mode
            toggleFabVisibility(false)
            fileActions?.setBottomBarVisibility(false)

            // Hide sort options view in multi-selection mode
            binding.optionsLayout.visibility = View.GONE

            return true
        }

        /**
         * Updates available action in menu depending on current selection.
         */
        override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
            val checkedFilesWithSyncInfo = fileListAdapter.getCheckedItems()
            val checkedCount = checkedFilesWithSyncInfo.size
            val title = resources.getQuantityString(
                R.plurals.items_selected_count,
                checkedCount,
                checkedCount
            )
            mode?.title = title

            checkedFiles = checkedFilesWithSyncInfo.map { it.file }

            val checkedFilesSync = checkedFilesWithSyncInfo.map {
                OCFileSyncInfo(
                    fileId = it.file.id!!,
                    uploadWorkerUuid = it.uploadWorkerUuid,
                    downloadWorkerUuid = it.downloadWorkerUuid,
                    isSynchronizing = it.isSynchronizing
                )
            }

            val displaySelectAll = checkedCount != fileListAdapter.itemCount - 1 // -1 because one of them is the footer :S
            mainFileListViewModel.filterMenuOptions(
                checkedFiles, checkedFilesSync,
                displaySelectAll, isMultiselection = true
            )

            if (checkedFiles.size == 1) {
                mainFileListViewModel.getAppRegistryForMimeType(checkedFiles.first().mimeType, isMultiselection = true)
            } else {
                menu?.let {
                    openInWebProviders.forEach { (_, menuItemId) ->
                        it.removeItem(menuItemId)
                    }
                    openInWebProviders = emptyMap()
                }
            }
            setRolesAccessibilityToMenuItems()

            return true
        }

        private fun setRolesAccessibilityToMenuItems() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val roleAccessibilityDescription = getString(R.string.button_role_accessibility)
                menu?.apply {
                    findItem(R.id.file_action_select_all)?.contentDescription =
                        "${getString(R.string.actionbar_select_all)} $roleAccessibilityDescription"
                    findItem(R.id.action_select_inverse)?.contentDescription =
                        "${getString(R.string.actionbar_select_inverse)} $roleAccessibilityDescription"
                    findItem(R.id.action_open_file_with)?.contentDescription =
                        "${getString(R.string.actionbar_open_with)} $roleAccessibilityDescription"
                    findItem(R.id.action_rename_file)?.contentDescription =
                        "${getString(R.string.common_rename)} $roleAccessibilityDescription"
                    findItem(R.id.action_move)?.contentDescription =
                        "${getString(R.string.actionbar_move)} $roleAccessibilityDescription"
                    findItem(R.id.action_copy)?.contentDescription =
                        "${getString(R.string.copy)} $roleAccessibilityDescription"
                    findItem(R.id.action_send_file)?.contentDescription =
                        "${getString(R.string.actionbar_send_file)} $roleAccessibilityDescription"
                    findItem(R.id.action_set_available_offline)?.contentDescription =
                        "${getString(R.string.set_available_offline)} $roleAccessibilityDescription"
                    findItem(R.id.action_unset_available_offline)?.contentDescription =
                        "${getString(R.string.unset_available_offline)} $roleAccessibilityDescription"
                    findItem(R.id.action_see_details)?.contentDescription =
                        "${getString(R.string.actionbar_see_details)} $roleAccessibilityDescription"
                    findItem(R.id.action_remove_file)?.contentDescription =
                        "${getString(R.string.common_remove)} $roleAccessibilityDescription"
                }
            }
        }

        override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean =
            onFileActionChosen(item?.itemId)

        override fun onDestroyActionMode(mode: ActionMode?) {
            setDrawerStatus(enabled = true)
            actionMode = null

            requireActivity().findViewById<View>(R.id.qsfera_app_bar).isFocusableInTouchMode = true

            // reset to previous color
            requireActivity().window.statusBarColor = statusBarColor!!

            // show or hide FAB on multi selection mode exit
            showOrHideFab(mainFileListViewModel.fileListOption.value, mainFileListViewModel.currentFolderDisplayed.value)

            fileActions?.setBottomBarVisibility(true)

            // Show sort options view when multi-selection mode finish
            binding.optionsLayout.visibility = View.VISIBLE

            fileListAdapter.clearSelection()
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = MainFileListFragmentBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        isMultiPersonal = capabilityViewModel.checkMultiPersonal()
        initViews()
        subscribeToViewModels()
    }

    override fun onResume() {
        super.onResume()
        if (browserOpened) {
            browserOpened = false
            fileOperationsViewModel.performOperation(
                FileOperation.RefreshFolderOperation(
                    folderToRefresh = mainFileListViewModel.getFile(),
                    shouldSyncContents = !isPickingAFolder(),
                )
            )
        }
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        (menu.findItem(R.id.action_search).actionView as SearchView).run {
            setOnQueryTextListener(this@MainFileListFragment)
            queryHint = resources.getString(R.string.actionbar_search)
        }
        (menu.findItem(R.id.action_select_all)).setOnMenuItemClickListener {
            fileListAdapter.selectAll()
            updateActionModeAfterTogglingSelected()
            true
        }
        if (isPickingAFolder() || getCurrentSpace()?.isPersonal == false) {
            menu.findItem(R.id.action_share_current_folder)?.itemId?.let { menu.removeItem(it) }
        } else {
            menu.findItem(R.id.action_share_current_folder)?.setOnMenuItemClickListener {
                fileActions?.onShareFileClicked(mainFileListViewModel.getFile())
                true
            }
        }
    }

    private fun initViews() {
        setHasOptionsMenu(true)
        statusBarColorActionMode = ContextCompat.getColor(requireContext(), R.color.action_mode_status_bar_background)

        // Set view and footer correctly
        if (mainFileListViewModel.isGridModeSetAsPreferred()) {
            layoutManager =
                StaggeredGridLayoutManager(
                    ColumnQuantity(requireContext(), R.layout.grid_item).calculateNoOfColumns(),
                    RecyclerView.VERTICAL
                )
            viewType = ViewType.VIEW_TYPE_GRID
        } else {
            layoutManager = StaggeredGridLayoutManager(1, RecyclerView.VERTICAL)
            viewType = ViewType.VIEW_TYPE_LIST
        }

        binding.optionsLayout.viewTypeSelected = viewType

        // Set RecyclerView and its adapter.
        binding.recyclerViewMainFileList.layoutManager = layoutManager

        fileListAdapter = FileListAdapter(
            context = requireContext(),
            layoutManager = layoutManager,
            isPickerMode = isPickingAFolder(),
            listener = this@MainFileListFragment,
        )

        binding.recyclerViewMainFileList.adapter = fileListAdapter

        // Set Swipe to refresh and its listener
        binding.swipeRefreshMainFileList.isEnabled = mainFileListViewModel.fileListOption.value != FileListOption.AV_OFFLINE
        binding.swipeRefreshMainFileList.setOnRefreshListener {
            fileOperationsViewModel.performOperation(
                FileOperation.RefreshFolderOperation(
                    folderToRefresh = mainFileListViewModel.getFile(),
                    shouldSyncContents = !isPickingAFolder(), // For picking a folder option, we just need a refresh
                )
            )
        }

        // Set Refresh FAB and its listener
        binding.fabRefresh.setOnClickListener {
            if (fileOperationsViewModel.refreshFolderLiveData.value!!.peekContent().isLoading) {
                showMessageInSnackbar(message = getString(R.string.fab_refresh_sync_in_progress))
            } else {
                fileOperationsViewModel.performOperation(
                    FileOperation.RefreshFolderOperation(
                        folderToRefresh = mainFileListViewModel.getFile(),
                        shouldSyncContents = false,
                    )
                )
                hideRefreshFab()
            }
        }

        // Set SortOptions and its listeners
        binding.optionsLayout.onSortOptionsListener = this
        setViewTypeSelector(SortOptionsView.AdditionalView.CREATE_FOLDER)

        showOrHideFab(requireArguments().getParcelable(ARG_FILE_LIST_OPTION)!!, requireArguments().getParcelable(ARG_INITIAL_FOLDER_TO_DISPLAY)!!)

        setFabMainContentDescription()

        setTextHintRootToolbar()
    }

    private fun setTextHintRootToolbar() {
        val searchViewRootToolbar = requireActivity().findViewById<SearchView>(R.id.root_toolbar_search_view)
        searchViewRootToolbar.queryHint = getString(R.string.actionbar_search)
    }

    private fun setViewTypeSelector(additionalView: SortOptionsView.AdditionalView) {
        if (isPickingAFolder()) {
            binding.optionsLayout.onCreateFolderListener = this
            binding.optionsLayout.selectAdditionalView(additionalView)
        }
    }

    private fun toggleSelection(position: Int) {
        fileListAdapter.toggleSelection(position)
        updateActionModeAfterTogglingSelected()
    }

    private fun subscribeToViewModels() {
        /* MainFileListViewModel observables */
        // Observe the current folder displayed
        observeCurrentFolderDisplayed()

        // Observe the current space to update the toolbar
        // We can't rely exclusively on the [currentFolderDisplayed] because sometimes retrieving the space takes more time
        observeSpace()

        // Observe the list of app registries that allow creating new files
        observeAppRegistryToCreateFiles()

        // Observe the open in web action to trigger browser
        observeOpenInWebFlow()

        // Observe the menu filtered options in multiselection
        observeMenuOptions()

        // Observe the app registry in multiselection
        observeAppRegistryMimeType()

        // Observe the menu filtered options for a single file
        observeMenuOptionsSingleFile()

        // Observe the app registry for a single file
        observeAppRegistryMimeTypeSingleFile()

        // Observe the file list UI state
        observeFileListUiState()

        /* FileOperationsViewModel observables */
        // Observe the refresh folder operation
        observeRefreshFolder()

        // Observe the create file with app provider operation
        observeCreateFileWithAppProvider()

        // Observe the check if file is local and not available offline operation
        observeCheckIfFileIsLocalAndNotAvailableOffline()

        /* TransfersViewModel observables */
        // Observe transfers
        observeTransfers()

    }

    private fun observeCurrentFolderDisplayed() {
        collectLatestLifecycleFlow(mainFileListViewModel.currentFolderDisplayed) { currentFolderDisplayed: OCFile ->
            fileActions?.onCurrentFolderUpdated(currentFolderDisplayed, mainFileListViewModel.getSpace())
            val fileListOption = mainFileListViewModel.fileListOption.value
            val refreshFolderNeeded = fileListOption.isAllFiles() ||
                    (!fileListOption.isAllFiles() &&
                            currentFolderDisplayed.remotePath != ROOT_PATH &&
                            !fileListOption.isAvailableOffline())
            if (refreshFolderNeeded) {
                fileOperationsViewModel.performOperation(
                    FileOperation.RefreshFolderOperation(
                        folderToRefresh = currentFolderDisplayed,
                        shouldSyncContents = !isPickingAFolder(), // For picking a folder option, we just need a refresh
                    )
                )
            }
            showOrHideFab(fileListOption, currentFolderDisplayed)
            if (currentFolderDisplayed.hasAddSubdirectoriesPermission) {
                setViewTypeSelector(SortOptionsView.AdditionalView.CREATE_FOLDER)
            } else {
                setViewTypeSelector(SortOptionsView.AdditionalView.HIDDEN)
            }
            numberOfUploadsRefreshed = 0
            hideRefreshFab()
        }
    }

    private fun observeSpace() {
        collectLatestLifecycleFlow(mainFileListViewModel.space) { currentSpace: OCSpace? ->
            currentSpace?.let {
                fileActions?.onCurrentFolderUpdated(mainFileListViewModel.getFile(), currentSpace)
            }
        }
    }
    private fun observeAppRegistryToCreateFiles() {
        collectLatestLifecycleFlow(mainFileListViewModel.appRegistryToCreateFiles) { listAppRegistry ->
            binding.fabNewfile.isVisible = listAppRegistry.isNotEmpty()
            registerFabNewFileListener(listAppRegistry)
        }
    }

    private fun observeOpenInWebFlow() {
        collectLatestLifecycleFlow(mainFileListViewModel.openInWebFlow) {
            if (it != null) {
                val uiResult = it.peekContent()
                if (uiResult is UIResult.Success) {
                    browserOpened = true
                    val builder = CustomTabsIntent.Builder().build()
                    builder.launchUrl(
                        requireActivity(),
                        Uri.parse(uiResult.data)
                    )
                } else if (uiResult is UIResult.Error) {
                    // Mimetypes not supported via open in web, send 500
                    if (uiResult.error is InstanceNotConfiguredException) {
                        val message =
                            getString(R.string.open_in_web_error_generic) + " " +
                                    getString(R.string.error_reason) +
                                    " " + getString(R.string.open_in_web_error_not_supported)
                        this.showMessageInSnackbar(message, Snackbar.LENGTH_LONG)
                    } else if (uiResult.error is TooEarlyException) {
                        this.showMessageInSnackbar(getString(R.string.open_in_web_error_too_early), Snackbar.LENGTH_LONG)
                    } else {
                        this.showErrorInSnackbar(
                            R.string.open_in_web_error_generic,
                            uiResult.error
                        )
                    }
                }
                mainFileListViewModel.resetOpenInWebFlow()
                currentDefaultApplication = null
            }
        }
    }

    private fun observeMenuOptions() {
        collectLatestLifecycleFlow(mainFileListViewModel.menuOptions) { menuOptions ->
            val hasWritePermission = if (checkedFiles.size == 1) {
                checkedFiles.first().hasWritePermission
            } else {
                false
            }
            menu?.filterMenuOptions(menuOptions, hasWritePermission)
        }
    }

    private fun observeAppRegistryMimeType() {
        collectLatestLifecycleFlow(mainFileListViewModel.appRegistryMimeType) { appRegistryMimeType ->
            val appProviders = appRegistryMimeType?.appProviders
            menu?.let {
                openInWebProviders = addOpenInWebMenuOptions(it, openInWebProviders, appProviders)
            }
        }
    }

    private fun observeMenuOptionsSingleFile() {
        collectLatestLifecycleFlow(mainFileListViewModel.menuOptionsSingleFile) { menuOptions ->
            fileSingleFile?.let { file ->
                val fileOptionsBottomSheetSingleFile = layoutInflater.inflate(R.layout.file_options_bottom_sheet_fragment, null)
                val dialog = BottomSheetDialog(requireContext())
                dialog.setContentView(fileOptionsBottomSheetSingleFile)

                val fileOptionsBottomSheetSingleFileBehavior: BottomSheetBehavior<*> =
                    BottomSheetBehavior.from(fileOptionsBottomSheetSingleFile.parent as View)
                val closeBottomSheetButton = fileOptionsBottomSheetSingleFile.findViewById<ImageView>(R.id.close_bottom_sheet)
                closeBottomSheetButton.setOnClickListener {
                    dialog.hide()
                    dialog.dismiss()
                }

                val fileNameBottomSheet = fileOptionsBottomSheetSingleFile.findViewById<TextView>(R.id.file_name_bottom_sheet)
                fileNameBottomSheet.text = file.fileName

                val thumbnailBottomSheet = fileOptionsBottomSheetSingleFile.findViewById<ImageView>(R.id.thumbnail_bottom_sheet)
                val account = AccountUtils.getCurrentQSferaAccount(requireContext())
                thumbnailBottomSheet.load(
                    ThumbnailsRequester.getPreviewUriForFile(file, account),
                    ThumbnailsRequester.getContentAddressedImageLoader(account)
                ) {
                    placeholder(MimetypeIconUtil.getFileTypeIconId(file.mimeType, file.fileName))
                    error(MimetypeIconUtil.getFileTypeIconId(file.mimeType, file.fileName))
                }

                val fileSizeBottomSheet = fileOptionsBottomSheetSingleFile.findViewById<TextView>(R.id.file_size_bottom_sheet)
                fileSizeBottomSheet.text = DisplayUtils.bytesToHumanReadable(file.length, requireContext(), true)

                val fileLastModBottomSheet = fileOptionsBottomSheetSingleFile.findViewById<TextView>(R.id.file_last_mod_bottom_sheet)
                fileLastModBottomSheet.text = DisplayUtils.getRelativeTimestamp(requireContext(), file.modificationTimestamp)

                fileOptionsBottomSheetSingleFileLayout = fileOptionsBottomSheetSingleFile.findViewById(R.id.file_options_bottom_sheet_layout)
                menuOptions.forEach { menuOption ->
                    setMenuOption(menuOption, file, dialog)
                }
                // Disable drag gesture
                fileOptionsBottomSheetSingleFileBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
                    override fun onStateChanged(bottomSheet: View, newState: Int) {
                        if (newState == BottomSheetBehavior.STATE_DRAGGING) {
                            fileOptionsBottomSheetSingleFileBehavior.state = BottomSheetBehavior.STATE_EXPANDED
                        }
                    }

                    override fun onSlide(bottomSheet: View, slideOffset: Float) {}
                })
                dialog.setOnShowListener { fileOptionsBottomSheetSingleFileBehavior.peekHeight = fileOptionsBottomSheetSingleFile.measuredHeight }
                dialog.show()
                mainFileListViewModel.getAppRegistryForMimeType(file.mimeType, isMultiselection = false)
            }
        }
    }

    private fun setMenuOption(menuOption: FileMenuOption, file: OCFile, dialog: BottomSheetDialog) {
        val fileOptionItemView = BottomSheetFragmentItemView(requireContext())
        fileOptionItemView.apply {
            title = if (menuOption.toResId() == R.id.action_open_file_with && !file.hasWritePermission) {
                getString(R.string.actionbar_open_with_read_only)
            } else {
                getString(menuOption.toStringResId())
            }
            itemIcon = ResourcesCompat.getDrawable(resources, menuOption.toDrawableResId(), null)
            setOnClickListener {
                when (menuOption) {
                    FileMenuOption.SELECT_ALL -> {
                        // Not applicable here
                    }

                    FileMenuOption.SELECT_INVERSE -> {
                        // Not applicable here
                    }

                    FileMenuOption.DOWNLOAD, FileMenuOption.SYNC -> {
                        syncFiles(listOf(file))
                    }

                    FileMenuOption.RENAME -> {
                        val dialogRename = RenameFileDialogFragment.newInstance(file)
                        dialogRename.show(requireActivity().supportFragmentManager, FRAGMENT_TAG_RENAME_FILE)
                    }

                    FileMenuOption.MOVE -> {
                        val action = Intent(activity, FolderPickerActivity::class.java)
                        action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, arrayListOf(file))
                        action.putExtra(FolderPickerActivity.EXTRA_PICKER_MODE, FolderPickerActivity.PickerMode.MOVE)
                        requireActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__MOVE_FILES)
                    }

                    FileMenuOption.COPY -> {
                        val action = Intent(activity, FolderPickerActivity::class.java)
                        action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, arrayListOf(file))
                        action.putExtra(FolderPickerActivity.EXTRA_PICKER_MODE, FolderPickerActivity.PickerMode.COPY)
                        requireActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__COPY_FILES)
                    }

                    FileMenuOption.REMOVE -> {
                        filesToRemove = listOf(file)
                        fileOperationsViewModel.showRemoveDialog(filesToRemove)
                    }

                    FileMenuOption.OPEN_WITH -> {
                        fileActions?.openFile(file)
                    }

                    FileMenuOption.CANCEL_SYNC -> {
                        fileActions?.cancelFileTransference(arrayListOf(file))
                    }

                    FileMenuOption.SHARE -> {
                        fileActions?.onShareFileClicked(file)
                    }

                    FileMenuOption.DETAILS -> {
                        fileActions?.showDetails(file)
                    }

                    FileMenuOption.SEND -> {
                        if (!file.isAvailableLocally) { // Download the file
                            Timber.d("${file.remotePath} : File must be downloaded")
                            fileActions?.initDownloadForSending(file)
                        } else {
                            fileActions?.sendDownloadedFile(file)
                        }
                    }

                    FileMenuOption.SET_AV_OFFLINE -> {
                        fileOperationsViewModel.performOperation(FileOperation.SetFilesAsAvailableOffline(listOf(file)))
                        if (file.isFolder) {
                            fileOperationsViewModel.performOperation(
                                FileOperation.SynchronizeFolderOperation(
                                    folderToSync = file,
                                    accountName = file.owner,
                                    isActionSetFolderAvailableOfflineOrSynchronize = true,
                                )
                            )
                        } else {
                            fileOperationsViewModel.performOperation(FileOperation.SynchronizeFileOperation(file, file.owner))
                        }
                    }

                    FileMenuOption.UNSET_AV_OFFLINE -> {
                        fileOperationsViewModel.performOperation(FileOperation.UnsetFilesAsAvailableOffline(listOf(file)))
                    }
                }
                dialog.hide()
                dialog.dismiss()
            }
        }
        fileOptionsBottomSheetSingleFileLayout!!.addView(fileOptionItemView)
    }

    private fun observeAppRegistryMimeTypeSingleFile() {
        collectLatestLifecycleFlow(mainFileListViewModel.appRegistryMimeTypeSingleFile) { appRegistryMimeType ->
            fileSingleFile?.let { file ->
                val appProviders = appRegistryMimeType?.appProviders
                appProviders?.forEach { appRegistryProvider ->
                    val appProviderItemView = BottomSheetFragmentItemView(requireContext())
                    appProviderItemView.apply {
                        title = getString(R.string.ic_action_open_with_web, appRegistryProvider.name)
                        itemIcon = try {
                            removeDefaultTint()
                            getDrawableFromUrl(requireContext(), appRegistryProvider.icon)
                        } catch (e: Exception) {
                            Timber.e(e, "An exception occurred while Glide is trying to load an image")
                            addDefaultTint(R.color.bottom_sheet_fragment_item_color)
                            ResourcesCompat.getDrawable(resources, R.drawable.ic_open_in_web, null)
                        }
                        setOnClickListener {
                            mainFileListViewModel.openInWeb(file.remoteId!!, appRegistryProvider.name)
                            fileOperationsViewModel.setLastUsageFile(file)
                        }
                    }
                    fileOptionsBottomSheetSingleFileLayout!!.addView(appProviderItemView, 1)
                }
            }
            fileSingleFile = null
        }
    }

    private suspend fun getDrawableFromUrl(context: Context, url: String): Drawable? =
        withContext(Dispatchers.IO) {
            Glide.with(context)
                .load(url)
                .fitCenter()
                .submit()
                .get()
        }

    private fun observeFileListUiState() {
        collectLatestLifecycleFlow(mainFileListViewModel.fileListUiState) { fileListUiState ->
            if (fileListUiState !is MainFileListViewModel.FileListUiState.Success) return@collectLatestLifecycleFlow

            fileListAdapter.updateFileList(
                filesToAdd = fileListUiState.folderContent,
                fileListOption = fileListUiState.fileListOption,
            )
            showOrHideEmptyView(fileListUiState)

            binding.spaceHeader.root.apply {
                if ((fileListUiState.space?.isProject == true || (fileListUiState.space?.isPersonal == true && isMultiPersonal)) &&
                    fileListUiState.folderToDisplay?.remotePath == ROOT_PATH && fileListUiState.fileListOption != FileListOption.AV_OFFLINE) {
                    isVisible = true
                    animate().translationY(0f).duration = 100
                } else {
                    animate().translationY(-height.toFloat()).withEndAction { isVisible = false }
                }
            }

            val spaceSpecialImage = fileListUiState.space?.getSpaceSpecialImage()
            if (spaceSpecialImage != null) {
                val account = AccountUtils.getCurrentQSferaAccount(requireContext())
                binding.spaceHeader.spaceHeaderImage.load(
                    ThumbnailsRequester.getPreviewUriForSpaceSpecial(spaceSpecialImage),
                    if (account != null) ThumbnailsRequester.getContentAddressedImageLoader(account)
                    else ThumbnailsRequester.getContentAddressedImageLoader()
                ) {
                    placeholder(R.drawable.ic_spaces)
                    error(R.drawable.ic_spaces)
                }
            }
            binding.spaceHeader.spaceHeaderName.text = fileListUiState.space?.name
            binding.spaceHeader.spaceHeaderSubtitle.text = fileListUiState.space?.description

            actionMode?.invalidate()
        }
    }

    private fun observeRefreshFolder() {
        fileOperationsViewModel.refreshFolderLiveData.observe(viewLifecycleOwner) {
            binding.syncProgressBar.isIndeterminate = it.peekContent().isLoading
            binding.swipeRefreshMainFileList.isRefreshing = it.peekContent().isLoading

            // Refresh the spaces and update the quota
            spacesListViewModel.refreshSpacesFromServer()

            hideRefreshFab()
        }
    }

    private fun observeCreateFileWithAppProvider() {
        collectLatestLifecycleFlow(fileOperationsViewModel.createFileWithAppProviderFlow) {
            val uiResult = it?.peekContent()
            if (uiResult is UIResult.Error) {
                val errorMessage =
                    uiResult.error?.parseError(resources.getString(R.string.create_file_fail_msg), resources, false)
                showMessageInSnackbar(
                    message = errorMessage.toString()
                )
            } else if (uiResult is UIResult.Success) {
                val fileId = uiResult.data
                val appName = currentDefaultApplication
                if (fileId != null && appName != null) {
                    mainFileListViewModel.openInWeb(fileId, appName)
                }
            }
        }
    }

    private fun observeCheckIfFileIsLocalAndNotAvailableOffline() {
        collectLatestLifecycleFlow(fileOperationsViewModel.checkIfFileIsLocalAndNotAvailableOfflineSharedFlow) {
            val fileActivity = (requireActivity() as FileActivity)
            when (it) {
                is UIResult.Loading -> {
                    fileActivity.showLoadingDialog(R.string.common_loading)
                }
                is UIResult.Success -> {
                    fileActivity.dismissLoadingDialog()
                    it.data?.let { result -> onShowRemoveDialog(filesToRemove, result) }
                }

                is UIResult.Error -> {
                    fileActivity.dismissLoadingDialog()
                    showMessageInSnackbar(resources.getString(R.string.common_error_unknown))
                }
            }
        }
    }

    private fun observeTransfers() {
        val maxUploadsToRefresh = resources.getInteger(R.integer.max_uploads_to_refresh)
        collectLatestLifecycleFlow(transfersViewModel.transfersWithSpaceStateFlow) { transfers ->
            if (transfers.isNotEmpty()) {
                val newlySucceededTransfers = transfers.map { it.first }.filter {
                    it.status == TransferStatus.TRANSFER_SUCCEEDED &&
                            it.accountName == AccountUtils.getCurrentQSferaAccount(requireContext()).name
                }
                val safeSucceededTransfers = succeededTransfers
                if (safeSucceededTransfers == null) {
                    succeededTransfers = newlySucceededTransfers
                } else if (safeSucceededTransfers != newlySucceededTransfers) {
                    val differentNewlySucceededTransfers = newlySucceededTransfers.filter { it !in safeSucceededTransfers }
                    differentNewlySucceededTransfers.forEach { transfer ->
                        numberOfUploadsRefreshed++
                        val currentFolder = mainFileListViewModel.getFile()
                        if (transfer.remotePath.toPath().parent!!.toString() == currentFolder.remotePath.toPath().toString()) {
                            if (numberOfUploadsRefreshed <= maxUploadsToRefresh) {
                                if (!fileOperationsViewModel.refreshFolderLiveData.value!!.peekContent().isLoading) {
                                    fileOperationsViewModel.performOperation(
                                        FileOperation.RefreshFolderOperation(
                                            folderToRefresh = currentFolder,
                                            shouldSyncContents = false,
                                        )
                                    )
                                }
                            } else {
                                binding.fabRefresh.apply {
                                    isVisible = true
                                    animate().translationY(0f).duration = 100
                                }
                            }
                        }
                    }

                    succeededTransfers = newlySucceededTransfers
                }
            }
        }

    }

    fun navigateToFolderId(folderId: Long) {
        mainFileListViewModel.navigateToFolderId(folderId)
    }

    fun navigateToFolder(folder: OCFile) {
        mainFileListViewModel.updateFolderToDisplay(newFolderToDisplay = folder)
    }

    private fun showOrHideEmptyView(fileListUiState: MainFileListViewModel.FileListUiState.Success) {
        binding.recyclerViewMainFileList.isVisible = fileListUiState.folderContent.isNotEmpty()

        with(binding.emptyDataParent) {
            root.isVisible = fileListUiState.folderContent.isEmpty()

            if (fileListUiState.fileListOption.isSharedByLink() && fileListUiState.space != null) {
                // Temporary solution for shares space
                listEmptyDatasetIcon.setImageResource(R.drawable.ic_server_shares)
                listEmptyDatasetTitle.setText(R.string.shares_list_empty_title)
                listEmptyDatasetSubTitle.setText(R.string.shares_list_empty_subtitle)
            } else {
                listEmptyDatasetIcon.setImageResource(fileListUiState.fileListOption.toDrawableRes())
                listEmptyDatasetTitle.setText(fileListUiState.fileListOption.toTitleStringRes())
                listEmptyDatasetSubTitle.setText(fileListUiState.fileListOption.toSubtitleStringRes())
            }
        }
    }

    private fun hideRefreshFab() {
        binding.fabRefresh.apply {
            animate().translationY(-this.height.toFloat() * 2).withEndAction { this.isVisible = false }
        }
    }

    override fun onSortTypeListener(sortType: SortType, sortOrder: SortOrder) {
        val sortBottomSheetFragment = newInstance(sortType, sortOrder)
        sortBottomSheetFragment.sortDialogListener = this
        sortBottomSheetFragment.show(childFragmentManager, SortBottomSheetFragment.TAG)
    }

    override fun onViewTypeListener(viewType: ViewType) {
        binding.optionsLayout.viewTypeSelected = viewType

        if (viewType == ViewType.VIEW_TYPE_LIST) {
            mainFileListViewModel.setListModeAsPreferred()
            layoutManager.spanCount = 1

        } else {
            mainFileListViewModel.setGridModeAsPreferred()
            layoutManager.spanCount = ColumnQuantity(requireContext(), R.layout.grid_item).calculateNoOfColumns()
        }

        fileListAdapter.notifyItemRangeChanged(0, fileListAdapter.itemCount)
    }

    override fun onSortSelected(sortType: SortType) {
        binding.optionsLayout.sortTypeSelected = sortType

        mainFileListViewModel.updateSortTypeAndOrder(sortType, binding.optionsLayout.sortOrderSelected)
    }

    private fun isPickingAFolder(): Boolean {
        val args = arguments
        return args != null && args.getBoolean(ARG_PICKING_A_FOLDER, false)
    }

    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }

    fun updateFileListOption(newFileListOption: FileListOption, file: OCFile) {
        mainFileListViewModel.updateFileListOption(newFileListOption)
        binding.swipeRefreshMainFileList.isEnabled = newFileListOption != FileListOption.AV_OFFLINE
        mainFileListViewModel.updateFolderToDisplay(file)
        showOrHideFab(newFileListOption, file)
    }

    /**
     * Check whether the fab should be shown or hidden depending on the [FileListOption] and
     * the current folder displayed permissions
     *
     * Show FAB when [FileListOption.ALL_FILES] and not picking a folder
     * Hide FAB When [FileListOption.SHARED_BY_LINK], [FileListOption.AV_OFFLINE] or picking a folder
     *
     * @param newFileListOption new file list option to enable.
     */
    private fun showOrHideFab(newFileListOption: FileListOption, currentFolder: OCFile) {
        if (!newFileListOption.isAllFiles() || isPickingAFolder() ||
            (!currentFolder.hasAddFilePermission && !currentFolder.hasAddSubdirectoriesPermission)) {
            toggleFabVisibility(false)
        } else {
            toggleFabVisibility(true)
            if (!currentFolder.hasAddFilePermission) {
                binding.fabUpload.isVisible = false
                binding.fabNewfile.isVisible = false
            } else if (!currentFolder.hasAddSubdirectoriesPermission) {
                binding.fabMkdir.isVisible = false
            }
            registerFabMainListener()
            registerFabUploadListener()
            registerFabMkDirListener()
            registerFabNewShortcutListener()
            binding.apply {
                fabUpload.isFocusable = false
                fabMkdir.isFocusable = false
                fabNewfile.isFocusable = false
                fabNewshortcut.isFocusable = false
            }
        }
    }

    /**
     * Sets the 'visibility' state of the main FAB and its mini FABs contained in the fragment.
     *
     * When 'false' is set, FAB visibility is set to View.GONE programmatically.
     * Mini FABs are automatically hidden after hiding the main one.
     *
     * @param shouldBeShown Desired visibility for the FAB.
     */
    private fun toggleFabVisibility(shouldBeShown: Boolean) {
        binding.fabMain.isVisible = shouldBeShown
        binding.fabUpload.isVisible = shouldBeShown
        binding.fabMkdir.isVisible = shouldBeShown
    }

    private fun registerFabMainListener() {
        binding.apply {
            fabMain.findViewById<View>(R.id.fab_expand_menu_button).setOnClickListener {
                fabMain.toggle()
                fabUpload.isFocusable = isFabExpanded()
                fabMkdir.isFocusable = isFabExpanded()
                fabNewfile.isFocusable = isFabExpanded()
                fabNewshortcut.isFocusable = isFabExpanded()
                if (fabMain.isExpanded) {
                    binding.fabMain.findViewById<AddFloatingActionButton>(com.getbase.floatingactionbutton.R.id.fab_expand_menu_button)
                        .contentDescription = getString(R.string.content_description_add_new_content_expanded)
                } else {
                    setFabMainContentDescription()
                }
            }
        }
    }

    /**
     * Registers [android.view.View.OnClickListener] on the 'Upload' mini FAB for the linked action.
     */
    private fun registerFabUploadListener() {
        binding.fabUpload.setOnClickListener {
            openBottomSheetToUploadFiles()
            collapseFab()
        }
    }

    /**
     * Registers [android.view.View.OnClickListener] on the 'New folder' mini FAB for the linked action.
     */
    private fun registerFabMkDirListener() {
        binding.fabMkdir.setOnClickListener {
            val dialog = CreateFolderDialogFragment.newInstance(mainFileListViewModel.getFile(), this)
            dialog.show(requireActivity().supportFragmentManager, DIALOG_CREATE_FOLDER)
            collapseFab()
        }
    }

    /**
     * Registers [android.view.View.OnClickListener] on the 'New document' mini FAB for the linked action.
     */
    private fun registerFabNewFileListener(listAppRegistry: List<AppRegistryMimeType>) {
        binding.fabNewfile.setOnClickListener {
            openBottomSheetToCreateNewFile(listAppRegistry)
            collapseFab()
        }
    }

    /**
     * Registers [android.view.View.OnClickListener] on the 'New shortcut' mini FAB for the linked action.
     */
    private fun registerFabNewShortcutListener() {
        binding.fabNewshortcut.setOnClickListener {
            val dialog = CreateShortcutDialogFragment.newInstance(mainFileListViewModel.getFile(), this)
            dialog.show(requireActivity().supportFragmentManager, DIALOG_CREATE_SHORTCUT)
            collapseFab()
        }
    }

    fun collapseFab() {
        binding.apply {
            fabMain.collapse()
            fabUpload.isFocusable = false
            fabMkdir.isFocusable = false
            fabNewfile.isFocusable = false
            fabNewshortcut.isFocusable = false
        }

    }

    fun isFabExpanded() = binding.fabMain.isExpanded

    fun setFabMainContentDescription() {
        binding.fabMain.findViewById<AddFloatingActionButton>(com.getbase.floatingactionbutton.R.id.fab_expand_menu_button).contentDescription =
            getString(R.string.content_description_add_new_content)
    }

    private fun openBottomSheetToUploadFiles() {
        val uploadBottomSheet = layoutInflater.inflate(R.layout.upload_bottom_sheet_fragment, null)
        val dialog = BottomSheetDialog(requireContext())
        dialog.setContentView(uploadBottomSheet)
        val uploadFromFilesItemView: BottomSheetFragmentItemView = uploadBottomSheet.findViewById(R.id.upload_from_files_item_view)
        val uploadFromCameraItemView: BottomSheetFragmentItemView = uploadBottomSheet.findViewById(R.id.upload_from_camera_item_view)
        val uploadToTextView = uploadBottomSheet.findViewById<TextView>(R.id.upload_to_text_view)
        uploadFromFilesItemView.setOnClickListener {
            uploadActions?.uploadFromFileSystem()
            dialog.hide()
        }
        uploadFromCameraItemView.setOnClickListener {
            uploadActions?.uploadFromCamera()
            dialog.hide()
        }
        uploadToTextView.text = String.format(
            resources.getString(R.string.upload_to),
            resources.getString(R.string.app_name)
        )
        val uploadBottomSheetBehavior: BottomSheetBehavior<*> = BottomSheetBehavior.from(uploadBottomSheet.parent as View)
        dialog.setOnShowListener { uploadBottomSheetBehavior.setPeekHeight(uploadBottomSheet.measuredHeight) }
        dialog.show()
    }

    private fun openBottomSheetToCreateNewFile(listAppRegistry: List<AppRegistryMimeType>) {
        val newFileBottomSheet = layoutInflater.inflate(R.layout.newfile_bottom_sheet_fragment, null)
        val dialog = BottomSheetDialog(requireContext())
        dialog.setContentView(newFileBottomSheet)
        val docTypesBottomSheetLayout = newFileBottomSheet.findViewById<LinearLayout>(R.id.doc_types_bottom_sheet_layout)
        listAppRegistry.forEach { appRegistry ->
            val documentTypeItemView = BottomSheetFragmentItemView(requireContext())
            documentTypeItemView.apply {
                removeDefaultTint()
                title = appRegistry.name
                itemIcon = ResourcesCompat.getDrawable(resources, MimetypeIconUtil.getFileTypeIconId(appRegistry.mimeType, appRegistry.ext), null)
                if (appRegistry.ext == FILE_DOCXF_EXTENSION) {
                    itemIcon?.setTintList(ColorStateList.valueOf(ContextCompat.getColor(context, R.color.file_docxf)))
                }
                setOnClickListener {
                    showFilenameTextDialog(appRegistry.ext)
                    currentDefaultApplication = appRegistry.appProviders.find { it.productName == appRegistry.defaultApplication}?.name
                        ?: appRegistry.defaultApplication
                    dialog.hide()
                }
            }
            docTypesBottomSheetLayout.addView(documentTypeItemView)
        }

        val newFileBottomSheetBehavior: BottomSheetBehavior<*> = BottomSheetBehavior.from(newFileBottomSheet.parent as View)
        dialog.setOnShowListener { newFileBottomSheetBehavior.setPeekHeight(newFileBottomSheet.measuredHeight) }
        dialog.show()
    }

    private fun showFilenameTextDialog(fileExtension: String?) {
        val dialogView = layoutInflater.inflate(R.layout.dialog_upload_text, null)
        dialogView.filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(requireContext())

        val input = dialogView.findViewById<TextInputEditText>(R.id.inputFileName)
        val inputLayout: TextInputLayout = dialogView.findViewById(R.id.inputTextLayout)
        input.requestFocus()

        val builder = AlertDialog.Builder(requireContext()).apply {
            setView(dialogView)
            setTitle(R.string.uploader_upload_text_dialog_title)
            setCancelable(false)
            setPositiveButton(android.R.string.ok) { _, _ ->
                val currentFolder = mainFileListViewModel.getFile()
                val filename = input.text.toString()
                var error: String? = null

                if (error != null) {
                    showMessageInSnackbar(error)
                } else {
                    val filenameWithExtension = "$filename.$fileExtension"
                    fileOperationsViewModel.performOperation(
                        FileOperation.CreateFileWithAppProviderOperation(
                            currentFolder.owner,
                            currentFolder.remoteId!!,
                            filenameWithExtension
                        )
                    )
                }
            }
            setNegativeButton(android.R.string.cancel, null)
        }
        val alertDialog = builder.create()

        input.doOnTextChanged { text, _, _, _ ->
            val okButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
            var error: String? = null
            if (text.isNullOrBlank()) {
                okButton.isEnabled = false
                error = getString(R.string.uploader_upload_text_dialog_filename_error_empty)
            } else if (text.length > MAX_FILENAME_LENGTH) {
                error = String.format(
                    getString(R.string.uploader_upload_text_dialog_filename_error_length_max),
                    MAX_FILENAME_LENGTH
                )
            } else if (forbiddenChars.any { text.contains(it) }) {
                error = getString(R.string.filename_forbidden_characters)
            } else {
                okButton.isEnabled = true
                error = null
                inputLayout.error = error
            }

            if (error != null) {
                okButton.isEnabled = false
                inputLayout.error = error
            }
        }


        alertDialog.apply {
            window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
            show()
        }
    }

    private fun onShowRemoveDialog(filesToRemove: List<OCFile>, isAvailableLocallyAndNotAvailableOffline: Boolean) {
        val dialog = RemoveFilesDialogFragment.newInstance(ArrayList(filesToRemove), isAvailableLocallyAndNotAvailableOffline)
        dialog.show(requireActivity().supportFragmentManager, TAG_REMOVE_FILES_DIALOG_FRAGMENT)
        fileListAdapter.clearSelection()
        updateActionModeAfterTogglingSelected()
    }

    override fun createShortcutFileFromApp(fileName: String, url: String) {
        val fileContent = """
                [InternetShortcut]
                URL=$url
                """.trimIndent()
        val storageDir = requireActivity().externalCacheDir
        val shortcutFile = File(storageDir, "$fileName.url")
        shortcutFile.writeText(fileContent)
        val shortcutFilePath = shortcutFile.absolutePath
        uploadActions?.uploadShortcutFileFromApp(arrayOf(shortcutFilePath))
    }

    override fun onFolderNameSet(newFolderName: String, parentFolder: OCFile) {
        fileOperationsViewModel.performOperation(FileOperation.CreateFolder(newFolderName, parentFolder))
        fileOperationsViewModel.createFolder.observe(viewLifecycleOwner, Event.EventObserver { uiResult: UIResult<Unit> ->
            if (uiResult is UIResult.Error) {
                val errorMessage =
                    uiResult.error?.parseError(resources.getString(R.string.create_dir_fail_msg), resources, false)
                showMessageInSnackbar(
                    message = errorMessage.toString()
                )
            }
        })
    }

    override fun onCreateFolderListener() {
        val dialog = CreateFolderDialogFragment.newInstance(mainFileListViewModel.getFile(), this)
        dialog.show(requireActivity().supportFragmentManager, DIALOG_CREATE_FOLDER)
    }

    override fun onQueryTextSubmit(query: String?): Boolean = false

    override fun onQueryTextChange(newText: String?): Boolean {
        newText?.let { mainFileListViewModel.updateSearchFilter(it) }
        return true
    }

    fun setSearchListener(searchView: SearchView) {
        searchView.setOnQueryTextListener(this)
    }

    /**
     * Call this, when the user presses the up button.
     *
     *
     * Tries to move up the current folder one level. If the parent folder was removed from the
     * database, it continues browsing up until finding an existing folders.
     *
     *
     */
    fun onBrowseUp() {
        mainFileListViewModel.manageBrowseUp()
    }

    /**
     * Use this to query the [OCFile] that is currently
     * being displayed by this fragment
     *
     * @return The currently viewed OCFile
     */
    fun getCurrentFile(): OCFile =
        mainFileListViewModel.getFile()

    fun getCurrentSpace(): OCSpace? =
        mainFileListViewModel.getSpace()

    private fun setDrawerStatus(enabled: Boolean) {
        (activity as FileActivity).setDrawerLockMode(if (enabled) DrawerLayout.LOCK_MODE_UNLOCKED else DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
    }

    /**
     * Start the appropriate action(s) on the currently selected files given menu selected by the user.
     *
     * @param menuId Identifier of the action menu selected by the user
     * @return 'true' if the menu selection started any action, 'false' otherwise.
     */
    @SuppressLint("UseRequireInsteadOfGet")
    private fun onFileActionChosen(menuId: Int?): Boolean {
        var handled: Boolean
        val checkedFilesWithSyncInfo = fileListAdapter.getCheckedItems() as ArrayList<OCFileWithSyncInfo>

        if (checkedFilesWithSyncInfo.isEmpty()) {
            return false
        } else if (checkedFilesWithSyncInfo.size == 1) {
            /// Action possible on a single file
            val singleFile = checkedFilesWithSyncInfo.first().file
            handled = onSingleFileActionChosen(menuId, singleFile)
        }

        /// Actions possible on a batch of files
        val checkedFiles = checkedFilesWithSyncInfo.map { it.file } as ArrayList<OCFile>
        handled = onCheckedFilesActionChosen(menuId, checkedFiles)
        return handled
    }

    private fun onSingleFileActionChosen(menuId: Int?, singleFile: OCFile): Boolean {
        openInWebProviders.forEach { (openInWebProviderName, menuItemId) ->
            if (menuItemId == menuId) {
                mainFileListViewModel.openInWeb(singleFile.remoteId!!, openInWebProviderName)
                return true
            }
        }

        return when (menuId) {
            R.id.action_share_file -> {
                fileActions?.onShareFileClicked(singleFile)
                fileListAdapter.clearSelection()
                updateActionModeAfterTogglingSelected()
                true
            }

            R.id.action_open_file_with -> {
                fileActions?.openFile(singleFile)
                fileListAdapter.clearSelection()
                updateActionModeAfterTogglingSelected()
                true
            }

            R.id.action_rename_file -> {
                val dialog = RenameFileDialogFragment.newInstance(singleFile)
                dialog.show(requireActivity().supportFragmentManager, FRAGMENT_TAG_RENAME_FILE)
                fileListAdapter.clearSelection()
                updateActionModeAfterTogglingSelected()
                true
            }

            R.id.action_see_details -> {
                fileListAdapter.clearSelection()
                updateActionModeAfterTogglingSelected()
                fileActions?.showDetails(singleFile)
                true
            }

            R.id.action_sync_file -> {
                syncFiles(listOf(singleFile))
                true
            }

            R.id.action_send_file -> {
                //Obtain the file
                if (!singleFile.isAvailableLocally) { // Download the file
                    Timber.d("%s : File must be downloaded", singleFile.remotePath)
                    fileActions?.initDownloadForSending(singleFile)
                } else {
                    fileActions?.sendDownloadedFile(singleFile)
                }
                true
            }

            R.id.action_set_available_offline -> {
                fileOperationsViewModel.performOperation(FileOperation.SetFilesAsAvailableOffline(listOf(singleFile)))
                if (singleFile.isFolder) {
                    fileOperationsViewModel.performOperation(
                        FileOperation.SynchronizeFolderOperation(
                            folderToSync = singleFile,
                            accountName = singleFile.owner,
                            isActionSetFolderAvailableOfflineOrSynchronize = true,
                        )
                    )
                } else {
                    fileOperationsViewModel.performOperation(FileOperation.SynchronizeFileOperation(singleFile, singleFile.owner))
                }
                true
            }

            R.id.action_unset_available_offline -> {
                fileOperationsViewModel.performOperation(FileOperation.UnsetFilesAsAvailableOffline(listOf(singleFile)))
                true
            }

            else -> {
                false
            }
        }
    }

    private fun onCheckedFilesActionChosen(menuId: Int?, checkedFiles: ArrayList<OCFile>): Boolean =
        when (menuId) {
            R.id.file_action_select_all -> {
                fileListAdapter.selectAll()
                updateActionModeAfterTogglingSelected()
                true
            }

            R.id.action_select_inverse -> {
                fileListAdapter.selectInverse()
                updateActionModeAfterTogglingSelected()
                true
            }

            R.id.action_remove_file -> {
                filesToRemove = checkedFiles
                fileOperationsViewModel.showRemoveDialog(filesToRemove)
                true
            }

            R.id.action_download_file,
            R.id.action_sync_file -> {
                syncFiles(checkedFiles)
                true
            }

            R.id.action_cancel_sync -> {
                fileActions?.cancelFileTransference(checkedFiles)
                true
            }

            R.id.action_set_available_offline -> {
                fileOperationsViewModel.performOperation(FileOperation.SetFilesAsAvailableOffline(checkedFiles))
                checkedFiles.forEach { ocFile ->
                    if (ocFile.isFolder) {
                        fileOperationsViewModel.performOperation(FileOperation.SynchronizeFolderOperation(ocFile, ocFile.owner))
                    } else {
                        fileOperationsViewModel.performOperation(FileOperation.SynchronizeFileOperation(ocFile, ocFile.owner))
                    }
                }
                true
            }

            R.id.action_unset_available_offline -> {
                fileOperationsViewModel.performOperation(FileOperation.UnsetFilesAsAvailableOffline(checkedFiles))
                true
            }

            R.id.action_send_file -> {
                requireActivity().sendDownloadedFilesByShareSheet(checkedFiles)
                true
            }

            R.id.action_move -> {
                val action = Intent(activity, FolderPickerActivity::class.java)
                action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, checkedFiles)
                action.putExtra(FolderPickerActivity.EXTRA_PICKER_MODE, FolderPickerActivity.PickerMode.MOVE)
                requireActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__MOVE_FILES)
                fileListAdapter.clearSelection()
                updateActionModeAfterTogglingSelected()
                true
            }

            R.id.action_copy -> {
                val action = Intent(activity, FolderPickerActivity::class.java)
                action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, checkedFiles)
                action.putExtra(FolderPickerActivity.EXTRA_PICKER_MODE, FolderPickerActivity.PickerMode.COPY)
                requireActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__COPY_FILES)
                fileListAdapter.clearSelection()
                updateActionModeAfterTogglingSelected()
                true
            }

            else -> {
                false
            }
        }

    /**
     * Update or remove the actionMode after applying any change to the selected items.
     */
    private fun updateActionModeAfterTogglingSelected() {
        val selectedItems = fileListAdapter.selectedItemCount
        if (selectedItems == 0) {
            actionMode?.finish()
        } else {
            if (actionMode == null) {
                actionMode = (requireActivity() as AppCompatActivity).startSupportActionMode(actionModeCallback)
            }
            actionMode?.apply {
                title = selectedItems.toString()
                invalidate()
            }
        }
    }

    override fun onItemClick(ocFileWithSyncInfo: OCFileWithSyncInfo, position: Int) {
        if (actionMode != null) {
            toggleSelection(position)
            return
        }

        val ocFile = ocFileWithSyncInfo.file

        if (ocFile.isFolder) {
            mainFileListViewModel.updateFolderToDisplay(ocFile)
        } else { // Click on a file
            fileActions?.onFileClicked(ocFile)
        }
    }

    override fun onLongItemClick(position: Int): Boolean {
        if (isPickingAFolder()) return false

        if (actionMode == null) {
            actionMode = (requireActivity() as AppCompatActivity).startSupportActionMode(actionModeCallback)
            // Notify all when enabling multiselection for the first time to show checkboxes on every single item.
            fileListAdapter.notifyDataSetChanged()
        }
        toggleSelection(position)
        return true
    }

    override fun onThreeDotButtonClick(fileWithSyncInfo: OCFileWithSyncInfo) {
        val file = fileWithSyncInfo.file
        fileSingleFile = file
        val fileSync = OCFileSyncInfo(
            fileId = fileWithSyncInfo.file.id!!,
            uploadWorkerUuid = fileWithSyncInfo.uploadWorkerUuid,
            downloadWorkerUuid = fileWithSyncInfo.downloadWorkerUuid,
            isSynchronizing = fileWithSyncInfo.isSynchronizing
        )
        mainFileListViewModel.filterMenuOptions(
            listOf(file), listOf(fileSync),
            displaySelectAll = false, isMultiselection = false
        )
    }

    private fun syncFiles(files: List<OCFile>) {
        for (file in files) {
            if (file.isFolder) {
                fileOperationsViewModel.performOperation(
                    FileOperation.SynchronizeFolderOperation(
                        folderToSync = file,
                        accountName = file.owner,
                        isActionSetFolderAvailableOfflineOrSynchronize = true,
                    )
                )
            } else {
                fileOperationsViewModel.performOperation(FileOperation.SynchronizeFileOperation(fileToSync = file, accountName = file.owner))
            }
        }
    }

    fun setProgressBarAsIndeterminate(indeterminate: Boolean) {
        Timber.d("Setting progress visibility to %s", indeterminate)
        binding.shadowView.visibility = View.GONE
        binding.syncProgressBar.apply {
            visibility = View.VISIBLE
            isIndeterminate = indeterminate
            postInvalidate()
        }
    }

    interface FileActions {
        fun onCurrentFolderUpdated(newCurrentFolder: OCFile, currentSpace: OCSpace? = null)
        fun onFileClicked(file: OCFile)
        fun onShareFileClicked(file: OCFile)
        fun initDownloadForSending(file: OCFile)
        fun showDetails(file: OCFile)
        fun syncFile(file: OCFile)
        fun openFile(file: OCFile)
        fun sendDownloadedFile(file: OCFile)
        fun cancelFileTransference(files: ArrayList<OCFile>)
        fun setBottomBarVisibility(isVisible: Boolean)
    }

    interface UploadActions {
        fun uploadFromCamera()
        fun uploadShortcutFileFromApp(shortcutFilePath: Array<String>)
        fun uploadFromFileSystem()
    }

    companion object {
        val ARG_PICKING_A_FOLDER = "${MainFileListFragment::class.java.canonicalName}.ARG_PICKING_A_FOLDER}"
        val ARG_INITIAL_FOLDER_TO_DISPLAY = "${MainFileListFragment::class.java.canonicalName}.ARG_INITIAL_FOLDER_TO_DISPLAY}"
        val ARG_FILE_LIST_OPTION = "${MainFileListFragment::class.java.canonicalName}.FILE_LIST_OPTION}"
        val ARG_ACCOUNT_NAME = "${MainFileListFragment::class.java.canonicalName}.ARG_ACCOUNT_NAME}"
        const val MAX_FILENAME_LENGTH = 223
        val forbiddenChars = listOf('/', '\\')

        private const val DIALOG_CREATE_FOLDER = "DIALOG_CREATE_FOLDER"
        private const val DIALOG_CREATE_SHORTCUT = "DIALOG_CREATE_SHORTCUT"

        private const val FILE_DOCXF_EXTENSION = "docxf"

        @JvmStatic
        fun newInstance(
            initialFolderToDisplay: OCFile,
            pickingAFolder: Boolean = false,
            fileListOption: FileListOption = FileListOption.ALL_FILES,
            accountName: String,
        ): MainFileListFragment {
            val args = Bundle()
            args.putParcelable(ARG_INITIAL_FOLDER_TO_DISPLAY, initialFolderToDisplay)
            args.putBoolean(ARG_PICKING_A_FOLDER, pickingAFolder)
            args.putParcelable(ARG_FILE_LIST_OPTION, fileListOption)
            args.putString(ARG_ACCOUNT_NAME, accountName)
            return MainFileListFragment().apply { arguments = args }
        }
    }
}

