//
//  QualitySelectorView+Sections.swift
//  Yattee
//
//  Section content views for QualitySelectorView.
//

import SwiftUI

extension QualitySelectorView {
    // MARK: - Main Content Views

    @ViewBuilder
    var loadingContent: some View {
        VStack(spacing: 16) {
            Spacer()
            ProgressView()
                .controlSize(.large)
            Text(String(localized: "player.quality.loading"))
                .font(.subheadline)
                .foregroundStyle(.secondary)
            Spacer()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }

    @ViewBuilder
    var emptyContent: some View {
        ContentUnavailableView(
            String(localized: "player.quality.unavailable"),
            systemImage: "film.stack",
            description: Text(String(localized: "player.quality.unavailable.description"))
        )
    }

    @ViewBuilder
    var downloadedContent: some View {
        ScrollView {
            VStack(spacing: 16) {
                if !hasOnlineStreams {
                    downloadInfoSection
                    loadOnlineStreamsButton
                } else {
                    onlineStreamsAfterDownload
                }

                if showTabPicker {
                    generalSectionContent
                }
            }
            .padding()
        }
    }

    @ViewBuilder
    var streamsContent: some View {
        ScrollView {
            VStack(spacing: 16) {
                if showTabPicker && availableTabs.count > 1 {
                    mediaSelectionRows
                } else {
                    tabContent
                }

                if showTabPicker {
                    generalSectionContent
                }
            }
            #if os(tvOS)
            // Explicit symmetric padding so rows are evenly inset from the
            // half-screen panel's edges; matches the queue panel's 80pt
            // breathing room so the focus halo around row buttons doesn't
            // crowd the panel boundaries.
            .padding(.horizontal, 80)
            .padding(.vertical, 24)
            #else
            .padding()
            #endif
        }
        .onAppear {
            selectedTab = initialTab
        }
    }

    // MARK: - Tab Content

    @ViewBuilder
    private var mediaSelectionRows: some View {
        #if os(tvOS)
        // tvOS: each row is its own rounded card with vertical spacing so the
        // system focus hover effect has clean bounds (no clipped dividers).
        VStack(spacing: 8) {
            mediaSelectionRow(
                destination: .video,
                label: String(localized: "player.quality.video"),
                systemImage: "film",
                value: currentVideoDisplayValue
            )
            .focused($inlinePanelInitialFocus)
            if availableTabs.contains(.audio) {
                mediaSelectionRow(
                    destination: .audio,
                    label: String(localized: "stream.audio"),
                    systemImage: "speaker.wave.2",
                    value: currentAudioDisplayValue
                )
            }
            if availableTabs.contains(.subtitles) {
                mediaSelectionRow(
                    destination: .subtitles,
                    label: String(localized: "stream.subtitles"),
                    systemImage: "captions.bubble",
                    value: currentSubtitlesDisplayValue
                )
            }
        }
        #else
        VStack(spacing: 0) {
            mediaSelectionRow(
                destination: .video,
                label: String(localized: "player.quality.video"),
                systemImage: "film",
                value: currentVideoDisplayValue
            )
            if availableTabs.contains(.audio) {
                Divider()
                mediaSelectionRow(
                    destination: .audio,
                    label: String(localized: "stream.audio"),
                    systemImage: "speaker.wave.2",
                    value: currentAudioDisplayValue
                )
            }
            if availableTabs.contains(.subtitles) {
                Divider()
                mediaSelectionRow(
                    destination: .subtitles,
                    label: String(localized: "stream.subtitles"),
                    systemImage: "captions.bubble",
                    value: currentSubtitlesDisplayValue
                )
            }
        }
        .cardBackground()
        #endif
    }

    @ViewBuilder
    private func mediaSelectionRow(
        destination: QualitySelectorDestination,
        label: String,
        systemImage: String,
        value: String
    ) -> some View {
        NavigationLink(value: destination) {
            HStack {
                Label(label, systemImage: systemImage)
                    .font(.headline)
                Spacer()
                Text(value)
                    .foregroundStyle(.secondary)
                Image(systemName: "chevron.right")
                    .font(.caption.weight(.semibold))
                    .foregroundStyle(.tertiary)
            }
            .padding(.vertical, 12)
            .padding(.horizontal, 20)
            .contentShape(Rectangle())
        }
        #if os(tvOS)
        .buttonStyle(TVSettingsRowButtonStyle())
        #else
        .buttonStyle(.plain)
        #endif
    }

    // MARK: - Display Value Computed Properties

    private var currentVideoDisplayValue: String {
        guard let stream = currentStream else {
            return String(localized: "stream.subtitles.none")
        }
        let format = StreamFormat.detect(from: stream)
        if format == .hls || format == .dash {
            return format == .hls ? "HLS" : "DASH"
        }
        return displayStream(for: stream).qualityLabel
    }

    private var currentAudioDisplayValue: String {
        if embeddedAudioTracks.count > 1,
           let trackID = currentEmbeddedAudioTrackID,
           let track = embeddedAudioTracks.first(where: { $0.trackID == trackID }) {
            return track.displayName
        }
        if isCurrentStreamMuxed {
            return String(localized: "player.quality.audioFromVideo.short")
        }
        if let audio = selectedAudioStream ?? currentAudioStream {
            return parseAudioTrackName(audio).language
        }
        return String(localized: "stream.audio.default")
    }

    private var currentSubtitlesDisplayValue: String {
        if let trackID = currentEmbeddedSubtitleTrackID,
           let track = embeddedSubtitleTracks.first(where: { $0.trackID == trackID }) {
            return track.displayName
        }
        return currentCaption?.displayName ?? String(localized: "stream.subtitles.off")
    }

    // MARK: - Detail Content Views

    @ViewBuilder
    var videoDetailContent: some View {
        detailContent(title: String(localized: "player.quality.video")) {
            if !adaptiveStreams.isEmpty {
                adaptiveSectionContent
            }
            if !videoStreams.isEmpty {
                videoSectionContent
            }
        }
    }

    @ViewBuilder
    var audioDetailContent: some View {
        detailContent(title: String(localized: "stream.audio")) {
            audioSectionContent
        }
    }

    @ViewBuilder
    var subtitlesDetailContent: some View {
        detailContent(title: String(localized: "stream.subtitles")) {
            subtitlesSectionContent
        }
    }

    /// Shared layout for the pushed detail screens. On tvOS the destination
    /// gets the same glass backdrop + custom title bar as the panel root so
    /// the visual treatment is continuous as the user navigates in.
    @ViewBuilder
    private func detailContent<Content: View>(
        title: String,
        @ViewBuilder content: () -> Content
    ) -> some View {
        #if os(tvOS)
        VStack(alignment: .leading, spacing: 0) {
            ZStack {
                Text(title)
                    .font(.system(size: 32, weight: .semibold))
                    .foregroundStyle(.primary)
            }
            .frame(maxWidth: .infinity)
            .padding(.horizontal, 80)
            .padding(.top, 32)
            .padding(.bottom, 16)

            ScrollView {
                VStack(spacing: 16) {
                    content()
                }
                .padding(.horizontal, 80)
                .padding(.vertical, 24)
            }
        }
        .background(
            Rectangle()
                .fill(.ultraThinMaterial)
                .ignoresSafeArea()
        )
        .ignoresSafeArea(.container, edges: .horizontal)
        #else
        ScrollView {
            VStack(spacing: 16) {
                content()
            }
            .padding()
        }
        .background(ListBackgroundStyle.grouped.color)
        .navigationTitle(title)
        #if os(iOS)
        .navigationBarTitleDisplayMode(.inline)
        #endif
        #endif
    }

    @ViewBuilder
    private var tabContent: some View {
        if selectedTab == .video && !adaptiveStreams.isEmpty {
            adaptiveSectionContent
        }

        switch selectedTab {
        case .video:
            if !videoStreams.isEmpty {
                videoSectionContent
            }
        case .audio:
            audioSectionContent
        case .subtitles:
            subtitlesSectionContent
        }
    }

    @ViewBuilder
    private var onlineStreamsAfterDownload: some View {
        if availableTabs.count > 1 {
            mediaSelectionRows
        } else {
            if selectedTab == .video && !adaptiveStreams.isEmpty {
                adaptiveSectionContent
            }

            switch selectedTab {
            case .video:
                if !videoStreams.isEmpty {
                    videoSectionContent
                }
            case .audio:
                audioSectionContent
            case .subtitles:
                subtitlesSectionContent
            }
        }
    }

    // MARK: - General Section

    @ViewBuilder
    var generalSectionContent: some View {
        #if os(tvOS)
        // tvOS rows are styled individually to match the Settings rows.
        VStack(spacing: 8) {
            playbackSpeedRow

            audioModeRow
        }
        #else
        VStack(spacing: 0) {
            playbackSpeedRow

            Divider()

            audioModeRow

            Divider()

            lockControlsRow
        }
        .cardBackground()
        #endif
    }

    @ViewBuilder
    private var playbackSpeedRow: some View {
        HStack {
            Label(String(localized: "player.quality.playbackSpeed"), systemImage: "gauge.with.needle")
                .font(.headline)

            Spacer()

            HStack(spacing: 8) {
                Button {
                    if let newRate = previousRate() {
                        onRateChanged?(newRate)
                    }
                } label: {
                    Image(systemName: "minus")
                        .font(.body.weight(.medium))
                        .frame(minWidth: 18, minHeight: 18)
                        .contentShape(Rectangle())
                }
                .buttonStyle(.bordered)
                .disabled(previousRate() == nil)

                Menu {
                    ForEach(PlaybackRate.allCases) { rate in
                        Button {
                            onRateChanged?(rate)
                        } label: {
                            if currentRate == rate {
                                Label(rate.displayText, systemImage: "checkmark")
                            } else {
                                Text(rate.displayText)
                            }
                        }
                    }
                } label: {
                    Text(currentRate.displayText)
                        .font(.body.weight(.medium))
                        .lineLimit(1)
                        .fixedSize(horizontal: true, vertical: false)
                        .frame(minWidth: 80)
                }
                .playbackSpeedMenuStyle()

                Button {
                    if let newRate = nextRate() {
                        onRateChanged?(newRate)
                    }
                } label: {
                    Image(systemName: "plus")
                        .font(.body.weight(.medium))
                        .frame(minWidth: 18, minHeight: 18)
                        .contentShape(Rectangle())
                }
                .buttonStyle(.bordered)
                .disabled(nextRate() == nil)
            }
        }
        #if os(tvOS)
        .padding(.vertical, 14)
        .padding(.horizontal, 20)
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(
            RoundedRectangle(cornerRadius: 14, style: .continuous)
                .fill(Color.white.opacity(0.08))
        )
        #else
        .padding(.vertical, 12)
        .padding(.horizontal, 12)
        #endif
    }

    @ViewBuilder
    private var audioModeRow: some View {
        #if os(tvOS)
        Button {
            onAudioModeToggled?(!isAudioMode)
        } label: {
            HStack {
                Label(String(localized: "player.quality.audioMode"), systemImage: "music.note")
                    .font(.headline)

                Spacer()

                Text(isAudioMode ? String(localized: "common.on") : String(localized: "common.off"))
                    .foregroundStyle(.secondary)
            }
            .padding(.vertical, 14)
            .padding(.horizontal, 20)
            .frame(maxWidth: .infinity, alignment: .leading)
            .contentShape(Rectangle())
        }
        .buttonStyle(TVSettingsRowButtonStyle())
        #else
        HStack {
            Label(String(localized: "player.quality.audioMode"), systemImage: "music.note")
                .font(.headline)

            Spacer()

            Toggle("", isOn: Binding(
                get: { isAudioMode },
                set: { onAudioModeToggled?($0) }
            ))
            .labelsHidden()
        }
        .padding(.vertical, 12)
        .padding(.horizontal, 12)
        #endif
    }

    #if !os(tvOS)
    @ViewBuilder
    private var lockControlsRow: some View {
        HStack {
            Label(String(localized: "player.quality.lockControls"), systemImage: "lock")
                .font(.headline)

            Spacer()

            Toggle("", isOn: Binding(
                get: { isControlsLocked },
                set: { onLockToggled?($0) }
            ))
            .labelsHidden()
        }
        .padding(.vertical, 12)
        .padding(.horizontal, 12)
    }
    #endif

    // MARK: - Download Info Section

    @ViewBuilder
    var downloadInfoSection: some View {
        if let download = currentDownload {
            // Video section
            VStack(alignment: .leading, spacing: 8) {
                Label(String(localized: "player.quality.video"), systemImage: "film")
                    .font(.subheadline.weight(.semibold))
                    .foregroundStyle(.secondary)

                VStack(spacing: 0) {
                    DownloadedVideoRowView(download: download, showAdvancedDetails: showAdvancedStreamDetails)
                        .padding(.vertical, 8)
                        .padding(.horizontal, 12)
                }
                .cardBackground()
            }

            // Audio section (if separate audio track)
            if download.localAudioPath != nil {
                VStack(alignment: .leading, spacing: 8) {
                    Label(String(localized: "stream.audio"), systemImage: "speaker.wave.2")
                        .font(.subheadline.weight(.semibold))
                        .foregroundStyle(.secondary)

                    VStack(spacing: 0) {
                        DownloadedAudioRowView(download: download, showAdvancedDetails: showAdvancedStreamDetails)
                            .padding(.vertical, 8)
                            .padding(.horizontal, 12)
                    }
                    .cardBackground()
                }
            }

            // Subtitles section (if downloaded)
            if download.localCaptionPath != nil {
                VStack(alignment: .leading, spacing: 8) {
                    Label(String(localized: "stream.subtitles"), systemImage: "captions.bubble")
                        .font(.subheadline.weight(.semibold))
                        .foregroundStyle(.secondary)

                    VStack(spacing: 0) {
                        CaptionRowView(
                            caption: nil,
                            isSelected: currentCaption == nil,
                            isPreferred: false,
                            onTap: {
                                onCaptionSelected(nil)
                                performDismiss()
                            }
                        )
                        .padding(.vertical, 8)
                        .padding(.horizontal, 12)

                        Divider()

                        DownloadedCaptionRowView(
                            download: download,
                            localCaptionURL: localCaptionURL,
                            currentCaption: currentCaption,
                            onCaptionSelected: onCaptionSelected,
                            onDismiss: { performDismiss() }
                        )
                        .padding(.vertical, 8)
                        .padding(.horizontal, 12)
                    }
                    .cardBackground()
                }
            }
        }
    }

    @ViewBuilder
    var loadOnlineStreamsButton: some View {
        Button {
            onLoadOnlineStreams()
        } label: {
            HStack {
                if isLoadingOnlineStreams {
                    ProgressView()
                        .controlSize(.small)
                } else {
                    Image(systemName: "arrow.clockwise")
                }
                Text(String(localized: "player.quality.loadOnline"))
            }
            .frame(maxWidth: .infinity)
            .padding(.vertical, 12)
        }
        .buttonStyle(.bordered)
        .disabled(isLoadingOnlineStreams)
    }

    // MARK: - Adaptive Section

    @ViewBuilder
    var adaptiveSectionContent: some View {
        VStack(alignment: .leading, spacing: 8) {
            Label(String(localized: "stream.adaptive"), systemImage: "antenna.radiowaves.left.and.right")
                .font(.subheadline.weight(.semibold))
                .foregroundStyle(.secondary)

            #if os(tvOS)
            VStack(spacing: 8) {
                ForEach(adaptiveStreams, id: \.url) { stream in
                    AdaptiveStreamRowView(
                        stream: stream,
                        isSelected: stream.url == currentStream?.url,
                        onTap: {
                            handleAdaptiveStreamTap(stream)
                        }
                    )
                }
            }
            #else
            VStack(spacing: 0) {
                ForEach(Array(adaptiveStreams.enumerated()), id: \.element.url) { index, stream in
                    if index > 0 {
                        Divider()
                    }
                    AdaptiveStreamRowView(
                        stream: stream,
                        isSelected: stream.url == currentStream?.url,
                        onTap: {
                            handleAdaptiveStreamTap(stream)
                        }
                    )
                    .padding(.vertical, 10)
                    .padding(.horizontal, 12)
                }
            }
            .cardBackground()
            #endif
        }
    }

    private func handleAdaptiveStreamTap(_ stream: Stream) {
        if isPlayingDownloadedContent {
            onSwitchToOnlineStream(stream, nil)
        } else {
            onStreamSelected(stream, nil)
        }
        performDismiss()
    }

    // MARK: - Video Section

    @ViewBuilder
    var videoSectionContent: some View {
        VStack(spacing: 16) {
            // Recommended section
            if !recommendedVideoStreams.isEmpty {
                VStack(alignment: .leading, spacing: 8) {
                    Label(String(localized: "player.quality.recommended"), systemImage: "bolt.fill")
                        .font(.subheadline.weight(.semibold))
                        .foregroundStyle(.secondary)

                    #if os(tvOS)
                    VStack(spacing: 8) {
                        ForEach(recommendedVideoStreams, id: \.url) { stream in
                            videoStreamRow(stream)
                        }
                    }
                    #else
                    VStack(spacing: 0) {
                        ForEach(Array(recommendedVideoStreams.enumerated()), id: \.element.url) { index, stream in
                            if index > 0 {
                                Divider()
                            }
                            videoStreamRow(stream)
                                .padding(.vertical, 8)
                                .padding(.horizontal, 12)
                        }
                    }
                    .cardBackground()
                    #endif
                }
            }

            // Other section (software decode)
            if showAdvancedStreamDetails && !otherVideoStreams.isEmpty {
                VStack(alignment: .leading, spacing: 8) {
                    Label(String(localized: "player.quality.other"), systemImage: "cpu")
                        .font(.subheadline.weight(.semibold))
                        .foregroundStyle(.secondary)

                    #if os(tvOS)
                    VStack(spacing: 8) {
                        ForEach(otherVideoStreams, id: \.url) { stream in
                            videoStreamRow(stream)
                        }
                    }
                    #else
                    VStack(spacing: 0) {
                        ForEach(Array(otherVideoStreams.enumerated()), id: \.element.url) { index, stream in
                            if index > 0 {
                                Divider()
                            }
                            videoStreamRow(stream)
                                .padding(.vertical, 8)
                                .padding(.horizontal, 12)
                        }
                    }
                    .cardBackground()
                    #endif
                }
            }
        }
    }

    /// A media-source Stream (local folder, WebDAV, SMB) carries no
    /// resolution/codec/fps metadata (`MediaFile.toStream` cannot know them
    /// before demux), which would label the row "Unknown". Once mpv reports
    /// the loaded file's video track, fill those fields for display. Gated to
    /// local files and the currently playing stream — the track info describes
    /// whatever mpv has loaded. The enriched copy is display-only — selection
    /// and tap handling keep using the original stream (compared by URL).
    func displayStream(for stream: Stream) -> Stream {
        guard stream.resolution == nil,
              !stream.isAudioOnly,
              stream.url.isFileURL || stream.url == currentStream?.url,
              let track = embeddedVideoTrack,
              let width = track.width,
              let height = track.height else {
            return stream
        }
        return Stream(
            url: stream.url,
            resolution: StreamResolution(width: width, height: height),
            format: stream.format,
            videoCodec: track.codec ?? stream.videoCodec,
            audioCodec: stream.audioCodec,
            bitrate: stream.bitrate,
            fileSize: stream.fileSize,
            isLive: stream.isLive,
            mimeType: stream.mimeType,
            httpHeaders: stream.httpHeaders,
            fps: track.fps.map { Int($0.rounded()) }
        )
    }

    @ViewBuilder
    private func videoStreamRow(_ stream: Stream) -> some View {
        let isDownloadedStream: Bool = stream.url.isFileURL
        let isSelected: Bool = stream.isMuxed
            ? stream.url == currentStream?.url
            : stream.url == selectedVideoStream?.url
        let isPreferredQuality: Bool = stream.resolution == preferredQuality.maxResolution
        // Metadata-less media-source streams get resolution/codec/fps filled
        // from the mpv-reported video track for display; the warning must use
        // the enriched codec too or a nil codec reads as software-decoded
        let display: Stream = displayStream(for: stream)

        VideoStreamRowView(
            stream: display,
            isSelected: isSelected,
            isPreferredQuality: isPreferredQuality,
            isDownloaded: isDownloadedStream,
            showAdvancedDetails: showAdvancedStreamDetails,
            requiresSoftwareDecode: !display.isMuxed && display.videoCodec != nil && requiresSoftwareDecode(display.videoCodec),
            onTap: {
                handleVideoStreamTap(stream, isDownloaded: isDownloadedStream)
            }
        )
    }

    private func handleVideoStreamTap(_ stream: Stream, isDownloaded: Bool) {
        if isDownloaded {
            if stream.isMuxed {
                onStreamSelected(stream, nil)
                performDismiss()
            } else {
                selectedVideoStream = stream
                if let audio = selectedAudioStream {
                    onStreamSelected(stream, audio)
                    performDismiss()
                }
            }
        } else if isPlayingDownloadedContent {
            let audioStream: Stream? = stream.isVideoOnly ? (selectedAudioStream ?? defaultAudioStream) : nil
            onSwitchToOnlineStream(stream, audioStream)
            performDismiss()
        } else {
            if stream.isMuxed {
                onStreamSelected(stream, nil)
                performDismiss()
            } else {
                selectedVideoStream = stream
                if let audio = selectedAudioStream {
                    onStreamSelected(stream, audio)
                    performDismiss()
                }
            }
        }
    }

    // MARK: - Audio Section

    @ViewBuilder
    var audioSectionContent: some View {
        if embeddedAudioTracks.count > 1 {
            embeddedAudioTracksContent
        } else if isCurrentStreamMuxed {
            VStack(spacing: 0) {
                HStack {
                    Image(systemName: "info.circle")
                        .foregroundStyle(.secondary)
                    Text(String(localized: "player.quality.audioFromVideo"))
                        .font(.subheadline)
                        .foregroundStyle(.secondary)
                    Spacer()
                }
                .padding(.vertical, 12)
                .padding(.horizontal, 12)
            }
            .cardBackground()
        } else {
            #if os(tvOS)
            VStack(spacing: 8) {
                ForEach(audioStreams, id: \.url) { stream in
                    audioStreamRow(stream)
                }
            }
            #else
            VStack(spacing: 0) {
                ForEach(Array(audioStreams.enumerated()), id: \.element.url) { index, stream in
                    if index > 0 {
                        Divider()
                    }
                    audioStreamRow(stream)
                        .padding(.vertical, 8)
                        .padding(.horizontal, 12)
                }
            }
            .cardBackground()
            #endif
        }
    }

    /// Embedded (in-container) audio tracks, switched live via mpv `aid`.
    @ViewBuilder
    private var embeddedAudioTracksContent: some View {
        #if os(tvOS)
        VStack(spacing: 8) {
            ForEach(embeddedAudioTracks) { track in
                embeddedAudioTrackRow(track)
            }
        }
        #else
        VStack(spacing: 0) {
            ForEach(Array(embeddedAudioTracks.enumerated()), id: \.element.id) { index, track in
                if index > 0 {
                    Divider()
                }
                embeddedAudioTrackRow(track)
                    .padding(.vertical, 8)
                    .padding(.horizontal, 12)
            }
        }
        .cardBackground()
        #endif
    }

    @ViewBuilder
    private func embeddedAudioTrackRow(_ track: MPVTrack) -> some View {
        EmbeddedTrackRowView(
            track: track,
            isSelected: track.trackID == currentEmbeddedAudioTrackID,
            isPreferred: track.matchesLanguage(preferredAudioLanguage),
            showAdvancedDetails: showAdvancedStreamDetails,
            onTap: {
                onEmbeddedAudioTrackSelected(track.trackID)
                performDismiss()
            }
        )
    }

    @ViewBuilder
    private func audioStreamRow(_ stream: Stream) -> some View {
        let isSelected: Bool = stream.url == selectedAudioStream?.url
        let isPreferred: Bool = preferredAudioLanguage.map { (stream.audioLanguage ?? "").hasPrefix($0) } ?? false
        let trackInfo: AudioTrackInfo = parseAudioTrackName(stream)

        AudioStreamRowView(
            stream: stream,
            isSelected: isSelected,
            isPreferred: isPreferred,
            showAdvancedDetails: showAdvancedStreamDetails,
            trackInfo: trackInfo,
            onTap: {
                handleAudioStreamTap(stream)
            }
        )
    }

    private func handleAudioStreamTap(_ stream: Stream) {
        selectedAudioStream = stream
        if currentStream?.isAudioOnly == true {
            // Audio mode / audio-only content: the tapped track IS the main stream
            onStreamSelected(stream, nil)
            performDismiss()
        } else if let video = selectedVideoStream, video.isVideoOnly {
            onStreamSelected(video, stream)
            performDismiss()
        }
    }

    // MARK: - Subtitles Section

    @ViewBuilder
    var subtitlesSectionContent: some View {
        #if os(tvOS)
        VStack(spacing: 8) {
            CaptionRowView(
                caption: nil,
                isSelected: currentCaption == nil && currentEmbeddedSubtitleTrackID == nil,
                isPreferred: false,
                onTap: {
                    handleCaptionTap(nil)
                }
            )

            ForEach(embeddedSubtitleTracks) { track in
                embeddedSubtitleTrackRow(track)
            }

            ForEach(sortedCaptions) { caption in
                CaptionRowView(
                    caption: caption,
                    isSelected: caption.id == currentCaption?.id,
                    isPreferred: isCaptionPreferred(caption),
                    onTap: {
                        handleCaptionTap(caption)
                    }
                )
            }
        }
        #else
        VStack(spacing: 0) {
            CaptionRowView(
                caption: nil,
                isSelected: currentCaption == nil && currentEmbeddedSubtitleTrackID == nil,
                isPreferred: false,
                onTap: {
                    handleCaptionTap(nil)
                }
            )
            .padding(.vertical, 8)
            .padding(.horizontal, 12)

            ForEach(embeddedSubtitleTracks) { track in
                Divider()

                embeddedSubtitleTrackRow(track)
                    .padding(.vertical, 8)
                    .padding(.horizontal, 12)
            }

            ForEach(sortedCaptions) { caption in
                Divider()

                CaptionRowView(
                    caption: caption,
                    isSelected: caption.id == currentCaption?.id,
                    isPreferred: isCaptionPreferred(caption),
                    onTap: {
                        handleCaptionTap(caption)
                    }
                )
                .padding(.vertical, 8)
                .padding(.horizontal, 12)
            }

            if canLoadExternalSubtitles {
                Divider()

                loadSubtitleFileRow
                    .padding(.vertical, 8)
                    .padding(.horizontal, 12)
            }
        }
        .cardBackground()
        #endif
    }

    #if !os(tvOS)
    /// Row that opens a file picker to load an external subtitle file into
    /// the current media-source playback.
    private var loadSubtitleFileRow: some View {
        Button {
            #if os(iOS)
            showingSubtitleFilePicker = true
            #elseif os(macOS)
            SubtitleFilePanel.present { url in
                handlePickedSubtitleFile(url)
            }
            #endif
        } label: {
            HStack(spacing: 8) {
                Image(systemName: "folder.badge.plus")
                    .foregroundStyle(.secondary)
                Text("stream.subtitles.loadFromFile")
                    .font(.headline)
                Spacer()
            }
            .frame(maxWidth: .infinity, minHeight: 36)
            .contentShape(Rectangle())
        }
        .buttonStyle(.plain)
    }

    func handlePickedSubtitleFile(_ url: URL) {
        appEnvironment?.playerService.loadExternalSubtitleFile(from: url)
        performDismiss()
    }
    #endif

    @ViewBuilder
    private func embeddedSubtitleTrackRow(_ track: MPVTrack) -> some View {
        EmbeddedTrackRowView(
            track: track,
            isSelected: track.trackID == currentEmbeddedSubtitleTrackID,
            isPreferred: track.matchesLanguage(preferredSubtitlesLanguage),
            showAdvancedDetails: showAdvancedStreamDetails,
            onTap: {
                // Re-tapping the selected track turns subtitles off (parity
                // with external caption rows)
                if track.trackID == currentEmbeddedSubtitleTrackID {
                    onEmbeddedSubtitleTrackSelected(nil)
                } else {
                    onEmbeddedSubtitleTrackSelected(track.trackID)
                }
                performDismiss()
            }
        )
    }

    private func isCaptionPreferred(_ caption: Caption) -> Bool {
        guard let preferred = preferredSubtitlesLanguage else { return false }
        return caption.baseLanguageCode == preferred || caption.languageCode.hasPrefix(preferred)
    }

    private func handleCaptionTap(_ caption: Caption?) {
        // Note: the Off row needs no embedded-track handling — caption "off"
        // sets sid=no and clears the embedded intent in PlayerService.
        if caption?.id == currentCaption?.id && caption != nil {
            onCaptionSelected(nil)
        } else {
            onCaptionSelected(caption)
        }
        performDismiss()
    }
}

#if os(tvOS)
/// Row button style for Settings-style navigation/selection rows on tvOS.
/// Avoids the default focus lift/scale so rows stay aligned; focus state is
/// communicated via a background tint and a thin stroke.
struct TVSettingsRowButtonStyle: ButtonStyle {
    @Environment(\.isFocused) private var isFocused

    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .foregroundStyle(.white)
            .background(
                RoundedRectangle(cornerRadius: 14, style: .continuous)
                    .fill(isFocused ? Color.white.opacity(0.22) : Color.white.opacity(0.08))
            )
            .overlay(
                RoundedRectangle(cornerRadius: 14, style: .continuous)
                    .stroke(isFocused ? Color.white.opacity(0.4) : .clear, lineWidth: 2)
            )
            .contentShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
            .animation(.easeInOut(duration: 0.15), value: isFocused)
    }
}
#endif

private extension View {
    @ViewBuilder
    func playbackSpeedMenuStyle() -> some View {
        #if os(tvOS)
        self
        #else
        // On macOS Sequoia the borderlessButton menu stretches to fill the
        // available width unless it is pinned to its intrinsic size.
        self.menuStyle(.borderlessButton)
            .fixedSize()
        #endif
    }
}
