impl LocalCameraPreview {
    fn new(picture: &gtk::Picture, status_label: &gtk::Label) -> Self {
        let latest = Arc::new(Mutex::new(None::<PreviewFrame>));
        let status_text = Arc::new(Mutex::new(CAMERA_PREVIEW_IDLE.to_string()));
        let generation = Arc::new(AtomicU64::new(0));
        let running = Arc::new(AtomicBool::new(false));
        let mirrored = Arc::new(AtomicBool::new(false));

        picture.set_paintable(Some(&camera_preview_placeholder_texture()));

        {
            let picture = picture.clone();
            let status_label = status_label.clone();
            let latest = Arc::clone(&latest);
            let status_text = Arc::clone(&status_text);
            let mirrored = Arc::clone(&mirrored);
            glib::timeout_add_local(Duration::from_millis(120), move || {
                let next = latest.lock().ok().and_then(|mut slot| slot.take());
                if let Some(mut frame) = next {
                    if mirrored.load(Ordering::Acquire) {
                        mirror_preview_frame(&mut frame);
                    }
                    let bytes = glib::Bytes::from_owned(frame.rgba);
                    let texture = gdk::MemoryTexture::new(
                        frame.width,
                        frame.height,
                        gdk::MemoryFormat::R8g8b8a8,
                        &bytes,
                        frame.stride,
                    );
                    picture.set_paintable(Some(&texture));
                }
                if let Ok(text) = status_text.lock() {
                    status_label.set_text(text.as_str());
                }
                glib::ControlFlow::Continue
            });
        }

        Self {
            picture: picture.clone(),
            latest,
            status_text,
            generation,
            running,
            mirrored,
            selected_device: None,
            selected_mode: None,
            relay_preview_path: None,
        }
    }

    fn is_running(&self) -> bool {
        self.running.load(Ordering::Acquire)
    }

    fn is_device_preview_running(&self) -> bool {
        self.is_running() && self.relay_preview_path.is_none()
    }

    fn is_relay_file_running(&self) -> bool {
        self.is_running() && self.relay_preview_path.is_some()
    }

    fn set_mirrored(&mut self, mirrored: bool) {
        self.mirrored.store(mirrored, Ordering::Release);
    }

    fn mirrored(&self) -> bool {
        self.mirrored.load(Ordering::Acquire)
    }

    fn set_selected(&mut self, camera: Option<&str>) -> Result<()> {
        self.selected_device = normalize_camera_selection(camera);

        if self.is_running() {
            self.stop();
            self.start()?;
            return Ok(());
        }

        self.set_status(match self.selected_device.as_deref() {
            Some(camera) => format!(
                "Selected {camera}. Start Preview to confirm webcam framing here before you launch the relay."
            ),
            None => CAMERA_PREVIEW_IDLE.to_string(),
        });
        Ok(())
    }

    fn set_selected_mode(&mut self, mode: Option<CameraMode>) -> Result<()> {
        self.selected_mode = mode;

        if self.is_device_preview_running() {
            self.stop();
            self.start()?;
            return Ok(());
        }

        if let Some(camera) = self.selected_device.as_deref() {
            let quality = self
                .selected_mode
                .map(CameraMode::short_label)
                .unwrap_or_else(|| "default quality".to_string());
            self.set_status(format!(
                "Selected {camera} at {quality}. Start Preview to confirm webcam framing here before you launch the relay."
            ));
        }
        Ok(())
    }

    fn toggle(&mut self) -> Result<bool> {
        if self.is_running() {
            self.stop();
            return Ok(false);
        }
        self.start()?;
        Ok(true)
    }

    fn start(&mut self) -> Result<()> {
        gst::init().context("initialising in-launcher camera preview")?;
        let selected = self
            .selected_device
            .clone()
            .ok_or_else(|| anyhow!("select a camera before starting the in-launcher preview"))?;
        self.relay_preview_path = None;
        let mode = self.selected_mode;
        let device = resolve_camera_device(&selected);
        let latest = Arc::clone(&self.latest);
        let status_text = Arc::clone(&self.status_text);
        let generation = Arc::clone(&self.generation);
        let running = Arc::clone(&self.running);
        let token = generation.fetch_add(1, Ordering::AcqRel) + 1;
        running.store(true, Ordering::Release);
        self.set_status(format!("Starting local preview for {selected}..."));

        std::thread::spawn(move || {
            if let Err(err) = run_camera_preview_feed(
                selected,
                device,
                mode,
                token,
                latest,
                status_text.clone(),
                generation.clone(),
                running.clone(),
            ) && generation.load(Ordering::Acquire) == token
            {
                running.store(false, Ordering::Release);
                if let Ok(mut status) = status_text.lock() {
                    *status = format!("Camera preview failed: {err}");
                }
            }
        });
        Ok(())
    }

    fn start_relay_file(&mut self, path: PathBuf, selected: String) -> Result<()> {
        if self.is_running()
            && self
                .relay_preview_path
                .as_ref()
                .is_some_and(|existing| existing == &path)
        {
            return Ok(());
        }
        if self.is_running() {
            self.stop();
        }

        let latest = Arc::clone(&self.latest);
        let status_text = Arc::clone(&self.status_text);
        let generation = Arc::clone(&self.generation);
        let running = Arc::clone(&self.running);
        let token = generation.fetch_add(1, Ordering::AcqRel) + 1;
        running.store(true, Ordering::Release);
        self.relay_preview_path = Some(path.clone());
        self.set_status(format!(
            "Waiting for relay webcam frames from {selected}..."
        ));

        std::thread::spawn(move || {
            run_camera_file_preview_feed(
                path,
                selected,
                token,
                latest,
                status_text,
                generation,
                running,
            );
        });
        Ok(())
    }

