package ac.mdiq.podcini.ui.compose

import ac.mdiq.podcini.PodciniApp.Companion.getAppContext
import ac.mdiq.podcini.R
import ac.mdiq.podcini.automation.playEpisodeAtTime
import ac.mdiq.podcini.net.download.RequestTye
import ac.mdiq.podcini.net.sync.SynchronizationSettings.isProviderConnected
import ac.mdiq.podcini.net.sync.SynchronizationSettings.wifiSyncEnabledKey
import ac.mdiq.podcini.net.sync.model.EpisodeAction
import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink
import ac.mdiq.podcini.net.sync.transceive.DiscoveredReceiver
import ac.mdiq.podcini.net.sync.transceive.listenForUDPBroadcasts
import ac.mdiq.podcini.net.sync.transceive.sendEpisodes
import ac.mdiq.podcini.playback.PlaybackStarter
import ac.mdiq.podcini.playback.base.InTheatre.actQueue
import ac.mdiq.podcini.playback.base.InTheatre.theatres
import ac.mdiq.podcini.shared.getEntityId
import ac.mdiq.podcini.shared.nowInMillis
import ac.mdiq.podcini.sources.SourceGatewayClient
import ac.mdiq.podcini.sources.clientByEpisode
import ac.mdiq.podcini.sources.clientshaveLikeCounts
import ac.mdiq.podcini.sources.clientshaveViewCounts
import ac.mdiq.podcini.sources.sourceClients
import ac.mdiq.podcini.storage.database.addToAssQueue
import ac.mdiq.podcini.storage.database.addToQueue
import ac.mdiq.podcini.storage.database.allFeeds
import ac.mdiq.podcini.storage.database.allowForAutoDelete
import ac.mdiq.podcini.storage.database.appAttribs
import ac.mdiq.podcini.storage.database.appPrefs
import ac.mdiq.podcini.storage.database.deleteMedia
import ac.mdiq.podcini.storage.database.eraseEpisodes
import ac.mdiq.podcini.storage.database.queuesLive
import ac.mdiq.podcini.storage.database.realm
import ac.mdiq.podcini.storage.database.removeFromAllQueues
import ac.mdiq.podcini.storage.database.removeFromAllQueuesQuiet
import ac.mdiq.podcini.storage.database.runOnIOScope
import ac.mdiq.podcini.storage.database.shelveToFeed
import ac.mdiq.podcini.storage.database.upsert
import ac.mdiq.podcini.storage.database.upsertBlk
import ac.mdiq.podcini.storage.database.upsertBlkEmb
import ac.mdiq.podcini.storage.model.DownloadResult
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.model.Feed.Companion.DEFAULT_INTERVALS
import ac.mdiq.podcini.storage.model.Feed.Companion.INTERVAL_UNITS
import ac.mdiq.podcini.storage.model.Feed.Companion.intervalMillis
import ac.mdiq.podcini.storage.model.ShareLog
import ac.mdiq.podcini.storage.model.ShareLog.Status
import ac.mdiq.podcini.storage.model.Timer
import ac.mdiq.podcini.storage.model.Todo
import ac.mdiq.podcini.storage.model.toEpisode
import ac.mdiq.podcini.storage.specs.EpisodeFilter
import ac.mdiq.podcini.storage.specs.EpisodeFilter.EpisodesFilterGroup
import ac.mdiq.podcini.storage.specs.EpisodeSortOrder
import ac.mdiq.podcini.storage.specs.EpisodeSortOrder.Companion.fromCode
import ac.mdiq.podcini.storage.specs.EpisodeState
import ac.mdiq.podcini.storage.specs.Rating
import ac.mdiq.podcini.storage.utils.durationStringFull
import ac.mdiq.podcini.storage.utils.durationStringShort
import ac.mdiq.podcini.storage.utils.loadChapters
import ac.mdiq.podcini.storage.utils.toAndroidUri
import ac.mdiq.podcini.ui.actions.ActionButton.Companion.playVideoIfNeeded
import ac.mdiq.podcini.ui.screens.SearchBy
import ac.mdiq.podcini.ui.utils.SearchAlgo
import ac.mdiq.podcini.ui.utils.ShownotesWebView
import ac.mdiq.podcini.utils.EventFlow
import ac.mdiq.podcini.utils.FlowEvent
import ac.mdiq.podcini.utils.Logd
import ac.mdiq.podcini.utils.Loge
import ac.mdiq.podcini.utils.Logs
import ac.mdiq.podcini.utils.Logt
import ac.mdiq.podcini.utils.LogtFor
import ac.mdiq.podcini.utils.ShownotesCleaner
import ac.mdiq.podcini.utils.formatDateTimeFlex
import ac.mdiq.podcini.utils.fullDateTimeString
import ac.mdiq.podcini.utils.sessionLogs
import ac.mdiq.podcini.utils.shareFeedItemFile
import ac.mdiq.podcini.utils.shareFeedItemLinkWithDownloadLink
import ac.mdiq.podcini.utils.shareLink
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.view.Gravity
import androidx.collection.LruCache
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Checkbox
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import io.github.xilinjia.krdb.query.Sort
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.datetime.DatePeriod
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.atTime
import kotlinx.datetime.format
import kotlinx.datetime.format.char
import kotlinx.datetime.minus
import kotlinx.datetime.number
import kotlinx.datetime.plus
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
import kotlin.time.Instant

private const val TAG = "ComposeEpisodes"

@Composable
fun ShareDialog(item: Episode, onDismiss: () -> Unit) {
    val hasMedia = remember { true }
    val downloaded = remember { hasMedia && item.downloaded }
    val hasDownloadUrl = remember { hasMedia && item.downloadUrl != null }

    var position by remember { mutableIntStateOf(1) }

    var isChecked by remember { mutableStateOf(false) }
    val ctx = LocalContext.current

    AlertDialog(modifier = Modifier.border(1.dp, MaterialTheme.colorScheme.tertiary, MaterialTheme.shapes.extraLarge), onDismissRequest = { onDismiss() },
        title = { Text(stringResource(R.string.share_label), style = CustomTextStyles.titleCustom) },
        text = {
            Column {
                Row(verticalAlignment = Alignment.CenterVertically) {
                    RadioButton(selected = position == 1, onClick = { position = 1 })
                    Text(stringResource(R.string.share_dialog_for_social))
                }
                if (hasDownloadUrl) Row(verticalAlignment = Alignment.CenterVertically) {
                    RadioButton(selected = position == 2, onClick = { position = 2 })
                    Text(stringResource(R.string.share_dialog_media_address))
                }
                if (downloaded) Row(verticalAlignment = Alignment.CenterVertically) {
                    RadioButton(selected = position == 3, onClick = { position = 3 })
                    Text(stringResource(R.string.share_dialog_media_file_label))
                }
                HorizontalDivider(modifier = Modifier.fillMaxWidth().padding(top = 5.dp))
                Row(verticalAlignment = Alignment.CenterVertically) {
                    Checkbox(checked = isChecked, onCheckedChange = { isChecked = it })
                    Text(stringResource(R.string.share_playback_position_dialog_label))
                }
            }
        },
        confirmButton = {
            TextButton(onClick = {
                when (position) {
                    1 -> shareFeedItemLinkWithDownloadLink(ctx, item, isChecked)
                    2 -> {
                        if (!item.downloadUrl.isNullOrEmpty()) shareLink(ctx, item.downloadUrl!!)
                        else LogtFor(TAG, item.id, "Episode download url is not valid, ignored.")
                    }
                    3 -> shareFeedItemFile(ctx, item)
                }
                onDismiss()
            }) { Text(text = "OK") }
        },
        dismissButton = { TextButton(onClick = { onDismiss() }) { Text(stringResource(R.string.cancel_label)) } }
    )
}

