import SwiftUI import TVAnarchyCore /// Horizontal upcoming-episode rail — only items after the current one. struct PlayerQueueRail: View { @Bindable var controller: PlayerController @Bindable var library: LibraryController @Bindable var playlist: PlaylistController private var paths: [String] { if !controller.playbackQueuePaths.isEmpty { return controller.playbackQueuePaths } return playlist.queue.map(\.path) } private var upcoming: [(offset: Int, path: String)] { guard paths.count > 1 else { return [] } let cur = currentIndex ?? -1 let start = cur + 1 guard start < paths.count else { return [] } return paths.enumerated().compactMap { idx, path in idx >= start ? (idx, path) : nil } } var body: some View { if !upcoming.isEmpty { VStack(alignment: .leading, spacing: 6) { HStack(spacing: 6) { Text("Up next") .font(.caption) .foregroundStyle(.secondary) Text("· \(upcoming.count) episode\(upcoming.count == 1 ? "" : "s")") .font(.caption2) .foregroundStyle(.tertiary) } ScrollViewReader { proxy in ScrollView(.horizontal, showsIndicators: upcoming.count > 4) { HStack(spacing: 8) { ForEach(upcoming, id: \.path) { item in queueChip(path: item.path, index: item.offset) .id(item.path) } } } .onAppear { if let first = upcoming.first?.path { proxy.scrollTo(first, anchor: .leading) } } .onChange(of: currentIndex) { if let first = upcoming.first?.path { withAnimation(.easeOut(duration: 0.2)) { proxy.scrollTo(first, anchor: .leading) } } } } } } } private func queueChip(path: String, index: Int) -> some View { let parts = PlayerController.queueChipParts(for: path, library: library) return VStack(alignment: .leading, spacing: 3) { if !parts.code.isEmpty { Text(parts.code) .font(.caption2.bold().monospacedDigit()) .foregroundStyle(.secondary) } Text(parts.title.isEmpty ? PlayerController.label(for: path, library: library) : parts.title) .font(.caption) .lineLimit(2) .frame(minWidth: 130, maxWidth: 170, alignment: .leading) } .padding(.horizontal, 10).padding(.vertical, 8) .background(Color.white.opacity(0.06), in: RoundedRectangle(cornerRadius: 8)) .overlay { RoundedRectangle(cornerRadius: 8) .strokeBorder(.white.opacity(0.1), lineWidth: 1) } .help(PlayerController.label(for: path, library: library)) } private var currentIndex: Int? { if !controller.playbackQueuePaths.isEmpty, paths == controller.playbackQueuePaths { return controller.currentQueueIndex } if paths == playlist.queue.map(\.path), let pos = controller.activeSnapshot.status.playlistPos { return pos } return nil } }