package dev.castarr.tv.ui

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.focusRequester
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.ui.PlayerView
import androidx.tv.material3.ClickableSurfaceDefaults
import androidx.tv.material3.LocalContentColor
import androidx.tv.material3.Surface
import androidx.tv.material3.Text
import dev.castarr.tv.AppState
import dev.castarr.tv.data.isEpgPlaceholder
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

/**
 * Fullscreen playback. OK opens the auto-hiding overlay whose transport
 * buttons are ordinary focusable controls; media/channel keys are handled
 * in MainActivity.onKeyDown.
 */
@Composable
fun PlayerScreen(state: AppState) {
    val focusRequester = remember { FocusRequester() }

    LaunchedEffect(state.overlayVisible) {
        // While the overlay is hidden the root grabs focus so list items
        // underneath stop reacting; the overlay's play button takes over
        // when it appears.
        if (!state.overlayVisible) focusRequester.requestFocus()
    }
    LaunchedEffect(state.playerVisible, state.playerState, state.overlayVisible, state.overlayPing) {
        if (state.overlayVisible && state.playerState == "playing") {
            kotlinx.coroutines.delay(5000)
            state.overlayVisible = false
        }
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(CastarrColors.bgDeep)
            .focusRequester(focusRequester)
            .focusable()
            .onKeyEvent { false },
    ) {
        AndroidView(
            factory = { context ->
                PlayerView(context).apply {
                    useController = false
                    player = state.player.player
                }
            },
            modifier = Modifier.fillMaxSize(),
        )

        if (state.overlayVisible) {
            Overlay(state)
        }
    }
}

/** Soft drop shadow so overlay text stays readable on bright video. */
private val OverlayText = TextStyle(
    shadow = Shadow(Color(0xB3000000), Offset(0f, 2f), blurRadius = 8f),
)

private fun formatClock(millis: Long): String =
    SimpleDateFormat("HH:mm", Locale.GERMANY).format(Date(millis))