    fn stop(&mut self) {
        let was_relay_file = self.relay_preview_path.take().is_some();
        self.running.store(false, Ordering::Release);
        self.generation.fetch_add(1, Ordering::AcqRel);
        if let Ok(mut latest) = self.latest.lock() {
            *latest = None;
        }
        self.picture
            .set_paintable(Some(&camera_preview_placeholder_texture()));
        let message = if was_relay_file {
            "Relay webcam preview stopped.".to_string()
        } else {
            match self.selected_device.as_deref() {
                Some(camera) => {
                    let quality = self
                        .selected_mode
                        .map(CameraMode::short_label)
                        .unwrap_or_else(|| "default quality".to_string());
                    format!(
                        "Local preview stopped. {camera} at {quality} stays selected for the next relay launch."
                    )
                }
                None => CAMERA_PREVIEW_IDLE.to_string(),
            }
        };
        self.set_status(message);
    }

    fn set_status(&self, text: String) {
        if let Ok(mut status) = self.status_text.lock() {
            *status = text;
        }
    }
}

pub(super) fn camera_preview_placeholder_texture() -> gdk::Texture {
    let path = format!(
        "{}/assets/placeholders/webcam_disabled.png",
        env!("CARGO_MANIFEST_DIR")
    );
    gdk::Texture::from_filename(path).unwrap_or_else(|_| {
        let rgba = vec![
            12_u8;
            (CAMERA_PREVIEW_DEFAULT_WIDTH * CAMERA_PREVIEW_DEFAULT_HEIGHT * 4) as usize
        ];
        let bytes = glib::Bytes::from_owned(rgba);
        glib::object::Cast::upcast(gdk::MemoryTexture::new(
            CAMERA_PREVIEW_DEFAULT_WIDTH,
            CAMERA_PREVIEW_DEFAULT_HEIGHT,
            gdk::MemoryFormat::R8g8b8a8,
            &bytes,
            (CAMERA_PREVIEW_DEFAULT_WIDTH * 4) as usize,
        ))
    })
}

fn mirror_preview_frame(frame: &mut PreviewFrame) {
    let width = frame.width.max(0) as usize;
    if width == 0 {
        return;
    }
    for row in frame.rgba.chunks_exact_mut(frame.stride) {
        let row = &mut row[..width * 4];
        for left_pixel in 0..(width / 2) {
            let right_pixel = width - 1 - left_pixel;
            for channel in 0..4 {
                row.swap(left_pixel * 4 + channel, right_pixel * 4 + channel);
            }
        }
    }
}

impl LocalMicrophoneMonitor {
    fn start(
        source: Option<&str>,
        sink: Option<&str>,
        recent_audio: Arc<Mutex<Vec<u8>>>,
        level: Arc<Mutex<f64>>,
    ) -> Result<Self> {
        gst::init().context("initialising microphone preview")?;
        let source = source
            .filter(|value| !value.trim().is_empty())
            .ok_or_else(|| anyhow!("select a microphone before starting Monitor Mic"))?
            .to_string();
        let sink = sink
            .filter(|value| !value.trim().is_empty())
            .map(ToOwned::to_owned);
        if let Ok(mut buffer) = recent_audio.lock() {
            buffer.clear();
        }
        if let Ok(mut meter) = level.lock() {
            *meter = 0.0;
        }

        let running = Arc::new(AtomicBool::new(true));
        let generation = Arc::new(AtomicU64::new(1));
        let running_handle = Arc::clone(&running);
        let generation_handle = Arc::clone(&generation);
        let token = generation.load(Ordering::Acquire);

        std::thread::spawn(move || {
            let _ = run_microphone_monitor_feed(
                &source,
                sink.as_deref(),
                token,
                recent_audio,
                level,
                generation_handle,
                running_handle,
            );
        });

        Ok(Self {
            running,
            generation,
        })
    }

    fn is_running(&self) -> bool {
        self.running.load(Ordering::Acquire)
    }

    fn stop(&mut self) {
        self.running.store(false, Ordering::Release);
        self.generation.fetch_add(1, Ordering::AcqRel);
    }
}

impl LocalMicrophoneLevelProbe {
    fn start(path: PathBuf, level: Arc<Mutex<f64>>) -> Self {
        let running = Arc::new(AtomicBool::new(true));
        let generation = Arc::new(AtomicU64::new(1));
        let running_handle = Arc::clone(&running);
        let generation_handle = Arc::clone(&generation);
        let path_handle = path.clone();
        let token = generation.load(Ordering::Acquire);
        std::thread::spawn(move || {
            run_microphone_level_probe(
                path_handle,
                token,
                level,
                generation_handle,
                running_handle,
            );
        });
        Self {
            path,
            running,
            generation,
        }
    }

    fn is_running(&self) -> bool {
        self.running.load(Ordering::Acquire)
    }

    fn is_running_for(&self, path: &Path) -> bool {
        self.is_running() && self.path == path
    }

    fn stop(&mut self) {
        self.running.store(false, Ordering::Release);
        self.generation.fetch_add(1, Ordering::AcqRel);
    }
}
