package dev.stave.domain.renderer
import dev.stave.domain.model.DidlItem
import kotlinx.coroutines.flow.StateFlow
import kotlin.time.Duration
/**
* The thing that actually plays audio. Implemented by
* [dev.stave.playback.LocalRenderer] (ExoPlayer; also mirrors to a Cast
* session in the play flavour) and
* [dev.stave.upnp.renderer.RemoteUpnpRenderer]; selected by
* [dev.stave.upnp.renderer.RendererCoordinator].
*/
interface Renderer {
val state: StateFlow<PlaybackState>
/**
* Replace the queue with [tracks], position at [startIndex], and start playback.
*
* @param autoplay When true (default), playback begins immediately — the normal
* "user clicked track / album" flow. When false, the queue is set and the
* transport prepares but stays Stopped, waiting for an explicit [play] call.
* Used by the UPnP MediaRenderer's setAVTransportURI handler: the AVTransport
* spec says SetURI loads media into Stopped and the control point follows up
* with Play(), and we have to honor that ordering or buggy control points
* double-toggle and end up in their own Paused state out of sync with ours.
*/
suspend fun load(tracks: List<DidlItem.Track>, startIndex: Int = 0, autoplay: Boolean = true)
/** Append [tracks] to the end of the queue. If the queue is empty, they become the queue
* but are NOT auto-started — the user explicitly asked to queue, not to play. */
suspend fun enqueue(tracks: List<DidlItem.Track>)
/** Insert [tracks] immediately after the currently-playing track. If the queue is empty,
* the tracks become the queue AND playback begins. */
suspend fun playNext(tracks: List<DidlItem.Track>)
/** Jump directly to queue position [index] and start playing. Out-of-range is a no-op. */
suspend fun skipToQueueItem(index: Int)
/** Remove queue item at [index]. Removing the current item advances to the next; removing
* the last remaining item stops playback. */
suspend fun removeFromQueue(index: Int)
/** Reorder the queue by moving the item at [from] to position [to]. */
suspend fun moveInQueue(from: Int, to: Int)
/** Empty the queue and stop playback. */
suspend fun clearQueue()
suspend fun play()
suspend fun pause()
suspend fun stop()
suspend fun seekTo(position: Duration)
suspend fun next()
suspend fun previous()
suspend fun setVolume(volumePercent: Int)
/** Cycle repeat mode: OFF → ALL → ONE → OFF. */
suspend fun setRepeatMode(mode: RepeatMode)
/** Shuffle toggle. Turning on saves the current queue order; turning off restores it.
* Implementation is responsible for the save/restore — UI just flips the flag. */
suspend fun setShuffle(enabled: Boolean)
}
enum class RepeatMode { OFF, ALL, ONE }
data class PlaybackState(
val current: DidlItem.Track? = null,
val queue: List<DidlItem.Track> = emptyList(),
val queueIndex: Int = -1,
val isPlaying: Boolean = false,
val isBuffering: Boolean = false,
val isStopped: Boolean = true,
val position: Duration = Duration.ZERO,
val duration: Duration = Duration.ZERO,
val volumePercent: Int = 100,
val repeatMode: RepeatMode = RepeatMode.OFF,
val shuffleEnabled: Boolean = false,
val error: String? = null,
)