@Composable
private fun Overlay(state: AppState) {
    val playFocus = remember { FocusRequester() }
    LaunchedEffect(Unit) { playFocus.requestFocus() }
    // The key-UP of the very press that opened this overlay arrives after
    // the play button grabbed focus and would click it (instant pause).
    // Swallow select-releases whose press the overlay never saw.
    var sawSelectDown by remember { mutableStateOf(false) }

    Box(
        Modifier
            .fillMaxSize()
            // Any key while the overlay is open keeps it open a while longer.
            .onPreviewKeyEvent { event ->
                state.pingOverlay()
                val select = event.key == Key.DirectionCenter ||
                    event.key == Key.Enter || event.key == Key.NumPadEnter
                when {
                    select && event.type == KeyEventType.KeyDown -> {
                        sawSelectDown = true
                        false
                    }
                    select && event.type == KeyEventType.KeyUp && !sawSelectDown -> true
                    else -> false
                }
            },
    ) {
        Box(
            Modifier
                .fillMaxWidth()
                .height(280.dp)
                .align(Alignment.BottomCenter)
                .background(
                    Brush.verticalGradient(
                        listOf(Color.Transparent, CastarrColors.bgDeep.copy(alpha = 0.94f))
                    )
                )
        )
        state.connectedRemote?.let { name ->
            Row(
                modifier = Modifier
                    .align(Alignment.TopEnd)
                    .padding(top = 22.dp, end = 30.dp),
                verticalAlignment = Alignment.CenterVertically,
            ) {
                Text(name, color = CastarrColors.muted, fontFamily = AppFont,
                        style = OverlayText, fontSize = 12.sp)
                Spacer(Modifier.width(8.dp))
                Box(
                    Modifier
                        .size(6.dp)
                        .clip(CircleShape)
                        .background(CastarrColors.accent)
                )
            }
        }
        Column(
            modifier = Modifier
                .align(Alignment.BottomStart)
                .fillMaxWidth()
                .padding(horizontal = 36.dp)
                .padding(bottom = 24.dp),
        ) {
            // No status word while simply playing, and no LIVE badge — a
            // red dot reads as "recording" (user feedback).
            val statusWord = when (state.playerState) {
                "paused" -> "Pausiert"
                "buffering" -> "Lädt…"
                "reconnecting" -> "Verbindung wird wiederhergestellt…"
                "error" -> "Sender gerade nicht erreichbar"
                else -> null
            }
            if (statusWord != null) {
                Row {
                    Spacer(Modifier.weight(1f))
                    Text(
                        statusWord,
                        color = CastarrColors.muted,
                        fontFamily = AppFont,
                        style = OverlayText,
                        fontSize = 12.sp,
                    )
                }
                Spacer(Modifier.height(4.dp))
            }
            Row(verticalAlignment = Alignment.Bottom) {
                // Channel number inline before the name (matches the list
                // and the number-pad zapping).
                val number = state.currentChannel?.let { current ->
                    state.activeChannels().indexOfFirst { it.url == current.url } + 1
                } ?: 0
                if (number > 0) {
                    Text(
                        "$number",
                        color = CastarrColors.accent,
                        fontFamily = AppFont,
                        style = OverlayText,
                        fontSize = 30.sp,
                        fontWeight = FontWeight.SemiBold,
                    )
                    Spacer(Modifier.width(14.dp))
                }
                Text(
                    state.currentChannel?.name.orEmpty(),
                    color = CastarrColors.fg,
                    fontFamily = AppFont,
                    style = OverlayText,
                    fontSize = 30.sp,
                    fontWeight = FontWeight.Medium,
                )
            }

            val info = state.currentChannel?.let { state.nowNext(it) }
            val now = info?.now?.takeUnless { isEpgPlaceholder(it.title) }
            if (now != null) {
                Spacer(Modifier.height(2.dp))
                Row(verticalAlignment = Alignment.Bottom) {
                    Text(
                        now.title,
                        color = CastarrColors.fg,
                        fontFamily = AppFont,
                        style = OverlayText,
                        fontSize = 18.sp,
                    )
                    Spacer(Modifier.width(14.dp))
                    Text(
                        "${formatClock(now.start)} – ${formatClock(now.stop)}",
                        color = Color(0xFFD7DBE0),
                        fontFamily = AppFont,
                        style = OverlayText,
                        fontSize = 14.sp,
                    )
                }
            }
            info?.next?.takeUnless {
                isEpgPlaceholder(it.title) || it.title == now?.title
            }?.let { next ->
                Spacer(Modifier.height(2.dp))
                Text(
                    "Danach: ${next.title} (${formatClock(next.start)})",
                    color = CastarrColors.muted,
                    fontFamily = AppFont,
                        style = OverlayText,
                    fontSize = 14.sp,
                )
            }

            Spacer(Modifier.height(12.dp))
            // Live with EPG: progress of the running programme. Otherwise
            // playback position; unknown duration fills the bar.
            val fraction = when {
                now != null && now.stop > now.start ->
                    ((System.currentTimeMillis() - now.start).toFloat() / (now.stop - now.start))
                        .coerceIn(0f, 1f)
                state.isLive || state.durationMs <= 0 -> 1f
                else -> (state.positionMs.toFloat() / state.durationMs).coerceIn(0f, 1f)
            }
            Box(
                Modifier
                    .fillMaxWidth()
                    .height(3.dp)
                    .clip(RoundedCornerShape(1.5.dp))
                    .background(Color(0x59FFFFFF))
            ) {
                Box(
                    Modifier
                        .fillMaxWidth(fraction)
                        .height(3.dp)
                        .background(CastarrColors.accent)
                )
            }

            Spacer(Modifier.height(16.dp))
            Row(verticalAlignment = Alignment.CenterVertically) {
                if (state.playerState == "error") {
                    // One clear action instead of silently dropping back to
                    // the channel list.
                    PillButton("Erneut versuchen", focusRequester = playFocus) {
                        state.retryPlayback()
                    }
                    Spacer(Modifier.width(10.dp))
                    IconButton(icon = PillIcon.STOP, contentDescription = "Beenden") {
                        state.stopPlayback()
                    }
                } else {
                    IconButton(
                        icon = if (state.playerState == "paused") PillIcon.PLAY else PillIcon.PAUSE,
                        contentDescription = if (state.playerState == "paused") "Weiter" else "Pause",
                        focusRequester = playFocus,
                    ) { state.player.toggle() }
                    Spacer(Modifier.width(10.dp))
                    IconButton(icon = PillIcon.STOP, contentDescription = "Beenden") {
                        state.stopPlayback()
                    }
                }
                if (state.audioTracks.size > 1) {
                    Spacer(Modifier.width(10.dp))
                    val current = state.audioTracks.firstOrNull { it.selected }
                    PillButton(label = "Ton: ${current?.label ?: "–"}") { state.cycleAudio(1) }
                }
                Spacer(Modifier.weight(1f))
                Text(
                    "▲ ▼ Sender wechseln",
                    color = CastarrColors.faint,
                    fontFamily = AppFont,
                        style = OverlayText,
                    fontSize = 12.sp,
                )
            }
        }
    }
}

