// SettingsView's shared sections — each setting's Section is defined exactly once here and
// composed by the per-platform bodies in SettingsView.swift.
//
// 2026-07 settings revamp: every field carries its explanation DIRECTLY under it in the same
// cell (the `described` helper in SettingsView+Support) — the old per-section footer paragraphs
// collected several fields' explanations into one blob nobody could match back to its row.
// Where a picker's meaning depends on the selection (touch mode, modifier layout, prioritize),
// the description is DYNAMIC — it explains the current choice. The only footers left are the
// one-line "Applies from the next session." form notes.
//
// The SAME builders edit settings profiles (design/client-settings-profiles.md §5.1 —
// SettingsView+Scope): a control's binding comes from `scoped(...)` rather than `@AppStorage`, so
// it writes whichever layer the scope switcher selected, and `described(_:field:)` marks the row
// when the edited profile overrides it. Rows that are NOT profileable — tier G (this device's
// hardware and endpoints) and tier H (properties of a host) — are gated on `!inProfileScope` and
// simply don't render there; sections that would end up empty don't either.
//
// Category map (SettingsCategory): General = session/app behavior, Display = everything about
// the picture (resolution lives HERE), Input = touch/keyboard/mouse, Audio, Controllers, About.

#if os(iOS)
import CoreHaptics
#endif
import PunktfunkKit
import SwiftUI

extension SettingsView {
    // MARK: - Display: Resolution

    // NOTE: the Section content is deliberately split into the small named builders below — as one
    // inline expression the iOS branch (wheel + 3-way refresh + bitrate rows) blew Swift's
    // type-checker budget ("unable to type-check this expression in reasonable time"), which
    // failed exactly one slice: the iOS archive (macOS/tvOS never compile that branch).
    @ViewBuilder var resolutionSection: some View {
        Section("Resolution") {
            #if os(iOS) || os(macOS)
            // Match-window (design/midstream-resolution-resize.md D1): follow the session
            // window/scene, renegotiating the host mode on a resize. Off → the explicit mode below.
            // NO marker here even though this toggle writes one: match-window, width and
            // height are ONE override (they are reset together), and hanging its marker off the
            // first of the two controls that drive it read as if the toggle alone were
            // overridden. It goes under the size control below, for the group.
            described(effective.matchWindow
                ? "The host follows this window's size — pixel-exact through every resize."
                : "Streams the fixed mode below, scaled to the window.") {
                Toggle("Match window", isOn: scoped(SettingsFields.matchWindow))
            }
            #endif
            #if os(iOS)
            iosResolutionWheel
            overrideMarker(OverlayField.resolution)
            iosRefreshRows
            Button("Use this display's mode") { fillFromMainScreen() }
            #elseif os(macOS)
            HStack {
                TextField(
                    "Resolution", value: scoped(SettingsFields.width),
                    format: .number.grouping(.never))
                Text("×")
                TextField("", value: scoped(SettingsFields.height), format: .number.grouping(.never))
                    .labelsHidden()
            }
            overrideMarker(OverlayField.resolution)
            described("The host drives a real output at exactly this size — no scaling.",
                field: "refresh_hz") {
                TextField(
                    "Refresh rate (Hz)", value: scoped(SettingsFields.refreshHz),
                    format: .number.grouping(.never))
            }
            LabeledContent("") {
                Button("Use this display's mode") { fillFromMainScreen() }
            }
            #endif
        }
    }

    #if os(iOS)
    // MARK: - Display: Resolution (iOS wheel)

