import SwiftUI
import AoqClientSdk
import UniformTypeIdentifiers

struct AudioDeviceView: View {
    @StateObject private var mgr = AoqEngineManager.shared

    @State private var workDir: String = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.path
    @State private var enableDumpAudio: Bool = false
    @State private var extras: String = ""
    @State private var token: String = ""
    @State private var sid: String = ""
    @State private var certificate: String = ""
    /* relay 接入点列表的 JSON 表示，默认单个 127.0.0.1:8006 */
    @State private var relayEndpointsJSON: String = "[\n  {\n    \"endpoint\" : \"127.0.0.1\",\n    \"port\" : 8006\n  }\n]"
    @State private var workspaceIdHash: String = ""
    @State private var captureChannel: Int = 1
    @State private var captureIsExternal: Bool = false
    @State private var captureIsVoipMode: Bool = false
    @State private var playerChannel: Int = 1
    @State private var playerIsExternal: Bool = false
    @State private var playerIsDefaultSpeaker: Bool = true
    @State private var playerIsVoipMode: Bool = false
    @State private var isMuted: Bool = false

    // Audio encoder config
    @State private var audioCodec: AoqEncoderType = .audioPCM
    @State private var audioEncodeSampleRate: Int = 16000
    @State private var audioEncodeChannel: Int = 1
    @State private var audioEncodeBitrate: Int = 24000

    // Audio decoder config
    @State private var audioDecodeCodec: AoqEncoderType = .audioPCM
    @State private var audioDecodeSampleRate: Int = 16000
    @State private var audioDecodeChannel: Int = 1
    @State private var audioDecodeBitrate: Int = 24000

    // Audio Frame Observer
    @State private var audioFrameEnabled: Bool = false
    @State private var audioFrameSampleRate: Int = 48000
    @State private var audioFrameChannels: Int = 1
    @State private var audioFrameMode: AoqAudioObserverMode = .readOnly
    @State private var audioFrameSource: AoqAudioSource = .captured

    // External audio stream
    @State private var extStreamId: String = "ext_audio_1"
    @State private var extTrackFormat: AoqEncoderType = .audioPCM
    @State private var extSampleRate: Int = 48000
    @State private var extChannels: Int = 1
    @State private var extPlayoutVolume: Double = 100
    @State private var extPublishVolume: Double = 100
    @State private var extVolumeType: AoqAudioStreamDirection = .publish
    @State private var extVolumeSet: Double = 100
    @State private var extAudioFilePath: String = ""
    @State private var showAudioFilePicker: Bool = false

    // Audio File
    @State private var audioFileId: String = "file1"
    @State private var audioFilePath: String = ""
    @State private var audioFileCycles: String = "-1"
    @State private var audioFileStartPosMs: String = "0"
    @State private var audioFilePublishVolume: Double = 100
    @State private var audioFilePlayoutVolume: Double = 100
    @State private var audioFilePositionMs: String = "0"
    @State private var showAudioFileMixPicker: Bool = false

    var body: some View {
        ScrollView {
            VStack(spacing: 16) {
                statusCard
                AppServerAllocateSection(
                    token: $token,
                    sid: $sid,
                    certificate: $certificate,
                    relayEndpointsJSON: $relayEndpointsJSON,
                    workspaceIdHash: $workspaceIdHash
                )
                connectCard
                captureCard
                playerCard
                externalStreamCard
                audioFileCard
                audioFrameObserverCard
                logCard
            }
            .padding(16)
        }
        .background(Color(uiColor: .systemGroupedBackground))
        .navigationTitle("音频设备API")
        .navigationBarTitleDisplayMode(.inline)
        .sheet(isPresented: $showAudioFilePicker) {
            DocumentPickerView { url in
                extAudioFilePath = url.path
            }
        }
    }

    // MARK: - Status

    private var statusCard: some View {
        CardSection(title: "引擎状态") {
            StatusRow(title: "SDK 版本", value: mgr.sdkVersion)
            StatusRow(title: "引擎实例", value: mgr.isEngineCreated ? "已创建" : "未创建",
                      color: mgr.isEngineCreated ? .green : .secondary)
            StatusRow(title: "连接状态", value: mgr.isConnected ? "已连接" : "未连接",
                      color: mgr.isConnected ? .green : .secondary)
            StatusRow(title: "最近错误", value: mgr.lastError.isEmpty ? "-" : mgr.lastError,
                      color: mgr.lastError.isEmpty ? .secondary : .red)
        }
    }