@Composable
fun TodoDialog(episode: Episode, todo: Todo? = null, onDismiss: () -> Unit) {
    CommonDialogSurface(onDismiss = onDismiss) {
        Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
            
            var title by remember { mutableStateOf(TextFieldValue(todo?.title ?: "")) }
            BasicTextField(value = title, onValueChange = { title = it }, textStyle = TextStyle(fontSize = 16.sp, color = textColor), modifier = Modifier.fillMaxWidth().height(40.dp).padding(start = 10.dp, end = 10.dp, bottom = 10.dp).border(1.dp, MaterialTheme.colorScheme.primary, MaterialTheme.shapes.small))
            var addNote by remember { mutableStateOf(false) }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Checkbox(checked = addNote, onCheckedChange = { addNote = it })
                Text(text = stringResource(R.string.add_notes), style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 10.dp))
            }
            var note by remember { mutableStateOf(TextFieldValue(todo?.note ?: "")) }
            if (addNote) BasicTextField(value = note, onValueChange = { note = it }, textStyle = TextStyle(fontSize = 12.sp, color = textColor), modifier = Modifier.fillMaxWidth().height(100.dp).padding(start = 10.dp, end = 10.dp, bottom = 10.dp).border(1.dp, MaterialTheme.colorScheme.primary, MaterialTheme.shapes.small))
            val sysTime = remember { nowInMillis() }
            var dueTime by remember { mutableLongStateOf(0) }
            var addDueTime by remember { mutableStateOf((todo?.dueTime ?: 0) > 0) }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Checkbox(checked = addDueTime, onCheckedChange = {
                    addDueTime = it
                    if (todo != null) {
                        dueTime = if (addDueTime) todo.dueTime
                        else 0
                    }
                })
                Text(text = stringResource(R.string.set_due_time), style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 10.dp))
            }
            val zdt = remember(addDueTime) {
                val epochMillis = if (todo != null) {
                    if (todo.dueTime > 0) todo.dueTime
                    else if (addDueTime) sysTime
                    else 0
                } else sysTime
                Instant.fromEpochMilliseconds(epochMillis).toLocalDateTime(TimeZone.currentSystemDefault())
            }
            var year by remember(zdt) { mutableIntStateOf(zdt.year) }
            var month by remember(zdt) { mutableIntStateOf(zdt.month.number) }
            var date by remember(zdt) { mutableIntStateOf(zdt.day) }
            var hour by remember(zdt) { mutableIntStateOf(zdt.hour) }
            var minute by remember(zdt) { mutableIntStateOf(zdt.minute) }
            if (addDueTime) {
                Text(stringResource(R.string.year) + " " + stringResource(R.string.month) + " " + stringResource(R.string.date))
                Row(verticalAlignment = Alignment.CenterVertically) {
                    NumberEditor(year, "", nz = true, instant = true, modifier = Modifier.weight(0.5f)) { year = it }
                    NumberEditor(month, "", nz = true, instant = true, modifier = Modifier.weight(0.4f)) { month = it }
                    NumberEditor(date, "", nz = true, instant = true, modifier = Modifier.weight(0.4f)) { date = it }
                }
                Text(stringResource(R.string.hour) + " " + stringResource(R.string.minute))
                Row(verticalAlignment = Alignment.CenterVertically) {
                    NumberEditor(hour, "", nz = true, instant = true, modifier = Modifier.weight(0.4f)) { hour = it }
                    NumberEditor(minute, "", nz = true, instant = true, modifier = Modifier.weight(0.4f)) { minute = it }
                }
            }
            Button(onClick = {
                runOnIOScope {
                    try {
                        if (addDueTime) dueTime = LocalDateTime(year = year, month = month, day = date, hour = hour, minute = minute, second = 0, nanosecond = 0).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
                        if (todo == null) {
                            val todo_ = todo ?: Todo()
                            todo_.id = getEntityId()
                            todo_.title = title.text
                            todo_.note = note.text
                            todo_.dueTime = dueTime
                            upsert(episode) { it.todos.add(todo_) }
                        } else upsertBlkEmb(todo) { todo_ ->
                            todo_.title = title.text
                            todo_.note = note.text
                            todo_.dueTime = dueTime
                        }
                        if (dueTime > 0) playEpisodeAtTime(dueTime, episode.id)
                    } catch (e: Throwable) { Loge(TAG, "editing Todo error: ${e.message}")}
                }
                onDismiss()
            }) { Text(stringResource(R.string.confirm_label)) }
        }
    }
}

val webDataCache = LruCache<Long, String>(10)

fun Context.findActivity(): Activity? = when (this) {
    is Activity -> this
    is ContextWrapper -> baseContext.findActivity()
    else -> null
}

