"""Radio / continuous-mix endpoints (§6.5). Stateless: the client passes the seed + already-queued ids and pulls more as the queue drains (offline-first infinite feed). Degrades gracefully when no ML service is configured — the recommendation service falls back to metadata. """ from fastapi import APIRouter from app.api.deps import ( AlbumRepoDep, ArtistRepoDep, CurrentUser, RecommendationServiceDep, ) from app.api.schemas.radio import RadioRequest, RadioResponse, RadioTrackOut from app.api.v1.tracks import _build_track_out from app.application.recommendation_service import RadioPick router = APIRouter(prefix="/radio", tags=["radio"]) async def _to_response( source: str, picks: list[RadioPick], artist_repo: ArtistRepoDep, album_repo: AlbumRepoDep, ) -> RadioResponse: tracks = [p.track for p in picks] artist_ids = list({t.artist_id for t in tracks}) album_ids = list({t.album_id for t in tracks if t.album_id is not None}) artists = {a.id: a for a in await artist_repo.get_many(artist_ids)} albums = {a.id: a for a in await album_repo.get_many(album_ids)} outs = await _build_track_out(tracks, artists, albums) return RadioResponse( source=source, tracks=[ RadioTrackOut(track=out, reason=pick.reason) for out, pick in zip(outs, picks, strict=True) ], ) async def _run_radio( body: RadioRequest, user: CurrentUser, service: RecommendationServiceDep, artist_repo: ArtistRepoDep, album_repo: AlbumRepoDep, ) -> RadioResponse: source, picks = await service.radio( user_id=user.id, seed_track_id=body.seed_track_id, from_likes=body.from_likes, exploration=body.exploration, limit=body.count, exclude_ids=body.exclude_ids, ) return await _to_response(source, picks, artist_repo, album_repo) @router.post("") async def start_radio( body: RadioRequest, user: CurrentUser, service: RecommendationServiceDep, artist_repo: ArtistRepoDep, album_repo: AlbumRepoDep, ) -> RadioResponse: """Start a radio from a seed track or the caller's likes.""" return await _run_radio(body, user, service, artist_repo, album_repo) @router.post("/next") async def next_radio_track( body: RadioRequest, user: CurrentUser, service: RecommendationServiceDep, artist_repo: ArtistRepoDep, album_repo: AlbumRepoDep, ) -> RadioResponse: """Fetch more tracks as the radio queue drains (pass ``exclude_ids``).""" return await _run_radio(body, user, service, artist_repo, album_repo)