    // MARK: - Connect

    private var connectCard: some View {
        CardSection(title: "连接配置") {
            LabeledInput(label: "Token", text: $token, placeholder: "输入 Token")
            LabeledInput(label: "SID", text: $sid, placeholder: "输入 SID")
            LabeledInput(label: "Certificate", text: $certificate, placeholder: "输入 Certificate")
            relayEndpointsEditor
            LabeledInput(label: "WsIdHash", text: $workspaceIdHash, placeholder: "工作空间哈希 ID")
            Toggle("音频 Dump", isOn: $enableDumpAudio)
                .tint(.blue)

            HStack(spacing: 12) {
                Button("创建引擎") {
                    mgr.createEngine(workDir: workDir, enableDumpAudio: enableDumpAudio, extras: extras)
                }
                .buttonStyle(.bordered)
                .disabled(mgr.isEngineCreated)

                Button("销毁引擎") {
                    mgr.destroyEngine()
                }
                .buttonStyle(.bordered)
                .tint(.red)
                .disabled(!mgr.isEngineCreated)
            }

            audioEncoderCard
            audioDecoderCard

            HStack(spacing: 12) {
                Button("连接") {
                    let audioTrack = AoqTrackParam()
                    audioTrack.trackType = .audio
                    let dataTrack = AoqTrackParam()
                    dataTrack.trackType = .data
                    let encCfg = AoqAudioCodecConfig()
                    encCfg.codecType = audioCodec
                    encCfg.sampleRate = audioEncodeSampleRate
                    encCfg.channel = audioEncodeChannel
                    encCfg.bitrate = audioEncodeBitrate
                    mgr.setAudioEncoderConfig(encCfg)
                    let decCfg = AoqAudioCodecConfig()
                    decCfg.codecType = audioDecodeCodec
                    decCfg.sampleRate = audioDecodeSampleRate
                    decCfg.channel = audioDecodeChannel
                    decCfg.bitrate = audioDecodeBitrate
                    mgr.setAudioDecoderConfig(decCfg)
                    mgr.connect(token: token, sid: sid, certificate: certificate,
                               relayEndpoints: parseRelayEndpoints(json: relayEndpointsJSON),
                               workspaceIdHash: workspaceIdHash,
                               publishTracks: [audioTrack, dataTrack],
                               subscribeTracks: [audioTrack, dataTrack])
                }
                .buttonStyle(.borderedProminent)
                .disabled(!mgr.isEngineCreated || mgr.isConnected)

                Button("断开") {
                    mgr.disconnect()
                }
                .buttonStyle(.bordered)
                .tint(.red)
                .disabled(!mgr.isConnected)
            }
        }
    }

    // MARK: - Audio Encoder / Decoder Cards

    /* relay 接入点 JSON 多行编辑器 */
    private var relayEndpointsEditor: some View {
        VStack(alignment: .leading, spacing: 4) {
            HStack(alignment: .top) {
                Text("RelayEndpoints")
                    .frame(width: 110, alignment: .leading)
                    .foregroundStyle(.secondary)
                TextEditor(text: $relayEndpointsJSON)
                    .font(.system(.caption, design: .monospaced))
                    .frame(minHeight: 80, maxHeight: 140)
                    .overlay(
                        RoundedRectangle(cornerRadius: 4)
                            .stroke(Color.secondary.opacity(0.3))
                    )
            }
            Text("格式：[{\"endpoint\":\"ip\",\"port\":8443}]，由服务器返回或手动编辑")
                .font(.caption2)
                .foregroundStyle(.secondary)
        }
    }