@Composable
fun EpisodeDetails(episode: Episode, fetchWebdata: Boolean = true, fetchChapters: Boolean = false, onDismiss: ()->Unit) {
    val context by rememberUpdatedState(LocalContext.current)
    val activity = context.findActivity() ?: error("WebView requires an Activity context")

    var webviewData by remember { mutableStateOf<String?>("") }
    var playerLocal: ExoPlayer? by remember { mutableStateOf(null) }

    var showEditComment by remember { mutableStateOf(false) }
    var showTagsSettingDialog by remember { mutableStateOf(false) }
    var showTodoDialog by remember { mutableStateOf(false) }
    var onTodo by remember { mutableStateOf<Todo?>(null) }

    LaunchedEffect(Unit) { if (fetchChapters) withContext(Dispatchers.IO) { loadChapters(episode, false) }}

    if (showEditComment) {
        var commentText by remember { mutableStateOf(TextFieldValue(episode.compileCommentText())) }
        CommentEditingDialog(textState = commentText, onTextChange = { commentText = it }, onDismiss = { showEditComment = false},
            onSave = { runOnIOScope { upsert(episode) { it.addComment(commentText.text, false) } } })
    }
    if (showTagsSettingDialog) TagSettingDialog(TagType.Episode, episode.tags, onDismiss = { showTagsSettingDialog = false }) { tags ->
        runOnIOScope { upsert(episode) {
            it.tags.clear()
            it.tags.addAll(tags)
        } }
    }
    if (showTodoDialog) TodoDialog(episode, onTodo) { showTodoDialog = false}

    val playerLogs = remember(episode.id) { sessionLogs.filter { it.contains(episode.id.toString()) } }
    val dlLogs = remember(episode.id) { realm.query(DownloadResult::class).query("feedfileId == ${episode.id} AND feedfileType == ${RequestTye.FEEDMEDIA.ordinal}").sort("completionTime",  Sort.DESCENDING).find() }

    LaunchedEffect(episode) {
        Logd(TAG, "LaunchedEffect(episode, episodeId)")
        suspend fun buildCleanedNotes(curItem: Episode, shownotesCleaner: ShownotesCleaner?): Pair<Episode, String?> {
            var curItem_ = curItem
            val url = curItem_.downloadUrl
            var cleanedNotes: String? = null
            val client = clientByEpisode(curItem_)
            if (client != null && curItem_.description?.startsWith("Short:") == true) {
                Logd(TAG, "buildCleanedNotes getting extended description: ${curItem_.title}")
                try {
                    val desc = client.withProvider { it.getEpisodeDescription(url) }
                    cleanedNotes = if (!desc.isNullOrBlank()) {
                        curItem_ = upsertBlk(curItem_) { it.description = desc }
                        shownotesCleaner?.processShownotes(desc, curItem_.duration)
                    } else shownotesCleaner?.processShownotes(curItem_.description ?: "", curItem_.duration)
                } catch (e: Exception) { Loge(TAG, "StreamInfo error: ${e.message}") }
            } else cleanedNotes = shownotesCleaner?.processShownotes(curItem_.description ?: "", curItem_.duration)
            return Pair(curItem_, cleanedNotes)
        }
        if (fetchWebdata) {
            webviewData = webDataCache[episode.id]
            if (webviewData.isNullOrBlank()) {
//                Logd(TAG, "description: ${episode.description}")
                withContext(Dispatchers.IO) {
                    episode.let {
                        webviewData = buildCleanedNotes(episode, ShownotesCleaner()).second
                        if (!webviewData.isNullOrBlank()) webDataCache.put(it.id, webviewData!!)
                    }
                }
            }
        }
        if (episode.clips.isNotEmpty()) {
            if (playerLocal == null) playerLocal = ExoPlayer.Builder(getAppContext()).build()
        } else {
            playerLocal?.release()
            playerLocal = null
        }
    }

    Column {
        val todos = remember(episode.id) { episode.todos }
        if (todos.isNotEmpty()) {
            var show by remember { mutableStateOf(false) }
            var showDone by remember { mutableStateOf(false) }
            Row(modifier = Modifier.padding(start = 15.dp, end = 10.dp, top = 5.dp, bottom = 5.dp), verticalAlignment = Alignment.CenterVertically) {
                Text("Todos:", color = MaterialTheme.colorScheme.primary, style = CustomTextStyles.titleCustom, modifier = Modifier.clickable {
                    onTodo = null
                    showTodoDialog = true
                })
                Spacer(Modifier.width(50.dp))
                Checkbox(checked = show, onCheckedChange = { show = it })
                Text(text = stringResource(R.string.show), style = MaterialTheme.typography.bodyLarge.merge())
                if (show) {
                    Spacer(Modifier.width(50.dp))
                    Checkbox(checked = showDone, onCheckedChange = { showDone = it })
                    Text(text = stringResource(R.string.show_done), style = MaterialTheme.typography.bodyLarge.merge())
                }
            }
            if (show) Column(modifier = Modifier.padding(start = 20.dp).fillMaxWidth()) {
                val localTime = remember { nowInMillis() }
                for (todo in todos) {
                    var done by remember(todo) { mutableStateOf(todo.completed) }
                    if (!done || showDone) {
                        Row(verticalAlignment = Alignment.CenterVertically) {
                            Checkbox(checked = done, onCheckedChange = {
                                done = it
                                upsertBlkEmb(todo) { todo -> todo.completed = done }
                            })
                            Text(text = todo.title, style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.clickable {
                                onTodo = todo
                                showTodoDialog = true
                            })
                            Spacer(Modifier.weight(1f))
                            Icon(ImageVector.vectorResource(id = R.drawable.ic_delete), contentDescription = "delete", modifier = Modifier.padding(end = 15.dp).clickable { upsertBlk(episode) { it.todos.remove(todo) } })
                        }
                        if (todo.dueTime > 0) {
                            val dueText = remember(todo.dueTime) { "D:" + formatDateTimeFlex(todo.dueTime) }
                            val bgColor = if (localTime > todo.dueTime) Color.Cyan else MaterialTheme.colorScheme.surface
                            Text(dueText, color = textColor, style = MaterialTheme.typography.bodySmall, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(start = 20.dp).background(bgColor))
                        }
                    }
                }
            }
        }

        if (episode.tagsAsString.isNotBlank()) Text("Tags: ${episode.tagsAsString}", color = MaterialTheme.colorScheme.primary, style = CustomTextStyles.titleCustom, modifier = Modifier.padding(start = 15.dp, top = 5.dp, bottom = 5.dp).clickable { showTagsSettingDialog = true })
        if (episode.comment.isNotBlank()) {
            Text(stringResource(R.string.comments), color = MaterialTheme.colorScheme.primary, style = CustomTextStyles.titleCustom, modifier = Modifier.padding(start = 15.dp, top = 5.dp, bottom = 5.dp).clickable { showEditComment = true })
            Text(episode.comment, color = textColor, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(start = 15.dp, bottom = 5.dp))
        }
        if (episode.marks.isNotEmpty()) {
            var markToRemove by remember { mutableLongStateOf(0L) }
            if (markToRemove != 0L) {
                AlertDialog(modifier = Modifier.border(1.dp, MaterialTheme.colorScheme.tertiary, MaterialTheme.shapes.extraLarge), onDismissRequest = { markToRemove = 0L }, text = { Text(stringResource(R.string.ask_remove_mark, markToRemove)) }, confirmButton = {
                    TextButton(onClick = {
                        upsertBlk(episode) { it.marks.remove(markToRemove) }
                        markToRemove = 0L
                    }) { Text(stringResource(R.string.confirm_label)) }
                }, dismissButton = { TextButton(onClick = { markToRemove = 0L }) { Text(stringResource(R.string.cancel_label)) } })
            }
            Text(stringResource(R.string.marks), style = CustomTextStyles.titleCustom, modifier = Modifier.padding(start = 15.dp, top = 5.dp, bottom = 5.dp))
            FlowRow(modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp), horizontalArrangement = Arrangement.spacedBy(15.dp)) {
                episode.marks.forEach { mark ->
                    FilterChip(label = { Text(durationStringShort(mark, false)) }, selected = false,
                        onClick = {
                            if (episode.id == theatres[0].mPlayer?.curEpisode?.id) {
                                if (!theatres[0].mPlayer!!.isPlaying) theatres[0].mPlayer?.play()
                                theatres[0].mPlayer?.seekTo(mark.toInt())
                            } else Logt(TAG, context.getString(R.string.play_mark_msg))
                        },
                        trailingIcon = { Icon(imageVector = Icons.Filled.Delete, contentDescription = "delete", modifier = Modifier.size(FilterChipDefaults.IconSize).padding(start = 3.dp).clickable { markToRemove = mark }) }
                    )
                }
            }
        }

        if (episode.clips.isNotEmpty()) {
            var cliptToRemove by remember { mutableStateOf("") }
            if (cliptToRemove.isNotBlank()) {
                AlertDialog(modifier = Modifier.border(1.dp, MaterialTheme.colorScheme.tertiary, MaterialTheme.shapes.extraLarge), onDismissRequest = { cliptToRemove = "" }, text = { Text(stringResource(R.string.ask_remove_clip, cliptToRemove.substringBefore("."))) }, confirmButton = {
                    TextButton(onClick = {
                        runOnIOScope {
                            episode.getClipFile(cliptToRemove).delete()
                            upsert(episode) { it.clips.remove(cliptToRemove) }
                            withContext(Dispatchers.Main) { cliptToRemove = "" }
                        }
                    }) { Text(stringResource(R.string.confirm_label)) }
                }, dismissButton = { TextButton(onClick = { cliptToRemove = "" }) { Text(stringResource(R.string.cancel_label)) } })
            }
            Text(stringResource(R.string.clips), style = CustomTextStyles.titleCustom, modifier = Modifier.padding(start = 15.dp, top = 5.dp, bottom = 5.dp))
            FlowRow(modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp), horizontalArrangement = Arrangement.spacedBy(15.dp)) {
                episode.clips.forEach { clip ->
                    FilterChip(label = { Text(clip.substringBefore(".")) }, selected = false,
                        onClick = {
                            val file = episode.getClipFile(clip)
                            val uri = file.toAndroidUri()
                            Logd(TAG, "clip file: ${file.absPath} uri: $uri")
                            if (playerLocal != null && uri != null && runBlocking { file.exists() }) {
                                playerLocal!!.setMediaItem(MediaItem.fromUri(uri))
                                playerLocal!!.prepare()
                                playerLocal!!.play()
                            } else Loge(TAG, "clip file doesn't exist: ${file.absPath}")
                        },
                        trailingIcon = { Icon(imageVector = Icons.Filled.Delete, contentDescription = "delete", modifier = Modifier.size(FilterChipDefaults.IconSize).padding(start = 3.dp).clickable { cliptToRemove = clip }) }
                    )
                }
            }
        }
        //        Logd(TAG, "episode.related: ${episode.related.size}")
        if (episode.related.isNotEmpty()) {
            var showTodayStats by remember { mutableStateOf(false) }
            if (showTodayStats) RelatedEpisodesDialog(episode) { showTodayStats = false }
            Text(stringResource(R.string.related), color = MaterialTheme.colorScheme.primary, style = CustomTextStyles.titleCustom, modifier = Modifier.padding(start = 15.dp, top = 10.dp, bottom = 10.dp).clickable {
                showTodayStats = true
//                onDismiss()   // TODO: can't close the composable here
            })
        }

        //                    if (!episode?.chapters.isNullOrEmpty()) Text(stringResource(id = R.string.chapters_label), color = textColor, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(start = 15.dp, top = 10.dp, bottom = 5.dp).clickable(onClick = { showChaptersDialog = true }))
        if (episode.chapters.isNotEmpty()) {
            val chapters = remember { episode.chapters }
            Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 5.dp)) {
                Text(stringResource(R.string.chapters_label))
                var curChapterIndex by remember { mutableIntStateOf(-1) }
                for (index in chapters.indices) {
                    val ch = remember(index) { chapters[index] }
                    //            Text(ch.link?: "")
                    Row(modifier = Modifier.clickable {
                        when {
                            theatres[0].mPlayer?.curEpisode?.id == episode.id -> {
                                if (!theatres[0].mPlayer!!.isPlaying) theatres[0].mPlayer?.play()
                                theatres[0].mPlayer?.seekTo(ch.start.toInt())
                            }
                            theatres[1].mPlayer?.curEpisode?.id == episode.id -> {
                                if (!theatres[1].mPlayer!!.isPlaying) theatres[1].mPlayer?.play()
                                theatres[1].mPlayer?.seekTo(ch.start.toInt())
                            }
                            else -> {
                                PlaybackStarter(episode).shouldStreamThisTime(episode.fileUrl == null).start(0)
                                playVideoIfNeeded(episode)
                                theatres[0].mPlayer?.seekTo(ch.start.toInt())
                            }
                        }
                        curChapterIndex = index
                    }) {
                        Text(durationStringFull(ch.start.toInt()), color = buttonColor, modifier = Modifier.padding(end = 5.dp))
                        Text(ch.title ?: "No title", color = textColor, fontWeight = if (index == curChapterIndex) FontWeight.Bold else FontWeight.Normal)
                    }
                }
            }
        }
        if (dlLogs.isNotEmpty() || playerLogs.isNotEmpty()) {
            var showLogs by remember { mutableStateOf(false) }
            Text(stringResource(R.string.logs), color = MaterialTheme.colorScheme.primary, style = CustomTextStyles.titleCustom, modifier = Modifier.padding(start = 15.dp, top = 16.dp, bottom = 4.dp).clickable { showLogs = !showLogs})
            if (showLogs) Column(modifier = Modifier.padding(10.dp)) {
                for (log in dlLogs) {
                    val message = stringResource(if (!log.isSuccessful) R.string.failed else R.string.download_successful)
                    Row {
                        Text(formatDateTimeFlex(log.completionTime))
                        Text(": $message", color = textColor)
                    }
                    if (!log.isSuccessful) {
                        Column(modifier = Modifier.padding(start = 10.dp)) {
                            Text(log.reasonDetailed)
                            Text(stringResource(log.reason?.res ?: R.string.download_error_error_unknown))
                        }
                    }
                    Spacer(Modifier.width(4.dp))
                }
                for (log in playerLogs) Text(log.replace(episode.id.toString(), ""))
            }
        }
        Text(stringResource(R.string.description_label), color = textColor, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(start = 16.dp, top = 10.dp, bottom = 4.dp))
        AndroidView(modifier = Modifier.fillMaxSize(),
            factory = {
                ShownotesWebView(activity).apply {
                    setTimecodeSelectedListener { time: Int -> theatres[0].mPlayer?.seekTo(time) }
                    setPageFinishedListener { postDelayed({ }, 50) }
                } },
            update = { view ->
                val currentTag = view.tag as? String
                if (currentTag != webviewData) {
                    view.tag = webviewData
                    view.loadDataWithBaseURL("about:blank", if (webviewData.isNullOrBlank()) "No notes" else webviewData!!, "text/html", "utf-8", "about:blank")
                }
        })
    }
}

