package com.github.libretube.ui.activities

import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewTreeObserver
import android.widget.ScrollView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.widget.SearchView
import androidx.constraintlayout.motion.widget.Key
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.net.toUri
import androidx.core.os.bundleOf
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.allViews
import androidx.core.view.children
import androidx.core.view.isNotEmpty
import androidx.core.widget.NestedScrollView

import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.onNavDestinationSelected
import androidx.navigation.ui.setupWithNavController
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.BuildConfig
import com.github.libretube.NavDirections
import com.github.libretube.R
import com.github.libretube.compat.PictureInPictureCompat
import com.github.libretube.constants.IntentData
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.databinding.ActivityMainBinding
import com.github.libretube.db.DatabaseHelper
import com.github.libretube.db.obj.SearchHistoryItem
import com.github.libretube.enums.ImportFormat
import com.github.libretube.enums.SearchType
import com.github.libretube.enums.TopLevelDestination
import com.github.libretube.extensions.anyChildFocused
import com.github.libretube.helpers.ImportHelper
import com.github.libretube.helpers.IntentHelper
import com.github.libretube.helpers.NavBarHelper
import com.github.libretube.helpers.NavigationHelper
import com.github.libretube.helpers.NetworkHelper
import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.helpers.ThemeHelper
import com.github.libretube.parcelable.PlayerData
import com.github.libretube.ui.dialogs.ErrorDialog
import com.github.libretube.ui.dialogs.ImportTempPlaylistDialog
import com.github.libretube.ui.extensions.onSystemInsets
import com.github.libretube.ui.fragments.DownloadsFragment
import com.github.libretube.ui.models.DownloadsViewModel
import com.github.libretube.ui.models.PlaylistViewModel
import com.github.libretube.ui.models.SearchViewModel
import com.github.libretube.ui.models.SubscriptionsViewModel
import com.github.libretube.ui.preferences.BackupRestoreSettings
import com.github.libretube.ui.preferences.BackupRestoreSettings.Companion.FILETYPE_ANY
import com.github.libretube.util.UpdateChecker
import com.github.libretube.helpers.TokenWatchdog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import com.github.libretube.api.JsonHelper
import com.github.libretube.api.obj.Playlist
import kotlinx.serialization.decodeFromString
import com.github.libretube.api.PlaylistsHelper

class MainActivity : AbstractPlayerHostActivity() {
    private var binding: ActivityMainBinding? = null

    lateinit var navController: NavController
    private var startFragmentId = R.id.homeFragment

    private val subscriptionsViewModel: SubscriptionsViewModel by viewModels()

    // search related stuff
    private lateinit var searchView: SearchView
    private lateinit var searchItem: MenuItem
    private var savedSearchQuery: String? = null
    private var shouldOpenSuggestions = true
    private var currentSearchType: SearchType = SearchType.ONLINE
    private val searchViewModel: SearchViewModel by viewModels()
    private val downloadViewModel: DownloadsViewModel by viewModels()
    private val playlistViewModel: PlaylistViewModel by viewModels()

