/// Forward camera bus warnings/errors into the relay log with the device label.
#[cfg(not(coverage))]
fn spawn_camera_bus_logger(pipeline: &gst::Pipeline, device: String) {
    let Some(bus) = pipeline.bus() else {
        return;
    };
    std::thread::spawn(move || {
        use gst::MessageView::{Error, StateChanged, Warning};
        for msg in bus.iter_timed(gst::ClockTime::NONE) {
            match msg.view() {
                StateChanged(s)
                    if s.current() == gst::State::Playing
                        && msg.src().is_some_and(|source| {
                            source.type_() == gst::Pipeline::static_type()
                        }) =>
                {
                    tracing::info!(%device, "📸 camera pipeline ▶️");
                }
                Error(e) => tracing::error!(
                    %device,
                    "📸💥 camera: {} ({})",
                    e.error(),
                    e.debug().unwrap_or_default()
                ),
                Warning(w) => tracing::warn!(
                    %device,
                    "📸⚠️ camera: {} ({})",
                    w.error(),
                    w.debug().unwrap_or_default()
                ),
                _ => {}
            }
        }
    });
}

/// Keep the coverage harness deterministic while preserving production bus logging.
#[cfg(coverage)]
fn spawn_camera_bus_logger(pipeline: &gst::Pipeline, _device: String) {
    let _ = pipeline.bus();
}

#[cfg(not(coverage))]
fn buildable_encoder(encoder: &'static str) -> bool {
    gst::ElementFactory::find(encoder).is_some()
        && gst::ElementFactory::make(encoder).build().is_ok()
}

#[cfg(not(coverage))]
fn supported_encoder_property(
    encoder: &'static str,
    properties: &[&'static str],
) -> Option<&'static str> {
    let element = gst::ElementFactory::make(encoder).build().ok()?;
    properties
        .iter()
        .copied()
        .find(|property| element.find_property(property).is_some())
}

impl Drop for CameraCapture {
    /// Stop the local preview tap and release the GStreamer camera pipeline.
    fn drop(&mut self) {
        if let Some(running) = &self.preview_tap_running {
            running.store(false, Ordering::Release);
        }
        let _ = self.pipeline.set_state(gst::State::Null);
        if let Some(child) = &mut self.ffmpeg_child {
            let _ = child.kill();
            let _ = child.wait();
        }
    }
}