@Composable
fun RelatedEpisodesDialog(episode: Episode, onDismiss: () -> Unit) {
    // TODO: somehow, episode is not updated after unrelate
    AlertDialog(properties = DialogProperties(usePlatformDefaultWidth = false), modifier = Modifier.fillMaxWidth().height(300.dp).padding(5.dp).border(1.dp, MaterialTheme.colorScheme.tertiary, MaterialTheme.shapes.extraLarge), onDismissRequest = { onDismiss() },  confirmButton = {},
        text = {
            Logd(TAG, "episode.related: ${episode.related.size}")
            EpisodeLazyColumn(episode.related.toList(), layoutMode = LayoutMode.FeedTitle.ordinal, forceFeedImage = true, showActionButtons = false,
                actionButtonCB = {e1, _ ->
                    runOnIOScope {
                        realm.write {
                            val es = query(Episode::class, "id IN $0", listOf(episode.id, e1.id)).find()
                            if (es.size == 2) {
                                es[0].related.remove(es[1])
                                es[1].related.remove(es[0])
                            }
                        }
                    }
                } ) },
        dismissButton = { TextButton(onClick = { onDismiss() }) { Text(stringResource(R.string.cancel_label)) } } )
}

@Composable
fun ChooseRatingDialog(selected: List<Episode>, onDismiss: () -> Unit) {
    CommonPopupCard(onDismiss = onDismiss) {
        Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
            for (rating in Rating.entries.reversed()) {
                Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp)
                    .clickable {
                        runOnIOScope { for (e in selected) upsert(e) { it.setRating(rating) } }
                        onDismiss()
                    }) {
                    Icon(imageVector = ImageVector.vectorResource(id = rating.res), "")
                    Text(rating.name, Modifier.padding(start = 4.dp))
                }
            }
        }
    }
}

@Composable
fun PlayStateDialog(selected: List<Episode>, onDismiss: () -> Unit, futureCB: (EpisodeState)->Unit, ignoreCB: ()->Unit) {
    CommonPopupCard(onDismiss = onDismiss) {
        Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
            for (state in EpisodeState.entries.reversed()) {
                if (state.userSet) {
                    Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp).clickable {
                        when (state) {
                            EpisodeState.IGNORED -> ignoreCB()
                            EpisodeState.AGAIN, EpisodeState.LATER -> futureCB(state)
                            else -> runOnIOScope {
                                for (e in selected) {
                                    val hasAlmostEnded = e.hasAlmostEnded()
                                    var item_ = upsert(e) { it.setPlayState(state, hasAlmostEnded) }
                                    when (state) {
                                        EpisodeState.UNPLAYED -> {
                                            if (isProviderConnected && item_.feed?.isLocal != true) {
                                                val actionNew: EpisodeAction = EpisodeAction.Builder(item_, EpisodeAction.NEW).currentTimestamp().build()
                                                SynchronizationQueueSink.enqueueEpisodeActionIfSyncActive(actionNew)
                                            }
                                        }
                                        EpisodeState.PLAYED -> {
                                            if (hasAlmostEnded) item_ = upsertBlk(item_) { it.playbackCompletionTime = nowInMillis() }
                                            val shouldAutoDelete = if (item_.feed == null) false else allowForAutoDelete(item_.feed!!) //                                            item = item_
                                            if (hasAlmostEnded && shouldAutoDelete) {
                                                item_ = deleteMedia(item_)
                                                if (appPrefs.deleteRemovesFromQueue) removeFromAllQueues(listOf(item_))
                                            } else if (appPrefs.removeFromQueueMarkPlayed) removeFromAllQueues(listOf(item_))
                                            if (item_.feed?.isLocal != true && (isProviderConnected || wifiSyncEnabledKey)) { // not all items have media, Gpodder only cares about those that do
                                                if (isProviderConnected) {
                                                    val actionPlay: EpisodeAction = EpisodeAction.Builder(item_, EpisodeAction.PLAY).currentTimestamp().started(item_.duration / 1000).position(item_.duration / 1000).total(item_.duration / 1000).build()
                                                    SynchronizationQueueSink.enqueueEpisodeActionIfSyncActive(actionPlay)
                                                }
                                            }
                                        }
                                        EpisodeState.QUEUE -> if (item_.feed?.queue != null) addToAssQueue(listOf(e))
                                        else -> {}
                                    }
                                }
                            }
                        }
                        onDismiss()
                    }) {
                        Icon(imageVector = ImageVector.vectorResource(id = state.res), "")
                        Text(state.name, Modifier.padding(start = 4.dp))
                    }
                }
            }
        }
    }
}

@Composable
fun PutToQueueDialog(selected: List<Episode>, onDismiss: () -> Unit) {
    CommonPopupCard(onDismiss = onDismiss) {
        Column(modifier = Modifier.verticalScroll(rememberScrollState()).padding(16.dp), verticalArrangement = Arrangement.spacedBy(1.dp)) {
            var removeChecked by remember { mutableStateOf(false) }
            var toQueue by remember { mutableStateOf(actQueue) }
            FlowRow(horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.padding(10.dp)) {
                for (q in queuesLive) {
                    FilterChip(label = { Text(q.name) }, onClick = { toQueue = q }, selected = toQueue == q, border = filterChipBorder(toQueue == q) )
                }
            }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Checkbox(checked = removeChecked, onCheckedChange = { removeChecked = it })
                Text(text = stringResource(R.string.remove_from_other_queues), style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 10.dp))
            }
            Row {
                Spacer(Modifier.weight(1f))
                Button(onClick = {
                    runOnIOScope {
                        if (removeChecked) {
                            val toRemove = mutableSetOf<Long>()
                            val toRemoveCur = mutableListOf<Episode>()
                            selected.forEach { e -> if (actQueue.contains(e)) toRemoveCur.add(e) }
                            selected.forEach { e ->
                                for (q in queuesLive) {
                                    if (q.contains(e)) {
                                        toRemove.add(e.id)
                                        break
                                    }
                                }
                            }
                            if (toRemove.isNotEmpty()) removeFromAllQueuesQuiet(toRemove.toList(), false)
                            if (toRemoveCur.isNotEmpty()) EventFlow.postEvent(FlowEvent.QueueEvent.removed(toRemoveCur))
                        }
                        addToQueue(selected, toQueue)
                    }
                    onDismiss()
                }) { Text(stringResource(R.string.confirm_label)) }
            }
        }
    }
}

@Composable
fun ShelveDialog(selected: List<Episode>, onDismiss: () -> Unit) {
    val synthetics = allFeeds.filter { it.id in 100..1000 }
    CommonPopupCard(onDismiss = onDismiss) {
        Column(modifier = Modifier.verticalScroll(rememberScrollState()).padding(16.dp), verticalArrangement = Arrangement.spacedBy(1.dp)) {
            var removeChecked by remember { mutableStateOf(false) }
            var toFeed by remember { mutableStateOf<Feed?>(null) }
            if (synthetics.isNotEmpty()) {
                for (f in synthetics) {
                    Row(verticalAlignment = Alignment.CenterVertically) {
                        RadioButton(selected = toFeed == f, onClick = { toFeed = f })
                        Text(f.title ?: "No title")
                    }
                }
                Row(verticalAlignment = Alignment.CenterVertically) {
                    Checkbox(checked = removeChecked, onCheckedChange = { removeChecked = it })
                    Text(text = stringResource(R.string.remove_from_current_feed), style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 10.dp))
                }
            } else Text(text = stringResource(R.string.create_synthetic_first_note))
            if (toFeed != null) Row {
                Spacer(Modifier.weight(1f))
                Button(onClick = {
                    runOnIOScope { shelveToFeed(selected, toFeed!!, removeChecked) }
                    onDismiss()
                }) { Text(stringResource(R.string.confirm_label)) }
            }
        }
    }
}

@Composable
fun EraseEpisodesDialog(selected: List<Episode>, feed: Feed?, onDismiss: () -> Unit) {
    CommonPopupCard(onDismiss = onDismiss) {
        val message = stringResource(R.string.erase_episodes_confirmation_msg)
        var textState by remember { mutableStateOf(TextFieldValue("")) }
        if (feed == null) Text(stringResource(R.string.not_erase_message), modifier = Modifier.padding(10.dp))
        else Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
            Text(message + ": ${selected.size}")
            Text(stringResource(R.string.reason_to_delete_msg))
            BasicTextField(value = textState, onValueChange = { textState = it }, textStyle = TextStyle(fontSize = 16.sp, color = textColor), modifier = Modifier.fillMaxWidth().height(100.dp).padding(start = 10.dp, end = 10.dp, bottom = 10.dp).border(1.dp, MaterialTheme.colorScheme.primary, MaterialTheme.shapes.small))
            Button(onClick = {
                CoroutineScope(Dispatchers.IO).launch { eraseEpisodes(selected, textState.text) }
                onDismiss()
            }) { Text(stringResource(R.string.confirm_label)) }
        }
    }
}

@Composable
fun EpisodeTimetableDialog(episode: Episode, onDismiss: () -> Unit, cb: (Timer)->Unit) {
    CommonDialogSurface(onDismiss = onDismiss) {
        val timers = remember(appAttribs.timetable) { appAttribs.timetable.filter { it.episodeId == episode.id } }
        Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
            for (timer in timers) {
                Row {
                    Text(fullDateTimeString(timer.triggerTime), modifier = Modifier.clickable{ cb(timer) })
                    Spacer(Modifier.width(100.dp))
                    Icon(imageVector = Icons.Filled.Delete, contentDescription = "delete", modifier = Modifier.clickable {
                        timer.cancel()
                        upsertBlk(appAttribs) { it.timetable.remove(timer) }
                    })
                }
            }
        }
    }
}

