fn send_response(fd: i32, _req: libc::c_ulong, _payload: &[u8]) -> Result<()> {
    let _ = fd;
    anyhow::bail!("coverage harness does not send ioctl responses")
}

#[cfg(coverage)]
fn send_stall(fd: i32, _req: libc::c_ulong) -> Result<()> {
    let _ = fd;
    anyhow::bail!("coverage harness does not send stall ioctls")
}

#[cfg(coverage)]
fn build_streaming_control(cfg: &UvcConfig, ctrl_len: usize) -> [u8; STREAM_CTRL_SIZE_MAX] {
    let mut buf = [0u8; STREAM_CTRL_SIZE_MAX];

    write_le16(&mut buf[0..2], 1);
    buf[2] = 1;
    buf[3] = uvc_frame_index_for_mode(cfg.width, cfg.height);
    write_le32(&mut buf[4..8], cfg.interval);
    write_le16(&mut buf[8..10], 0);
    write_le16(&mut buf[10..12], 0);
    write_le16(&mut buf[12..14], 0);
    write_le16(&mut buf[14..16], 0);
    write_le16(&mut buf[16..18], 0);
    write_le32(&mut buf[18..22], cfg.frame_size);
    write_le32(&mut buf[22..26], cfg.max_packet);
    if ctrl_len >= STREAM_CTRL_SIZE_15 {
        write_le32(&mut buf[26..30], 48_000_000);
        buf[30] = 0x03;
        buf[31] = 0x01;
        buf[32] = 0x01;
        buf[33] = 0x01;
    }

    buf
}

fn uvc_frame_index_for_mode(width: u32, height: u32) -> u8 {
    if !uvc_advertises_multiple_mjpeg_frames() {
        return 1;
    }
    match (width, height) {
        (1920, 1080) => 1,
        (1280, 720) => 2,
        _ => 2,
    }
}

fn uvc_frame_index_for_request(_requested: u8, cfg: &UvcConfig) -> u8 {
    uvc_frame_index_for_mode(cfg.width, cfg.height)
}

fn uvc_advertises_multiple_mjpeg_frames() -> bool {
    let base = std::path::Path::new(CONFIGFS_UVC_BASE).join("streaming/mjpeg/m");
    base.join("1080p").exists() && base.join("720p").exists()
}

fn uvc_frame_size_for_index(frame_index: u8, fallback: u32) -> u32 {
    match frame_index {
        1 | 2 => fallback,
        _ => fallback,
    }
}

#[cfg(coverage)]
fn parse_ctrl_request(data: [u8; 64]) -> UsbCtrlRequest {
    UsbCtrlRequest {
        b_request_type: data[0],
        b_request: data[1],
        w_value: u16::from_le_bytes([data[2], data[3]]),
        w_index: u16::from_le_bytes([data[4], data[5]]),
        w_length: u16::from_le_bytes([data[6], data[7]]),
    }
}

#[cfg(coverage)]
fn parse_request_data(data: [u8; 64]) -> UvcRequestData {
    let length = i32::from_le_bytes([data[0], data[1], data[2], data[3]]);
    let mut out = [0u8; UVC_DATA_SIZE];
    out.copy_from_slice(&data[4..64]);
    UvcRequestData { length, data: out }
}

#[cfg(coverage)]
fn stream_ctrl_len() -> usize {
    let value = env_u32("LESAVKA_UVC_CTRL_LEN", STREAM_CTRL_SIZE_15 as u32) as usize;
    match value {
        STREAM_CTRL_SIZE_11 | STREAM_CTRL_SIZE_15 => value,
        _ => STREAM_CTRL_SIZE_11,
    }
}

#[cfg(coverage)]
fn env_u32(name: &str, default: u32) -> u32 {
    env::var(name)
        .ok()
        .and_then(|v| v.parse::<u32>().ok())
        .unwrap_or(default)
}

#[cfg(coverage)]
fn env_u8(name: &str) -> Option<u8> {
    env::var(name).ok().and_then(|v| v.parse::<u8>().ok())
}

#[cfg(coverage)]
fn env_u32_opt(name: &str) -> Option<u32> {
    env::var(name).ok().and_then(|v| v.parse::<u32>().ok())
}

#[cfg(coverage)]
fn read_u32_file(path: &str) -> Option<u32> {
    std::fs::read_to_string(path)
        .ok()
        .and_then(|v| v.trim().parse::<u32>().ok())
}

#[cfg(coverage)]
fn read_u32_first(path: &str) -> Option<u32> {
    std::fs::read_to_string(path)
        .ok()
        .and_then(|v| v.split_whitespace().next()?.parse::<u32>().ok())
}

#[cfg(coverage)]
fn read_configfs_snapshot() -> Option<ConfigfsSnapshot> {
    None
}

#[cfg(coverage)]
fn log_configfs_snapshot(state: &mut UvcState, _label: &str) {
    let _ = state;
}

#[cfg(coverage)]
fn adjust_length(mut bytes: Vec<u8>, w_length: u16) -> Vec<u8> {
    let want = (w_length as usize).min(UVC_DATA_SIZE);
    bytes.resize(want, 0);
    bytes.truncate(want);
    bytes
}

#[cfg(coverage)]
fn write_le16(dst: &mut [u8], val: u16) {
    let bytes = val.to_le_bytes();
    dst[0] = bytes[0];
    dst[1] = bytes[1];
}

#[cfg(coverage)]
fn write_le32(dst: &mut [u8], val: u32) {
    let bytes = val.to_le_bytes();
    dst[0] = bytes[0];
    dst[1] = bytes[1];
    dst[2] = bytes[2];
    dst[3] = bytes[3];
}

#[cfg(coverage)]
fn read_le32(src: &[u8], offset: usize) -> u32 {
    u32::from_le_bytes([
        src[offset],
        src[offset + 1],
        src[offset + 2],
        src[offset + 3],
    ])
}
