/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.downloads

import android.app.Activity
import android.app.Dialog
import android.text.method.ScrollingMovementMethod
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.view.Window
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import androidx.annotation.VisibleForTesting
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
import androidx.core.view.children
import androidx.viewbinding.ViewBinding
import mozilla.components.feature.downloads.databinding.MozacDownloaderChooserPromptBinding
import mozilla.components.feature.downloads.toMegabyteOrKilobyteString
import mozilla.components.feature.downloads.ui.DownloaderApp
import mozilla.components.feature.downloads.ui.DownloaderAppAdapter
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.DialogScrimBinding
import org.mozilla.fenix.databinding.StartDownloadDialogLayoutBinding
import org.mozilla.fenix.ext.settings

/**
 * Parent of all download views that can mimic a modal [Dialog].
 *
 * @param activity The [Activity] in which the dialog will be shown.
 * Used to update the activity [Window] to best mimic a modal dialog.
 */
abstract class StartDownloadDialog(
    private val activity: Activity,
) {
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    internal var binding: ViewBinding? = null

    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    internal var container: ViewGroup? = null
    private var scrim: DialogScrimBinding? = null

    @VisibleForTesting
    internal var onDismiss: () -> Unit = {}

    /**
     * Show the download view.
     *
     * @param container The [ViewGroup] in which the download view will be inflated.
     */
    fun show(container: ViewGroup): StartDownloadDialog {
        this.container = container

        val dialogParent = container.parent as? ViewGroup
        dialogParent?.let {
            scrim = DialogScrimBinding.inflate(LayoutInflater.from(activity), dialogParent, true).apply {
                this.scrim.setOnClickListener {
                    // Empty listener needed to prevent clicking through.
                }
            }
        }

        setupView()

        if (activity.settings().accessibilityServicesEnabled) {
            disableSiblingsAccessibility(dialogParent)
        }

        container.apply {
            val params = layoutParams as CoordinatorLayout.LayoutParams
            params.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
            layoutParams = params

            // Set a higher elevation than the toolbar sibling which we should cover.
            elevation = activity.resources.getDimension(R.dimen.browser_fragment_download_dialog_elevation)
            visibility = View.VISIBLE
        }
        return this
    }

    /**
     * Set a callback for when the download view is dismissed.
     *
     * @param callback The callback for when the view is dismissed.
     */
    fun onDismiss(callback: () -> Unit): StartDownloadDialog {
        this.onDismiss = callback
        return this
    }

    /**
     * Immediately dismiss the current download view if it is shown.
     * This will restore the previous UI removing any other layout / window customizations.
     */
    fun dismiss() {
        scrim?.let {
            (it.root.parent as? ViewGroup)?.removeView(it.root)
        }
        binding?.let {
            (it.root.parent as? ViewGroup)?.removeView(it.root)
        }
        enableSiblingsAccessibility(container?.parent as? ViewGroup)

        container?.visibility = View.GONE

        onDismiss()
    }

    @VisibleForTesting
    internal fun enableSiblingsAccessibility(parent: ViewGroup?) {
        parent?.children
            ?.filterNot { it.id == R.id.startDownloadDialogContainer }
            ?.forEach {
                ViewCompat.setImportantForAccessibility(
                    it,
                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES,
                )
            }
    }

    @VisibleForTesting
    internal fun disableSiblingsAccessibility(parent: ViewGroup?) {
        parent?.children
            ?.filterNot { it.id == R.id.startDownloadDialogContainer }
            ?.forEach {
                ViewCompat.setImportantForAccessibility(
                    it,
                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
                )
            }
    }

    /**
     * Bind all download data to the download view.
     */
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    internal abstract fun setupView()
}

/**
 * A download view mimicking a modal dialog that allows the user to download a file with the current application.
 *
 * @param activity The [Activity] in which the dialog will be shown.
 * Used to update the activity [Window] to best mimic a modal dialog.
 * @param filename Name of the file to be downloaded. It wil be shown without any modification.
 * @param contentSize Size of the file to be downloaded expressed as a number of bytes.
 * It will automatically be parsed to the appropriate kilobyte or megabyte value before being shown.
 * @param positiveButtonAction Callback for when the user interacts with the dialog to start the download.
 * @param negativeButtonAction Callback for when the user interacts with the dialog to dismiss it.
 */
class FirstPartyDownloadDialog(
    private val activity: Activity,
    private val filename: String,
    private val contentSize: Long,
    private val positiveButtonAction: () -> Unit,
    private val negativeButtonAction: () -> Unit,
) : StartDownloadDialog(activity) {
    override fun setupView() {
        val dialog = StartDownloadDialogLayoutBinding.inflate(LayoutInflater.from(activity), container, true)
            .also { binding = it }

        if (contentSize > 0L) {
            val contentSize = contentSize.toMegabyteOrKilobyteString()
            dialog.title.text =
                activity.getString(R.string.mozac_feature_downloads_dialog_title2, contentSize)
        }

        dialog.filename.text = filename
        dialog.filename.movementMethod = ScrollingMovementMethod()

        dialog.downloadButton.setOnClickListener {
            positiveButtonAction()
            dismiss()
        }

        dialog.closeButton.setOnClickListener {
            negativeButtonAction()
            dismiss()
        }

        if (activity.settings().accessibilityServicesEnabled) {
            // Ensure the title of the dialog is focused and read by talkback first.
            dialog.root.viewTreeObserver.addOnGlobalLayoutListener(
                object : OnGlobalLayoutListener {
                    override fun onGlobalLayout() {
                        dialog.root.viewTreeObserver.removeOnGlobalLayoutListener(this)
                        dialog.title.run {
                            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED)
                            performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
                        }
                    }
                },
            )
        }
    }
}

/**
 * A download view mimicking a modal dialog that presents the user with a list of all apps
 * that can handle the download request.
 *
 * @param activity The [Activity] in which the dialog will be shown.
 * Used to update the activity [Window] to best mimic a modal dialog.
 * @param downloaderApps List of all applications that can handle the download request.
 * @param onAppSelected Callback for when the user chooses a specific application to handle the download request.
 * @param negativeButtonAction Callback for when the user interacts with the dialog to dismiss it.
 */
class ThirdPartyDownloadDialog(
    private val activity: Activity,
    private val downloaderApps: List<DownloaderApp>,
    private val onAppSelected: (DownloaderApp) -> Unit,
    private val negativeButtonAction: () -> Unit,
) : StartDownloadDialog(activity) {
    override fun setupView() {
        val dialog = MozacDownloaderChooserPromptBinding.inflate(LayoutInflater.from(activity), container, true)
            .also { binding = it }

        val recyclerView = dialog.appsList
        recyclerView.adapter = DownloaderAppAdapter(activity, downloaderApps) { app ->
            onAppSelected(app)
            dismiss()
        }

        dialog.closeButton.setOnClickListener {
            negativeButtonAction()
            dismiss()
        }
    }
}