    // registering for activity results is only possible, this here should have been part of
    // PlaylistOptionsBottomSheet instead if Android allowed us to
    private var playlistExportFormat: ImportFormat = ImportFormat.NEWPIPE
    private var exportPlaylistId: String? = null
    private val createPlaylistsFile = registerForActivityResult(
        ActivityResultContracts.CreateDocument(FILETYPE_ANY)
    ) { uri ->
        if (uri == null) return@registerForActivityResult

        lifecycleScope.launch(Dispatchers.IO) {
            ImportHelper.exportPlaylists(
                this@MainActivity,
                uri,
                playlistExportFormat,
                selectedPlaylistIds = listOf(exportPlaylistId!!)
            )
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        installSplashScreen()
        super.onCreate(savedInstanceState)

        // show noInternet Activity if no internet available on app startup
        if (!NetworkHelper.isNetworkAvailable(this)) {
            val noInternetIntent = Intent(this, NoInternetActivity::class.java)
            startActivity(noInternetIntent)
            finish()
            return
        }

        try {
            initializeMainContent()
            // Start token watchdog to validate stored access code periodically
            TokenWatchdog.start(this)
        } catch (e: Exception) {
            android.util.Log.e("MainActivity", "Error during initializeMainContent: ${e.message}", e)
            e.printStackTrace()
            finish()
        }
    }


    private fun initializeMainContent() {
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding!!.root)

        // manually apply additional padding for edge-to-edge compatibility
        // see https://developer.android.com/develop/ui/views/layout/edge-to-edge
        binding!!.root.onSystemInsets { _, systemBarInsets ->
            // there's a possibility that the paddings are not being applied properly when
            // exiting from player's fullscreen. Adding OnGlobalLayoutListener serves as
            // a workaround for this issue
            binding!!.root.viewTreeObserver.addOnGlobalLayoutListener(object :
                ViewTreeObserver.OnGlobalLayoutListener {
                override fun onGlobalLayout() {
                    with(binding!!.appBarLayout) {
                        setPadding(
                            paddingLeft,
                            systemBarInsets.top,
                            paddingRight,
                            paddingBottom
                        )
                    }
                    with(binding!!.bottomNav) {
                        setPadding(
                            paddingLeft,
                            paddingTop,
                            paddingRight,
                            systemBarInsets.bottom
                        )
                    }
                    binding!!.root.viewTreeObserver.removeOnGlobalLayoutListener(this)
                }
            })
        }

        binding!!.bottomNav.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
            val transition = binding!!.root.getTransition(R.id.bottom_bar_transition)
            transition.keyFrameList.forEach { keyFrame ->
                // These frame positions are hardcoded in activity_main_scene.xml!
                for (key in keyFrame.getKeyFramesForView(binding!!.bottomNav.id)) {
                    if (key.framePosition == 1) key.setValue(
                        Key.TRANSLATION_Y,
                        binding!!.bottomNav.height
                    )
                }
                for (key in keyFrame.getKeyFramesForView(binding!!.container.id)) {
                    if (key.framePosition == 100) key.setValue(
                        Key.TRANSLATION_Y,
                        -binding!!.bottomNav.height
                    )
                }
            }
            binding!!.root.scene.setTransition(transition)
        }

        // Check update automatically
        if (PreferenceHelper.getBoolean(PreferenceKeys.AUTOMATIC_UPDATE_CHECKS, false)) {
            lifecycleScope.launch(Dispatchers.IO) {
                UpdateChecker(this@MainActivity).checkUpdate(false)
            }
        }

        // set the action bar for the activity
        setSupportActionBar(binding!!.toolbar)

        val navHostFragment = binding!!.fragment.getFragment<NavHostFragment>()
        navController = navHostFragment.navController
        binding!!.bottomNav.setupWithNavController(navController)

        // save start tab fragment id and apply navbar style
        startFragmentId = try {
            NavBarHelper.applyNavBarStyle(binding!!.bottomNav)
        } catch (_: Exception) {
            R.id.homeFragment
        }

        // set default tab as start fragment
        navController.graph = navController.navInflater.inflate(R.navigation.nav).also {
            it.setStartDestination(startFragmentId)
        }

        // Prevent duplicate entries into backstack, if selected item and current
        // visible fragment is different, then navigate to selected item.
        binding!!.bottomNav.setOnItemReselectedListener {
            if (it.itemId != navController.currentDestination?.id) {
                navigateToBottomSelectedItem(it)
            } else {
                // get the current fragment
                val fragment = navHostFragment.childFragmentManager.fragments.firstOrNull()
                tryScrollToTop(fragment?.requireView())
            }
        }

        binding!!.bottomNav.setOnItemSelectedListener {
            navigateToBottomSelectedItem(it)
        }

        if (binding!!.bottomNav.menu.children.none { it.itemId == startFragmentId }) deselectBottomBarItems()

        binding!!.toolbar.title = "YouTube Pro Premium"

        // handle error logs
        PreferenceHelper.getErrorLog().ifBlank { null }?.let {
            if (!BuildConfig.DEBUG)
                ErrorDialog().show(supportFragmentManager, null)
        }

        setupSubscriptionsBadge()

        loadIntentData()

