package link.roadio.riff.player.library import link.roadio.cuetree.NodeKind import link.roadio.riff.ui.BrowserItem import link.roadio.riff.ui.BrowserKind import link.roadio.riff.ui.BrowserRowKey internal fun CuetreeBrowseCatalog.trackKey( index: Int, folder: Boolean = false, base: Int? = null, ): BrowserRowKey { val node = snapshot.nodes[index] val label = node.attributes["title"]?.firstOrNull() ?: if (node.kind == NodeKind.FRAGMENT) node.name else node.source?.name.orEmpty() return BrowserRowKey( "${if (folder) "folder-track-row" else "track-row"}:$index" + (base?.let { ":$it" } ?: ""), label, BrowserKind.TRACK, actionTier = !facts.bookAt(index), ) } internal fun CuetreeBrowseCatalog.trackRow( index: Int, folder: Boolean = false, base: Int? = null, album: link.roadio.cuetree.Album? = null, ): BrowserItem = playback.track(index, folder, album, base, resolveBinding = false).copy(id = trackKey(index, folder, base).id, catalog = this) internal fun CuetreeBrowseCatalog.albumKey(index: Int): BrowserRowKey = BrowserRowKey( "album:${snapshot.albumId(index).toULong().toString(16)}", facts.albumLabels[index], BrowserKind.ALBUM, snapshot.albumTitle(index), actionTier = !facts.albumBook(index), ) internal fun CuetreeBrowseCatalog.albumRow(index: Int): BrowserItem = cached("album-row:$index") { val album = snapshot.albums[index] val book = facts.albumBook(index) BrowserItem( albumKey(index).id, album.playerDisplayTitle(), BrowserKind.ALBUM, "album", playable = snapshot.albumAvailable(index), audiobook = book, aiReactionEligible = !book, albumGroupId = album.id, comparisonNameOverride = album.title, catalog = this, children = rows("album-tracks:$index", { album.sources.toIntArray() }, { trackKey(it) }, { trackRow(it, album = album) }), ) } internal fun CuetreeBrowseCatalog.artistKey(index: Int): BrowserRowKey = BrowserRowKey("artist:${snapshot.artistId(index).toULong().toString(16)}", snapshot.artistName(index), BrowserKind.ARTIST) internal fun CuetreeBrowseCatalog.artistRow(index: Int): BrowserItem = cached("artist-row:$index") { val artist = snapshot.artists[index] BrowserItem( artistKey(index).id, artist.name, BrowserKind.ARTIST, "artist", playable = true, catalog = this, children = rows("artist-albums:$index", { artist.albums .map(::albumIndex) .filter { it >= 0 && albumVisible(it) } .toIntArray() }, ::albumKey, ::albumRow), ) } internal fun CuetreeBrowseCatalog.genreKey(index: Int): BrowserRowKey = BrowserRowKey( "genre:${snapshot.genreId(index).toULong().toString(16)}", snapshot.genreName(index), BrowserKind.GENRE, genreId = snapshot.genreDictionaryId(index), ) internal fun CuetreeBrowseCatalog.genreRow(index: Int): BrowserItem = cached("genre-row:$index") { val genre = snapshot.genres[index] BrowserItem( genreKey(index).id, genre.name, BrowserKind.GENRE, "genre", playable = true, genreId = genre.ref.dictionaryId, catalog = this, children = rows("genre-albums:$index", { genre.albums .map(::albumIndex) .filter { it >= 0 && albumVisible(it) } .toIntArray() }, ::albumKey, ::albumRow), ) } internal fun CuetreeBrowseCatalog.folderKey(index: Int) = BrowserRowKey("folder:${snapshot.nodeId(index).hi.toULong().toString(16)}", snapshot.nodeSourceName(index), BrowserKind.FOLDER) internal fun CuetreeBrowseCatalog.folderRow(index: Int): BrowserItem = cached("folder-row:$index") { val folder = folderKey(index) BrowserItem( folder.id, folder.label, BrowserKind.FOLDER, playable = folderPlayable(index), audiobook = folderBook(index), catalog = this, children = rows("folder-children:$index", { folderChildren(index) }, { folderChildKey(it) }, { folderChildRow(it) }), ) } internal fun CuetreeBrowseCatalog.fragments(base: Int): List = (facts.containers.byBase[base] ?: intArrayOf()) .flatMap { facts.containers.records[it] .sources .asList() }.distinct() internal fun CuetreeBrowseCatalog.folderChildren( index: Int, ignoreLeadingArticles: Boolean = false, ): IntArray = snapshot .childrenOf(index) .filter { snapshot.nodeAvailable(it) } .flatMap { child -> when (snapshot.nodeKind(child)) { NodeKind.DIRECTORY -> { listOf(child) } NodeKind.MEDIA -> { val containers = facts.containers.byBase[child] when { containers != null -> containers.map { -1 - it } child in facts.containers.structuredBases -> emptyList() else -> listOf(child) } } else -> { emptyList() } } }.toIntArray() internal fun CuetreeBrowseCatalog.orderedFolderChildren( index: Int, ignoreLeadingArticles: Boolean, ): IntArray = folderChildren(index) .map { child -> val key = folderChildKey(child) val name = link.roadio.riff.ui .browseComparisonName(key.label, ignoreLeadingArticles && key.kind == BrowserKind.FOLDER) Triple( child, key, java.text.Normalizer .normalize(name, java.text.Normalizer.Form.NFKC) .lowercase(java.util.Locale.ROOT), ) }.sortedWith( compareBy> { it.second.kind == BrowserKind.TRACK }.thenBy { it.third }.thenBy { it.second.comparisonName }.thenBy { it.second.id }, ).map { it.first } .toIntArray() internal fun CuetreeBrowseCatalog.folderChildKey(index: Int): BrowserRowKey = if (index < 0) { val container = -1 - index BrowserRowKey( fileId(container), facts.albumLabels[facts.containers.records[container].album], BrowserKind.FOLDER, actionTier = !facts.containerBook(container), ) } else if (snapshot.nodeKind(index) == NodeKind.DIRECTORY) { folderKey(index) } else { trackKey(index, true, index) } internal fun CuetreeBrowseCatalog.folderChildRow(index: Int): BrowserItem = if (index < 0 ) { fileRow(-1 - index) } else if (snapshot.nodeKind(index) == NodeKind.DIRECTORY) { folderRow(index) } else { trackRow(index, true, index) } internal fun CuetreeBrowseCatalog.fileRow(index: Int): BrowserItem = cached("file-row:$index") { val container = facts.containers.records[index] val album = snapshot.albums[container.album] BrowserItem( fileId(index), facts.albumLabels[container.album], BrowserKind.FOLDER, playable = facts.containerPlayable(index), audiobook = facts.containerBook(index), aiReactionEligible = !facts.containerBook(index), catalog = this, children = rows( "file-members:$index", { container.sources.filter(snapshot::nodeAvailable).toIntArray() }, { trackKey(it, true, container.base) }, { trackRow(it, true, container.base, album) }, ), ) } /** Cached source facts only: no provider checks and no descendant display rows. */ internal fun CuetreeBrowseCatalog.folderPlayable(index: Int): Boolean = facts.folderPlayable[index] internal fun CuetreeBrowseCatalog.folderBook(index: Int): Boolean { var cursor: Int? = index while (cursor != null) { projection.audiobookOverrides["folder:${snapshot.nodeId(cursor).hi.toULong().toString(16)}"]?.let { return it == link.roadio.riff.player.storage.StoredAudiobookOverride.BOOK } cursor = snapshot.parentOf(cursor) } return false }