    /// Touch-first: a rotating wheel of common resolutions (this device's own mode first) — the
    /// same family as the Clock/Timer pickers. The host renders a virtual output at exactly the
    /// chosen mode, so these are real pixel sizes. The last wheel row, "Custom…", reveals
    /// width/height/refresh fields for an arbitrary mode (see `iosRefreshRows`).
    @ViewBuilder private var iosResolutionWheel: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text("Resolution")
                .font(.geist(15, relativeTo: .subheadline))
                .foregroundStyle(.secondary)
            Picker("Resolution", selection: resolutionSelection) {
                ForEach(resolutionChoices, id: \.tag) { choice in
                    Text(choice.label).tag(choice.tag)
                }
            }
            .labelsHidden()
            .pickerStyle(.wheel)
            .frame(maxHeight: 140)
            Text("The host drives a real output at exactly this mode — no scaling.")
                .font(.geist(13, relativeTo: .footnote))
                .foregroundStyle(.secondary)
                .fixedSize(horizontal: false, vertical: true)
                .modifier(CaptionWidth()) // the same reading cap + control column as `described`
        }
    }

    /// Custom W×H(+Hz) fields, a segmented refresh picker, or a static single-rate row.
    @ViewBuilder private var iosRefreshRows: some View {
        if isCustomResolution {
            // Arbitrary entry: type the exact width × height (and refresh) the host should drive.
            HStack {
                TextField("Width", value: scoped(SettingsFields.width),
                          format: .number.grouping(.never))
                    .keyboardType(.numberPad)
                Text("×")
                TextField("Height", value: scoped(SettingsFields.height),
                          format: .number.grouping(.never))
                    .labelsHidden()
                    .keyboardType(.numberPad)
            }
            // A row built from an HStack of TextFields otherwise insets its bottom separator to
            // the inner content, clipping the hairline under "Width"; pin it to the cell edge.
            .alignmentGuide(.listRowSeparatorLeading) { _ in 0 }
            LabeledContent("Refresh rate") {
                TextField("Hz", value: scoped(SettingsFields.refreshHz),
                          format: .number.grouping(.never))
                    .keyboardType(.numberPad)
                    .multilineTextAlignment(.trailing)
            }
        } else if refreshChoices.count > 1 {
            VStack(alignment: .leading, spacing: 6) {
                Text("Refresh rate")
                    .font(.geist(15, relativeTo: .subheadline))
                    .foregroundStyle(.secondary)
                Picker("Refresh rate", selection: scoped(SettingsFields.refreshHz)) {
                    ForEach(refreshChoices, id: \.self) { rate in
                        Text("\(rate) Hz").tag(rate)
                    }
                }
                .labelsHidden()
                .pickerStyle(.segmented)
                overrideMarker("refresh_hz")
            }
        } else {
            // A device with a single supported rate (e.g. 60 Hz) has nothing to pick.
            LabeledContent("Refresh rate") {
                Text("\(effective.refreshHz) Hz").foregroundStyle(.secondary)
            }
        }
    }

    /// Sentinel wheel tag for the "Custom…" row. Real tags are "WxH" (digits + "x"), so this can't
    /// collide with a resolution.
    private static let customResolutionTag = "custom"

    /// Wheel rows: the resolution modes (device native first — see `SettingsOptions`), then a
    /// "Custom…" row that reveals the numeric fields.
    private var resolutionChoices: [(label: String, tag: String)] {
        SettingsOptions.resolutionModes()
            .map { (label: "\($0.name)  ·  \($0.w) × \($0.h)", tag: "\($0.w)x\($0.h)") }
            + [(label: "Custom…", tag: Self.customResolutionTag)]
    }

    private var presetResolutionTags: Set<String> {
        Set(SettingsOptions.resolutionModes().map { "\($0.w)x\($0.h)" })
    }

    /// True when the editable custom fields should show: the wheel is parked on "Custom…" (sticky),
    /// or the effective size simply isn't one of the presets (e.g. a value synced from a Mac, or a
    /// profile's own override) — so a non-preset mode stays editable without a persisted flag.
    private var isCustomResolution: Bool {
        customMode || !presetResolutionTags.contains("\(effective.width)x\(effective.height)")
    }

    /// The wheel works in "WxH" tags so one selection drives both width and height; the custom
    /// sentinel toggles `customMode` instead of writing a size.
    private var resolutionSelection: Binding<String> {
        Binding(
            get: {
                isCustomResolution
                    ? Self.customResolutionTag
                    : "\(effective.width)x\(effective.height)"
            },
            set: { tag in
                if tag == Self.customResolutionTag {
                    customMode = true
                    return
                }
                customMode = false
                let parts = tag.split(separator: "x").compactMap { Int($0) }
                guard parts.count == 2 else { return }
                setResolution(width: parts[0], height: parts[1])
            })
    }

    /// Refresh rates this device can display, plus any stored custom value (see `SettingsOptions`).
    private var refreshChoices: [Int] {
        SettingsOptions.refreshRates(including: effective.refreshHz)
    }
    #endif

    // MARK: - Display: Quality

    @ViewBuilder var qualitySection: some View {
        Section("Quality") {
            #if !os(tvOS)
            renderScaleRow
            bitrateRows
            #endif
            described("A preference — the host falls back if it can't encode it.",
                      field: "codec") {
                Picker("Video codec", selection: scoped(SettingsFields.codec)) {
                    ForEach(SettingsOptions.codecs, id: \.tag) { option in
                        Text(option.label).tag(option.tag)
                    }
                }
            }
            described("HDR10 when the host sends it and this display supports it. HEVC only.",
                field: "hdr_enabled") {
                Toggle("10-bit HDR", isOn: scoped(SettingsFields.hdrEnabled))
            }
            described("Sharper text and UI, at more bandwidth. For desktop work; HEVC only.",
                field: "enable_444") {
                Toggle("Full chroma (4:4:4)", isOn: scoped(SettingsFields.enable444))
            }
        }
    }

    #if !os(tvOS)
    /// Render-scale picker + the resulting host resolution. > 1 supersamples (sharper, at more
    /// bandwidth AND client decode); < 1 renders under native (lighter). The presenter resamples the
    /// decoded frame to this display, so the multiplier is where the sharpness/cost trade-off lives.
    @ViewBuilder var renderScaleRow: some View {
        described(renderScaleDescription, field: "render_scale") {
            Picker("Render scale", selection: scoped(SettingsFields.renderScale)) {
                ForEach(RenderScale.presets, id: \.self) { scale in
                    Text(RenderScale.label(scale)).tag(scale)
                }
            }
        }
    }

    /// Render scale explained, with the CONCRETE host resolution when it applies — the cost made
    /// legible. Only the explicit mode can show it (match-window derives the base from the live
    /// window, not these fields).
    private var renderScaleDescription: String {
        var text = "Above native is sharper, below is lighter on the host and link."
        let settings = effective
        if settings.renderScale != 1.0, !settings.matchWindow {
            let mode = RenderScale.apply(
                baseWidth: settings.width, baseHeight: settings.height,
                scale: settings.renderScale,
                maxDimension: RenderScale.maxDimension(codec: settings.codec))
            text += " Host renders \(Int(mode.width))×\(Int(mode.height))."
        }
        return text
    }

    /// The automatic-bitrate toggle + manual slider (and the >1 Gbps warning) rows.
    @ViewBuilder private var bitrateRows: some View {
        described("Uses the host's default, 20 Mbps. Off to set it yourself.",
            field: "bitrate_kbps") {
            Toggle("Automatic bitrate", isOn: automaticBitrate)
        }
        if effective.bitrateKbps != 0 {
            HStack(spacing: 12) {
                Slider(value: bitrateSlider, in: 0...1) {
                    Text("Bitrate")
                }
                Text(SpeedTestSheet.mbpsLabel(kbps: effective.bitrateKbps))
                    .monospacedDigit()
                    .foregroundStyle(.secondary)
                    .frame(minWidth: 76, alignment: .trailing)
            }
            if effective.bitrateKbps > 1_000_000 {
                Label(Self.gigabitWarning, systemImage: "exclamationmark.triangle.fill")
                    .font(.geist(12, relativeTo: .caption))
                    .foregroundStyle(.orange)
            }
        }
    }
    #endif

    // MARK: - Display: Presentation

    // The presentation intent (design/apple-presentation-rebuild.md — replaced the visible
    // stage picker): latency (newest-wins, zero queue) vs smoothness (a small deliberate jitter
    // buffer). The stage ladder survives only as the hidden PUNKTFUNK_PRESENTER debug env lever.
    @ViewBuilder var presentationSection: some View {
        Section("Presentation") {
            described(effective.presentPriority == "smooth"
                ? "A small buffer evens out network hiccups, at its worth of added latency."
                : "Frames show as soon as they arrive; a hiccup repeats or skips one.",
                field: "present_priority") {
                Picker("Prioritize", selection: scoped(SettingsFields.presentPriority)) {
                    ForEach(SettingsOptions.presentPriorities, id: \.tag) { option in
                        Text(option.tag == SettingsOptions.presentPriorityDefault
                            ? "\(option.label) (default)" : option.label)
                            .tag(option.tag)
                    }
                }
            }
            if effective.presentPriority == "smooth" {
                described("Each frame costs one refresh of latency and absorbs one of jitter.",
                    field: "smooth_buffer") {
                    Picker("Buffer", selection: scoped(SettingsFields.smoothBuffer)) {
                        ForEach(
                            SettingsOptions.smoothBuffers(refreshHz: effective.refreshHz),
                            id: \.tag
                        ) { option in
                            Text(option.label).tag(option.tag)
                        }
                    }
                }
            }
            // Non-tvOS: the Apple TV drives a fixed HDMI mode, so there's no adaptive refresh.
            #if !os(tvOS)
            described("A ProMotion or adaptive-sync display follows the stream's rate — "
                + "smoother motion.", field: "allow_vrr") {
                Toggle("Allow VRR", isOn: scoped(SettingsFields.allowVRR))
            }
            #endif
            // macOS-only: iOS/tvOS layers always present on the display's vsync, so the choice
            // only exists on the Mac (the layer's own sync stays off — see MetalVideoPresenter).
            #if os(macOS)
            described("Even pacing, at up to one refresh of added latency.", field: "vsync") {
                Toggle("V-Sync", isOn: scoped(SettingsFields.vsync))
            }
            // The DCP swapID-panic mitigation's user handle (see DefaultsKey.windowedSafePresent
            // for the saga). Default ON: turning it off re-arms a WHOLE-MACHINE kernel panic on
            // affected setups, so the caption says so in plain words.
            described(effective.windowedSafePresent
                ? "Windowed streams present in step with the compositor — avoids a macOS "
                    + "display-driver crash, at a small latency cost."
                : "Windowed streams use the fastest path. On some high-refresh Macs this can "
                    + "kernel-panic the machine.", field: "windowed_safe_present") {
                Toggle("Safe windowed presentation", isOn: scoped(SettingsFields.windowedSafePresent))
            }
            #endif
        }
    }

    // MARK: - Display: Host output

    @ViewBuilder var hostOutputSection: some View {
        Section {
            described("The backend the host drives its virtual output with — honored only if "
                + "available.",
                field: "compositor") {
                Picker("Compositor", selection: scoped(SettingsFields.compositor)) {
                    ForEach(SettingsOptions.compositors, id: \.tag) { option in
                        Text(option.label).tag(option.tag)
                    }
                }
            }
        } header: {
            Text("Host output")
        } footer: {
            // The one form-level note (deliberately not repeated on every row above).
            Text("Display changes apply from the next session.")
                .font(.geist(12, relativeTo: .caption))
                .foregroundStyle(.secondary)
        }
    }

    // MARK: - General: Session

    /// Empty in profile scope everywhere but macOS: auto-wake is a property of the host and this
    /// network, and background keep-alive is a property of this device — neither is something
    /// "Game" and "Work" would ever differ on (§3, tiers H and G).
    private var showsSessionSection: Bool {
        #if os(macOS)
        return true
        #else
        return !inProfileScope
        #endif
    }

    @ViewBuilder var sessionSection: some View {
        if showsSessionSection {
            Section("Session") {
                #if os(macOS)
                described("Go fullscreen when a session starts; return to a window on the host "
                    + "list.", field: "fullscreen_on_stream") {
                    Toggle(
                        "Fullscreen while streaming",
                        isOn: scoped(SettingsFields.fullscreenWhileStreaming))
                }
                #endif
                if !inProfileScope {
                    described("Sends Wake-on-LAN to a sleeping saved host and waits for it.") {
                        Toggle("Auto-wake on connect", isOn: $autoWakeEnabled)
                    }
                }
                #if os(iOS)
                if !inProfileScope {
                    described("Audio and the connection stay live when you switch away; video "
                        + "pauses.") {
                        Toggle("Keep streaming in background", isOn: $backgroundKeepAlive)
                    }
                    if backgroundKeepAlive {
                        described("Ends a backgrounded session so it can't run down the "
                            + "battery.") {
                            Picker("Disconnect after", selection: $backgroundTimeoutMinutes) {
                                Text("1 minute").tag(1)
                                Text("5 minutes").tag(5)
                                Text("10 minutes").tag(10)
                                Text("30 minutes").tag(30)
                            }
                        }
                    }
                }
                #endif
            }
        }
    }

    // MARK: - General: Statistics overlay

    @ViewBuilder var overlaySection: some View {
        Section("Statistics") {
            described(Self.statisticsDescription, field: "stats_verbosity") {
                Picker("Statistics overlay", selection: scoped(SettingsFields.statsVerbosity)) {
                    ForEach(StatsVerbosity.allCases, id: \.rawValue) { tier in
                        Text(tier.label).tag(tier.rawValue)
                    }
                }
            }
            // Which corner the overlay sits in is a property of this device's screen, not of a
            // profile (tier G).
            if !inProfileScope {
                Picker("Position", selection: $hudPlacement) {
                    ForEach(HUDPlacement.allCases) { placement in
                        Text(placement.label).tag(placement.rawValue)
                    }
                }
                .disabled(effective.statsVerbosity == StatsVerbosity.off.rawValue)
            }
        }
    }

    // MARK: - General: Library

    @ViewBuilder var librarySection: some View {
        // An app-level feature switch for this device (tier G) — the whole section collapses in
        // profile scope rather than rendering an empty group.
        if !inProfileScope {
            Section("Library") {
                described("Adds “Browse Library…” to paired hosts — launch their games "
                    + "directly.") {
                    Toggle("Show game library", isOn: $libraryEnabled)
                }
                if libraryEnabled {
                    described("How the controller-optimized library arranges titles: Shelf is the "
                        + "coverflow, Grid shows more at once.") {
                        Picker("Library view", selection: $libraryViewRaw) {
                            ForEach(LibraryArrangement.all, id: \.stored) { arrangement in
                                Text(arrangement.label).tag(arrangement.stored)
                            }
                        }
                    }
                    described("Opens a library on its platform groups first; a library with one "
                        + "platform still opens on the shelf.") {
                        Toggle("Start in collections", isOn: $libraryCollections)
                    }
                }
            }
        }
    }

    // MARK: - Input

    #if os(iOS)
    /// Touch-input model (iPhone + iPad) plus the iPad-only pointer-capture toggle: lock the
    /// mouse/trackpad for relative movement (games) vs forward an absolute cursor position.
    @ViewBuilder var pointerSection: some View {
        Section("Touch & pointer") {
            described(touchModeDescription, field: "touch_mode") {
                Picker("Touch input", selection: scoped(SettingsFields.touchMode)) {
                    Text("Trackpad").tag(TouchInputMode.trackpad.rawValue)
                    Text("Direct pointer").tag(TouchInputMode.pointer.rawValue)
                    Text("Touch passthrough").tag(TouchInputMode.touch.rawValue)
                }
            }
            // Whether a hardware mouse attached to THIS iPad gets locked is a fact about this
            // device's input hardware (tier G), not about how a host is streamed.
            if !inProfileScope, UIDevice.current.userInterfaceIdiom == .pad {
                described("Locks a hardware mouse for mouse-look. Needs the stream "
                    + "fullscreen.") {
                    Toggle("Capture pointer for games", isOn: $pointerCapture)
                }
            }
        }
    }

    /// The SELECTED touch mode explained — dynamic, so the caption always describes what the
    /// picker currently does instead of narrating all three modes at once.
    private var touchModeDescription: String {
        switch TouchInputMode(rawValue: effective.touchMode) ?? .trackpad {
        case .trackpad:
            // The one caption where length is earned: this is a gesture reference, not an
            // explanation. Only the two gestures a trackpad user would not guess are listed.
            return "Drives the host cursor like a trackpad — two-finger tap right-clicks, "
                + "two-finger drag scrolls."
        case .pointer:
            return "The host cursor jumps to wherever you touch."
        case .touch:
            return "Real multi-touch reaches the host."
        }
    }
    #endif

    #if !os(tvOS)
    /// Keyboard & mouse forwarding — applies wherever a hardware keyboard/mouse drives the stream
    /// (always on macOS; an attached keyboard/mouse on iPad). Absent on tvOS (no such input path).
    @ViewBuilder var inputSection: some View {
        Section("Keyboard & mouse") {
            #if os(macOS)
            described(mouseModeDescription, field: "mouse_mode") {
                Picker("Mouse input", selection: scoped(SettingsFields.mouseMode)) {
                    Text("Capture (games)").tag(MouseInputMode.capture.rawValue)
                    Text("Desktop (absolute)").tag(MouseInputMode.desktop.rawValue)
                }
            }
            described(inhibitShortcutsDescription, field: "inhibit_shortcuts") {
                Toggle("Capture system shortcuts", isOn: scoped(SettingsFields.inhibitShortcuts))
                    // Turning it ON is the moment to ask for Accessibility — never at stream start,
                    // where a TCC dialog over a captured stream would be the surprise.
                    .onChange(of: effective.inhibitShortcuts) { was, on in
                        if on, !was, !accessibilityTrusted { InputCapture.requestSystemShortcutAccess() }
                    }
                if effective.inhibitShortcuts, !accessibilityTrusted {
                    Button("Allow Accessibility access…") {
                        InputCapture.requestSystemShortcutAccess()
                        // The prompt's own "Open System Settings" only shows the FIRST time the system
                        // asks; after that the user has to find the pane themselves — open it for them.
                        if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") {
                            NSWorkspace.shared.open(url)
                        }
                    }
                }
            }
            .onReceive(NotificationCenter.default.publisher(
                for: NSApplication.didBecomeActiveNotification
            )) { _ in accessibilityTrusted = InputCapture.systemShortcutsAvailable }
            #endif
            described(
                (ModifierLayout(rawValue: effective.modifierLayout) ?? .mac).detail,
                field: "modifier_layout"
            ) {
                Picker("Modifier keys", selection: scoped(SettingsFields.modifierLayout)) {
                    ForEach(ModifierLayout.allCases, id: \.self) { layout in
                        Text(layout.label).tag(layout.rawValue)
                    }
                }
            }
            described("Reverses the wheel and trackpad scroll direction sent to the host.",
                      field: "invert_scroll") {
                Toggle("Invert scroll direction", isOn: scoped(SettingsFields.invertScroll))
            }
        }
    }

    #if os(macOS)
    /// Dynamic like the captions above, because the setting genuinely has no effect under the
    /// desktop mouse model (system chords stay local there on every client) — and a toggle that
    /// silently does nothing should say so instead of leaving the user to find out.
    private var inhibitShortcutsDescription: String {
        if (MouseInputMode(rawValue: effective.mouseMode) ?? .capture) == .desktop {
            return "No effect under the desktop mouse model — switch Mouse input to Capture."
        }
        if accessibilityTrusted {
            return "Sends ⌘ shortcuts — ⌘Space, ⌘Tab and Mission Control included — to the host "
                + "while captured. ⌘⎋ always stays local — it releases capture."
        }
        return "Sends the app's ⌘ shortcuts (⌘Q, ⌘W, ⌘H…) to the host while captured. ⌘Space, "
            + "⌘Tab and Mission Control need Accessibility access — macOS claims them before any "
            + "app sees them. ⌘⎋ always stays local — it releases capture."
    }

    /// The SELECTED mouse model explained — dynamic, like the touch-mode caption.
    private var mouseModeDescription: String {
        switch MouseInputMode(rawValue: effective.mouseMode) ?? .capture {
        case .capture:
            return "Locks the pointer and sends relative motion — best for games. ⌃⌥⇧M "
                + "switches live."
        case .desktop:
            return "The pointer moves freely and sends absolute positions — best for desktop "
                + "work."
        }
    }
    #endif
    #endif

    // MARK: - Audio

    @ViewBuilder var audioSection: some View {
        Section {
            described("The speaker layout requested from the host.", field: "audio_channels") {
                Picker("Audio channels", selection: scoped(SettingsFields.audioChannels)) {
                    ForEach(SettingsOptions.audioChannels, id: \.tag) { option in
                        Text(option.label).tag(option.tag)
                    }
                }
            }
            // Offered at every channel count. This row used to be hidden unless the session was
            // stereo; the frame ladder is channel-aware, so surround negotiates a shorter frame
            // rather than being impossible, and the caption is where the cases that genuinely do
            // not fit are stated (see `audioFormatCaption`). A hidden row silently discards a
            // choice and explains nothing.
            described(audioFormatCaption, field: "audio_format") {
                Picker("Audio quality", selection: scoped(SettingsFields.audioFormat)) {
                    ForEach(SettingsOptions.audioFormats, id: \.tag) { option in
                        Text(option.label).tag(option.tag)
                    }
                }
            }
            #if os(macOS)
            // Which speaker THIS Mac plays through is this device's audio routing (tier G).
            if !inProfileScope {
                described("Where host audio plays on this Mac.") {
                    Picker("Speaker", selection: $speakerUID) {
                        Text("System default").tag("")
                        ForEach(outputDevices) { device in
                            Text(device.name).tag(device.uid)
                        }
                        if !speakerUID.isEmpty,
                           !outputDevices.contains(where: { $0.uid == speakerUID }) {
                            Text("Unavailable device").tag(speakerUID)
                        }
                    }
                }
            }
            #endif
            described("This device's microphone feeds the host's virtual mic.",
                      field: "mic_enabled") {
                Toggle("Send microphone to the host", isOn: scoped(SettingsFields.micEnabled))
            }
            described(echoCancelCaption, field: "echo_cancel") {
                Toggle("Echo cancellation", isOn: scoped(SettingsFields.echoCancel))
                    .disabled(!effective.micEnabled)
            }
            #if os(macOS)
            if !inProfileScope {
                Picker("Microphone", selection: $micUID) {
                    Text("System default").tag("")
                    ForEach(inputDevices) { device in
                        Text(device.name).tag(device.uid)
                    }
                    if !micUID.isEmpty,
                       !inputDevices.contains(where: { $0.uid == micUID }) {
                        Text("Unavailable device").tag(micUID)
                    }
                }
                .disabled(!effective.micEnabled)
                // Multi-channel interfaces only: the mic sits on ONE discrete input, so let the
                // user pick it. Auto sums every channel (a lone hot mic still passes at full
                // level).
                if micChannelCount > 1 {
                    described("Pick the input your mic is on; Auto sums every channel.") {
                        Picker("Microphone channel", selection: $micChannel) {
                            Text("Auto (all channels)").tag(0)
                            ForEach(1...micChannelCount, id: \.self) { ch in
                                Text("Channel \(ch)").tag(ch)
                            }
                        }
                        .disabled(!effective.micEnabled)
                    }
                }
            }
            #endif
        } header: {
            Text("Audio")
        } footer: {
            // The lossless gates live HERE, once, rather than as a rider on each of the five
            // lossless rows. This sentence is what keeps `audioFormatCaption` honest — see its
            // doc comment: the picker must never read as a promise of the resolved format.
            Text("Applies from the next session. Lossless falls back to Standard if the host or "
                + "this device's output declines it.")
                .font(.geist(12, relativeTo: .caption))
                .foregroundStyle(.secondary)
        }
    }

    /// The SELECTED audio format: what it is, and what it costs on the wire. That is the whole
    /// per-row caption. The ways a request can come to nothing — the host's own switch (off by
    /// default), its capture gate, this device's output being unable to open the rate — are stated
    /// ONCE in the section footer instead of riding all five lossless rows.
    ///
    /// ⚠ The rule this caption exists to keep is the design's: **the UI states the RESOLVED
    /// format, never the requested one.** Nothing here may read as a guarantee — the HUD's
    /// `audioFormatLabel` is built from the connection's `Welcome`, and that is the only place a
    /// format is asserted as fact. The footer's "falls back to Standard" line is what carries that
    /// now that the per-row gate riders are gone; do not drop it without replacing it.
    private var audioFormatCaption: String {
        let choice = AudioFormatChoice(setting: effective.audioFormat)
        guard choice != .opus else {
            return "Compressed 256 kbps Opus — effectively transparent."
        }
        // Stereo cost at 24-bit, from `pcm::bitrate_kbps`. 5.1 is three times it and 7.1 four, so
        // the surround rider below states the multiplier rather than repeating the table.
        let mbps: String
        switch choice {
        case .opus: mbps = "" // unreachable — the guard above returns
        case .lossless441: mbps = "2.1"
        case .lossless48: mbps = "2.3"
        case .lossless882: mbps = "4.2"
        case .lossless96: mbps = "4.6"
        case .lossless1764: mbps = "8.5"
        }
        let head = "Bit-exact PCM — about \(mbps) Mbps on top of the video."
        guard effective.audioChannels > 2 else { return head }
        // Surround is offered at every rate, so what it costs has to be said out loud. The plane
        // sends one frame per datagram and never fragments, so more channels buy a SHORTER frame
        // rather than a bigger packet — a packet rate, not an impossibility, right up until no rung
        // on the ladder fits at all. Where that line falls, at 24-bit and the default datagram size
        // (`pcm::frame_us_for` against ~1 387 B of payload): 44.1 kHz 5.1/7.1 and 48 kHz 7.1 land
        // on the 1 ms rung, 48 kHz 5.1 on 1.5 ms, and 88.2 kHz upward fit NOTHING. Two different
        // sentences, because "it will cost you" and "it will not happen" are two different things
        // to tell someone.
        guard choice == .lossless441 || choice == .lossless48 else {
            return head + " Surround needs 48 kHz or lower."
        }
        return head + (effective.audioChannels == 6
            ? " Three times that on 5.1."
            : " Four times that on 7.1.")
    }

    /// Honest about the macOS escape hatch: the voice processor only follows the system
    /// default devices, so hand-picked endpoints silently keep the raw path (see
    /// SessionAudio's topology note) — better said here than discovered mid-call.
    private var echoCancelCaption: String {
        let base = "Filters the stream's own audio out of the mic pickup."
        #if os(macOS)
        return base + " Only on the system default devices."
        #else
        return base
        #endif
    }

    // MARK: - Controllers

    @ViewBuilder var controllersSection: some View {
        Section {
            // The master switch, above everything it governs. Profileable, so it renders in
            // both scopes: a "Work" profile can decline to forward what "Game" forwards.
            described("Sends this device's controllers to the host. Off if they already reach it "
                + "another way.",
                field: "gamepad_forwarding") {
                Toggle("Forward controllers", isOn: scoped(SettingsFields.gamepadForwarding))
            }
            // Which physical pad this device forwards, and what its own haptics do, are facts
            // about THIS device (tier G) — only the virtual pad the host creates is profileable.
            if !inProfileScope {
                if gamepads.controllers.isEmpty {
                    Text("No controllers detected")
                        .foregroundStyle(.secondary)
                } else {
                    ForEach(gamepads.controllers) { controller in
                        controllerRow(controller)
                    }
                }
                described("Which pad is player 1. Automatic picks the newest connection.") {
                    Picker("Use controller", selection: $gamepads.preferredID) {
                        ForEach(controllerOptions, id: \.tag) { option in
                            Text(option.label).tag(option.tag)
                        }
                    }
                    .disabled(!effective.gamepadForwarding)
                }
            }
            described("The virtual pad the host creates — Automatic matches your controller.",
                field: "gamepad") {
                Picker("Controller type", selection: scoped(SettingsFields.gamepadType)) {
                    ForEach(SettingsOptions.padTypes, id: \.tag) { option in
                        Text(option.label).tag(option.tag)
                    }
                }
                .disabled(!effective.gamepadForwarding)
            }
            described("Where guide and share presses go while streaming.",
                field: "system_buttons") {
                Picker("Guide button", selection: scoped(SettingsFields.systemButtons)) {
                    Text("Automatic").tag("auto")
                    Text("Send to host").tag("forward")
                    Text("This device").tag("local")
                }
                .disabled(!effective.gamepadForwarding)
            }
            described("Hold Select for the host's guide button; keep holding for its "
                + "quick-access menu.",
                field: "guide_gesture") {
                Picker("Hold Select for guide", selection: scoped(SettingsFields.guideGesture)) {
                    Text("Automatic").tag("auto")
                    Text("On").tag("on")
                    Text("Off").tag("off")
                }
                .disabled(!effective.gamepadForwarding)
            }
            #if os(iOS)
            // iPhone only in practice: hidden where the device itself can't play haptics (iPad).
            if !inProfileScope, CHHapticEngine.capabilitiesForHardware().supportsHaptics {
                described("Plays player 1's rumble on the phone itself — for pads without "
                    + "motors.") {
                    Toggle("Rumble on this iPhone", isOn: $rumbleOnDevice)
                }
            }
            // The rumble mirror's sibling, data flowing the other way: hidden where the
            // device has no motion hardware, engages only while the player-1 controller
            // reports no rotation rate of its own.
            if !inProfileScope, DeviceGyro.isAvailable {
                described("Sends this device's motion as player 1's when the controller has no "
                    + "gyro.") {
                    Toggle("Gyro from this device", isOn: $gyroFromDevice)
                }
            }
            #endif
            #if !os(tvOS)
            if !inProfileScope {
                described("A controller-friendly layout for the host list and library.") {
                    Toggle("Gamepad-optimized browsing", isOn: $gamepadUIEnabled)
                }
                // Only meaningful while the switch above is on, so it is HIDDEN rather than
                // disabled when it isn't: a picker whose every option decides nothing is worse
                // than no picker, and this Section is short enough that nothing jumps far.
                if gamepadUIEnabled {
                    described("Always keeps it up — otherwise touch returns when the last "
                        + "controller disconnects.") {
                        Picker("Show it", selection: $gamepadUIMode) {
                            ForEach(SettingsOptions.gamepadUIModes, id: \.tag) { option in
                                Text(option.label).tag(option.tag)
                            }
                        }
                    }
                }
            }
            #endif
            #if DEBUG && !os(tvOS)
            if !inProfileScope {
                Button("Test Controller…") { showControllerTest = true }
                    .disabled(gamepads.active == nil)
                    .sheet(isPresented: $showControllerTest) { ControllerTestView() }
            }
            #endif
        } header: {
            Text("Controllers")
        } footer: {
            Text("Applies from the next session.")
                .font(.geist(12, relativeTo: .caption))
                .foregroundStyle(.secondary)
        }
    }
}