        showUserInfoDialogIfNeeded()
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.action_bar, menu)

        // stuff for the search in the topBar
        val searchItem = menu.findItem(R.id.action_search)
        this.searchItem = searchItem
        searchView = searchItem.actionView as SearchView

        // automatically set a different search icon in the playlists
        navController.addOnDestinationChangedListener { _, destination, _ ->
            currentSearchType = when (destination.id) {
                R.id.downloadsFragment -> SearchType.DOWNLOADS
                R.id.playlistFragment -> SearchType.PLAYLIST
                else -> SearchType.ONLINE
            }
            // clear query in unused page so that they're reset when visiting the page the next time
            if (currentSearchType != SearchType.DOWNLOADS) downloadViewModel.setQuery(null)
            if (currentSearchType != SearchType.PLAYLIST) playlistViewModel.setQuery(null)

            val searchIconResource = when (currentSearchType) {
                SearchType.DOWNLOADS -> R.drawable.ic_download_search
                SearchType.PLAYLIST -> R.drawable.ic_playlist_search
                SearchType.ONLINE -> R.drawable.ic_search
            }

            searchItem.setIcon(searchIconResource)
        }

        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
            override fun onQueryTextSubmit(query: String): Boolean {
                searchView.clearFocus()

                // playlist and download search don't do anything on submit
                // as they search while typing
                if (currentSearchType != SearchType.ONLINE) return true

                // handle inserted YouTube-like URLs and directly open the referenced
                // channel, playlist or video instead of showing search results
                if (query.toHttpUrlOrNull() != null) {
                    val queryIntent = IntentHelper.resolveType(query.toUri())

                    val didNavigate = navigateToMediaByIntent(queryIntent) {
                        navController.popBackStack(R.id.searchFragment, true)
                        searchItem.collapseActionView()
                    }
                    if (didNavigate) return true
                }

                navController.navigate(NavDirections.showSearchResults(query))

                addSearchQueryToHistory(query)

                return true
            }

            override fun onQueryTextChange(newText: String?): Boolean {
                if (!shouldOpenSuggestions) return true

                // Prevent navigation when search view is collapsed
                if (searchView.isIconified ||
                    binding!!.bottomNav.menu.children.any {
                        it.itemId == navController.currentDestination?.id
                    }
                ) {
                    return true
                }

                // prevent malicious navigation when the search view is getting collapsed
                val destIds = listOf(
                    R.id.searchResultFragment,
                    R.id.channelFragment,
                    R.id.playlistFragment
                )
                if (navController.currentDestination?.id in destIds && newText == null) {
                    return false
                }

                when (currentSearchType) {
                    SearchType.ONLINE -> {
                        if (navController.currentDestination?.id != R.id.searchFragment) {
                            navController.navigate(
                                R.id.searchFragment,
                                bundleOf(IntentData.query to newText)
                            )
                        } else {
                            searchViewModel.setQuery(newText)
                        }
                    }

                    SearchType.PLAYLIST -> {
                        playlistViewModel.setQuery(newText)
                    }

                    SearchType.DOWNLOADS -> {
                        downloadViewModel.setQuery(newText)
                    }
                }

                return true
            }
        })

        searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
            override fun onMenuItemActionExpand(item: MenuItem): Boolean {
                if (currentSearchType == SearchType.ONLINE && navController.currentDestination?.id != R.id.searchResultFragment) {
                    searchViewModel.setQuery(null)
                    navController.navigate(R.id.openSearch)
                }
                item.setShowAsAction(
                    MenuItem.SHOW_AS_ACTION_ALWAYS or MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
                )
                return true
            }

            override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
                // Handover back press to `BackPressedDispatcher` if not on a root destination
                if (navController.previousBackStackEntry != null) {
                    this@MainActivity.onBackPressedDispatcher.onBackPressed()
                }

                // Suppress collapsing of search when search in progress.
                return !isSearchInProgress()
            }
        })

        // handle search queries passed by the intent
        if (savedSearchQuery != null) {
            searchItem.expandActionView()
            searchView.setQuery(savedSearchQuery, true)
            savedSearchQuery = null
        }

        return super.onCreateOptionsMenu(menu)
    }

    /**
     * Update the query text in the search bar without opening the search suggestions
     */
    fun setQuerySilent(query: String) {
        if (!this::searchView.isInitialized) return

        shouldOpenSuggestions = false
        searchView.setQuery(query, false)
        shouldOpenSuggestions = true
    }

    /**
     * Update the query text in the search bar and load the search suggestions
     * @param submit whether to immediately load the search results (not suggestions)
     */
    fun setQuery(query: String, submit: Boolean) {
        if (::searchView.isInitialized) searchView.setQuery(query, submit)
    }

    private fun loadIntentData() {
        // If activity is running in PiP mode, then start it in front.
        if (PictureInPictureCompat.isInPictureInPictureMode(this)) {
            val nIntent = Intent(this, MainActivity::class.java)
            nIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
            startActivity(nIntent)
        }

        if (intent?.getBooleanExtra(IntentData.maximizePlayer, false) == true) {
            // attempt to open the current video player fragment
            if (intent?.getBooleanExtra(IntentData.audioOnly, false) == false) {
                runOnPlayerFragment { binding!!.playerMotionLayout.transitionToStart(); true }
                return
            }

            // if it's an audio only session, attempt to maximize the audio player or create a new one
            if (runOnAudioPlayerFragment { binding!!.playerMotionLayout.transitionToStart(); true }) return

            val offlinePlayer = intent!!.getBooleanExtra(IntentData.offlinePlayer, false)
            NavigationHelper.openAudioPlayerFragment(this, offlinePlayer = offlinePlayer)
            return
        }

        // navigate to (temporary) playlist or channel if available
        if (navigateToMediaByIntent(intent)) return

        // Get saved search query if available
        intent?.getStringExtra(IntentData.query)?.let {
            savedSearchQuery = it
        }

        // Open the Downloads screen if requested
        if (intent?.getBooleanExtra(IntentData.OPEN_DOWNLOADS, false) == true) {
            navController.navigate(R.id.downloadsFragment)
            return
        }

        // Handle navigation from app shortcuts (Home, Trends, etc.)
        intent?.getStringExtra(IntentData.fragmentToOpen)?.let {
            ShortcutManagerCompat.reportShortcutUsed(this, it)
            when (it) {
                TopLevelDestination.Home.route -> navController.navigate(R.id.homeFragment)
                TopLevelDestination.Trends.route -> navController.navigate(R.id.trendsFragment)
                TopLevelDestination.Subscriptions.route -> navController.navigate(R.id.subscriptionsFragment)
                TopLevelDestination.Library.route -> navController.navigate(R.id.libraryFragment)
            }
        }

        // Rebind the download service if the user is currently downloading
        if (intent?.getBooleanExtra(IntentData.downloading, false) == true) {
            (supportFragmentManager.fragments.find { it is NavHostFragment })
                ?.childFragmentManager?.fragments?.forEach { fragment ->
                    (fragment as? DownloadsFragment)?.bindDownloadService()
                }
        }

        // Handle incoming shared text (possible playlist JSON)
        if (intent?.action == Intent.ACTION_SEND && intent.type?.startsWith("text") == true) {
            val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
            if (!sharedText.isNullOrEmpty()) {
                try {
                    val sharedPlaylist = JsonHelper.json.decodeFromString<Playlist>(sharedText)

                    val message = "Import playlist '${sharedPlaylist.name ?: ""}' with ${sharedPlaylist.relatedStreams.size} videos?"
                    MaterialAlertDialogBuilder(this)
                        .setTitle(getString(R.string.importsuccess))
                        .setMessage(message)
                        .setPositiveButton(R.string.importsuccess) { _, _ ->
                            lifecycleScope.launch(Dispatchers.IO) {
                                try {
                                    val newId = PlaylistsHelper.createPlaylist(sharedPlaylist.name ?: getString(R.string.createPlaylist))
                                        ?: run {
                                            withContext(Dispatchers.Main) {
                                                Toast.makeText(this@MainActivity, getString(R.string.error), Toast.LENGTH_SHORT).show()
                                            }
                                            return@launch
                                        }

                                    if (sharedPlaylist.relatedStreams.isNotEmpty()) {
                                        PlaylistsHelper.addToPlaylist(newId, *sharedPlaylist.relatedStreams.toTypedArray())
                                    }
                                    withContext(Dispatchers.Main) {
                                        Toast.makeText(this@MainActivity, getString(R.string.success), Toast.LENGTH_SHORT).show()
                                    }
                                } catch (e: Exception) {
                                    withContext(Dispatchers.Main) {
                                        Toast.makeText(this@MainActivity, getString(R.string.error), Toast.LENGTH_SHORT).show()
                                    }
                                }
                            }
                        }
                        .setNegativeButton(R.string.cancel, null)
                        .show()
                } catch (e: Exception) {
                    // Not a playlist JSON or failed to parse; ignore
                }
            }
        }
    }

    /**
     * Navigates to the channel, video or playlist provided in the [Intent] if available
     *
     * @return Whether the method handled the event and triggered the navigation to a new fragment
     */
    fun navigateToMediaByIntent(intent: Intent, actionBefore: () -> Unit = {}): Boolean {
        intent.getStringExtra(IntentData.channelId)?.let {
            actionBefore()
            navController.navigate(NavDirections.openChannel(channelId = it))
            return true
        }
        intent.getStringExtra(IntentData.channelName)?.let {
            actionBefore()
            navController.navigate(NavDirections.openChannel(channelName = it))
            return true
        }
        intent.getStringExtra(IntentData.playlistId)?.let {
            actionBefore()
            navController.navigate(NavDirections.openPlaylist(playlistId = it))
            return true
        }
        intent.getStringArrayExtra(IntentData.videoIds)?.let {
            actionBefore()
            ImportTempPlaylistDialog()
                .apply {
                    arguments = bundleOf(
                        IntentData.playlistName to intent.getStringExtra(IntentData.playlistName),
                        IntentData.videoIds to it
                    )
                }
                .show(supportFragmentManager, null)
            return true
        }

        intent.getStringExtra(IntentData.videoId)?.let {
            // the below explained work around only seems to work on Android 11 and above
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && binding!!.bottomNav.menu.isNotEmpty()) {
                // the bottom navigation bar has to be created before opening the video
                // otherwise the player layout measures aren't calculated properly
                // and the miniplayer is opened at a closed state and overlapping the navigation bar
                binding!!.bottomNav.viewTreeObserver.addOnGlobalLayoutListener(object :
                    ViewTreeObserver.OnGlobalLayoutListener {
                    override fun onGlobalLayout() {
                        navigationVideo(it)

                        binding!!.bottomNav.viewTreeObserver.removeOnGlobalLayoutListener(this)
                    }
                })
            } else {
                navigationVideo(it)
            }

            return true
        }

        return false
    }

    private fun navigationVideo(videoId: String) {
        NavigationHelper.navigateVideo(
            context = this@MainActivity,
            playerData = PlayerData(
                videoId = videoId,
                timestamp = intent.getLongExtra(IntentData.timeStamp, 0L)
            ),
        )
    }

    private fun navigateToBottomSelectedItem(item: MenuItem): Boolean {
        if (item.itemId == R.id.subscriptionsFragment) {
            binding!!.bottomNav.removeBadge(R.id.subscriptionsFragment)
        }

        // Remove focus from search view when navigating to bottom view.
        searchItem.collapseActionView()

        return item.onNavDestinationSelected(navController)
    }

    override fun onUserLeaveHint() {
        super.onUserLeaveHint()

        runOnPlayerFragment {
            onUserLeaveHint()
            true
        }
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        this.intent = intent
        loadIntentData()
    }

    override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
        if (runOnPlayerFragment { this@runOnPlayerFragment.onKeyUp(keyCode, event) }) {
            return true
        }

        return super.onKeyUp(keyCode, event)
    }

    fun startPlaylistExport(
        playlistId: String,
        playlistName: String,
        format: ImportFormat,
        includeTimestamp: Boolean
    ) {
        playlistExportFormat = format
        exportPlaylistId = playlistId

        val fileName =
            BackupRestoreSettings.getExportFileName(this, format, playlistName, includeTimestamp)
        createPlaylistsFile.launch(fileName)
    }

    private fun showUserInfoDialogIfNeeded() {
        // don't show the update information dialog for debug builds
        if (BuildConfig.DEBUG) return

        val lastShownVersionCode =
            PreferenceHelper.getInt(PreferenceKeys.LAST_SHOWN_INFO_MESSAGE_VERSION_CODE, -1)

        // mapping of version code to info message
        val infoMessages = emptyList<Pair<Int, String>>()

        val message =
            infoMessages.lastOrNull { (versionCode, _) -> versionCode > lastShownVersionCode }?.second
                ?: return

        MaterialAlertDialogBuilder(this)
            .setTitle(R.string.update_information)
            .setMessage(message)
            .setNegativeButton(R.string.okay, null)
            .setPositiveButton(R.string.never_show_again) { _, _ ->
                PreferenceHelper.putInt(
                    PreferenceKeys.LAST_SHOWN_INFO_MESSAGE_VERSION_CODE,
                    BuildConfig.VERSION_CODE
                )
            }
            .show()
    }

    override fun minimizePlayerContainerLayout() {
        binding!!.mainMotionLayout.transitionToEnd()
    }

    override fun maximizePlayerContainerLayout() {
        binding!!.mainMotionLayout.transitionToStart()

        // Ensure any orphaned loading indicators are hidden when restoring the player
        runOnPlayerFragment {
            try {
                hideVideoLoadingIndicator()
            } catch (_: Exception) {
            }
            true
        }

        // Also hide the audio fragment thumbnail loader if present
        runOnAudioPlayerFragment {
            try {
                binding.progress.visibility = View.GONE
            } catch (_: Exception) {
            }
            true
        }
    }

    override fun setPlayerContainerProgress(progress: Float) {
        if (!NavBarHelper.hasTabs()) return

        binding!!.mainMotionLayout.progress = progress
    }

    /**
     * @return whether the search view focus was cleared successfully
     */
    override fun clearSearchViewFocus(): Boolean {
        if (!this::searchView.isInitialized || !searchView.anyChildFocused()) return false

        searchView.clearFocus()
        return true
    }

    private fun tryScrollToTop(view: View?) {
        // Attempt to scroll various container types to top
        // Search for any ScrollView, NestedScrollView, or RecyclerView in the view hierarchy
        view?.let {
            val scrollView = findScrollView(it)
            if (scrollView != null) {
                scrollView.smoothScrollTo(0, 0)
                return@let
            }

            val nestedScrollView = findNestedScrollView(it)
            if (nestedScrollView != null) {
                nestedScrollView.smoothScrollTo(0, 0)
                return@let
            }

            val recyclerView = findRecyclerView(it)
            recyclerView?.smoothScrollToPosition(0)
        }
    }

    private fun findScrollView(view: View): ScrollView? {
        if (view is ScrollView) return view
        if (view is androidx.viewpager.widget.ViewPager) return null
        return when (view) {
            is androidx.viewpager2.widget.ViewPager2 -> null
            else -> (view as? android.view.ViewGroup)?.children?.firstNotNullOfOrNull { findScrollView(it) }
        }
    }

    private fun findNestedScrollView(view: View): NestedScrollView? {
        if (view is NestedScrollView) return view
        return (view as? android.view.ViewGroup)?.children?.firstNotNullOfOrNull { findNestedScrollView(it) }
    }

    private fun findRecyclerView(view: View): RecyclerView? {
        if (view is RecyclerView) return view
        return (view as? android.view.ViewGroup)?.children?.firstNotNullOfOrNull { findRecyclerView(it) }
    }


    private fun deselectBottomBarItems() {
        binding!!.bottomNav.menu.children.forEach {
            it.isChecked = false
        }
    }

    private fun setupSubscriptionsBadge() {
        // Add a badge to the subscriptions menu item if there are new subscriptions
        // This is a placeholder implementation
        if (subscriptionsViewModel.subscriptions.value?.isEmpty() != false) {
            binding!!.bottomNav.removeBadge(R.id.subscriptionsFragment)
        }
    }

    private fun addSearchQueryToHistory(query: String) {
        lifecycleScope.launch(Dispatchers.IO) {
            try {
                DatabaseHelper.addToSearchHistory(
                    SearchHistoryItem(
                        query = query
                    )
                )
            } catch (e: Exception) {
                // Silently ignore errors
            }
        }
    }

    private fun isSearchInProgress(): Boolean {
        // Check if the search view has text or is not iconified
        return if (this::searchView.isInitialized) {
            searchView.query.isNotEmpty() || !searchView.isIconified
        } else {
            false
        }
    }
}

