package com.ponzischeme89.memby.ui

import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bookmark
import androidx.compose.material.icons.filled.BookmarkBorder
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.material.icons.filled.Movie
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
import androidx.tv.material3.Icon
import androidx.tv.material3.Text
import coil.compose.AsyncImage
import com.ponzischeme89.memby.ServiceLocator
import com.ponzischeme89.memby.data.RelatedContent
import com.ponzischeme89.memby.data.ServerConfig
import com.ponzischeme89.memby.data.estimateSeriesPace
import com.ponzischeme89.memby.data.model.BaseItem
import com.ponzischeme89.memby.data.model.MediaRating
import com.ponzischeme89.memby.data.seriesPaceLabel
import com.ponzischeme89.memby.ui.detail.AiringNotice
import com.ponzischeme89.memby.ui.detail.DetailTab
import com.ponzischeme89.memby.ui.detail.DetailZone
import com.ponzischeme89.memby.ui.detail.availableSeasons
import com.ponzischeme89.memby.ui.detail.creditRows
import com.ponzischeme89.memby.ui.detail.defaultSeason
import com.ponzischeme89.memby.ui.detail.detailPositions
import com.ponzischeme89.memby.ui.detail.detailTab
import com.ponzischeme89.memby.ui.detail.detailTabs
import com.ponzischeme89.memby.ui.detail.episodeHeadline
import com.ponzischeme89.memby.ui.detail.episodesForSeason
import com.ponzischeme89.memby.ui.detail.formatAirDate
import com.ponzischeme89.memby.ui.detail.heroFacts
import com.ponzischeme89.memby.ui.detail.isPlayed
import com.ponzischeme89.memby.ui.detail.nextEpisodeToWatch
import com.ponzischeme89.memby.ui.detail.playbackProgress
import com.ponzischeme89.memby.ui.detail.primaryActionLabel
import com.ponzischeme89.memby.ui.detail.remainingLabel
import com.ponzischeme89.memby.ui.detail.seasonLabel
import com.ponzischeme89.memby.ui.detail.seriesEpisodeComparator
import com.ponzischeme89.memby.ui.detail.technicalSpecs
import com.ponzischeme89.memby.ui.detail.unwatchedCount
import java.util.TimeZone

/**
 * A series. Loads its episodes and hands them to [SeriesDetailContent], which is where the
 * layout lives — everything that decides how the screen looks is a parameter there, so it
 * can be previewed and screenshotted without a server behind it.
 */
@Composable
fun SeriesDetailsOverlay(
    item: BaseItem,
    onPlay: (BaseItem) -> Unit,
    onToggleFavorite: (BaseItem, Boolean) -> Unit,
    isMyShow: Boolean,
    onToggleMyShow: (BaseItem, Boolean) -> Unit,
    onClose: () -> Unit,
    onOpenItem: (BaseItem) -> Unit = {},
    airingNotice: AiringNotice? = null,
    modifier: Modifier = Modifier,
) {
    val repository = ServiceLocator.repository
    val settings by repository.settingsFlow.collectAsState(initial = repository.currentSettings)
    var episodes by remember(item.id) { mutableStateOf<List<BaseItem>?>(null) }
    var loadFailed by remember(item.id) { mutableStateOf(false) }
    var related by remember(item.id) { mutableStateOf<RelatedContent?>(null) }
    var trailer by remember(item.id) { mutableStateOf<BaseItem?>(null) }
    var ratings by remember(item.id) { mutableStateOf<List<MediaRating>>(emptyList()) }

    LaunchedEffect(item.id) {
        runCatching { repository.getSeriesEpisodes(item.id) }
            .onSuccess { episodes = it.sortedWith(seriesEpisodeComparator) }
            .onFailure {
                loadFailed = true
                episodes = emptyList()
            }
    }
    // Separate from the episode request on purpose: the two are independent, and the page
    // must not wait on "more like this" to show a show's own episodes.
    LaunchedEffect(item.id) {
        related = repository.getRelated(item)
    }
    LaunchedEffect(item.id) {
        trailer = repository.getLocalTrailer(item.id)
    }
    LaunchedEffect(item.id, settings.showRatingsStrip) {
        ratings = if (settings.showRatingsStrip) repository.getRatings(item) else emptyList()
    }

    SeriesDetailContent(
        item = item,
        episodes = episodes,
        loadFailed = loadFailed,
        onPlay = onPlay,
        onToggleFavorite = onToggleFavorite,
        isMyShow = isMyShow,
        onToggleMyShow = onToggleMyShow,
        related = related,
        trailer = trailer,
        ratings = ratings,
        showRatingsStrip = settings.showRatingsStrip,
        hideWatchedMovies = settings.hideWatchedMovies,
        onOpenItem = onOpenItem,
        airingNotice = airingNotice,
        modifier = modifier,
    )
}

