package com.futo.platformplayer

import android.app.NotificationManager
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.view.View
import android.view.ViewGroup
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.hls.playlist.DefaultHlsPlaylistParserFactory
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist
import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.media.models.ResultCapabilities
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor
import com.futo.platformplayer.api.media.models.streams.sources.HLSVariantAudioUrlSource
import com.futo.platformplayer.api.media.models.streams.sources.HLSVariantVideoUrlSource
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestAudioSource
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
import com.futo.platformplayer.api.media.platforms.js.JSClient
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSDashManifestRawAudioSource
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSDashManifestRawSource
import com.futo.platformplayer.downloads.VideoLocal
import com.futo.platformplayer.fragment.mainactivity.main.SubscriptionGroupFragment
import com.futo.platformplayer.helpers.VideoHelper
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.ImageVariable
import com.futo.platformplayer.models.Playlist
import com.futo.platformplayer.models.Subscription
import com.futo.platformplayer.models.SubscriptionGroup
import com.futo.platformplayer.parsers.HLS
import com.futo.platformplayer.parsers.HLS.MediaRendition
import com.futo.platformplayer.parsers.HLS.StreamInfo
import com.futo.platformplayer.parsers.HLS.VariantPlaylistReference
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StateDownloads
import com.futo.platformplayer.states.StateHistory
import com.futo.platformplayer.states.StateMeta
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.states.StatePlaylists
import com.futo.platformplayer.states.StateSubscriptionGroups
import com.futo.platformplayer.views.AnyAdapterView
import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny
import com.futo.platformplayer.views.LoaderView
import com.futo.platformplayer.views.adapters.viewholders.SubscriptionGroupBarViewHolder
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuFilters
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuGroup
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuRecycler
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuTextInput
import com.futo.platformplayer.views.pills.RoundButton
import com.futo.platformplayer.views.pills.RoundButtonGroup
import com.futo.platformplayer.views.video.FutoVideoPlayerBase
import isDownloadable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.ByteArrayInputStream
import androidx.core.net.toUri
import com.futo.platformplayer.fragment.mainactivity.main.SettingsFragment