@Composable
fun EditTimerDialog(timer: Timer, onDismiss: () -> Unit) {
    CommonPopupCard(onDismiss = onDismiss) {
        val zdt = remember(timer.triggerTime) { Instant.fromEpochMilliseconds(timer.triggerTime).toLocalDateTime(TimeZone.currentSystemDefault()) }
        var year by remember(zdt) { mutableIntStateOf(zdt.year) }
        var month by remember(zdt) { mutableIntStateOf(zdt.month.number) }
        var date by remember(zdt) { mutableIntStateOf(zdt.day) }
        var hour by remember(zdt) { mutableIntStateOf(zdt.hour) }
        var minute by remember(zdt) { mutableIntStateOf(zdt.minute) }
        Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
            Text(stringResource(R.string.year) + " " + stringResource(R.string.month) + " " + stringResource(R.string.date))
            Row(verticalAlignment = Alignment.CenterVertically) {
                NumberEditor(year, "", nz = true, instant = true, modifier = Modifier.weight(0.5f)) { year = it }
                NumberEditor(month, "", nz = true, instant = true, modifier = Modifier.weight(0.4f)) { month = it }
                NumberEditor(date, "", nz = true, instant = true, modifier = Modifier.weight(0.4f)) { date = it }
            }
            Text(stringResource(R.string.hour) + " " + stringResource(R.string.minute))
            Row(verticalAlignment = Alignment.CenterVertically) {
                NumberEditor(hour, "", nz = true, instant = true, modifier = Modifier.weight(0.4f)) { hour = it }
                NumberEditor(minute, "", nz = true, instant = true, modifier = Modifier.weight(0.4f)) { minute = it }
            }
            Button(onClick = {
                runOnIOScope {
                    try {
                        val triggerTime = LocalDateTime(year, month, date, hour, minute, 0).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
                        val timer_ = upsertBlkEmb(timer) { it.triggerTime = triggerTime }
                        timer_.reset()
                    } catch (e: Throwable) {
                        Loge(TAG, "editing timer error: ${e.message}")
                    }
                }
                onDismiss()
            }) { Text(stringResource(R.string.confirm_label)) }
        }
    }
}

@Composable
fun AddTimerDialog(episode: Episode, onDismiss: () -> Unit) {
    CommonPopupCard(onDismiss = onDismiss) {
        val zdt = remember { Instant.fromEpochMilliseconds(System.currentTimeMillis()).toLocalDateTime(TimeZone.currentSystemDefault()) }
        var year by remember(zdt) { mutableIntStateOf(zdt.year) }
        var month by remember(zdt) { mutableIntStateOf(zdt.month.number) }
        var date by remember(zdt) { mutableIntStateOf(zdt.day) }
        var hour by remember(zdt) { mutableIntStateOf(zdt.hour) }
        var minute by remember(zdt) { mutableIntStateOf(zdt.minute) }
        Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
            Text(stringResource(R.string.year) + " " + stringResource(R.string.month) + " " + stringResource(R.string.date))
            Row(verticalAlignment = Alignment.CenterVertically) {
                NumberEditor(year, "", nz = true, instant = true, modifier = Modifier.weight(0.5f)) { year = it }
                NumberEditor(month, "", nz = true, instant = true, modifier = Modifier.weight(0.4f)) { month = it }
                NumberEditor(date, "", nz = true, instant = true, modifier = Modifier.weight(0.4f)) { date = it }
            }
            Text(stringResource(R.string.hour) + " " + stringResource(R.string.minute))
            Row(verticalAlignment = Alignment.CenterVertically) {
                NumberEditor(hour, "", nz = true, instant = true, modifier = Modifier.weight(0.4f)) { hour = it }
                NumberEditor(minute, "", nz = true, instant = true, modifier = Modifier.weight(0.4f)) { minute = it }
            }
            var isRepeat by remember { mutableStateOf(false) }
            Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
                Checkbox(checked = isRepeat, onCheckedChange = { isChecked -> isRepeat = isChecked })
                Text(stringResource(R.string.repeat_current_media))
            }
            Button(onClick = {
                runOnIOScope {
                    try {
                        val triggerTime = LocalDateTime(year, month, date, hour, minute, 0).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
                        playEpisodeAtTime(triggerTime, episode.id, isRepeat)
                    } catch (e: Throwable) { Loge(TAG, "editing timer error: ${e.message}") }
                }
                onDismiss()
            }) { Text(stringResource(R.string.confirm_label)) }
        }
    }
}

@Composable
fun IgnoreEpisodesDialog(selected: List<Episode>, onDismiss: () -> Unit) {
    CommonPopupCard(onDismiss = onDismiss) {
        Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
            val message = stringResource(R.string.ignore_episodes_confirmation_msg)
            
            var textState by remember { mutableStateOf(TextFieldValue("")) }
            Text(message + ": ${selected.size}")
            Text(stringResource(R.string.reason_to_delete_msg))
            BasicTextField(value = textState, onValueChange = { textState = it }, textStyle = TextStyle(fontSize = 16.sp, color = textColor), modifier = Modifier.fillMaxWidth().height(100.dp).padding(start = 10.dp, end = 10.dp, bottom = 10.dp).border(1.dp, MaterialTheme.colorScheme.primary, MaterialTheme.shapes.small))
            Button(onClick = {
                CoroutineScope(Dispatchers.IO).launch {
                    try {
                        realm.write {
                            for (e in selected) {
                                val hasAlmostEnded = e.hasAlmostEnded()
                                findLatest(e)?.let {
                                    it.setPlayState(EpisodeState.IGNORED, hasAlmostEnded)
                                    it.addComment("Reason to ignore:\n${textState.text}")
                                }
                            }
                        }
                    } catch (e: Throwable) { Logs("IgnoreEpisodesDialog", e) }
                }
                onDismiss()
            }) { Text(stringResource(R.string.confirm_label)) }
        }
    }
}

@Composable
fun FutureStateDialog(selected: List<Episode>, state: EpisodeState, onDismiss: () -> Unit) {
    val message = stringResource(R.string.repeat_episodes_msg)
    CommonPopupCard(onDismiss = onDismiss) {
        Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
            Text(message + ": ${selected.size}")
            var intervals = remember { if (selected.size == 1) selected[0].feed?.repeatIntervals?.toMutableList() else null }
            if (intervals.isNullOrEmpty()) intervals = DEFAULT_INTERVALS.toMutableList()
            val units = INTERVAL_UNITS.map { stringResource(it) }
            var sel by remember { mutableIntStateOf(2) }
            for (i in intervals.indices) {
                Row(verticalAlignment = Alignment.CenterVertically) {
                    RadioButton(selected = i==sel, onClick = { sel = i })
                    NumberEditor(intervals[i], label = units[i], nz = false, instant = true, modifier = Modifier.width(150.dp)) { intervals[i] = it }
                }
            }
            Button(onClick = {
                CoroutineScope(Dispatchers.IO).launch {
                    try {
                        val interval = intervalMillis(intervals[sel], sel)
                        val dueTime = interval + nowInMillis()
                        realm.write {
                            for (e in selected) {
                                val hasAlmostEnded = e.hasAlmostEnded()
                                findLatest(e)?.let {
                                    it.setPlayState(state, hasAlmostEnded)
                                    it.repeatTime = dueTime
                                    it.repeatInterval = interval
                                }
                            }
                        }
                    } catch (e: Throwable) { Logs("AgainEpisodesDialog", e) }
                }
                onDismiss()
            }) { Text(stringResource(R.string.confirm_label)) }
        }
    }
}

