import SwiftUI
import AoqClientSdk

// MARK: - UIView → SwiftUI 桥接

private struct RenderUIViewRepresentable: UIViewRepresentable {
    var onReady: (UIView) -> Void

    func makeUIView(context: Context) -> UIView {
        let container = UIView()
        container.backgroundColor = .clear
        DispatchQueue.main.async { onReady(container) }
        return container
    }

    func updateUIView(_ uiView: UIView, context: Context) {}
}

// MARK: - VideoDeviceView

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

    @State private var workDir: String = NSTemporaryDirectory()
    @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 isCapturing: Bool = false
    @State private var captureIsExternal: Bool = false
    @State private var captureCameraDirection: AoqCameraDirection = .front
    @State private var localView: UIView?
    @State private var remoteView: UIView?
    @State private var localBound: Bool = false
    @State private var remoteBound: Bool = false
    @State private var selectedRenderMode: AoqRenderMode = .auto
    @State private var externalRawFormat: AoqVideoPixelFormat = .BGRA
    @State private var isEncodedPushing: Bool = false

    // Camera control
    @State private var switchCameraTargetDirection: AoqCameraDirection = .front

    // Video encoder config (1:1 对齐 C++ AoqVideoCodecConfig)
    @State private var encTrackType: AoqTrackType = .video
    @State private var encCodec: AoqEncoderType = .videoJpeg
    @State private var encWidth: String = "1280"
    @State private var encHeight: String = "720"
    @State private var encFps: String = "2"
    @State private var encBitrate: String = "500000"
    @State private var encMinBitrate: String = "128000"
    @State private var encKeyframeInterval: String = "2"
    @State private var encIsExternal: 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 capture
    @State private var audioCaptureChannel: Int = 1
    @State private var audioCaptureIsExternal: Bool = false
    @State private var audioCaptureIsVoipMode: Bool = false
    @State private var isAudioCaptured: Bool = false
    @State private var isAudioMuted: Bool = false

    // Audio player
    @State private var audioPlayerChannel: Int = 1
    @State private var audioPlayerIsExternal: Bool = false
    @State private var audioPlayerIsDefaultSpeaker: Bool = true
    @State private var audioPlayerIsVoipMode: Bool = false
    @State private var isAudioPlaying: Bool = false

    // 视频帧回调
    /// Observer delegate 注册状态，对应 SDK setVideoFrameObserver。
    @State private var videoFrameObserverRegistered: Bool = false
    /// 每个 AoqVideoSource 的回调配置。
    @State private var capturedObserverConfig  = SourceObserverConfig()
    @State private var preEncodeObserverConfig = SourceObserverConfig()
    @State private var remoteObserverConfig    = SourceObserverConfig()
    /// 配置面板中当前查看的 source。
    @State private var selectedObserverSource: AoqVideoSource = .captured

    var body: some View {
        ScrollView {
            VStack(spacing: 16) {
                videoPreviewArea
                statusCard
                AppServerAllocateSection(
                    token: $token,
                    sid: $sid,
                    certificate: $certificate,
                    relayEndpointsJSON: $relayEndpointsJSON,
                    workspaceIdHash: $workspaceIdHash
                )
                connectCard
                audioCaptureCard
                audioPlayerCard
                jpegPushCard
                captureCard
                renderCard
                cameraControlCard
                videoFrameObserverCard
                videoFrameStatsCard
                logCard
            }
            .padding(16)
        }
        .background(Color(uiColor: .systemGroupedBackground))
        .navigationTitle("视频设备API")
        .navigationBarTitleDisplayMode(.inline)
    }

    // MARK: - Video Preview

    private var videoPreviewArea: some View {
        VStack(spacing: 1) {
            previewPane(title: "本地预览",
                        onReady: { self.localView = $0 },
                        bound: localBound)
            previewPane(title: "远端渲染",
                        onReady: { self.remoteView = $0 },
                        bound: remoteBound)
        }
        .clipShape(RoundedRectangle(cornerRadius: 12))
    }

    @ViewBuilder
    private func previewPane(title: String,
                             onReady: @escaping (UIView) -> Void,
                             bound: Bool) -> some View {
        ZStack(alignment: .topLeading) {
            Color.black
            RenderUIViewRepresentable(onReady: onReady)
            HStack(spacing: 6) {
                Circle()
                    .fill(bound ? Color.green : Color.gray)
                    .frame(width: 8, height: 8)
                Text(title)
                    .font(.caption)
                    .foregroundStyle(.white)
            }
            .padding(8)
        }
        .frame(height: 120)
    }

    // 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")

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

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

            encoderCard
            audioEncoderCard
            audioDecoderCard

            HStack(spacing: 12) {
                Button("连接") {
                    applyEncoderConfig()
                    applyAudioConfig()
                    let audioTrack = AoqTrackParam()
                    audioTrack.trackType = .audio
                    let videoTrack = AoqTrackParam()
                    videoTrack.trackType = .video
                    let dataTrack = AoqTrackParam()
                    dataTrack.trackType = .data
                    let pubTracks: [AoqTrackParam] = [audioTrack, videoTrack, dataTrack]
                    let subTracks: [AoqTrackParam] = [audioTrack, dataTrack]
                    mgr.connect(token: token, sid: sid, certificate: certificate,
                               relayEndpoints: parseRelayEndpoints(json: relayEndpointsJSON),
                               workspaceIdHash: workspaceIdHash,
                               publishTracks: pubTracks,
                               subscribeTracks: subTracks)
                }
                .buttonStyle(.borderedProminent)
                .disabled(!mgr.isEngineCreated || mgr.isConnected)

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

    // MARK: - Encoder

    /* 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 encoderCard: some View {
        CardSection(title: "视频编码参数") {
            HStack {
                Text("轨道").foregroundStyle(.secondary).frame(width: 80, alignment: .leading)
                Picker("", selection: $encTrackType) {
                    Text("Video").tag(AoqTrackType.video)
                }
                .pickerStyle(.segmented)
            }

            HStack {
                Text("编码格式").foregroundStyle(.secondary).frame(width: 80, alignment: .leading)
                Picker("", selection: $encCodec) {
                    Text("H264").tag(AoqEncoderType.videoH264)
                    Text("Jpeg").tag(AoqEncoderType.videoJpeg)
                }
                .pickerStyle(.segmented)
            }

            HStack {
                Text("分辨率预设").foregroundStyle(.secondary).frame(width: 80, alignment: .leading)
                Menu {
                    Button("1920×1080") { encWidth = "1920"; encHeight = "1080" }
                    Button("1280×720")  { encWidth = "1280"; encHeight = "720"  }
                    Button("960×540")   { encWidth = "960";  encHeight = "540"  }
                    Button("640×480")   { encWidth = "640";  encHeight = "480"  }
                    Button("320×240")   { encWidth = "320";  encHeight = "240"  }
                } label: {
                    Text("\(encWidth)×\(encHeight)")
                }
                Spacer()
            }

            HStack {
                encNumberField(label: "宽(px)",   text: $encWidth)
                encNumberField(label: "高(px)",   text: $encHeight)
            }
            HStack {
                encNumberField(label: "fps",         text: $encFps)
                encNumberField(label: "关键帧(秒)",  text: $encKeyframeInterval)
            }
            HStack {
                encNumberField(label: "目标码率(bps)", text: $encBitrate)
                encNumberField(label: "最小码率(bps)", text: $encMinBitrate)
            }

            Toggle("外部编码", isOn: $encIsExternal)
                .tint(.blue)
        }
        .opacity(mgr.isEngineCreated ? 1 : 0.5)
        .disabled(!mgr.isEngineCreated)
    }

    @ViewBuilder
    private func encNumberField(label: String, text: Binding<String>) -> some View {
        HStack(spacing: 4) {
            Text(label).foregroundStyle(.secondary).font(.caption)
            TextField("", text: text)
                .textFieldStyle(.roundedBorder)
                .keyboardType(.numberPad)
                .frame(minWidth: 70)
        }
    }

    private func applyEncoderConfig() {
        let cfg = AoqVideoCodecConfig()
        cfg.trackType        = encTrackType
        cfg.codecType        = encCodec
        cfg.width            = Int(encWidth) ?? 1280
        cfg.height           = Int(encHeight) ?? 720
        cfg.fps              = Int(encFps) ?? 2
        cfg.bitrate          = Int(encBitrate) ?? 500000
        cfg.minBitrate       = Int(encMinBitrate) ?? 128000
        cfg.keyframeInterval = Int(encKeyframeInterval) ?? 2
        cfg.isExternal       = encIsExternal
        mgr.setVideoEncoderConfig(cfg)
    }

    // MARK: - Capture

    private var captureCard: some View {
        CardSection(title: "视频采集") {
            Toggle("外部采集（推送测试图）", isOn: $captureIsExternal)
                .disabled(isCapturing)

            if captureIsExternal {
                Picker("推帧格式", selection: $externalRawFormat) {
                    Text("BGRA").tag(AoqVideoPixelFormat.BGRA)
                    Text("I420").tag(AoqVideoPixelFormat.I420)
                    Text("NV12").tag(AoqVideoPixelFormat.NV12)
                    Text("NV21").tag(AoqVideoPixelFormat.NV21)
                }
                .pickerStyle(.segmented)
                .disabled(isCapturing)
            }

            Picker("摄像头方向", selection: $captureCameraDirection) {
                Text("前置").tag(AoqCameraDirection.front)
                Text("后置").tag(AoqCameraDirection.back)
            }
            .pickerStyle(.segmented)
            .disabled(isCapturing || captureIsExternal)

            HStack(spacing: 12) {
                Button("开始采集") {
                    mgr.startVideoCapture(isExternal: captureIsExternal,
                                          cameraDirection: captureCameraDirection)
                    if captureIsExternal {
                        mgr.startExternalRawTestPattern(format: externalRawFormat)
                    }
                    isCapturing = true
                }
                .buttonStyle(.bordered)
                .disabled(isCapturing)

                Button("停止采集") {
                    mgr.stopVideoCapture()
                    isCapturing = false
                    isEncodedPushing = false
                }
                .buttonStyle(.bordered)
                .disabled(!isCapturing)
            }
            StatusRow(title: "设备状态",
                      value: mgr.videoDeviceStateLabel,
                      color: videoStateColor)
        }
        .opacity(mgr.isEngineCreated ? 1 : 0.5)
        .disabled(!mgr.isEngineCreated)
    }

    // MARK: - JPEG Encoded Push

    private var jpegPushCard: some View {
        CardSection(title: "外部编码帧推送 (JPEG)") {
            HStack(spacing: 12) {
                Button("开始推送") {
                    mgr.startExternalJPEGEncodedTestPattern()
                    isEncodedPushing = true
                }
                .buttonStyle(.bordered)
                .disabled(isEncodedPushing)

                Button("停止推送") {
                    mgr.stopExternalFrameTimer()
                    isEncodedPushing = false
                }
                .buttonStyle(.bordered)
                .disabled(!isEncodedPushing)
            }
            StatusRow(title: "编码推送",
                      value: isEncodedPushing ? "推送中 (JPEG)" : "未推送",
                      color: isEncodedPushing ? .green : .secondary)
        }
        .opacity(mgr.isEngineCreated ? 1 : 0.5)
        .disabled(!mgr.isEngineCreated)
    }

    // MARK: - Render

    private var renderCard: some View {
        CardSection(title: "视频渲染") {
            Picker("渲染模式", selection: $selectedRenderMode) {
                Text("Auto").tag(AoqRenderMode.auto)
                Text("Stretch").tag(AoqRenderMode.stretch)
                Text("Fill").tag(AoqRenderMode.fill)
                Text("Crop").tag(AoqRenderMode.crop)
            }
            .pickerStyle(.segmented)

            Button(localBound ? "解绑本地视图" : "绑定本地视图") {
                if localBound {
                    mgr.setLocalView(nil, trackType: .video)
                    localBound = false
                } else {
                    guard let view = localView else { return }
                    mgr.setLocalView(view, trackType: .video, renderMode: selectedRenderMode)
                    localBound = true
                }
            }
            .buttonStyle(.bordered)
            .disabled(localView == nil)

            Button(remoteBound ? "解绑远端视图" : "绑定远端视图") {
                if remoteBound {
                    mgr.setRemoteView(nil, trackType: .video)
                    remoteBound = false
                } else {
                    guard let view = remoteView else { return }
                    mgr.setRemoteView(view, trackType: .video, renderMode: selectedRenderMode)
                    remoteBound = true
                }
            }
            .buttonStyle(.bordered)
            .disabled(remoteView == nil)

            StatusRow(title: "本地视图", value: localBound ? "已绑定" : "未绑定",
                      color: localBound ? .green : .secondary)
            StatusRow(title: "远端视图", value: remoteBound ? "已绑定" : "未绑定",
                      color: remoteBound ? .green : .secondary)
        }
        .opacity(mgr.isEngineCreated ? 1 : 0.5)
        .disabled(!mgr.isEngineCreated)
    }

    // MARK: - Audio Encoder / Decoder

    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))
    }

    private func applyAudioConfig() {
        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)
    }

    // MARK: - Audio Capture / Player

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

    private var audioPlayerCard: some View {
        CardSection(title: "音频播放") {
            Picker("通道数", selection: $audioPlayerChannel) {
                Text("1").tag(1)
                Text("2").tag(2)
            }
            .pickerStyle(.segmented)
            Toggle("外部播放", isOn: $audioPlayerIsExternal)
                .tint(.blue)
            Toggle("默认扬声器", isOn: $audioPlayerIsDefaultSpeaker)
                .tint(.blue)
            Toggle("VoIP 模式", isOn: $audioPlayerIsVoipMode)
                .tint(.blue)
            HStack(spacing: 12) {
                Button("开始播放") {
                    mgr.startAudioPlayer(channel: audioPlayerChannel,
                                         isExternal: audioPlayerIsExternal,
                                         isDefaultSpeaker: audioPlayerIsDefaultSpeaker,
                                         isVoipMode: audioPlayerIsVoipMode)
                    isAudioPlaying = true
                }
                .buttonStyle(.bordered)
                .disabled(isAudioPlaying)
                Button("停止播放") {
                    mgr.stopAudioPlayer()
                    isAudioPlaying = false
                }
                .buttonStyle(.bordered)
                .disabled(!isAudioPlaying)
            }
            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)
            }
            HStack(spacing: 12) {
                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: - Camera Control

    private var cameraControlCard: some View {
        CardSection(title: "相机控制") {
            Picker("切换目标方向", selection: $switchCameraTargetDirection) {
                Text("前置").tag(AoqCameraDirection.front)
                Text("后置").tag(AoqCameraDirection.back)
            }
            .pickerStyle(.segmented)

            Button("切换摄像头") {
                let ret = mgr.switchCamera(direction: switchCameraTargetDirection)
                if ret == 0 {
                    captureCameraDirection = switchCameraTargetDirection
                }
            }
            .buttonStyle(.bordered)
            .disabled(!isCapturing)

            StatusRow(title: "当前摄像头",
                      value: captureCameraDirection == .front ? "前置" : "后置",
                      color: .secondary)
        }
        .opacity(mgr.isEngineCreated && isCapturing ? 1 : 0.5)
        .disabled(!mgr.isEngineCreated || !isCapturing)
    }

    // MARK: - Video Frame Observer

    private var videoFrameObserverCard: some View {
        CardSection(title: "视频帧回调") {
            /* Set Observer：对应 SDK setVideoFrameObserver(self/nil)。 */
            Toggle("Set Observer", isOn: $videoFrameObserverRegistered)
                .onChange(of: videoFrameObserverRegistered) { newValue in
                    if newValue {
                        mgr.registerVideoFrameObserver()
                    } else {
                        mgr.unregisterVideoFrameObserver()
                        capturedObserverConfig.enabled  = false
                        preEncodeObserverConfig.enabled = false
                        remoteObserverConfig.enabled    = false
                    }
                }

            /* Source picker：标签后缀 ● 表示该位置已启用。 */
            Picker("", selection: $selectedObserverSource) {
                Text(observerPickerLabel(.captured,  short: "Cap")).tag(AoqVideoSource.captured)
                Text(observerPickerLabel(.preEncode, short: "PreEnc")).tag(AoqVideoSource.preEncode)
                Text(observerPickerLabel(.remote,    short: "Remote")).tag(AoqVideoSource.remote)
            }
            .pickerStyle(.segmented)

            observerConfigPanel(source: selectedObserverSource,
                                 config: selectedObserverConfigBinding)
        }
        .opacity(mgr.isEngineCreated ? 1 : 0.5)
        .disabled(!mgr.isEngineCreated)
    }

    /// 当前选中位置的配置面板。启用时字段全部 disabled。
    private func observerConfigPanel(source: AoqVideoSource,
                                       config: Binding<SourceObserverConfig>) -> some View {
        let disabled = config.wrappedValue.enabled
        // 不要用 .onChange(of: config.wrappedValue.enabled) ——
        // Picker 切到不同 source 时，config 这条 Binding 整体换源，
        // SwiftUI 会把"被观测值"从 capturedObserverConfig.enabled(=true)
        // 换成 remoteObserverConfig.enabled(=false)，看成 true→false 的"变化"
        // 触发 .onChange 的 else 分支；闭包里的 `source` 又因捕获时机不确定
        // 可能仍是上一次的 .captured，导致 mgr.disableVideoFrameObserverFor(.captured)
        // 把 Cap 真观察者关掉，Cap 统计就停了。
        //
        // 改成 custom Binding 在 setter 里直接调 SDK：副作用只发生在用户实际
        // 翻开关的瞬间，picker 切换不会触发。
        let enabledBinding = Binding<Bool>(
            get: { config.wrappedValue.enabled },
            set: { [mgr] newValue in
                guard config.wrappedValue.enabled != newValue else { return }
                config.wrappedValue.enabled = newValue
                let c = config.wrappedValue
                if newValue {
                    mgr.enableVideoFrameObserverFor(
                        videoSource: source,
                        format: c.format,
                        alignment: c.alignment,
                        mode: c.mode,
                        mirror: c.mirror,
                        dumpEnabled: c.dumpEnabled)
                } else {
                    mgr.disableVideoFrameObserverFor(videoSource: source)
                }
            }
        )
        return VStack(alignment: .leading, spacing: 6) {
            Toggle("启用", isOn: enabledBinding)

            HStack {
                Text("格式")
                    .frame(width: 50, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: config.format) {
                    Text("I420").tag(AoqVideoPixelFormat.I420)
                    Text("NV12").tag(AoqVideoPixelFormat.NV12)
                    Text("NV21").tag(AoqVideoPixelFormat.NV21)
                    Text("BGRA").tag(AoqVideoPixelFormat.BGRA)
                    Text("RGBA").tag(AoqVideoPixelFormat.RGBA)
                    Text("CVPB").tag(AoqVideoPixelFormat.cvPixelBuffer)
                }
                .disabled(disabled)
            }

            HStack {
                Text("对齐")
                    .frame(width: 50, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: config.alignment) {
                    Text("Def").tag(AoqVideoObserverAlignment.alignmentDefault)
                    Text("Even").tag(AoqVideoObserverAlignment.alignmentEven)
                    Text("4").tag(AoqVideoObserverAlignment.alignment4)
                    Text("8").tag(AoqVideoObserverAlignment.alignment8)
                    Text("16").tag(AoqVideoObserverAlignment.alignment16)
                }
                .pickerStyle(.segmented)
                .disabled(disabled)
            }

            HStack {
                Text("模式")
                    .frame(width: 50, alignment: .leading)
                    .foregroundStyle(.secondary)
                Picker("", selection: config.mode) {
                    Text("RO").tag(AoqVideoObserverMode.readOnly)
                    Text("RW").tag(AoqVideoObserverMode.readWrite)
                }
                .pickerStyle(.segmented)
                .disabled(disabled)
            }

            HStack {
                Toggle("镜像", isOn: config.mirror)
                    .disabled(disabled)
                Spacer().frame(width: 16)
                Toggle("Dump", isOn: config.dumpEnabled)
                    .disabled(disabled)
            }
        }
    }

    /// picker label：未启用显示 "Cap"，启用显示 "Cap ●"。
    private func observerPickerLabel(_ source: AoqVideoSource, short: String) -> String {
        observerConfig(for: source).enabled ? "\(short) ●" : short
    }

    private func observerConfig(for source: AoqVideoSource) -> SourceObserverConfig {
        switch source {
        case .captured:  return capturedObserverConfig
        case .preEncode: return preEncodeObserverConfig
        case .remote:    return remoteObserverConfig
        @unknown default: return SourceObserverConfig()
        }
    }

    private var selectedObserverConfigBinding: Binding<SourceObserverConfig> {
        switch selectedObserverSource {
        case .captured:  return $capturedObserverConfig
        case .preEncode: return $preEncodeObserverConfig
        case .remote:    return $remoteObserverConfig
        @unknown default: return $capturedObserverConfig
        }
    }

    // MARK: - Video Frame Stats

    private var videoFrameStatsCard: some View {
        CardSection(title: "视频帧统计", trailing: {
            Button("清空") {
                for s in [AoqVideoSource.captured, .preEncode, .remote] {
                    mgr.resetVideoFrameStats(s)
                }
            }
            .font(.caption)
        }) {
            videoStatRow(.captured)
            videoStatRow(.preEncode)
            videoStatRow(.remote)
        }
    }

    private func videoStatRow(_ source: AoqVideoSource) -> some View {
        let s = mgr.videoFrameStats[source] ?? VideoFrameStats()
        let label: String
        switch source {
        case .captured:  label = "Cap"
        case .preEncode: label = "PreEnc"
        case .remote:    label = "Remote"
        @unknown default: label = "?"
        }
        return HStack(spacing: 8) {
            Text(label).frame(width: 56, alignment: .leading).foregroundStyle(.secondary)
            Text("\(s.totalFrames)").frame(width: 60, alignment: .trailing)
            Text(String(format: "%.1f fps", s.fpsLastSecond)).frame(width: 70, alignment: .trailing)
            Text(s.width > 0 ? "\(s.width)×\(s.height)" : "-").frame(width: 90)
            Text(videoFormatTag(s.format)).frame(width: 50)
        }
        .font(.system(.caption, design: .monospaced))
    }

    // MARK: - Log

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

    // MARK: - Helpers

    private var videoStateColor: Color {
        let s = mgr.videoDeviceStateLabel
        if s.contains("已启动") { return .green }
        if s.contains("失败") { return .red }
        if s.contains("中") { return .orange }
        return .secondary
    }

    private func resetAllState() {
        isCapturing = false
        localBound = false
        remoteBound = false
        isAudioCaptured = false
        isAudioPlaying = false
        isAudioMuted = false
    }
}

// MARK: - Source-scoped Video Observer Config

/// 视频帧回调单个位置的配置。
struct SourceObserverConfig: Equatable {
    var enabled: Bool = false
    var format: AoqVideoPixelFormat = .I420
    var alignment: AoqVideoObserverAlignment = .alignmentDefault
    var mode: AoqVideoObserverMode = .readOnly
    var mirror: Bool = false
    var dumpEnabled: Bool = false
}

#Preview {
    NavigationStack {
        VideoDeviceView()
    }
}