/** [episodes] is null while they are still loading, as is [related]. */
@Composable
internal fun SeriesDetailContent(
    item: BaseItem,
    episodes: List<BaseItem>?,
    loadFailed: Boolean,
    onPlay: (BaseItem) -> Unit,
    onToggleFavorite: (BaseItem, Boolean) -> Unit,
    isMyShow: Boolean,
    onToggleMyShow: (BaseItem, Boolean) -> Unit,
    modifier: Modifier = Modifier,
    related: RelatedContent? = null,
    trailer: BaseItem? = null,
    ratings: List<MediaRating> = emptyList(),
    showRatingsStrip: Boolean = true,
    hideWatchedMovies: Boolean = false,
    onOpenItem: (BaseItem) -> Unit = {},
    airingNotice: AiringNotice? = null,
) {
    val remembered = remember(item.id) { detailPositions.get(item.id) }
    val seasons = remember(episodes) { availableSeasons(episodes.orEmpty()) }
    var selectedSeason by rememberSaveable(item.id) { mutableStateOf(remembered.season) }
    LaunchedEffect(seasons) {
        if (selectedSeason !in seasons) {
            selectedSeason = defaultSeason(episodes.orEmpty())
        }
    }
    val seasonEpisodes = remember(episodes, selectedSeason) {
        episodesForSeason(episodes.orEmpty(), selectedSeason)
    }
    val nextEpisode = remember(episodes) { nextEpisodeToWatch(episodes.orEmpty()) }
    val remaining = remember(episodes) { unwatchedCount(episodes.orEmpty()) }
    // Recalculated from the episode list itself, so finishing an episode, marking one
    // watched and a newly imported episode all move it with no cache to invalidate. The
    // day is read once per composition of this page rather than on a timer: an estimate
    // that ticks over at midnight under a page nobody is looking at is not worth a clock.
    val paceLabel = remember(episodes, item.id) {
        seriesPaceLabel(
            estimateSeriesPace(
                episodes = episodes.orEmpty(),
                nowMs = System.currentTimeMillis(),
                zoneOffsetMs = TimeZone.getDefault().getOffset(System.currentTimeMillis()),
                ongoing = item.isOngoingSeries,
            ),
        )
    }
    val specs = remember(item.id, item.mediaStreams) { technicalSpecs(item) }
    val credits = remember(item.id, item.people, item.genres) { creditRows(item) }
    val visibleRelated = remember(related, hideWatchedMovies) {
        visibleWithWatchedPreference(related?.items.orEmpty(), hideWatchedMovies)
    }

    // Fixed on the first frame from what the item *is*. A series has episodes, a cast and
    // technical details whether or not they have arrived yet, and a strip that waits for
    // the network is a strip that moves under the viewer's thumb.
    val tabs = remember(item.id) { detailTabs(isSeries = true) }
    val detailsLoaded = item.people.isNotEmpty() || item.mediaStreams.isNotEmpty()
    // A series opens on its cinematic hero and Overview just like a movie. Season and
    // rail positions are still remembered once the viewer enters Episodes.
    var tabKey by rememberSaveable(item.id) { mutableStateOf(DetailTab.OVERVIEW.key) }
    val selectedTab = detailTab(tabKey, tabs)

    val play = remember(item.id) { FocusRequester() }
    val tabStrip = remember(item.id) { FocusRequester() }
    // One requester per pane: Overview, Cast & Details and the Episodes empty states all
    // shared a single "information pane" requester, which left it attached to two live
    // nodes while AnimatedContent faded the outgoing one out.
    val overviewPane = remember(item.id) { FocusRequester() }
    val castPane = remember(item.id) { FocusRequester() }
    val episodesPane = remember(item.id) { FocusRequester() }
    val firstEpisode = remember(item.id) { FocusRequester() }
    val firstRelated = remember(item.id) { FocusRequester() }
    val seasonFocusRequesters = remember(seasons) { seasons.associateWith { FocusRequester() } }
    val episodeListState = rememberLazyListState(remembered.episodeIndex)
    val relatedListState = rememberLazyListState(remembered.relatedIndex)

    // Every one of these targets has to be attached to something on screen *right now*. A
    // `focusProperties` pointing at a requester that was never placed throws the moment the
    // viewer presses that direction — and while the episode request is in flight, on a
    // one-season show, or on any tab other than Episodes, neither the chips nor the cards
    // exist. [FocusRequester.Default] hands the press back to ordinary focus search.
    val episodesOpen = selectedTab == DetailTab.EPISODES
    val seasonStripShown = episodesOpen && seasons.size > 1
    val hasEpisodeCards = episodesOpen && seasonEpisodes.isNotEmpty()
    val selectedSeasonChip =
        if (seasonStripShown) seasonFocusRequesters[selectedSeason] else null
    val episodeEntry = selectedSeasonChip
        ?: firstEpisode.takeIf { hasEpisodeCards }
        ?: episodesPane
    val belowSeasons = if (hasEpisodeCards) firstEpisode else FocusRequester.Default
    val aboveEpisodes = selectedSeasonChip ?: tabStrip
    val contentEntry = when (selectedTab) {
        DetailTab.EPISODES -> episodeEntry
        DetailTab.MORE_LIKE_THIS -> firstRelated
        DetailTab.CAST_DETAILS -> castPane
        else -> overviewPane
    }

    DetailPositionMemory(
        itemId = item.id,
        tabKey = tabKey,
        season = selectedSeason,
        episodeIndex = { episodeListState.firstVisibleItemIndex },
        relatedIndex = { relatedListState.firstVisibleItemIndex },
    )
    RestoreDetailFocus(
        itemId = item.id,
        zone = DetailZone.PLAY,
        play = play,
        tabStrip = tabStrip,
        related = firstRelated,
        relatedReady = visibleRelated.isNotEmpty(),
        content = contentEntry,
        contentReady = true,
    )

    var confirmation by remember(item.id) { mutableStateOf<String?>(null) }
    LaunchedEffect(confirmation) {
        if (confirmation != null) {
            kotlinx.coroutines.delay(1_800L)
            confirmation = null
        }
    }

    DetailPageScaffold(
        item = item,
        facts = heroFacts(item, seasons.size),
        // A series' streams describe its episodes; when Emby gives them, they are as true
        // of the show as they are of a film, and the row this page opened from already
        // badges them.
        badges = mediaBadges(item),
        tabs = tabs,
        selectedTab = selectedTab,
        onSelectTab = { tabKey = it.key },
        playLabel = primaryActionLabel(item, nextEpisode),
        onPlay = { onPlay(nextEpisode ?: item) },
        playFocusRequester = play,
        tabFocusRequester = tabStrip,
        contentFocusRequester = contentEntry,
        modifier = modifier,
        progress = nextEpisode?.let(::playbackProgress) ?: 0f,
        progressLabel = nextEpisode?.let(::remainingLabel),
        paceLabel = paceLabel,
        reasons = related?.reasons?.takeIf(List<String>::isNotEmpty)
            ?: listOfNotNull(item.membyRecommendationReason?.takeIf(String::isNotBlank)),
        airingNotice = airingNotice,
        confirmation = confirmation,
        ratings = ratings,
        showRatingsStrip = showRatingsStrip,
        onZoneFocused = { zone -> detailPositions.update(item.id) { it.copy(zone = zone) } },
        heroActions = buildList {
            add(
                DetailHeroAction(
                    // A heart, not a plus: the bookmark beside it is also a "+" glyph, and
                    // two adjacent circles that both read as "add this" say nothing about
                    // which list is which. The heart is already what a home card, the
                    // screensaver and the Favourites row header use for this.
                    icon = if (item.isFavorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder,
                    description = if (item.isFavorite) "Remove from Favourites" else "Add to Favourites",
                    active = item.isFavorite,
                    onClick = {
                        val desired = !item.isFavorite
                        onToggleFavorite(item, desired)
                        confirmation = if (desired) "Added to Favourites" else "Removed from Favourites"
                    },
                ),
            )
            if (ServerConfig.isGateway) {
                add(DetailHeroAction(
                    // Filled/outline says on-or-off for both toggles, so the two differ by
                    // silhouette alone — heart against bookmark. The +/✓ variants said it
                    // a second way and borrowed the glyphs the other buttons use.
                    icon = if (isMyShow) Icons.Default.Bookmark else Icons.Default.BookmarkBorder,
                    description = if (isMyShow) "Remove from My Shows" else "Add to My Shows",
                    active = isMyShow,
                    onClick = { onToggleMyShow(item, !isMyShow) },
                ))
            }
            trailer?.let {
                add(DetailHeroAction(Icons.Default.Movie, "Play trailer", onClick = { onPlay(it) }))
            }
        },
    ) { visibleTab ->
        when (visibleTab) {
            DetailTab.OVERVIEW -> DetailOverviewPane(
                item = item,
                credits = credits,
                focusRequester = overviewPane,
                // The progress bar carries the position, so this only names the episode.
                supportingText = nextEpisode?.let { "Up next  ${episodeHeadline(it)}" },
            )
            DetailTab.EPISODES -> EpisodesPane(
                episodes = episodes,
                seasonEpisodes = seasonEpisodes,
                seasons = seasons,
                selectedSeason = selectedSeason,
                remaining = remaining,
                loadFailed = loadFailed,
                seasonStripShown = seasonStripShown,
                seasonFocusRequesters = seasonFocusRequesters,
                firstEpisodeFocusRequester = firstEpisode,
                belowSeasons = belowSeasons,
                aboveEpisodes = aboveEpisodes,
                tabFocusRequester = tabStrip,
                listState = episodeListState,
                emptyFocusRequester = episodesPane,
                onSelectSeason = { selectedSeason = it },
                onPlay = onPlay,
            )
            DetailTab.MORE_LIKE_THIS -> DetailMoreLikeThisPane(
                items = visibleRelated,
                loading = related == null,
                onSelect = onOpenItem,
                firstFocusRequester = firstRelated,
                listState = relatedListState,
            )
            DetailTab.CAST_DETAILS -> DetailCastAndDetailsPane(
                item = item,
                credits = credits,
                specs = specs,
                detailsLoaded = detailsLoaded,
                focusRequester = castPane,
            )
        }
    }
}

@Composable
private fun EpisodesPane(
    episodes: List<BaseItem>?,
    seasonEpisodes: List<BaseItem>,
    seasons: List<Int>,
    selectedSeason: Int?,
    remaining: Int,
    loadFailed: Boolean,
    seasonStripShown: Boolean,
    seasonFocusRequesters: Map<Int, FocusRequester>,
    firstEpisodeFocusRequester: FocusRequester,
    belowSeasons: FocusRequester,
    aboveEpisodes: FocusRequester,
    tabFocusRequester: FocusRequester,
    listState: LazyListState,
    emptyFocusRequester: FocusRequester,
    onSelectSeason: (Int) -> Unit,
    onPlay: (BaseItem) -> Unit,
) {
    Column(Modifier.fillMaxSize()) {
        Row(verticalAlignment = Alignment.CenterVertically) {
            if (seasonStripShown) {
                LazyRow(
                    horizontalArrangement = Arrangement.spacedBy(8.dp),
                    modifier = Modifier.weight(1f),
                ) {
                    items(seasons, key = { it }) { season ->
                        SeasonChip(
                            season = season,
                            selected = season == selectedSeason,
                            focusRequester = seasonFocusRequesters.getValue(season),
                            episodeFocusRequester = belowSeasons,
                            tabFocusRequester = tabFocusRequester,
                            onClick = { onSelectSeason(season) },
                        )
                    }
                }
            } else if (seasons.size == 1) {
                Text(
                    text = seasonLabel(seasons.first()),
                    color = DetailMutedText,
                    fontSize = 13.sp,
                    fontWeight = FontWeight.SemiBold,
                    modifier = Modifier.weight(1f),
                )
            } else {
                Spacer(Modifier.weight(1f))
            }
            if (episodes != null && remaining > 0) {
                Text(
                    text = "$remaining unwatched",
                    color = DetailQuietText,
                    fontSize = 12.sp,
                    maxLines = 1,
                    modifier = Modifier.padding(start = 14.dp),
                )
            }
        }
        Spacer(Modifier.height(14.dp))
        when {
            episodes == null -> DetailFocusablePane(emptyFocusRequester) { Text("Loading episodes…", color = DetailQuietText, fontSize = 15.sp) }
            loadFailed -> DetailFocusablePane(emptyFocusRequester) { Text("Episodes are temporarily unavailable.", color = DetailQuietText, fontSize = 15.sp) }
            seasonEpisodes.isEmpty() -> DetailFocusablePane(emptyFocusRequester) { Text("No episodes are available.", color = DetailQuietText, fontSize = 15.sp) }
            else -> LazyColumn(
                state = listState,
                verticalArrangement = Arrangement.spacedBy(10.dp),
                contentPadding = PaddingValues(end = 12.dp, bottom = 18.dp),
                modifier = Modifier.fillMaxSize(),
            ) {
                itemsIndexed(seasonEpisodes, key = { _, episode -> episode.id }) { index, episode ->
                    EpisodeCard(
                        episode = episode,
                        onClick = { onPlay(episode) },
                        seasonFocusRequester = aboveEpisodes,
                        isFirst = index == 0,
                        modifier = if (index == 0) {
                            Modifier.focusRequester(firstEpisodeFocusRequester)
                        } else {
                            Modifier
                        },
                    )
                }
            }
        }
    }
}

@Composable
private fun SeasonChip(
    season: Int,
    selected: Boolean,
    focusRequester: FocusRequester,
    episodeFocusRequester: FocusRequester,
    tabFocusRequester: FocusRequester,
    onClick: () -> Unit,
) {
    var focused by remember { mutableStateOf(false) }
    val shape = RoundedCornerShape(7.dp)
    Box(
        modifier = Modifier
            .clip(shape)
            .background(
                when {
                    focused -> Color.White
                    selected -> DetailAccent
                    else -> Color.White.copy(alpha = 0.07f)
                },
            )
            .border(1.dp, if (selected || focused) Color.Transparent else DetailHairline, shape)
            .focusRequester(focusRequester)
            .focusProperties {
                up = tabFocusRequester
                down = episodeFocusRequester
            }
            .onFocusChanged { focused = it.isFocused }
            .clickable(onClick = onClick)
            .padding(horizontal = 14.dp, vertical = 7.dp),
    ) {
        Text(
            text = seasonLabel(season),
            color = if (focused) Color.Black else Color.White,
            fontSize = 12.sp,
            fontWeight = FontWeight.SemiBold,
            maxLines = 1,
        )
    }
}

/**
 * A minimal D-pad row: the focused episode becomes the unmistakable selection.
 *
 * Shared with the episode page, which passes [isCurrent] for the one the page is about —
 * the season's list is otherwise identical there, and two copies of a row this fiddly is
 * how the two screens start disagreeing about what a watched episode looks like.
 */
@Composable
internal fun EpisodeCard(
    episode: BaseItem,
    onClick: () -> Unit,
    seasonFocusRequester: FocusRequester,
    isFirst: Boolean,
    modifier: Modifier = Modifier,
    isCurrent: Boolean = false,
) {
    val repository = ServiceLocator.repository
    var focused by remember { mutableStateOf(false) }
    val imageUrl = remember(episode.id) {
        repository.primaryUrl(episode, 480) ?: repository.backdropUrl(episode, 480)
    }
    val shape = RoundedCornerShape(8.dp)
    val progress = remember(episode.userData, episode.runTimeTicks) { playbackProgress(episode) }
    val scale by animateFloatAsState(
        targetValue = if (focused) 1.01f else 1f,
        animationSpec = tween(100),
        label = "episode-card-focus",
    )
    Row(
        modifier = modifier
            .fillMaxWidth()
            .height(132.dp)
            .graphicsLayer {
                scaleX = scale
                scaleY = scale
                translationY = if (focused) -1f else 0f
            }
            .zIndex(if (focused) 1f else 0f)
            .clip(shape)
            .background(
                when {
                    focused -> Color(0xFF23282C)
                    isCurrent -> DetailAccent.copy(alpha = 0.10f)
                    else -> Color.White.copy(alpha = 0.035f)
                },
            )
            .border(
                width = if (focused) 2.dp else 1.dp,
                color = when {
                    focused -> Color.White.copy(alpha = 0.30f)
                    isCurrent -> DetailAccent.copy(alpha = 0.55f)
                    else -> Color.White.copy(alpha = 0.08f)
                },
                shape = shape,
            )
            .focusProperties {
                up = if (isFirst) seasonFocusRequester else FocusRequester.Default
            }
            .onFocusChanged { focused = it.isFocused }
            .clickable(onClick = onClick)
            .padding(8.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Box(
            modifier = Modifier
                .width(208.dp)
                .fillMaxHeight()
                .clip(RoundedCornerShape(6.dp))
                .background(Color(0xFF15181C)),
        ) {
            AsyncImage(
                model = imageUrl,
                contentDescription = null,
                contentScale = ContentScale.Crop,
                modifier = Modifier.fillMaxSize(),
            )
            Box(
                Modifier
                    .fillMaxSize()
                    .border(
                        width = 1.dp,
                        color = if (focused) Color.White.copy(alpha = 0.28f) else Color.White.copy(alpha = 0.10f),
                        shape = RoundedCornerShape(6.dp),
                    ),
            )
            if (focused) {
                Box(
                    modifier = Modifier
                        .align(Alignment.Center)
                        .size(40.dp)
                        .clip(RoundedCornerShape(20.dp))
                        .background(Color.Black.copy(alpha = 0.76f)),
                    contentAlignment = Alignment.Center,
                ) {
                    Icon(
                        Icons.Default.PlayArrow,
                        contentDescription = "Play selected episode",
                        tint = Color.White,
                        modifier = Modifier.size(25.dp),
                    )
                }
            }
            if (episode.isPlayed) {
                Icon(
                    Icons.Default.CheckCircle,
                    contentDescription = "Watched",
                    tint = DetailAccent,
                    modifier = Modifier
                        .align(Alignment.TopEnd)
                        .padding(6.dp)
                        .size(16.dp),
                )
            }
            if (progress > 0f) {
                Box(
                    Modifier
                        .align(Alignment.BottomStart)
                        .fillMaxWidth()
                        .height(3.dp)
                        .background(Color.Black.copy(alpha = 0.65f)),
                ) {
                    Box(
                        Modifier
                            .fillMaxWidth(progress)
                            .height(3.dp)
                            .background(DetailAccent),
                    )
                }
            }
        }
        Spacer(Modifier.width(16.dp))
        Column(Modifier.weight(1f)) {
            Row(verticalAlignment = Alignment.CenterVertically) {
                episode.indexNumber?.let {
                    Text(
                        text = "$it.",
                        color = if (focused) Color(0xFFB7C0C6) else DetailQuietText,
                        fontSize = 15.sp,
                        fontWeight = FontWeight.Bold,
                        modifier = Modifier.padding(end = 7.dp),
                    )
                }
                Text(
                    text = episode.name,
                    color = if (focused) Color.White else DetailText,
                    fontSize = 15.sp,
                    fontWeight = FontWeight.Bold,
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                    modifier = Modifier.weight(1f),
                )
                if (isCurrent) {
                    Text(
                        text = "THIS EPISODE",
                        color = DetailAccent,
                        fontSize = 10.sp,
                        fontWeight = FontWeight.Bold,
                        maxLines = 1,
                        modifier = Modifier
                            .padding(start = 10.dp)
                            .clip(RoundedCornerShape(5.dp))
                            .background(DetailAccent.copy(alpha = 0.16f))
                            .padding(horizontal = 7.dp, vertical = 3.dp),
                    )
                }
                formatAirDate(episode.premiereDate)?.let {
                    Text(
                        text = it,
                        color = if (focused) Color(0xFFB7C0C6) else DetailQuietText,
                        fontSize = 12.sp,
                        maxLines = 1,
                        modifier = Modifier.padding(start = 12.dp),
                    )
                }
                episode.runtimeMinutes?.let {
                    Text(
                        text = "$it min",
                        color = if (focused) Color(0xFFE4E8EA) else DetailText,
                        fontSize = 13.sp,
                        fontWeight = FontWeight.SemiBold,
                        modifier = Modifier
                            .padding(start = 12.dp)
                            .clip(RoundedCornerShape(5.dp))
                            .background(
                                if (focused) Color.White.copy(alpha = 0.10f)
                                else Color.White.copy(alpha = 0.08f),
                            )
                            .padding(horizontal = 8.dp, vertical = 4.dp),
                    )
                }
            }
            Spacer(Modifier.height(7.dp))
            Text(
                text = episode.overview?.takeIf(String::isNotBlank) ?: "No plot summary available.",
                color = if (focused) Color(0xFFC4CBD0) else DetailMutedText,
                fontSize = 13.sp,
                lineHeight = 18.sp,
                maxLines = 3,
                overflow = TextOverflow.Ellipsis,
            )
        }
        Spacer(Modifier.width(12.dp))
    }
}