@Composable
fun EpisodesFilterDialog(filter_: EpisodeFilter, disabledSet: MutableSet<EpisodesFilterGroup> = mutableSetOf(), showAndOr: Boolean = true, onDismiss: () -> Unit, onFilterChanged: (EpisodeFilter) -> Unit) {
    //    val filterValuesSet = remember {  filter.propertySet ?: mutableSetOf() }
    Dialog(properties = DialogProperties(usePlatformDefaultWidth = false), onDismissRequest = { onDismiss() }) {
        val dialogWindowProvider = LocalView.current.parent as? DialogWindowProvider
        dialogWindowProvider?.window?.setGravity(Gravity.BOTTOM)
        Surface(modifier = Modifier.fillMaxWidth().padding(top = 10.dp, bottom = 10.dp).height(350.dp), color = MaterialTheme.colorScheme.surface.copy(alpha = 0.8f), shape = RoundedCornerShape(16.dp), border = BorderStroke(1.dp, borderColor)) {
            val buttonAltColor = lerp(MaterialTheme.colorScheme.tertiary, Color.Green, 0.5f)
            Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {
                var filter by remember { mutableStateOf(filter_.apply { if (andOr.isBlank()) andOr = "AND" }) }
                var andOr by remember { mutableStateOf(filter.andOr.ifBlank { "AND" }) }
                if (showAndOr) {
                    Row(modifier = Modifier.padding(start = 5.dp).fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.Absolute.Left, verticalAlignment = Alignment.CenterVertically) {
                        Text(stringResource(R.string.join_categories_with) + " :", style = MaterialTheme.typography.bodyMedium , color = textColor, modifier = Modifier.padding(end = 10.dp))
                        Spacer(Modifier.width(30.dp))
                        OutlinedButton(
                            modifier = Modifier.padding(0.dp), border = BorderStroke(2.dp, if (andOr == "OR") borderColor else buttonAltColor),
                            onClick = {
                                andOr = "AND"
                                filter.andOr = andOr
                                onFilterChanged(filter)
                            },
                        ) { Text(text = "AND", color = textColor) }
                        Spacer(Modifier.width(20.dp))
                        OutlinedButton(
                            modifier = Modifier.padding(0.dp), border = BorderStroke(2.dp, if (andOr == "AND") borderColor else buttonAltColor),
                            onClick = {
                                andOr = "OR"
                                filter.andOr = andOr
                                onFilterChanged(filter)
                            },
                        ) { Text(text = "OR", color = textColor) }
                    }
                    HorizontalDivider(color = MaterialTheme.colorScheme.onTertiaryContainer, thickness = 1.dp)
                }
                var selectNone by remember { mutableStateOf(false) }
                Column(modifier = Modifier.fillMaxWidth()) {
                    var expandRow by remember { mutableStateOf(false) }
                    val searchAlgo = remember { SearchAlgo() }
                    var queryText by remember { mutableStateOf(filter.extractText()) }
                    var showSearchBy by remember { mutableStateOf(false) }
                    LaunchedEffect(Unit) { searchAlgo.setSearchByAll() }
                    Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 5.dp, bottom = 2.dp).fillMaxWidth()) {
                        Text(stringResource(R.string.text_label) + "… :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleLarge, color = if (queryText.isBlank()) buttonColor else buttonAltColor, modifier = Modifier.clickable { expandRow = !expandRow })
                        Spacer(Modifier.width(20.dp))
                        if (expandRow) Text(stringResource(R.string.show_criteria), color = buttonColor, modifier = Modifier.clickable {showSearchBy = !showSearchBy})
                    }
                    if (expandRow) {
                        SearchBarRow(R.string.search_hint, defaultText = queryText, modifier = Modifier.fillMaxWidth().padding(start = 10.dp)) { query ->
                            Logd(TAG, "SearchBarRow cb query: $query")
                            if (query.isNotBlank()) {
                                selectNone = false
                                val queryWords = (if (query.contains(",")) query.split(",").map { it.trim() } else query.split("\\s+".toRegex())).dropWhile { it.isEmpty() }
                                val queryString = searchAlgo.episodesQueryString(0L, queryWords)
                                Logd(TAG, "SearchBarRow cb queryString: $queryString")
                                filter.addTextQuery(queryString)
                            } else filter.addTextQuery("")
                            queryText = query
                            onFilterChanged(filter)
                        }
                        if (showSearchBy) searchAlgo.SearchByGrid(setOf(SearchBy.AUTHOR))
                    }
                }
                if (appAttribs.episodeTagSet.isNotEmpty()) {
                    val tagList = remember { appAttribs.episodeTagSet.toList().sorted() }
                    Column(modifier = Modifier.fillMaxWidth()) {
                        val selectedList = remember { MutableList(tagList.size) { mutableStateOf(false) } }
                        val tagsSel = remember { mutableStateListOf<String>() }
                        var expandRow by remember { mutableStateOf(false) }
                        Row(modifier = Modifier.padding(start = 5.dp, bottom = 2.dp).fillMaxWidth()) {
                            Text(stringResource(R.string.tags_label) + "… :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleLarge, color = if (tagsSel.size == tagList.size) buttonColor else buttonAltColor, modifier = Modifier.clickable { expandRow = !expandRow })
                            if (expandRow) {
                                val cb = {
                                    for (i in tagList.indices) {
                                        if (selectedList[i].value) {
                                            filter.addTag(tagList[i])
                                            tagsSel.add(tagList[i])
                                        }
                                        else {
                                            filter.removeTag(tagList[i])
                                            tagsSel.remove(tagList[i])
                                        }
                                    }
                                    onFilterChanged(filter)
                                }
                                SelectLowerAllUpper(selectedList, lowerCB = cb, allCB = cb, upperCB = cb)
                            }
                        }
                        if (expandRow) ScrollRowGrid(columns = 3, itemCount = tagList.size, modifier = Modifier.padding(start = 10.dp)) { index ->
                            LaunchedEffect(Unit) { if (filter.containsTag(tagList[index])) selectedList[index].value = true }
                            OutlinedButton(
                                modifier = Modifier.padding(0.dp).heightIn(min = 20.dp).widthIn(min = 20.dp).wrapContentWidth(), border = BorderStroke(2.dp, if (selectedList[index].value) buttonAltColor else borderColor),
                                onClick = {
                                    selectNone = false
                                    selectedList[index].value = !selectedList[index].value
                                    if (selectedList[index].value) filter.addTag(tagList[index])
                                    else filter.removeTag(tagList[index])
                                    onFilterChanged(filter)
                                },
                            ) { Text(text = tagList[index], maxLines = 1, color = textColor) }
                        }
                    }
                }
                for (item in EpisodesFilterGroup.entries) {
                    if (item in disabledSet) continue
                    if (item.properties.size == 2) {
                        Row(modifier = Modifier.padding(start = 5.dp).fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.Absolute.Left, verticalAlignment = Alignment.CenterVertically) {
                            var selectedIndex by remember { mutableIntStateOf(-1) }
                            if (selectNone) selectedIndex = -1
                            LaunchedEffect(Unit) {
                                if (item.properties[0].filterId in filter.propertySet) selectedIndex = 0
                                else if (item.properties[1].filterId in filter.propertySet) selectedIndex = 1
                            }
                            Text(stringResource(item.nameRes) + " :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleLarge , color = textColor, modifier = Modifier.padding(end = 10.dp))
                            Spacer(Modifier.width(30.dp))
                            OutlinedButton(
                                modifier = Modifier.padding(0.dp), border = BorderStroke(2.dp, if (selectedIndex != 0) borderColor else buttonAltColor),
                                onClick = {
                                    if (selectedIndex != 0) {
                                        selectNone = false
                                        selectedIndex = 0
                                        filter.add(item.properties[0].filterId)
                                        filter.remove(item.properties[1].filterId)
                                    } else {
                                        selectedIndex = -1
                                        filter.remove(item.properties[0].filterId)
                                    }
                                    Logd("EpisodeFilterDialog", "filterValues = [${filter.propertySet}]")
                                    onFilterChanged(filter)
                                },
                            ) { Text(text = stringResource(item.properties[0].displayName), color = textColor) }
                            Spacer(Modifier.width(20.dp))
                            OutlinedButton(
                                modifier = Modifier.padding(0.dp), border = BorderStroke(2.dp, if (selectedIndex != 1) borderColor else buttonAltColor),
                                onClick = {
                                    if (selectedIndex != 1) {
                                        selectNone = false
                                        selectedIndex = 1
                                        filter.add(item.properties[1].filterId)
                                        filter.remove(item.properties[0].filterId)
                                    } else {
                                        selectedIndex = -1
                                        filter.remove(item.properties[1].filterId)
                                    }
                                    onFilterChanged(filter)
                                },
                            ) { Text(text = stringResource(item.properties[1].displayName), color = textColor) }
                            //                            Spacer(Modifier.weight(0.5f))
                        }
                    } else {
                        Column(modifier = Modifier.padding(start = 5.dp, bottom = 2.dp).fillMaxWidth()) {
                            val selectedList = remember { MutableList(item.properties.size) { mutableStateOf(false)} }
                            var expandRow by remember { mutableStateOf(false) }
                            when (item) {
                                EpisodesFilterGroup.DURATION -> {
                                    Text(stringResource(item.nameRes) + "… :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleLarge, color = buttonColor, modifier = Modifier.clickable { expandRow = !expandRow })
                                    if (expandRow) {
                                        Row(verticalAlignment = Alignment.CenterVertically) {
                                            var showIcon by remember { mutableStateOf(false) }
                                            var floor by remember { mutableIntStateOf(filter.durationFloor/1000) }
                                            var ceiling by remember { mutableIntStateOf(filter.durationCeiling/1000) }
                                            NumberEditor(floor, stringResource(R.string.floor_seconds), nz = true, instant = true, modifier = Modifier.weight(0.4f)) {
                                                floor = it
                                                showIcon = true
                                            }
                                            NumberEditor(ceiling, stringResource(R.string.ceiling_seconds), nz = true, instant = true, modifier = Modifier.weight(0.4f)) {
                                                ceiling = it
                                                showIcon = true
                                            }
                                            if (showIcon) Icon(imageVector = Icons.Filled.Settings, contentDescription = "Settings icon",
                                                modifier = Modifier.size(30.dp).padding(start = 10.dp).clickable {
                                                    val f = floor
                                                    val c = if (ceiling == 0) Int.MAX_VALUE else ceiling
                                                    filter.durationFloor = f * 1000
                                                    filter.durationCeiling = if (c < Int.MAX_VALUE) c * 1000 else c
                                                    showIcon = false
                                                })
                                        }
                                    }
                                }
                                EpisodesFilterGroup.TITLE_TEXT -> {
                                    Text(stringResource(item.nameRes) + "… :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleLarge, color = buttonColor, modifier = Modifier.clickable { expandRow = !expandRow })
                                    if (expandRow) {
                                        var showIcon by remember { mutableStateOf(false) }
                                        var titleText by remember { mutableStateOf((filter.titleText)) }
                                        TextField(value = titleText, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), label = { Text("Text in titles") }, singleLine = true,
                                            onValueChange = {
                                                titleText = it
                                                showIcon = true
                                            },
                                            trailingIcon = {
                                                if (showIcon) Icon(imageVector = Icons.Filled.Settings, contentDescription = "Settings icon",
                                                    modifier = Modifier.size(30.dp).clickable {
                                                        filter.titleText = titleText
                                                        showIcon = false
                                                    })
                                            })
                                    }
                                }
                                else -> {
                                    Row(modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.Absolute.Left, verticalAlignment = Alignment.CenterVertically) {
                                        Text(stringResource(item.nameRes) + "… :", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleLarge, color = buttonColor, modifier = Modifier.clickable { expandRow = !expandRow })
                                        if (expandRow) {
                                            val cb = {
                                                for (i in item.properties.indices) {
                                                    if (selectedList[i].value) filter.add(item.properties[i].filterId)
                                                    else filter.remove(item.properties[i].filterId)
                                                }
                                                onFilterChanged(filter)
                                            }
                                            SelectLowerAllUpper(selectedList, lowerCB = cb, allCB = cb, upperCB = cb)
                                        }
                                    }
                                }
                            }
                            if (expandRow) ScrollRowGrid(columns = 3, itemCount = item.properties.size) { index ->
                                if (selectNone) selectedList[index].value = false
                                LaunchedEffect(Unit) { if (item.properties[index].filterId in filter.propertySet) selectedList[index].value = true }
                                OutlinedButton(
                                    modifier = Modifier.padding(0.dp).heightIn(min = 20.dp).widthIn(min = 20.dp).wrapContentWidth(), border = BorderStroke(2.dp, if (selectedList[index].value) buttonAltColor else borderColor),
                                    onClick = {
                                        selectNone = false
                                        selectedList[index].value = !selectedList[index].value
                                        if (selectedList[index].value) {
                                            filter.add(item.properties[index].filterId)
                                            if (item.exclusive) for (i in selectedList.indices) {
                                                if (i != index) {
                                                    selectedList[i].value = false
                                                    filter.remove(item.properties[i].filterId)
                                                }
                                            }
                                        } else filter.remove(item.properties[index].filterId)
                                        onFilterChanged(filter)
                                    },
                                ) { Text(text = stringResource(item.properties[index].displayName), maxLines = 1, color = textColor) }
                            }
                        }
                    }
                }
                Row {
                    Spacer(Modifier.weight(0.3f))
                    Button(onClick = {
                        selectNone = true
                        filter.propertySet.clear()
                        onFilterChanged(filter)
                    }) { Text(stringResource(R.string.reset)) }
                    Spacer(Modifier.weight(0.4f))
                    Button(onClick = { onDismiss() }) { Text(stringResource(R.string.close_label)) }
                    Spacer(Modifier.weight(0.3f))
                }
            }
        }
    }
}

@Composable
fun EpisodeSortDialog(initOrder: EpisodeSortOrder, includeConditionals: List<EpisodeSortOrder> = listOf(), onDismiss: () -> Unit, onSelectionChanged: (EpisodeSortOrder?) -> Unit) {
    val viewCounts = remember { listOf(EpisodeSortOrder.VIEWS_ASC, EpisodeSortOrder.VIEWS_DESC, EpisodeSortOrder.VIEWS_SPEED_ASC, EpisodeSortOrder.VIEWS_SPEED_DESC) }
    val likeCounts = remember { listOf(EpisodeSortOrder.LIKES_ASC, EpisodeSortOrder.LIKES_DESC) }
    val orderList = remember { EpisodeSortOrder.entries.filterIndexed { index, order -> index % 2 != 0 && (!order.conditional || order in includeConditionals || order in ((if (clientshaveViewCounts()) viewCounts else listOf()) + (if (clientshaveLikeCounts()) likeCounts else listOf()) ) ) } }
    val buttonAltColor = lerp(MaterialTheme.colorScheme.tertiary, Color.Green, 0.5f)
    Dialog(properties = DialogProperties(usePlatformDefaultWidth = false), onDismissRequest = { onDismiss() }) {
        val dialogWindowProvider = LocalView.current.parent as? DialogWindowProvider
        dialogWindowProvider?.window?.setGravity(Gravity.BOTTOM)
        Surface(modifier = Modifier.fillMaxWidth().padding(top = 10.dp, bottom = 10.dp).height(250.dp), color = MaterialTheme.colorScheme.surface.copy(alpha = 0.7f), shape = RoundedCornerShape(16.dp), border = BorderStroke(1.dp, borderColor)) {
            var order by remember { mutableStateOf(initOrder) }
            var sortIndex by remember { mutableIntStateOf(orderList.indexOfFirst { it.code == order.code || it.code == order.code+1 }) }
            Column(Modifier.fillMaxSize().padding(start = 10.dp, end = 10.dp).verticalScroll(rememberScrollState())) {
                ScrollRowGrid(columns = 2, itemCount = orderList.size) { index ->
                    var dir by remember { mutableStateOf(order.code != orderList[sortIndex].code) }
                    OutlinedButton(modifier = Modifier.padding(2.dp), elevation = null, border = BorderStroke(2.dp, if (sortIndex != index) borderColor else buttonAltColor),
                        onClick = {
                            if (sortIndex == index) dir = !dir
                            sortIndex = index
                            order = fromCode(orderList[index].code - if (dir) 1 else 0)
                            onSelectionChanged(order)
                        }
                    ) { Text(text = stringResource(orderList[index].res) + if (dir) "\u00A0▲" else "\u00A0▼", color = textColor, maxLines = 1) }
                }
            }
        }
    }
}

@Composable
fun DatesFilterDialog(from: Long? = null, to: Long? = null, oldestDate: Long, onDismiss: ()->Unit, callback: (Long, Long) -> Unit) {
    @Composable
    fun MonthYearInput(default: String, onMonthYearChange: (String) -> Unit) {
        fun formatMonthYear(input: String): String {
            val sanitized = input.replace(Regex("[^0-9/]"), "")
            return when {
                sanitized.length > 7 -> sanitized.take(7)
                else -> sanitized
            }
        }
        fun isValidMonthYear(input: String): Boolean {
            val regex = Regex("^(0[1-9]|1[0-2])/\\d{4}$")
            return regex.matches(input)
        }
        var monthYear by remember { mutableStateOf(TextFieldValue(default)) }
        var isValid by remember { mutableStateOf(isValidMonthYear(monthYear.text)) }
        Column(modifier = Modifier.padding(16.dp)) {
            TextField(value = monthYear, label = { Text(stringResource(R.string.statistics_month_year)) },
                onValueChange = { input ->
                    monthYear = input
                    val formattedInput = formatMonthYear(monthYear.text)
                    isValid = isValidMonthYear(formattedInput)
                    if (isValid) onMonthYearChange(formattedInput)
                },
                isError = !isValidMonthYear(monthYear.text),
                modifier = Modifier.fillMaxWidth()
            )
            if (!isValid) Text(text = "Invalid format. Please use MM/YYYY.", color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodyMedium)
        }
    }
    fun convertMonthYearToUnixTime(monthYear: String, start: Boolean = true): Long? {
        val regex = Regex("^(0[1-9]|1[0-2])/\\d{4}$")
        if (!regex.matches(monthYear)) return null
        val (month, year) = monthYear.split("/").map { it.toInt() }
        val localDate = if (start) LocalDate(year, month, 1) else {
            val firstOfMonth = LocalDate(year, month, 1)
            firstOfMonth.plus(DatePeriod(months = 1)).minus(DatePeriod(days = 1))
        }
        return localDate.atTime(0, 0).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
    }
    fun convertUnixTimeToMonthYear(unixTime: Long): String {
        val instant = Instant.fromEpochMilliseconds(unixTime)
        val localDate = instant.toLocalDateTime(TimeZone.currentSystemDefault()).date
        val monthYearFormat = LocalDate.Format {
            monthNumber()
            char('/')
            year()
        }
        return localDate.format(monthYearFormat)
    }
    var timeFilterFrom by remember { mutableLongStateOf(from ?: 0L) }
    var timeFilterTo by remember { mutableLongStateOf(to ?: Long.MAX_VALUE) }
    var useAllTime by remember { mutableStateOf((from == null || from == 0L) && (to == null || to == Long.MAX_VALUE)) }
    val timeFrom = remember(timeFilterFrom) { if (timeFilterFrom == 0L) oldestDate else timeFilterFrom }
    val timeTo = remember(timeFilterTo) { if (timeFilterTo == Long.MAX_VALUE) nowInMillis() else timeFilterTo }
    AlertDialog(modifier = Modifier.border(1.dp, MaterialTheme.colorScheme.tertiary, MaterialTheme.shapes.extraLarge), onDismissRequest = { onDismiss() },
        title = { Text(stringResource(R.string.share_label), style = CustomTextStyles.titleCustom) },
        text = {
            Column {
                if (!useAllTime) {
                    Row(verticalAlignment = Alignment.CenterVertically) {
                        Text(stringResource(R.string.statistics_start_month))
                        MonthYearInput(convertUnixTimeToMonthYear(timeFrom)) { timeFilterFrom = convertMonthYearToUnixTime(it) ?: timeFrom }
                    }
                    Row(verticalAlignment = Alignment.CenterVertically) {
                        Text(stringResource(R.string.statistics_end_month))
                        MonthYearInput(convertUnixTimeToMonthYear(timeTo)) { timeFilterTo = convertMonthYearToUnixTime(it, false) ?: timeTo }
                    }
                }
                Row(verticalAlignment = Alignment.CenterVertically) {
                    Checkbox(checked = useAllTime, onCheckedChange = {
                        useAllTime = it
                        if (!useAllTime) {
                            timeFilterFrom = convertMonthYearToUnixTime(convertUnixTimeToMonthYear(timeFrom)) ?: timeFrom
                            timeFilterTo = convertMonthYearToUnixTime(convertUnixTimeToMonthYear(timeTo), false) ?: timeTo
                        }
                    })
                    Text(stringResource(R.string.statistics_filter_all_time))
                }
                Text(stringResource(R.string.statistics_speed_not_counted))
            }
        },
        confirmButton = {
            TextButton(onClick = {
                if (useAllTime) {
                    timeFilterFrom = 0L
                    timeFilterTo = Long.MAX_VALUE
                }
                callback(timeFilterFrom, timeFilterTo)
                onDismiss()
            }) { Text(text = "OK") }
        },
        dismissButton = { TextButton(onClick = { onDismiss() }) { Text(stringResource(R.string.cancel_label)) } }
    )
}

@Composable
fun MulticastDialog(selected: List<Episode>, onDismiss: ()->Unit) {
    var udpPort by remember(appAttribs.udpPort) { mutableIntStateOf(appAttribs.udpPort) }
    var synthName by remember { mutableStateOf("") }
    var receivers by remember { mutableStateOf<List<DiscoveredReceiver>>(listOf()) }
    val sendJobs = remember { mutableStateMapOf<String, Job>() }
    var started by remember { mutableStateOf(false) }
    LaunchedEffect(Unit) {
        withContext(Dispatchers.IO) {
            listenForUDPBroadcasts(udpPort) { list ->
                if (list.isNotEmpty()) {
                    receivers = list
                    Logd("MulticastDialog", "number of receivers: ${list.size}")
                }
            }
        }
    }
    fun cleanup() {
        sendJobs.forEach { it.value.cancel() }
        onDismiss()
    }
    LaunchedEffect(sendJobs.size) {
        Logd(TAG, "LaunchedEffect(sendJobs.size) ${sendJobs.size}")
        if (sendJobs.isNotEmpty()) started = true
        if (started && sendJobs.isEmpty()) cleanup()
    }
    DisposableEffect(Unit) { onDispose { cleanup() } }

    AlertDialog(modifier = Modifier.border(1.dp, MaterialTheme.colorScheme.tertiary, MaterialTheme.shapes.extraLarge), onDismissRequest = {  },
        title = { Text(stringResource(R.string.multicast_to_devices), style = CustomTextStyles.titleCustom) },
        text = {
            Column {
                Text(stringResource(R.string.send_to_device_sum))
                TextField(value = udpPort.toString(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), label = { Text(stringResource(R.string.broadcast_port)) }, singleLine = true, modifier = Modifier.padding(end = 8.dp), onValueChange = { udpPort = it.toIntOrNull() ?: 0 })
                FlowRow(horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.padding(10.dp)) {
                    for (receiver in receivers) FilterChip(label = { Text(receiver.name) }, selected = false, onClick = {})
                }
                TextField(value = synthName, label = { Text("in " + stringResource(R.string.synthetic)) }, singleLine = true, modifier = Modifier.padding(end = 8.dp), onValueChange = { synthName = it })
            }
        },
        confirmButton = {
            if (sendJobs.isEmpty() && receivers.isNotEmpty()) TextButton(onClick = {
                if (udpPort != appAttribs.udpPort) upsertBlk(appAttribs) { it.udpPort = udpPort }
                for (r in receivers) {
                    val job = sendEpisodes(r.ip, r.port, synthName, selected) {
                        sendJobs[r.ip]?.let { j ->
                            Logd(TAG, "closing job: ${r.ip} ${sendJobs.size}")
                            j.cancel()
                            sendJobs.remove(r.ip)
                        }
                    }
                    sendJobs[r.ip] = job
                }
            }) { Text(stringResource(R.string.send)) }
        },
        dismissButton = { TextButton(onClick = { cleanup() }) { Text(stringResource(R.string.cancel_label)) } }
    )
}

@Composable
fun ConfirmAddEpisodes(sharedUrls: List<String>, onDismiss: () -> Unit) {
    val YTSyndMap = remember { mutableStateMapOf<Int, Boolean>() }
    var synthetics by remember { mutableStateOf(allFeeds.filter { it.id in 1..1000 }) }
    Dialog(onDismissRequest = { onDismiss() }) {
        Card(modifier = Modifier.height(350.dp).padding(16.dp), shape = RoundedCornerShape(16.dp), border = BorderStroke(1.dp, borderColor)) {
            var toFeed by remember { mutableStateOf<Feed?>(null) }
            var showComfirmButton by remember { mutableStateOf(toFeed != null) }
            Column(modifier = Modifier.fillMaxWidth()) {
                Text(stringResource(R.string.add_to_feed), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
//                if (YTSyndMap.size < 4) {
//                    Button(onClick = {
//                        createYTSyndicates()
//                        synthetics = allFeeds.filter { it.id in 1..1000 }
//                    }) { Text(stringResource(R.string.create_YT_syndicates)) }
//                }
                if (synthetics.isNotEmpty()) {
                    Text(text = stringResource(R.string.add_to_synthetic_note), style = MaterialTheme.typography.bodySmall, color = textColor)
                    LaunchedEffect(synthetics) { synthetics.forEach { f-> if (f.id <= 4) YTSyndMap[f.id.toInt()] = true } }
                    LazyColumn(modifier = Modifier.weight(1f).padding(start = 10.dp, end = 10.dp), verticalArrangement = Arrangement.Top) {
                        items(synthetics, key = { it.id }) { f ->
                            Row(verticalAlignment = Alignment.CenterVertically) {
                                RadioButton(selected = toFeed == f, onClick = {
                                    toFeed = f
                                    showComfirmButton = true
                                })
                                Text(f.title ?: "No title", color = textColor)
                            }
                        }
                    }
                } else Text(text = stringResource(R.string.create_synthetic_first_note), color = textColor)
                var showProgress by remember { mutableStateOf(false) }
                if (showComfirmButton) {
                    Button(onClick = {
                        showComfirmButton = false
                        showProgress = true
                        CoroutineScope(Dispatchers.IO).launch {
                            fun addToSyndicate(episode: Episode, feed: Feed) : Int {
                                Logd(TAG, "addToYoutubeSyndicate: feed: ${feed.title}")
                                val episodes = feed.episodes
                                if (episodes.firstOrNull { it.identifyingValue == episode.identifyingValue } != null) return Status.EXISTING.ordinal
                                Logd(TAG, "addToSyndicate adding new episode: ${episode.title}")
                                episode.id = getEntityId()
                                episode.feedId = feed.id
                                upsertBlk(episode) {}
                                EventFlow.postStickyEvent(FlowEvent.FeedUpdatingEvent(false))
                                return 1
                            }

                            for (url in sharedUrls) {
                                val log = realm.query(ShareLog::class).query("url == $0", url).first().find()
                                suspend fun addEpisode(client: SourceGatewayClient,  url: String) {
                                    val episode = client.withProvider { it.buildEpisode(url)?.toEpisode() }
                                    if (episode != null) {
                                        val status = addToSyndicate(episode, toFeed!!)
                                        if (log != null) upsert(log) {
                                            it.title = episode.title
                                            it.status = status
                                        }
                                    } else {
                                        Loge(TAG, "Error adding media at url: $url")
                                        if (log != null) upsert(log) { it.details = "Can not build episode" }
                                    }
                                }
                                try {
                                    var client = sourceClients.find { it.withProvider { p-> p.canHandleUrl(url) == 1 } == true }
                                    if (client != null) addEpisode(client, url)
                                    else {
                                        client = sourceClients.find { it.withProvider { p-> p.canHandleUrl(url) == 0 } == true }
                                        if (client != null) addEpisode(client, url)
                                    }
                                } catch (e: Throwable) {
                                    Loge(TAG, "Receive share error: ${e.message}")
                                    if (log != null) upsert(log) { it.details = e.message ?: "error" }
                                }
                            }
                            withContext(Dispatchers.Main) { onDismiss() }
                        }
                    }) { Text(stringResource(R.string.confirm_label)) }
                }
                if (showProgress) CircularProgressIndicator(strokeWidth = 4.dp, modifier = Modifier.padding(start = 40.dp, end = 40.dp).width(30.dp).height(30.dp))
            }
        }
    }
}
