package dev.stave.cast
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import dev.stave.domain.model.DidlItem
import dev.stave.domain.renderer.RepeatMode
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
import javax.inject.Singleton
/**
* Libre flavor: Google Cast is compiled out. This class exists so common code
* can depend on [CastIntegration] unconditionally. All methods are no-ops;
* [isCasting] is always false.
*/
@Singleton
class CastIntegration @Inject constructor(
@Suppress("UnusedPrivateProperty") @ApplicationContext private val context: Context,
) {
val available: Boolean = false
private val _devices = MutableStateFlow<List<CastDevice>>(emptyList())
val devices: StateFlow<List<CastDevice>> = _devices.asStateFlow()
private val _session = MutableStateFlow<CastSession?>(null)
val session: StateFlow<CastSession?> = _session.asStateFlow()
val isCasting: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
// Match play-flavor surface so common code (LocalRenderer) can reference
// these flows unconditionally without a flavor check. All three are
// permanently zero/false in libre since no session can ever start.
val castIsPlaying: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
val castPositionMs: StateFlow<Long> = MutableStateFlow(0L).asStateFlow()
val castDurationMs: StateFlow<Long> = MutableStateFlow(0L).asStateFlow()
val castCurrentItemIndex: StateFlow<Int> = MutableStateFlow(-1).asStateFlow()
val userInitiatedDisconnect: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
fun consumeDisconnectIntent(): Boolean = false
// Diagnostic mirror of the play-flavor surface so shared code (CastPickerSheet)
// can render the same diagnostic UI in both flavors. In libre, the diagnostic
// always reports "compiled out" — which at least makes it clear to the user
// why the picker is empty.
val diagnostic: StateFlow<DiagnosticState> = MutableStateFlow(
DiagnosticState(
castContextOk = false,
mediaRouterOk = false,
playServices = "libre build — Cast compiled out",
rawRouteCount = 0,
filteredDeviceCount = 0,
tick = 0,
rawRouteNames = emptyList(),
)
).asStateFlow()
fun loadTrack(track: DidlItem.Track, autoplay: Boolean = true) = Unit
fun loadQueue(
tracks: List<DidlItem.Track>,
startIndex: Int = 0,
autoplay: Boolean = true,
repeat: RepeatMode = RepeatMode.OFF,
shuffle: Boolean = false,
) = Unit
fun setRepeatAndShuffle(repeat: RepeatMode, shuffle: Boolean) = Unit
fun queueAppend(tracks: List<DidlItem.Track>, fullQueue: List<DidlItem.Track> = tracks) = Unit
fun queueInsertAfterCurrent(tracks: List<DidlItem.Track>) = Unit
fun queueRemoveAt(index: Int) = Unit
fun queueMove(from: Int, to: Int) = Unit
fun consumeAttachWasResume(): Boolean = false
fun currentContentId(): String? = null
fun detachWithoutStopping() = Unit
fun play() = Unit
fun pause() = Unit
fun stop() = Unit
fun seekTo(positionMs: Long) = Unit
fun queueNext() = Unit
fun queuePrev() = Unit
fun queueJumpTo(index: Int) = Unit
// Maintainer, 2026-10-29: mirror of the play flavour's wait-then-jump (D4); no-op here.
suspend fun queueJumpToWhenReady(index: Int) = Unit
fun setVolume(volume: Double) = Unit
fun getDeviceVolume(): Double = 1.0
fun setDeviceVolume(volume: Double) = Unit
fun startDiscovery() = Unit
fun stopDiscovery() = Unit
fun connect(deviceId: String) = Unit
fun disconnect() = Unit
fun requestStatusRefresh() = Unit
}
data class CastDevice(
val id: String,
val friendlyName: String,
val isGroup: Boolean,
)
data class CastSession(
val deviceId: String,
val deviceName: String,
)
data class DiagnosticState(
val castContextOk: Boolean = false,
val castInitError: String? = null,
val mediaRouterOk: Boolean = false,
val playServices: String = "unknown",
val rawRouteCount: Int = 0,
val filteredDeviceCount: Int = 0,
val tick: Int = 0,
val rawRouteNames: List<String> = emptyList(),
)