private enum class PillIcon { PLAY, PAUSE, STOP }

/** Round icon-only transport button; the icons are drawn, not typed —
 * glyphs like U+23F8 render as emoji on Android. */
@Composable
private fun IconButton(
    icon: PillIcon,
    contentDescription: String,
    focusRequester: FocusRequester? = null,
    onClick: () -> Unit,
) {
    Surface(
        onClick = onClick,
        modifier = (if (focusRequester != null) Modifier.focusRequester(focusRequester) else Modifier)
            .semantics { this.contentDescription = contentDescription },
        shape = ClickableSurfaceDefaults.shape(CircleShape),
        colors = ClickableSurfaceDefaults.colors(
            containerColor = CastarrColors.surface.copy(alpha = 0.75f),
            contentColor = CastarrColors.fg,
            focusedContainerColor = CastarrColors.accent,
            focusedContentColor = CastarrColors.onAccent,
        ),
    ) {
        val color = LocalContentColor.current
        Box(Modifier.size(46.dp), contentAlignment = Alignment.Center) {
            Canvas(Modifier.size(15.dp)) {
                when (icon) {
                    PillIcon.PLAY -> drawPath(
                        Path().apply {
                            moveTo(0f, 0f)
                            lineTo(size.width, size.height / 2f)
                            lineTo(0f, size.height)
                            close()
                        },
                        color,
                    )
                    PillIcon.PAUSE -> {
                        val bar = size.width * 0.3f
                        drawRect(color, size = androidx.compose.ui.geometry.Size(bar, size.height))
                        drawRect(
                            color,
                            topLeft = androidx.compose.ui.geometry.Offset(size.width - bar, 0f),
                            size = androidx.compose.ui.geometry.Size(bar, size.height),
                        )
                    }
                    PillIcon.STOP -> drawRect(color)
                }
            }
        }
    }
}

@Composable
private fun PillButton(
    label: String,
    focusRequester: FocusRequester? = null,
    onClick: () -> Unit,
) {
    Surface(
        onClick = onClick,
        modifier = if (focusRequester != null) Modifier.focusRequester(focusRequester) else Modifier,
        shape = ClickableSurfaceDefaults.shape(RoundedCornerShape(999.dp)),
        colors = ClickableSurfaceDefaults.colors(
            containerColor = CastarrColors.surface.copy(alpha = 0.75f),
            contentColor = CastarrColors.fg,
            focusedContainerColor = CastarrColors.accent,
            focusedContentColor = CastarrColors.onAccent,
        ),
    ) {
        Text(
            label,
            fontFamily = AppFont,
                        style = OverlayText,
            fontSize = 14.sp,
            fontWeight = FontWeight.Medium,
            modifier = Modifier.padding(horizontal = 18.dp, vertical = 9.dp),
        )
    }
}
