package com.fabledsword.minstrel.player.output

import androidx.mediarouter.media.MediaRouter
import com.fabledsword.minstrel.player.output.upnp.UpnpRoute

/**
 * Narrow domain model for an audio output route. Independent of
 * [MediaRouter.RouteInfo] so the picker UI can preview without an
 * Android framework presence (RouteInfo can't be constructed in
 * JVM tests, mirroring the LikeMediaCallback constraint).
 *
 * The [protocol] field is a forward-compatibility hook for the
 * UPnP / DLNA / Sonos / Cast slice (scope captured in
 * docs/superpowers/specs/2026-06-03-android-output-picker-upnp-scope.md).
 * THIS slice always sets `protocol = SYSTEM` — the system-route
 * categories MediaRouter surfaces (built-in / wired / Bluetooth).
 */
data class OutputRoute(
    val id: String,
    val name: String,
    val description: String?,
    val kind: Kind,
    val protocol: Protocol,
) {
    enum class Kind { BuiltIn, Wired, Bluetooth, Cast, Other }

    enum class Protocol {
        /** System-managed routes — built-in speaker, wired, Bluetooth. */
        SYSTEM,

        /** Generic UPnP / DLNA renderers — reserved for the next slice. */
        UPNP,

        /** Chromecast via the Cast SDK — reserved for a later slice. */
        CAST,

        /** Sonos via the Sonos extension on top of UPnP — reserved. */
        SONOS,
    }

    companion object {
        /**
         * Lift a MediaRouter [route] into the domain model. Kind is
         * inferred from [MediaRouter.RouteInfo.getDeviceType]; unknown
         * device types fall through to [Kind.Other]. "Active" is NOT an
         * intrinsic of the route — the caller derives it by comparing to
         * the selected route (RouteSnapshot.current), because
         * RouteInfo.connectionState only reflects remote-route handshakes
         * and stays DISCONNECTED for local SYSTEM routes even when in use.
         */
        fun fromRouteInfo(route: MediaRouter.RouteInfo): OutputRoute {
            val kind = when (route.deviceType) {
                MediaRouter.RouteInfo.DEVICE_TYPE_BUILTIN_SPEAKER -> Kind.BuiltIn
                MediaRouter.RouteInfo.DEVICE_TYPE_WIRED_HEADSET,
                MediaRouter.RouteInfo.DEVICE_TYPE_WIRED_HEADPHONES,
                -> Kind.Wired
                MediaRouter.RouteInfo.DEVICE_TYPE_BLUETOOTH_A2DP -> Kind.Bluetooth
                MediaRouter.RouteInfo.DEVICE_TYPE_TV -> Kind.Other
                MediaRouter.RouteInfo.DEVICE_TYPE_SPEAKER -> Kind.Other
                else -> Kind.Other
            }
            return OutputRoute(
                id = route.id,
                name = route.name,
                description = route.description,
                kind = kind,
                protocol = Protocol.SYSTEM,
            )
        }

        /**
         * Lift a discovered UPnP renderer into the picker's domain
         * model. Used by the UPnP discovery controller to merge
         * network speakers into the same `OutputPickerController`
         * routes stream the system routes come through.
         *
         * UPnP devices have no MediaRouter connection-state concept —
         * they're always "available" on the LAN, and the picker's
         * selected-route rendering handles the "currently playing"
         * indicator.
         *
         * Subtitle is `manufacturer modelName` joined by a single
         * space, falling back to "Network speaker" when both fields
         * are blank.
         */
        fun fromUpnpRoute(route: UpnpRoute): OutputRoute {
            val description = listOfNotNull(
                route.manufacturer.takeIf { it.isNotBlank() },
                route.modelName.takeIf { it.isNotBlank() },
            ).joinToString(" ").ifBlank { "Network speaker" }
            return OutputRoute(
                id = route.id,
                name = route.name,
                description = description,
                kind = Kind.Other,
                protocol = Protocol.UPNP,
            )
        }
    }
}