    private var audioEncoderCard: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text("音频编码").font(.subheadline.bold())
            HStack {
                Text("编码")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $audioCodec) {
                    Text("PCM").tag(AoqEncoderType.audioPCM)
                    Text("Opus").tag(AoqEncoderType.audioOpus)
                }
                .pickerStyle(.segmented)
            }
            HStack {
                Text("采样率")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $audioEncodeSampleRate) {
                    Text("8000").tag(8000)
                    Text("16000").tag(16000)
                    Text("48000").tag(48000)
                }
                .pickerStyle(.segmented)
            }
            HStack {
                Text("声道数")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $audioEncodeChannel) {
                    Text("1").tag(1)
                    Text("2").tag(2)
                }
                .pickerStyle(.segmented)
            }
            HStack {
                Text("码率")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $audioEncodeBitrate) {
                    Text("12000").tag(12000)
                    Text("24000").tag(24000)
                    Text("48000").tag(48000)
                    Text("64000").tag(64000)
                }
                .pickerStyle(.segmented)
            }
        }
        .padding(12)
        .background(Color(uiColor: .secondarySystemGroupedBackground))
        .clipShape(RoundedRectangle(cornerRadius: 8))
    }

    private var audioDecoderCard: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text("音频解码").font(.subheadline.bold())
            HStack {
                Text("编码")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $audioDecodeCodec) {
                    Text("PCM").tag(AoqEncoderType.audioPCM)
                    Text("Opus").tag(AoqEncoderType.audioOpus)
                }
                .pickerStyle(.segmented)
            }
            HStack {
                Text("采样率")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $audioDecodeSampleRate) {
                    Text("8000").tag(8000)
                    Text("16000").tag(16000)
                    Text("48000").tag(48000)
                }
                .pickerStyle(.segmented)
            }
            HStack {
                Text("声道数")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $audioDecodeChannel) {
                    Text("1").tag(1)
                    Text("2").tag(2)
                }
                .pickerStyle(.segmented)
            }
            HStack {
                Text("码率")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $audioDecodeBitrate) {
                    Text("12000").tag(12000)
                    Text("24000").tag(24000)
                    Text("48000").tag(48000)
                    Text("64000").tag(64000)
                }
                .pickerStyle(.segmented)
            }
        }
        .padding(12)
        .background(Color(uiColor: .secondarySystemGroupedBackground))
        .clipShape(RoundedRectangle(cornerRadius: 8))
    }

    // MARK: - Capture

    private var captureCard: some View {
        CardSection(title: "音频采集") {
            Picker("通道数", selection: $captureChannel) {
                Text("1").tag(1)
                Text("2").tag(2)
            }
            .pickerStyle(.segmented)
            Toggle("外部采集", isOn: $captureIsExternal)
                .tint(.blue)
            Toggle("VoIP 模式", isOn: $captureIsVoipMode)
                .tint(.blue)
            HStack(spacing: 12) {
                Button("开始采集") {
                    mgr.startAudioCapture(channel: captureChannel,
                                          isExternal: captureIsExternal,
                                          isVoipMode: captureIsVoipMode)
                }
                .buttonStyle(.bordered)
                Button("停止采集") { mgr.stopAudioCapture() }
                    .buttonStyle(.bordered)
            }
            Toggle("静音采集", isOn: $isMuted)
                .tint(.blue)
                .onChange(of: isMuted) { newValue in
                    mgr.muteAudioCapture(newValue)
                }
        }
        .opacity(mgr.isEngineCreated ? 1 : 0.5)
        .disabled(!mgr.isEngineCreated)
    }

    // MARK: - Player

    private var playerCard: some View {
        CardSection(title: "音频播放") {
            Picker("通道数", selection: $playerChannel) {
                Text("1").tag(1)
                Text("2").tag(2)
            }
            .pickerStyle(.segmented)
            Toggle("外部播放", isOn: $playerIsExternal)
                .tint(.blue)
            Toggle("默认扬声器", isOn: $playerIsDefaultSpeaker)
                .tint(.blue)
            Toggle("VoIP 模式", isOn: $playerIsVoipMode)
                .tint(.blue)
            HStack(spacing: 12) {
                Button("开始播放") {
                    mgr.startAudioPlayer(channel: playerChannel,
                                         isExternal: playerIsExternal,
                                         isDefaultSpeaker: playerIsDefaultSpeaker,
                                         isVoipMode: playerIsVoipMode)
                }
                .buttonStyle(.bordered)
                Button("停止播放") { mgr.stopAudioPlayer() }
                    .buttonStyle(.bordered)
            }
            HStack(spacing: 12) {
                Button("暂停") { mgr.pauseAudioPlayer(fadeMs: -1) }
                    .buttonStyle(.bordered)
                Button("恢复") { mgr.resumeAudioPlayer(fadeMs: -1) }
                    .buttonStyle(.bordered)
                Button("打断") { mgr.interruptAudioPlayer(trackType: .audio, fadeMs: -1) }
                    .buttonStyle(.bordered)
                Button("扬声器") { mgr.enableSpeakerphone(true) }
                    .buttonStyle(.bordered)
                Button("听筒") { mgr.enableSpeakerphone(false) }
                    .buttonStyle(.bordered)
                Button("查询Mic") { _ = mgr.isSpeakerphoneEnabled() }
                    .buttonStyle(.bordered)
                    .tint(.orange)
            }
        }
        .opacity(mgr.isEngineCreated ? 1 : 0.5)
        .disabled(!mgr.isEngineCreated)
    }

    // MARK: - External Audio Stream

    private var externalStreamCard: some View {
        CardSection(title: "外部音频流") {
            LabeledInput(label: "StreamId", text: $extStreamId, placeholder: "外部音频流 ID")

            HStack {
                Text("格式")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $extTrackFormat) {
                    Text("PCM").tag(AoqEncoderType.audioPCM)
                    Text("OPUS").tag(AoqEncoderType.audioOpus)
                }
                .pickerStyle(.segmented)
            }
            HStack {
                Text("采样率")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $extSampleRate) {
                    Text("48000").tag(48000)
                    Text("32000").tag(32000)
                    Text("16000").tag(16000)
                }
                .pickerStyle(.segmented)
            }
            HStack {
                Text("声道数")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $extChannels) {
                    Text("1").tag(1)
                    Text("2").tag(2)
                }
                .pickerStyle(.segmented)
            }

            HStack(spacing: 12) {
                Button("添加外部流") {
                    let cfg = AoqAudioExternalStreamConfig()
                    cfg.trackType   = .audio
                    cfg.codecType   = extTrackFormat
                    cfg.channels    = extChannels
                    cfg.sampleRate  = extSampleRate
                    cfg.playoutVolume = Int(extPlayoutVolume)
                    cfg.publishVolume = Int(extPublishVolume)
                    mgr.addAudioExternalStream(streamId: extStreamId, config: cfg)
                }
                .buttonStyle(.bordered)
                Button("删除外部流") {
                    mgr.removeAudioExternalStream(streamId: extStreamId)
                }
                .buttonStyle(.bordered)
                .tint(.red)
            }

            HStack {
                Text("音量类型")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $extVolumeType) {
                    Text("Publish").tag(AoqAudioStreamDirection.publish)
                    Text("Playout").tag(AoqAudioStreamDirection.playout)
                }
                .pickerStyle(.segmented)
            }
            HStack {
                Text("音量")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Slider(value: $extVolumeSet, in: 0...100, step: 1)
                Text("\(Int(extVolumeSet))")
                    .frame(width: 36, alignment: .trailing)
                    .foregroundStyle(.secondary)
            }
            HStack(spacing: 12) {
                Button("设置音量") {
                    mgr.setAudioExternalStreamVolume(streamId: extStreamId,
                                                     type: extVolumeType,
                                                     volume: Int(extVolumeSet))
                }
                .buttonStyle(.bordered)
                Button("获取音量") {
                    let v = mgr.getAudioExternalStreamVolume(streamId: extStreamId, type: extVolumeType)
                    if v >= 0 { extVolumeSet = Double(v) }
                }
                .buttonStyle(.bordered)
            }

            HStack(spacing: 12) {
                Button("清空 Buffer") {
                    mgr.clearAudioExternalStreamBuffer(streamId: extStreamId, fadeoutMs: 0)
                }
                .buttonStyle(.bordered)
            }

            Divider()

            // 本地音频文件推流（任何与采样参数匹配的裸二进制文件均可）
            VStack(alignment: .leading, spacing: 4) {
                Text("音频文件")
                    .font(.caption)
                    .foregroundStyle(.secondary)
                HStack {
                    TextField("本地音频文件路径", text: $extAudioFilePath)
                        .textFieldStyle(.roundedBorder)
                        .font(.subheadline)
                    Button("选择") { showAudioFilePicker = true }
                        .buttonStyle(.bordered)
                }
            }
            HStack(spacing: 12) {
                Button(mgr.isAudioFilePushing ? "推流中..." : "开始 Push") {
                    let url = URL(fileURLWithPath: extAudioFilePath)
                    mgr.startPushAudioFile(streamId: extStreamId,
                                           fileURL: url,
                                           sampleRate: extSampleRate,
                                           channels: extChannels)
                }
                .buttonStyle(.bordered)
                .disabled(extAudioFilePath.isEmpty || mgr.isAudioFilePushing)
                Button("停止 Push") { mgr.stopPushAudioFile() }
                    .buttonStyle(.bordered)
                    .tint(.red)
                    .disabled(!mgr.isAudioFilePushing)
            }
        }
        .opacity(mgr.isEngineCreated ? 1 : 0.5)
        .disabled(!mgr.isEngineCreated)
    }

    // MARK: - Audio File Mix

    private var audioFileCard: some View {
        CardSection(title: "音频文件混音") {
            LabeledInput(label: "FileId", text: $audioFileId, placeholder: "file1")

            VStack(alignment: .leading, spacing: 4) {
                Text("文件路径")
                    .font(.caption)
                    .foregroundStyle(.secondary)
                HStack {
                    TextField("本地音频文件路径", text: $audioFilePath)
                        .textFieldStyle(.roundedBorder)
                        .font(.subheadline)
                    Button("选择") { showAudioFileMixPicker = true }
                        .buttonStyle(.bordered)
                }
            }
            .sheet(isPresented: $showAudioFileMixPicker) {
                DocumentPickerView { url in
                    audioFilePath = url.path
                }
            }

            HStack {
                Text("循环")
                    .frame(width: 40, alignment: .leading)
                    .foregroundStyle(.secondary)
                TextField("-1", text: $audioFileCycles)
                    .textFieldStyle(.roundedBorder)
                    .frame(width: 50)
                    .keyboardType(.numbersAndPunctuation)
                Text("起始ms")
                    .foregroundStyle(.secondary)
                TextField("0", text: $audioFileStartPosMs)
                    .textFieldStyle(.roundedBorder)
                    .frame(width: 60)
                    .keyboardType(.numberPad)
            }

            HStack {
                Text("推流音量")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Slider(value: $audioFilePublishVolume, in: 0...100, step: 1)
                Text("\(Int(audioFilePublishVolume))")
                    .frame(width: 30, alignment: .trailing)
                    .foregroundStyle(.secondary)
            }

            HStack {
                Text("播放音量")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Slider(value: $audioFilePlayoutVolume, in: 0...100, step: 1)
                Text("\(Int(audioFilePlayoutVolume))")
                    .frame(width: 30, alignment: .trailing)
                    .foregroundStyle(.secondary)
            }

            HStack(spacing: 10) {
                Button("开始") {
                    let cfg = AoqAudioFileMixConfig()
                    cfg.fileName       = audioFilePath
                    cfg.cycles         = Int(audioFileCycles) ?? -1
                    cfg.startPosMs     = Int(audioFileStartPosMs) ?? 0
                    cfg.publishVolume  = Int(audioFilePublishVolume)
                    cfg.playoutVolume  = Int(audioFilePlayoutVolume)
                    mgr.startAudioFile(fileId: audioFileId, config: cfg)
                }
                .buttonStyle(.bordered)
                Button("停止")  { mgr.stopAudioFile(fileId: audioFileId) }
                    .buttonStyle(.bordered)
                    .tint(.red)
                Button("暂停")  { mgr.pauseAudioFile(fileId: audioFileId) }
                    .buttonStyle(.bordered)
                Button("恢复")  { mgr.resumeAudioFile(fileId: audioFileId) }
                    .buttonStyle(.bordered)
            }

            HStack(spacing: 10) {
                Button("获取时长") { mgr.getAudioFileDuration(fileId: audioFileId) }
                    .buttonStyle(.bordered)
                Text("\(mgr.audioFileDurationMs)ms")
                    .foregroundStyle(.secondary)
                    .frame(minWidth: 70, alignment: .leading)
                Button("获取位置") { mgr.getAudioFileCurrentPosition(fileId: audioFileId) }
                    .buttonStyle(.bordered)
                Text("\(mgr.audioFileCurrentPosMs)ms")
                    .foregroundStyle(.secondary)
                    .frame(minWidth: 70, alignment: .leading)
            }

            HStack {
                Text("跳转ms")
                    .foregroundStyle(.secondary)
                TextField("0", text: $audioFilePositionMs)
                    .textFieldStyle(.roundedBorder)
                    .frame(width: 70)
                    .keyboardType(.numberPad)
                Button("设置位置") {
                    let pos = Int64(audioFilePositionMs) ?? 0
                    mgr.setAudioFilePositionMillis(fileId: audioFileId, positionMillis: pos)
                }
                .buttonStyle(.bordered)
            }

            HStack(spacing: 8) {
                Button("设置PubV") {
                    mgr.setAudioFileVolume(fileId: audioFileId, type: .publish, volume: Int(audioFilePublishVolume))
                }
                .buttonStyle(.bordered)
                Button("获取PubV") {
                    mgr.getAudioFileVolume(fileId: audioFileId, type: .publish)
                }
                .buttonStyle(.bordered)
                Text("\(mgr.audioFilePublishVolumeGet)")
                    .frame(width: 30, alignment: .trailing)
                    .foregroundStyle(.secondary)
            }

            HStack(spacing: 8) {
                Button("设置PlyV") {
                    mgr.setAudioFileVolume(fileId: audioFileId, type: .playout, volume: Int(audioFilePlayoutVolume))
                }
                .buttonStyle(.bordered)
                Button("获取PlyV") {
                    mgr.getAudioFileVolume(fileId: audioFileId, type: .playout)
                }
                .buttonStyle(.bordered)
                Text("\(mgr.audioFilePlayoutVolumeGet)")
                    .frame(width: 30, alignment: .trailing)
                    .foregroundStyle(.secondary)
            }

        }
        .opacity(mgr.isEngineCreated ? 1 : 0.5)
        .disabled(!mgr.isEngineCreated)
    }

    // MARK: - Audio Frame Observer

    private var audioFrameObserverCard: some View {
        CardSection(title: "音频帧回调") {
            Toggle("开启回调", isOn: $audioFrameEnabled)
                .tint(.blue)
                .onChange(of: audioFrameEnabled) { newValue in
                    if newValue {
                        mgr.startAudioFrameObserver(
                            audioSource: audioFrameSource,
                            sampleRate: audioFrameSampleRate,
                            channels: audioFrameChannels,
                            mode: audioFrameMode
                        )
                    } else {
                        mgr.stopAudioFrameObserver(audioSource: audioFrameSource)
                    }
                }

            HStack {
                Text("采样率")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $audioFrameSampleRate) {
                    Text("48000").tag(48000)
                    Text("16000").tag(16000)
                }
                .pickerStyle(.segmented)
                .disabled(audioFrameEnabled)
            }

            HStack {
                Text("声道")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $audioFrameChannels) {
                    Text("1").tag(1)
                    Text("2").tag(2)
                }
                .pickerStyle(.segmented)
                .disabled(audioFrameEnabled)
            }

            HStack {
                Text("模式")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $audioFrameMode) {
                    Text("ReadOnly").tag(AoqAudioObserverMode.readOnly)
                    Text("ReadWrite").tag(AoqAudioObserverMode.readWrite)
                }
                .pickerStyle(.segmented)
                .disabled(audioFrameEnabled)
            }

            HStack {
                Text("音频源")
                    .frame(width: 60, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: $audioFrameSource) {
                    Text("Cap").tag(AoqAudioSource.captured)
                    Text("3A").tag(AoqAudioSource.processCaptured)
                    Text("Pub").tag(AoqAudioSource.publish)
                    Text("Play").tag(AoqAudioSource.playback)
                }
                .disabled(audioFrameEnabled)
            }
        }
        .opacity(mgr.isEngineCreated ? 1 : 0.5)
        .disabled(!mgr.isEngineCreated)
    }

    // MARK: - Log

    private var logCard: some View {
        CardSection(title: "回调事件日志", trailing: {
            Button("清空") { mgr.clearLog() }
                .font(.caption)
        }) {
            LogView(events: mgr.events)
        }
    }
}

#Preview {
    NavigationStack {
        AudioDeviceView()
    }
}

// MARK: - Document Picker

/// SwiftUI 包装 UIDocumentPickerViewController，用于选择本地音频文件。
struct DocumentPickerView: UIViewControllerRepresentable {
    let onPick: (URL) -> Void

    func makeCoordinator() -> Coordinator { Coordinator(onPick: onPick) }

    func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
        let types: [UTType] = [.audio, .data, .item]
        let picker = UIDocumentPickerViewController(forOpeningContentTypes: types, asCopy: true)
        picker.allowsMultipleSelection = false
        picker.delegate = context.coordinator
        return picker
    }

    func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {}

    final class Coordinator: NSObject, UIDocumentPickerDelegate {
        let onPick: (URL) -> Void
        init(onPick: @escaping (URL) -> Void) { self.onPick = onPick }

        func documentPicker(_ controller: UIDocumentPickerViewController,
                            didPickDocumentsAt urls: [URL]) {
            if let url = urls.first { onPick(url) }
        }
    }
}