class UISlideOverlays {
    companion object {
        private const val TAG = "UISlideOverlays";

        fun showOverlay(container: ViewGroup, title: String, okButton: String?, onOk: ()->Unit,  vararg views: View): SlideUpMenuOverlay {
            var menu = SlideUpMenuOverlay(container.context, container, title, okButton, true, *views);

            menu.onOK.subscribe {
                menu.hide();
                onOk.invoke();
            };
            menu.show();
            return menu;
        }

        fun showQueueOptionsOverlay(context: Context, container: ViewGroup) {
            UISlideOverlays.showOverlay(container, "Queue options", null, {

            }, SlideUpMenuItem(context, R.drawable.ic_playlist, "Save as playlist", "", "Creates a new playlist with queue as videos", null, {
                val nameInput = SlideUpMenuTextInput(container.context, container.context.getString(R.string.name));
                val addPlaylistOverlay = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.create_new_playlist), container.context.getString(R.string.ok), false, nameInput);

                addPlaylistOverlay.onOK.subscribe {
                    val text = nameInput.text.trim()
                    if (text.isBlank()) {
                        return@subscribe;
                    }

                    addPlaylistOverlay.hide();
                    nameInput.deactivate();
                    nameInput.clear();
                    StatePlayer.instance.saveQueueAsPlaylist(text);
                    UIDialogs.appToast("Playlist [${text}] created");
                };

                addPlaylistOverlay.onCancel.subscribe {
                    nameInput.deactivate();
                    nameInput.clear();
                };

                addPlaylistOverlay.show();
                nameInput.activate();
            }, false));
        }

        fun showSubscriptionOptionsOverlay(subscription: Subscription, container: ViewGroup): SlideUpMenuOverlay {
            val items = arrayListOf<View>();

            val originalNotif = subscription.doNotifications;
            val originalLive = subscription.doFetchLive;
            val originalStream = subscription.doFetchStreams;
            val originalVideo = subscription.doFetchVideos;
            val originalPosts = subscription.doFetchPosts;

            val menu = SlideUpMenuOverlay(
                container.context,
                container,
                "Subscription Settings",
                null,
                true,
                listOf()
            );

            StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
                try {
                    val plugin = StatePlatform.instance.getChannelClient(subscription.channel.url);
                    val capabilities = plugin.getChannelCapabilities();

                    withContext(Dispatchers.Main) {
                        items.addAll(
                            listOf(
                                SlideUpMenuItem(
                                    container.context,
                                    R.drawable.ic_notifications,
                                    "Notifications",
                                    "",
                                    tag = "notifications",
                                    call = {
                                        subscription.doNotifications =
                                            menu?.selectOption(null, "notifications", true, true)
                                                ?: subscription.doNotifications;
                                    },
                                    invokeParent = false
                                ),
                                if (StateSubscriptionGroups.instance.getSubscriptionGroups()
                                        .isNotEmpty()
                                )
                                    SlideUpMenuGroup(
                                        container.context, "Subscription Groups",
                                        "You can select which groups this subscription is part of.",
                                        -1, listOf()
                                    ) else null,
                                if (StateSubscriptionGroups.instance.getSubscriptionGroups()
                                        .isNotEmpty()
                                )
                                    SlideUpMenuRecycler(container.context, "as") {
                                        val groups =
                                            ArrayList<SubscriptionGroup>(
                                                StateSubscriptionGroups.instance.getSubscriptionGroups()
                                                    .map {
                                                        SubscriptionGroup.Selectable(
                                                            it,
                                                            it.urls.contains(subscription.channel.url)
                                                        )
                                                    }
                                                    .sortedBy { !it.selected });
                                        var adapter: AnyAdapterView<SubscriptionGroup, SubscriptionGroupBarViewHolder>? =
                                            null;
                                        adapter = it.asAny(groups, RecyclerView.HORIZONTAL) {
                                            it.onClick.subscribe {
                                                if (it is SubscriptionGroup.Selectable) {
                                                    val actualGroup =
                                                        StateSubscriptionGroups.instance.getSubscriptionGroup(
                                                            it.id
                                                        )
                                                            ?: return@subscribe;
                                                    groups.clear();
                                                    if (it.selected)
                                                        actualGroup.urls.remove(subscription.channel.url);
                                                    else
                                                        actualGroup.urls.add(subscription.channel.url);

                                                    StateSubscriptionGroups.instance.updateSubscriptionGroup(
                                                        actualGroup
                                                    );
                                                    groups.addAll(
                                                        StateSubscriptionGroups.instance.getSubscriptionGroups()
                                                            .map {
                                                                SubscriptionGroup.Selectable(
                                                                    it,
                                                                    it.urls.contains(subscription.channel.url)
                                                                )
                                                            }
                                                            .sortedBy { !it.selected });
                                                    adapter?.notifyContentChanged();
                                                }
                                            }
                                        };
                                        return@SlideUpMenuRecycler adapter;
                                    } else null,
                                SlideUpMenuGroup(
                                    container.context, "Fetch Settings",
                                    "Depending on the platform you might not need to enable a type for it to be available.",
                                    -1, listOf()
                                ),
                                if (capabilities.hasType(ResultCapabilities.TYPE_LIVE)) SlideUpMenuItem(
                                    container.context,
                                    R.drawable.ic_live_tv,
                                    "Livestreams",
                                    "Check for livestreams",
                                    tag = "fetchLive",
                                    call = {
                                        subscription.doFetchLive =
                                            menu?.selectOption(null, "fetchLive", true, true)
                                                ?: subscription.doFetchLive;
                                    },
                                    invokeParent = false
                                ) else null,
                                if (capabilities.hasType(ResultCapabilities.TYPE_STREAMS)) SlideUpMenuItem(
                                    container.context,
                                    R.drawable.ic_play,
                                    "Streams",
                                    "Check for streams",
                                    tag = "fetchStreams",
                                    call = {
                                        subscription.doFetchStreams =
                                            menu?.selectOption(null, "fetchStreams", true, true)
                                                ?: subscription.doFetchStreams;
                                    },
                                    invokeParent = false
                                ) else null,
                                if (capabilities.hasType(ResultCapabilities.TYPE_VIDEOS))
                                    SlideUpMenuItem(
                                        container.context,
                                        R.drawable.ic_play,
                                        "Videos",
                                        "Check for videos",
                                        tag = "fetchVideos",
                                        call = {
                                            subscription.doFetchVideos =
                                                menu?.selectOption(null, "fetchVideos", true, true)
                                                    ?: subscription.doFetchVideos;
                                        },
                                        invokeParent = false
                                    ) else if (capabilities.hasType(ResultCapabilities.TYPE_MIXED) || capabilities.types.isEmpty())
                                    SlideUpMenuItem(
                                        container.context,
                                        R.drawable.ic_play,
                                        "Content",
                                        "Check for content",
                                        tag = "fetchVideos",
                                        call = {
                                            subscription.doFetchVideos =
                                                menu?.selectOption(null, "fetchVideos", true, true)
                                                    ?: subscription.doFetchVideos;
                                        },
                                        invokeParent = false
                                    ) else null,
                                if (capabilities.hasType(ResultCapabilities.TYPE_POSTS)) SlideUpMenuItem(
                                    container.context,
                                    R.drawable.ic_chat,
                                    "Posts",
                                    "Check for posts",
                                    tag = "fetchPosts",
                                    call = {
                                        subscription.doFetchPosts =
                                            menu?.selectOption(null, "fetchPosts", true, true)
                                                ?: subscription.doFetchPosts;
                                    },
                                    invokeParent = false
                                ) else null/*,,

                        SlideUpMenuGroup(container.context, "Actions",
                            "Various things you can do with this subscription",
                            -1, listOf())
                        SlideUpMenuItem(container.context, R.drawable.ic_list, "Add to Group", "", "btnAddToGroup", {
                            showCreateSubscriptionGroup(container, subscription.channel);
                        }, false)*/
                            ).filterNotNull()
                        );

                        menu.setItems(items);

                        if (subscription.doNotifications)
                            menu.selectOption(null, "notifications", true, true);
                        if (subscription.doFetchLive)
                            menu.selectOption(null, "fetchLive", true, true);
                        if (subscription.doFetchStreams)
                            menu.selectOption(null, "fetchStreams", true, true);
                        if (subscription.doFetchVideos)
                            menu.selectOption(null, "fetchVideos", true, true);
                        if (subscription.doFetchPosts)
                            menu.selectOption(null, "fetchPosts", true, true);

                        menu.onOK.subscribe {
                            subscription.save();
                            menu.hide(true);

                            if (subscription.doNotifications && !originalNotif) {
                                val mainContext = StateApp.instance.contextOrNull;
                                if (Settings.instance.subscriptions.subscriptionsBackgroundUpdateInterval == 0) {
                                    UIDialogs.toast(
                                        container.context,
                                        "Enable 'Background Update' in settings for notifications to work"
                                    );

                                    if (mainContext is MainActivity) {
                                        UIDialogs.showDialog(
                                            mainContext,
                                            R.drawable.ic_settings,
                                            "Background Updating Required",
                                            "You need to set a Background Updating interval for notifications",
                                            null,
                                            0,
                                            UIDialogs.Action("Cancel", {}),
                                            UIDialogs.Action("Configure", {
                                                StateApp.instance.activity?.let {
                                                    it.navigate(it.getFragment<SettingsFragment>(), mainContext.getString(R.string.background_update))
                                                }
                                            }, UIDialogs.ActionStyle.PRIMARY)
                                        );
                                    }
                                    return@subscribe;
                                } else if (!(mainContext?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).areNotificationsEnabled()) {
                                    UIDialogs.toast(
                                        container.context,
                                        "Android notifications are disabled"
                                    );
                                    if (mainContext is MainActivity) {
                                        mainContext.requestNotificationPermissions("Notifications are required for subscription updating and notifications to work");
                                    }
                                }
                            }
                        };
                        menu.onCancel.subscribe {
                            subscription.doNotifications = originalNotif;
                            subscription.doFetchLive = originalLive;
                            subscription.doFetchStreams = originalStream;
                            subscription.doFetchVideos = originalVideo;
                            subscription.doFetchPosts = originalPosts;
                        };

                        menu.setOk("Save");

                        menu.show();
                    }
                } catch (e: Throwable) {
                    Logger.e(TAG, "Failed to show subscription overlay.", e)
                }
            }

            return menu;
        }

        fun showAddToGroupOverlay(channel: IPlatformVideo, container: ViewGroup) {

        }

        @OptIn(UnstableApi::class)
        fun showHlsPicker(video: IPlatformVideoDetails, source: Any, sourceUrl: String, container: ViewGroup): SlideUpMenuOverlay {
            val items = arrayListOf<View>(LoaderView(container.context))
            val slideUpMenuOverlay = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.download_video), null, true, items)

            StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
                val masterPlaylistResponse = ManagedHttpClient().get(sourceUrl)
                check(masterPlaylistResponse.isOk) { "Failed to get master playlist: ${masterPlaylistResponse.code}" }

                val masterPlaylistContent = masterPlaylistResponse.body?.string()
                    ?: throw Exception("Master playlist content is empty")

                val resolvedPlaylistUrl = masterPlaylistResponse.url

                val videoButtons = arrayListOf<SlideUpMenuItem>()
                val audioButtons = arrayListOf<SlideUpMenuItem>()
                //TODO: Implement subtitles
                //val subtitleButtons = arrayListOf<SlideUpMenuItem>()

                var selectedVideoVariant: HLSVariantVideoUrlSource? = null
                var selectedAudioVariant: HLSVariantAudioUrlSource? = null
                //TODO: Implement subtitles
                //var selectedSubtitleVariant: HLSVariantSubtitleUrlSource? = null

                val masterPlaylist: HLS.MasterPlaylist
                try {
                    val inputStream = ByteArrayInputStream(masterPlaylistContent.toByteArray())
                    val playlist = DefaultHlsPlaylistParserFactory().createPlaylistParser()
                        .parse(sourceUrl.toUri(), inputStream)

                    if (playlist is HlsMediaPlaylist) {
                        if (source is IHLSManifestAudioSource) {
                            val variant = HLS.mediaRenditionToVariant(MediaRendition("AUDIO", playlist.baseUri, "Single Playlist", null, null, null, null, null))!!

                            val estSize = VideoHelper.estimateSourceSize(variant);
                            val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
                            audioButtons.add(SlideUpMenuItem(
                                container.context,
                                R.drawable.ic_music,
                                variant.name,
                                listOf(variant.language, variant.codec).mapNotNull { x -> x.ifEmpty { null } }.joinToString(", "),
                                (prefix + variant.codec).trim(),
                                tag = variant,
                                call = {
                                    selectedAudioVariant = variant
                                    slideUpMenuOverlay.selectOption(audioButtons, variant)
                                    slideUpMenuOverlay.setOk(container.context.getString(R.string.download))
                                },
                                invokeParent = false
                            ))
                        } else {
                            val variant = HLS.variantReferenceToVariant(VariantPlaylistReference(playlist.baseUri, StreamInfo(null, null, null, null, null, null, null, null, null)))

                            val estSize = VideoHelper.estimateSourceSize(variant);
                            val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
                            videoButtons.add(SlideUpMenuItem(
                                container.context,
                                R.drawable.ic_movie,
                                variant.name,
                                "${variant.width}x${variant.height}",
                                (prefix + variant.codec).trim(),
                                tag = variant,
                                call = {
                                    selectedVideoVariant = variant
                                    slideUpMenuOverlay.selectOption(videoButtons, variant)
                                    if (audioButtons.isEmpty()){
                                        slideUpMenuOverlay.setOk(container.context.getString(R.string.download))
                                    }
                                },
                                invokeParent = false
                            ))
                        }
                    } else if (playlist is HlsMultivariantPlaylist) {
                        masterPlaylist = HLS.parseMasterPlaylist(masterPlaylistContent, resolvedPlaylistUrl)

                        masterPlaylist.getAudioSources().forEach { it ->

                            val estSize = VideoHelper.estimateSourceSize(it);
                            val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
                            audioButtons.add(SlideUpMenuItem(
                                container.context,
                                R.drawable.ic_music,
                                it.name,
                                listOf(it.language, it.codec).mapNotNull { x -> x.ifEmpty { null } }.joinToString(", "),
                                (prefix + it.codec).trim(),
                                tag = it,
                                call = {
                                    selectedAudioVariant = it
                                    slideUpMenuOverlay.selectOption(audioButtons, it)
                                    slideUpMenuOverlay.setOk(container.context.getString(R.string.download))
                                },
                                invokeParent = false
                            ))
                        }

                        /*masterPlaylist.getSubtitleSources().forEach { it ->
                            subtitleButtons.add(SlideUpMenuItem(container.context, R.drawable.ic_music, it.name, listOf(it.format).mapNotNull { x -> x.ifEmpty { null } }.joinToString(", "), it, {
                                selectedSubtitleVariant = it
                                slideUpMenuOverlay.selectOption(subtitleButtons, it)
                                slideUpMenuOverlay.setOk(container.context.getString(R.string.download))
                            }, false))
                        }*/

                        masterPlaylist.getVideoSources().forEach {
                            val estSize = VideoHelper.estimateSourceSize(it);
                            val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
                            videoButtons.add(SlideUpMenuItem(
                                container.context,
                                R.drawable.ic_movie,
                                it.name,
                                "${it.width}x${it.height}",
                                (prefix + it.codec).trim(),
                                tag = it,
                                call = {
                                    selectedVideoVariant = it
                                    slideUpMenuOverlay.selectOption(videoButtons, it)
                                    if (audioButtons.isEmpty()){
                                        slideUpMenuOverlay.setOk(container.context.getString(R.string.download))
                                    }
                                },
                                invokeParent = false
                            ))
                        }
                    }

                    val newItems = arrayListOf<View>()
                    if (videoButtons.isNotEmpty()) {
                        newItems.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.video), videoButtons, videoButtons))
                    }
                    if (audioButtons.isNotEmpty()) {
                        newItems.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.audio), audioButtons, audioButtons))
                    }
                    //TODO: Implement subtitles
                    /*if (subtitleButtons.isNotEmpty()) {
                        newItems.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.subtitles), subtitleButtons, subtitleButtons))
                    }*/

                    slideUpMenuOverlay.onOK.subscribe {
                        //TODO: Fix SubtitleRawSource issue
                        StateDownloads.instance.download(video, selectedVideoVariant, selectedAudioVariant, null);
                        slideUpMenuOverlay.hide()
                    }

                    withContext(Dispatchers.Main) {
                        slideUpMenuOverlay.setItems(newItems)
                    }
                } catch (e: Throwable) {
                    if (masterPlaylistContent.lines().any { it.startsWith("#EXTINF:") }) {
                        withContext(Dispatchers.Main) {
                            if (source is IHLSManifestSource) {
                                StateDownloads.instance.download(video, HLSVariantVideoUrlSource("variant", 0, 0, "application/vnd.apple.mpegurl", "", null, 0, false, resolvedPlaylistUrl), null, null)
                                UIDialogs.toast(container.context, "Variant video HLS playlist download started")
                                slideUpMenuOverlay.hide()
                            } else if (source is IHLSManifestAudioSource) {
                                StateDownloads.instance.download(video, null, HLSVariantAudioUrlSource("variant", 0, "application/vnd.apple.mpegurl", "", "", null, false, false, resolvedPlaylistUrl), null)
                                UIDialogs.toast(container.context, "Variant audio HLS playlist download started")
                                slideUpMenuOverlay.hide()
                            } else {
                                throw NotImplementedError()
                            }
                        }
                    } else {
                        throw e
                    }
                }
            }

            return slideUpMenuOverlay.apply { show() }

        }

        fun showDownloadVideoOverlay(video: IPlatformVideoDetails, container: ViewGroup, contentResolver: ContentResolver? = null): SlideUpMenuOverlay? {
            val items = arrayListOf<View>();
            var menu: SlideUpMenuOverlay? = null;

            var descriptor = video.video;
            if(video is VideoLocal)
                descriptor = video.videoSerialized.video;


            val requiresAudio = descriptor is VideoUnMuxedSourceDescriptor;
            var selectedVideo: IVideoSource? = null;
            var selectedAudio: IAudioSource? = null;
            var selectedSubtitle: ISubtitleSource? = null;

            val videoSources = descriptor.videoSources;
            val audioSources = if(descriptor is VideoUnMuxedSourceDescriptor) descriptor.audioSources else null;
            val subtitleSources = video.subtitles;

            if(videoSources.isEmpty() && (audioSources?.size ?: 0) == 0) {
                UIDialogs.toast(container.context.getString(R.string.no_downloads_available), false);
                return null;
            }

            if(!VideoHelper.isDownloadable(video)) {
                Logger.i(TAG, "Attempted to open downloads without valid sources for [${video.name}]: ${video.url}");
                UIDialogs.toast( container.context.getString(R.string.no_downloadable_sources_yet));
                return null;
            }

            items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.video), videoSources,
                listOf((if (audioSources != null) listOf(SlideUpMenuItem(
                    container.context,
                    R.drawable.ic_movie,
                    container.context.getString(R.string.none),
                    container.context.getString(R.string.audio_only),
                    tag = "none",
                    call = {
                        selectedVideo = null;
                        menu?.selectOption(videoSources, "none");
                        if(selectedAudio != null || !requiresAudio)
                            menu?.setOk(container.context.getString(R.string.download));
                    },
                    invokeParent = false
                )) else listOf()) +
                videoSources
                .filter { it.isDownloadable() }
                .map {
                    when (it) {
                        is IVideoUrlSource -> {
                            val estSize = VideoHelper.estimateSourceSize(it);
                            val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
                            SlideUpMenuItem(
                                container.context,
                                R.drawable.ic_movie,
                                it.name,
                                "${it.width}x${it.height}",
                                (prefix + it.codec).trim(),
                                tag = it,
                                call = {
                                    selectedVideo = it
                                    menu?.selectOption(videoSources, it);
                                    if(selectedAudio != null || !requiresAudio)
                                        menu?.setOk(container.context.getString(R.string.download));
                                },
                                invokeParent = false
                            )
                        }

                        is JSDashManifestRawSource -> {
                            val estSize = VideoHelper.estimateSourceSize(it);
                            val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
                            SlideUpMenuItem(
                                container.context,
                                R.drawable.ic_movie,
                                it.name,
                                "${it.width}x${it.height}",
                                (prefix + it.codec).trim(),
                                tag = it,
                                call = {
                                    selectedVideo = it
                                    menu?.selectOption(videoSources, it);
                                    if(selectedAudio != null || !requiresAudio)
                                        menu?.setOk(container.context.getString(R.string.download));
                                },
                                invokeParent = false
                            )
                        }

                        is IHLSManifestSource -> {
                            SlideUpMenuItem(
                                container.context,
                                R.drawable.ic_movie,
                                it.name,
                                "HLS",
                                tag = it,
                                call = {
                                    showHlsPicker(video, it, it.url, container)
                                },
                                invokeParent = false
                            )
                        }

                        else -> {
                            Logger.w(TAG, "Unhandled source type for UISlideOverlay download items");
                            null;//throw Exception("Unhandled source type")
                        }
                    }
                }.filterNotNull()).flatten().toList()
            ));

            if(Settings.instance.downloads.getDefaultVideoQualityPixels() > 0 && videoSources.isNotEmpty()) {
                //TODO: Add HLS support here
                selectedVideo = VideoHelper.selectBestVideoSource(
                    videoSources.filter { it is IVideoSource && it.isDownloadable() }.asIterable(),
                    Settings.instance.downloads.getDefaultVideoQualityPixels(),
                    FutoVideoPlayerBase.PREFERED_VIDEO_CONTAINERS
                ) as IVideoSource?;
            }

            if (audioSources != null) {
                items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.audio), audioSources, audioSources
                    .filter { VideoHelper.isDownloadable(it) }
                    .map {
                        when (it) {
                            is IAudioUrlSource -> {
                                val estSize = VideoHelper.estimateSourceSize(it);
                                val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
                                SlideUpMenuItem(
                                    container.context,
                                    R.drawable.ic_music,
                                    it.name,
                                    "${it.bitrate}",
                                    (prefix + it.codec).trim(),
                                    tag = it,
                                    call = {
                                        selectedAudio = it
                                        menu?.selectOption(audioSources, it);
                                        menu?.setOk(container.context.getString(R.string.download));
                                    },
                                    invokeParent = false
                                );
                            }

                            is JSDashManifestRawAudioSource -> {
                                val estSize = VideoHelper.estimateSourceSize(it);
                                val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
                                SlideUpMenuItem(
                                    container.context,
                                    R.drawable.ic_music,
                                    it.name,
                                    "${it.bitrate}",
                                    (prefix + it.codec).trim(),
                                    tag = it,
                                    call = {
                                        selectedAudio = it
                                        menu?.selectOption(audioSources, it);
                                        menu?.setOk(container.context.getString(R.string.download));
                                    },
                                    invokeParent = false
                                );
                            }

                            is IHLSManifestAudioSource -> {
                                SlideUpMenuItem(
                                    container.context,
                                    R.drawable.ic_movie,
                                    it.name,
                                    "HLS Audio",
                                    tag = it,
                                    call = {
                                        showHlsPicker(video, it, it.url, container)
                                    },
                                    invokeParent = false
                                )
                            }

                            else -> {
                                Logger.w(TAG, "Unhandled source type for UISlideOverlay download items");
                                null;//throw Exception("Unhandled source type")
                            }
                        }
                    }.filterNotNull()));

                //TODO: Add HLS support here
                selectedAudio = VideoHelper.selectBestAudioSource(audioSources.filter { it is IAudioSource && it.isDownloadable() }.asIterable(),
                    FutoVideoPlayerBase.PREFERED_AUDIO_CONTAINERS,
                    Settings.instance.playback.getPrimaryLanguage(container.context),
                    if(Settings.instance.downloads.isHighBitrateDefault()) 9999999 else 1) as IAudioSource?;
            }

            if(contentResolver != null && subtitleSources.isNotEmpty()) {
                items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.subtitles), subtitleSources, subtitleSources.map {
                        SlideUpMenuItem(
                            container.context,
                            R.drawable.ic_edit,
                            it.name,
                            "",
                            tag = it,
                            call = {
                                if (selectedSubtitle == it) {
                                    selectedSubtitle = null;
                                    menu?.selectOption(subtitleSources, null);
                                } else {
                                    selectedSubtitle = it;
                                    menu?.selectOption(subtitleSources, it);
                                }
                            },
                            invokeParent = false
                        );
                    })
                );
            }

            menu = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.download_video), null, true, items);

            if(selectedVideo != null) {
                menu.selectOption(videoSources, selectedVideo);
            }
            if(selectedAudio != null) {
                audioSources?.let { audioSources -> menu.selectOption(audioSources, selectedAudio); };
            }
            if(selectedAudio != null || (!requiresAudio && selectedVideo != null)) {
                menu.setOk(container.context.getString(R.string.download));
            }

            menu.onOK.subscribe {
                val sv = selectedVideo
                if (sv is IHLSManifestSource) {
                    showHlsPicker(video, sv, sv.url, container)
                    return@subscribe
                }

                val sa = selectedAudio
                if (sa is IHLSManifestAudioSource) {
                    showHlsPicker(video, sa, sa.url, container)
                    return@subscribe
                }

                menu.hide();
                val subtitleToDownload = selectedSubtitle;
                if(selectedAudio != null || !requiresAudio) {
                    if (subtitleToDownload == null) {
                        StateDownloads.instance.download(video, selectedVideo, selectedAudio, null);
                    } else {
                        //TODO: Clean this up somewhere else, maybe pre-fetch instead of dup calls
                        StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
                            try {
                                val subtitleUri = subtitleToDownload.getSubtitlesURI();
                                //TODO: Remove uri dependency, should be able to work with raw aswell?
                                if (subtitleUri != null && contentResolver != null) {
                                    val subtitlesRaw = StateDownloads.instance.downloadSubtitles(subtitleToDownload, contentResolver);

                                    withContext(Dispatchers.Main) {
                                        StateDownloads.instance.download(video, selectedVideo, selectedAudio, subtitlesRaw);
                                    }
                                } else {
                                    withContext(Dispatchers.Main) {
                                        StateDownloads.instance.download(video, selectedVideo, selectedAudio, null);
                                    }
                                }
                            } catch (e: Throwable) {
                                Logger.e(TAG, "Failed download subtitles.", e);
                            }
                        }
                    }
                    if(!Settings.instance.downloads.shouldDownload()) {
                        UIDialogs.appToast("Download will start when you're back on wifi.\n" +
                                "(You can change this in settings)", true);
                    }
                }
            };
            return menu.apply { show() };
        }
        fun showDownloadVideoOverlay(video: IPlatformVideo, container: ViewGroup, useDetails: Boolean = false) {
            val handleUnknownDownload: ()->Unit = {
                showUnknownVideoDownload(container.context.getString(R.string.video), container) { px, bitrate ->
                    StateDownloads.instance.download(video, px, bitrate)
                };
            };
            if(!useDetails)
                handleUnknownDownload();
            else {
                val scope = StateApp.instance.scopeOrNull;

                if(scope != null) {
                    val loader = showLoaderOverlay(container.context.getString(R.string.fetching_video_details), container);
                    scope.launch(Dispatchers.IO) {
                        try {
                            val videoDetails = StatePlatform.instance.getContentDetails(video.url, false).await();
                            if(videoDetails !is IPlatformVideoDetails)
                                throw IllegalStateException("Not a video details");

                            withContext(Dispatchers.Main) {
                                if(showDownloadVideoOverlay(videoDetails, container, StateApp.instance.contextOrNull?.contentResolver) == null)
                                    loader.hide(true);
                            }
                        }
                        catch(ex: Throwable) {
                            Logger.e(TAG, "Fetching details for download failed due to: " + ex.message, ex);
                            withContext(Dispatchers.Main) {
                                UIDialogs.toast(container.context.getString(R.string.failed_to_fetch_details_for_download) + "\n" + ex.message);
                                handleUnknownDownload();
                                loader.hide(true);
                            }
                        }
                    }
                }
                else handleUnknownDownload();
            }
        }
        fun showDownloadPlaylistOverlay(playlist: Playlist, container: ViewGroup) {
            showUnknownVideoDownload(container.context.getString(R.string.playlist), container) { px, bitrate ->
                StateDownloads.instance.download(playlist, px, bitrate);
            };
        }
        fun showDownloadWatchlaterOverlay(container: ViewGroup) {
            showUnknownVideoDownload(container.context.getString(R.string.watch_later), container, { px, bitrate ->
                StateDownloads.instance.downloadWatchLater(px, bitrate);
            })
        }
        private fun showUnknownVideoDownload(toDownload: String, container: ViewGroup, cb: (Long?, Long?)->Unit) {
            val items = arrayListOf<View>();
            var menu: SlideUpMenuOverlay? = null;

            var targetPxSize: Long = 0;
            var targetBitrate: Long = 0;

            val resolutions = listOf(
                Triple<String, String, Long>(container.context.getString(R.string.none), container.context.getString(R.string.none), -1),
                Triple<String, String, Long>("480P", "720x480", 720*480),
                Triple<String, String, Long>("720P", "1280x720", 1280*720),
                Triple<String, String, Long>("1080P", "1920x1080", 1920*1080),
                Triple<String, String, Long>("1440P", "2560x1440", 2560*1440),
                Triple<String, String, Long>("2160P", "3840x2160", 3840*2160)
            );

            items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.target_resolution), "Video", resolutions.map {
                SlideUpMenuItem(
                    container.context,
                    R.drawable.ic_movie,
                    it.first,
                    it.second,
                    tag = it.third,
                    call = {
                        targetPxSize = it.third;
                        menu?.selectOption("Video", it.third);
                    },
                    invokeParent = false
                )
            }));

            items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.target_bitrate), "Bitrate", listOf(
                SlideUpMenuItem(
                    container.context,
                    R.drawable.ic_movie,
                    container.context.getString(R.string.low_bitrate),
                    "",
                    tag = 1,
                    call = {
                        targetBitrate = 1;
                        menu?.selectOption("Bitrate", 1);
                        menu?.setOk(container.context.getString(R.string.download));
                    },
                    invokeParent = false
                ),
                SlideUpMenuItem(
                    container.context,
                    R.drawable.ic_movie,
                    container.context.getString(R.string.high_bitrate),
                    "",
                    tag = 9999999,
                    call = {
                        targetBitrate = 9999999;
                        menu?.selectOption("Bitrate", 9999999);
                        menu?.setOk(container.context.getString(R.string.download));
                    },
                    invokeParent = false
                )
            )));


            menu = SlideUpMenuOverlay(container.context, container, "Download " + toDownload, null, true, items);

            if(Settings.instance.downloads.getDefaultVideoQualityPixels() != 0) {
                val defTarget = Settings.instance.downloads.getDefaultVideoQualityPixels();
                if(defTarget == -1) {
                    targetPxSize = -1;
                    menu.selectOption("Video", (-1).toLong());
                }
                else {
                    targetPxSize = resolutions.drop(1).minBy { Math.abs(defTarget - it.third) }.third;
                    menu.selectOption("Video", targetPxSize);
                }
            }
            if(Settings.instance.downloads.isHighBitrateDefault()) {
                targetBitrate = 9999999;
                menu.selectOption("Bitrate", 9999999);
                menu.setOk(container.context.getString(R.string.download));
            }
            else {
                targetBitrate = 1;
                menu.selectOption("Bitrate", 1);
                menu.setOk(container.context.getString(R.string.download));
            }

            menu.onOK.subscribe {
                menu.hide();
                cb(if(targetPxSize > 0) targetPxSize else null, if(targetBitrate > 0) targetBitrate else null);
            };
            menu.show();
        }

        fun showLoaderOverlay(text: String, container: ViewGroup): SlideUpMenuOverlay {
            val dp70 = 70.dp(container.context.resources);
            val dp15 = 15.dp(container.context.resources);
            val overlay = SlideUpMenuOverlay(container.context, container, text, null, true, listOf(
                LoaderView(container.context, true, dp70).apply {
                    this.setPadding(0, dp15, 0, dp15);
                }
            ), true);
            overlay.show();
            return overlay;
        }

        fun showCreateSubscriptionGroup(container: ViewGroup, initialChannel: IPlatformChannel? = null, onCreate: ((String) -> Unit)? = null): SlideUpMenuOverlay {
            val nameInput = SlideUpMenuTextInput(container.context, container.context.getString(R.string.name));
            val addSubGroupOverlay = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.create_new_subgroup), container.context.getString(R.string.ok), false, nameInput);

            addSubGroupOverlay.onOK.subscribe {
                val text = nameInput.text;
                if (text.isBlank()) {
                    return@subscribe;
                }

                addSubGroupOverlay.hide();
                nameInput.deactivate();
                nameInput.clear();
                if(onCreate == null)
                {
                    //TODO: Do this better, temp
                    StateApp.instance.contextOrNull?.let {
                        if(it is MainActivity) {
                            val subGroup = SubscriptionGroup(text);
                            if(initialChannel != null) {
                                subGroup.urls.add(initialChannel.url);
                                if(initialChannel.thumbnail != null)
                                    subGroup.image = ImageVariable(initialChannel.thumbnail);
                            }
                            it.navigate(it.getFragment<SubscriptionGroupFragment>(), subGroup);
                        }
                    }
                }
                else
                    onCreate(text)
            };

            addSubGroupOverlay.onCancel.subscribe {
                nameInput.deactivate();
                nameInput.clear();
            };

            addSubGroupOverlay.show();
            nameInput.activate();

            return addSubGroupOverlay
        }
        fun showCreatePlaylistOverlay(container: ViewGroup, onCreate: (String) -> Unit): SlideUpMenuOverlay {
            val nameInput = SlideUpMenuTextInput(container.context, container.context.getString(R.string.name));
            val addPlaylistOverlay = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.create_new_playlist), container.context.getString(R.string.ok), false, nameInput);

            addPlaylistOverlay.onOK.subscribe {
                val text = nameInput.text;
                if (text.isBlank()) {
                    return@subscribe;
                }

                addPlaylistOverlay.hide();
                nameInput.deactivate();
                nameInput.clear();
                onCreate(text)
            };

            addPlaylistOverlay.onCancel.subscribe {
                nameInput.deactivate();
                nameInput.clear();
            };

            addPlaylistOverlay.show();
            nameInput.activate();

            return addPlaylistOverlay
        }

        fun showVideoOptionsOverlay(video: IPlatformVideo, container: ViewGroup, vararg actions: SlideUpMenuItem): SlideUpMenuOverlay {
            val items = arrayListOf<View>();
            val lastUpdated = StatePlaylists.instance.getLastUpdatedPlaylist();

            val isLimited = video?.url != null && StatePlatform.instance.getContentClientOrNull(video!!.url)?.let {
                if (it is JSClient)
                    return@let it.config.reduceFunctionsInLimitedVersion && BuildConfig.IS_PLAYSTORE_BUILD
                else false;
            } ?: false;

            if (lastUpdated != null) {
                items.add(
                    SlideUpMenuGroup(container.context, container.context.getString(R.string.recently_used_playlist), "recentlyusedplaylist",
                        SlideUpMenuItem(container.context,
                            R.drawable.ic_playlist_add,
                            lastUpdated.name,
                            "${lastUpdated.videos.size} " + container.context.getString(R.string.videos),
                            tag = "",
                            call = {
                                if(StatePlaylists.instance.addToPlaylist(lastUpdated.id, video))
                                    UIDialogs.appToast("Added to playlist [${lastUpdated?.name}]", false);
                                StateDownloads.instance.checkForOutdatedPlaylists();
                            }))
                );
            }

            val allPlaylists = StatePlaylists.instance.getPlaylists();
            val queue = StatePlayer.instance.getQueue();
            val watchLater = StatePlaylists.instance.getWatchLater();
            items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.actions), "actions",
                (listOf(
                    if(!isLimited && !video.isLive)
                        SlideUpMenuItem(
                            container.context,
                            R.drawable.ic_download,
                            container.context.getString(R.string.download),
                            container.context.getString(R.string.download_the_video),
                            tag = "download",
                            call = {
                                showDownloadVideoOverlay(video, container, true);
                            },
                            invokeParent = false
                        ) else null,
                    SlideUpMenuItem(
                        container.context,
                        R.drawable.ic_share,
                        container.context.getString(R.string.share),
                        "Share the video",
                        tag = "share",
                        call = {
                            val url = if(video.shareUrl.isNotEmpty()) video.shareUrl else video.url;
                            container.context.startActivity(Intent.createChooser(Intent().apply {
                                action = Intent.ACTION_SEND;
                                putExtra(Intent.EXTRA_TEXT, url);
                                type = "text/plain";
                            }, null));
                        },
                        invokeParent = false
                    ),
                    SlideUpMenuItem(
                        container.context,
                        R.drawable.ic_visibility_off,
                        container.context.getString(R.string.hide_creator_from_home),
                        "",
                        tag = "hide_creator",
                        call = {
                            StateMeta.instance.addHiddenCreator(video.author.url);
                            UIDialogs.toast(container.context, "[${video.author.name}] hidden, you may need to reload home");
                        }))
                        + actions).filterNotNull()
            ));
            items.add(
                SlideUpMenuGroup(
                    container.context, container.context.getString(R.string.add_to), "addto",
                    SlideUpMenuItem(
                        container.context,
                        R.drawable.ic_queue_add,
                        container.context.getString(R.string.add_to_queue),
                        "${queue.size} " + container.context.getString(R.string.videos),
                        tag = "queue",
                        call = { StatePlayer.instance.addToQueue(video); }),
                    SlideUpMenuItem(
                        container.context,
                        R.drawable.ic_watchlist_add,
                        "${container.context.getString(R.string.add_to)} " + StatePlayer.TYPE_WATCHLATER + "",
                        "${watchLater.size} " + container.context.getString(R.string.videos),
                        tag = "watch later",
                        call = { StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video), true); }),
                    SlideUpMenuItem(
                        container.context,
                        R.drawable.ic_history,
                        container.context.getString(R.string.add_to_history),
                        "Mark as watched",
                        tag = "history",
                        call = { StateHistory.instance.markAsWatched(video); }),
                ));

            val playlistItems = arrayListOf<SlideUpMenuItem>();
            playlistItems.add(SlideUpMenuItem(
                container.context,
                R.drawable.ic_playlist_add,
                container.context.getString(R.string.new_playlist),
                container.context.getString(R.string.add_to_new_playlist),
                tag = "add_to_new_playlist",
                call = {
                    showCreatePlaylistOverlay(container) {
                        val playlist = Playlist(it, arrayListOf(SerializedPlatformVideo.fromVideo(video)));
                        StatePlaylists.instance.createOrUpdatePlaylist(playlist);
                    };
                },
                invokeParent = false
            ))

            for (playlist in allPlaylists) {
                playlistItems.add(SlideUpMenuItem(container.context,
                    R.drawable.ic_playlist_add,
                    "${container.context.getString(R.string.add_to)} " + playlist.name + "",
                    "${playlist.videos.size} " + container.context.getString(R.string.videos),
                    tag = "",
                    call = {
                        if(StatePlaylists.instance.addToPlaylist(playlist.id, video))
                            UIDialogs.appToast("Added to playlist [${playlist.name}]", false);
                        StateDownloads.instance.checkForOutdatedPlaylists();
                    }));
            }

            if(playlistItems.size > 0)
                items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.playlists), "", playlistItems));

            return SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.video_options), null, true, items).apply { show() };
        }


        fun showAddToOverlay(video: IPlatformVideo, container: ViewGroup, slideUpMenuOverlayUpdated: (SlideUpMenuOverlay) -> Unit): SlideUpMenuOverlay {

            val items = arrayListOf<View>();

            val lastUpdated = StatePlaylists.instance.getLastUpdatedPlaylist();

            if (lastUpdated != null) {
                items.add(
                    SlideUpMenuGroup(container.context, container.context.getString(R.string.recently_used_playlist), "recentlyusedplaylist",
                        SlideUpMenuItem(container.context,
                            R.drawable.ic_playlist_add,
                            lastUpdated.name,
                            "${lastUpdated.videos.size} " + container.context.getString(R.string.videos),
                            tag = "",
                            call = {
                                if(StatePlaylists.instance.addToPlaylist(lastUpdated.id, video))
                                    UIDialogs.appToast("Added to playlist [${lastUpdated?.name}]", false);
                                StateDownloads.instance.checkForOutdatedPlaylists();
                            }))
                );
            }

            val allPlaylists = StatePlaylists.instance.getPlaylists().sortedByDescending { maxOf(it.datePlayed, it.dateUpdate, it.dateCreation) };
            val queue = StatePlayer.instance.getQueue();
            val watchLater = StatePlaylists.instance.getWatchLater();
            items.add(
                SlideUpMenuGroup(
                    container.context, container.context.getString(R.string.other), "other",
                    SlideUpMenuItem(
                        container.context,
                        R.drawable.ic_queue_add,
                        container.context.getString(R.string.queue),
                        "${queue.size} " + container.context.getString(R.string.videos),
                        tag = "queue",
                        call = { StatePlayer.instance.addToQueue(video); }),
                    SlideUpMenuItem(
                        container.context,
                        R.drawable.ic_watchlist_add,
                        StatePlayer.TYPE_WATCHLATER,
                        "${watchLater.size} " + container.context.getString(R.string.videos),
                        tag = "watch later",
                        call = {
                            if(StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video), true))
                                UIDialogs.appToast("Added to watch later", false);
                            else
                                UIDialogs.toast(container.context.getString(R.string.already_in_watch_later))
                        }),
                )
            );

            val playlistItems = arrayListOf<SlideUpMenuItem>();
            playlistItems.add(SlideUpMenuItem(
                container.context,
                R.drawable.ic_playlist_add,
                container.context.getString(R.string.new_playlist),
                container.context.getString(R.string.add_to_new_playlist),
                tag = "add_to_new_playlist",
                call = {
                    slideUpMenuOverlayUpdated(showCreatePlaylistOverlay(container) {
                        val playlist = Playlist(it, arrayListOf(SerializedPlatformVideo.fromVideo(video)));
                        StatePlaylists.instance.createOrUpdatePlaylist(playlist);
                    });
                },
                invokeParent = false
            ))

            for (playlist in allPlaylists) {
                playlistItems.add(SlideUpMenuItem(container.context,
                    R.drawable.ic_playlist_add,
                    playlist.name,
                    "${playlist.videos.size} " + container.context.getString(R.string.videos),
                    tag = "",
                    call = {
                        if(StatePlaylists.instance.addToPlaylist(playlist.id, video))
                            UIDialogs.appToast("Added to playlist [${playlist.name}]", false);
                        StateDownloads.instance.checkForOutdatedPlaylists();
                    }));
            }

            if(playlistItems.size > 0)
                items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.playlists), "", playlistItems));

            return SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.add_to), null, true, items).apply { show() };
        }

        fun showFiltersOverlay(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List<String>, filterValues: HashMap<String, List<String>>): SlideUpMenuFilters {
            val overlay = SlideUpMenuFilters(lifecycleScope, container, enabledClientsIds, filterValues);
            overlay.show();
            return overlay;
        }


        fun showMoreButtonOverlay(container: ViewGroup, buttonGroup: RoundButtonGroup, ignoreTags: List<Any> = listOf(), invokeParents: Boolean = true, onPinnedbuttons: ((List<RoundButton>)->Unit)? = null): SlideUpMenuOverlay {
            val visible = buttonGroup.getVisibleButtons().filter { !ignoreTags.contains(it.tagRef) };
            val hidden = buttonGroup.getInvisibleButtons().filter { !ignoreTags.contains(it.tagRef) };

            val views = arrayOf(
                hidden
                    .map { btn -> SlideUpMenuItem(
                        container.context,
                        btn.iconResource,
                        btn.text.text.toString(),
                        "",
                        tag = "",
                        call = {
                            btn.handler?.invoke(btn);
                        },
                        invokeParent = invokeParents
                    ) as View  }.toTypedArray(),
                arrayOf(SlideUpMenuItem(
                    container.context,
                    R.drawable.ic_pin,
                    container.context.getString(R.string.change_pins),
                    container.context.getString(R.string.decide_which_buttons_should_be_pinned),
                    tag = "",
                    call = {
                        showOrderOverlay(container, container.context.getString(R.string.select_your_pins_in_order),  (visible + hidden).map { Pair(it.text.text.toString(), it.tagRef!!) }, {
                            val selected = it
                                .map { x -> visible.find { it.tagRef == x } ?: hidden.find { it.tagRef == x } }
                                .filter { it != null }
                                .map { it!! }
                                .toList();

                            onPinnedbuttons?.invoke(selected + (visible + hidden).filter { !selected.contains(it) });
                        });
                    },
                    invokeParent = false
                ))
            ).flatten().toTypedArray();

            return SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.more_options), null, true, *views).apply { show() };
        }
        fun showOrderOverlay(container: ViewGroup, title: String, options: List<Pair<String, Any>>, onOrdered: (List<Any>)->Unit, description: String? = null) {
            val selection: MutableList<Any> = mutableListOf();

            var overlay: SlideUpMenuOverlay? = null;

            overlay = SlideUpMenuOverlay(container.context, container, title, container.context.getString(R.string.save), true,
                listOf(
                    if(!description.isNullOrEmpty()) SlideUpMenuGroup(container.context, "", description, "", listOf()) else null,
                ).filterNotNull() +
                (options.map { SlideUpMenuItem(
                    container.context,
                    R.drawable.ic_move_up,
                    it.first,
                    "",
                    tag = it.second,
                    call = {
                        val overlayItem = overlay?.getSlideUpItemByTag(it.second);
                        if(overlay!!.selectOption(null, it.second, true, true)) {
                            if(!selection.contains(it.second)) {
                                selection.add(it.second);
                                if(overlayItem != null) {
                                    overlayItem.setSubText(selection.indexOf(it.second).toString());
                                }
                            }
                        } else {
                            selection.remove(it.second);
                            if(overlayItem != null) {
                                overlayItem.setSubText("");
                            }
                        }
                    },
                    invokeParent = false
                )
                }));
            overlay.onOK.subscribe {
                onOrdered.invoke(selection);
                overlay.hide();
            };

            overlay.show();
        }
    }
}