#[cfg(coverage)]
/// Returns the bounded UVC request-buffer count used by the helper.
///
/// Inputs: `LESAVKA_UVC_BUFFER_COUNT`. Outputs: a value clamped to the safe
/// range accepted by the helper. Why: coverage contracts need the same backlog
/// bound as the real UVC binary without opening a gadget device.
fn uvc_buffer_count() -> u32 {
    env_u32("LESAVKA_UVC_BUFFER_COUNT", DEFAULT_UVC_BUFFER_COUNT).clamp(1, 8)
}

#[cfg(coverage)]
/// Returns the idle frame-pump sleep interval used when no fresh frame is ready.
///
/// Inputs: `LESAVKA_UVC_IDLE_PUMP_MS`. Outputs: a duration in milliseconds.
/// Why: this value controls how quickly the UVC helper retries the freshness
/// spool when browsers are already streaming.
fn uvc_idle_pump_sleep() -> std::time::Duration {
    std::time::Duration::from_millis(env_u64(
        "LESAVKA_UVC_IDLE_PUMP_MS",
        DEFAULT_UVC_IDLE_PUMP_MS,
    ))
}

#[cfg(coverage)]
/// Returns the frame queue pacing period for the negotiated UVC frame rate.
///
/// Inputs: `LESAVKA_UVC_QUEUE_PACING` plus the active frame rate. Output:
/// `None` when pacing is disabled. Why: the RCT-facing host paces UVC
/// consumption, and user-space sleeps before QBUF can starve isochronous MJPEG.
fn uvc_queue_period(fps: u32) -> Option<std::time::Duration> {
    if !env_flag_enabled("LESAVKA_UVC_QUEUE_PACING", DEFAULT_UVC_QUEUE_PACING) {
        return None;
    }
    let fps = fps.max(1);
    Some(std::time::Duration::from_nanos(1_000_000_000 / u64::from(fps)))
}

#[cfg(coverage)]
/// Bound MJPEG frames before they enter the USB UVC helper.
///
/// Inputs: UVC mode plus optional `LESAVKA_UVC_FRAME_MAX_BYTES` or
/// `LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC`. Output: maximum accepted JPEG
/// byte length. Why: coverage tests should lock the artifact-prevention budget
/// that turns oversized UVC frames into freezes instead of grey half-frames.
fn uvc_frame_max_bytes(cfg: UvcConfig) -> usize {
    if let Ok(contract) = lesavka_server::uvc_contract::read_contract(
        &lesavka_server::uvc_contract::contract_path(),
    ) {
        return contract
            .enforced_frame_cap_bytes
            .min(cfg.frame_size)
            .min(MAX_MJPEG_FRAME_BYTES as u32) as usize;
    }
    let derived = derived_uvc_frame_max_bytes(cfg).min(cfg.frame_size as usize);
    env_u32_opt("LESAVKA_UVC_FRAME_MAX_BYTES")
        .filter(|limit| *limit > 0)
        .map(|limit| derived.min(limit as usize))
        .unwrap_or(derived)
}

#[cfg(coverage)]
fn derived_uvc_frame_max_bytes(cfg: UvcConfig) -> usize {
    derived_uvc_frame_max_bytes_for_transport(cfg.fps, cfg.max_packet, cfg.bulk)
}

#[cfg(coverage)]
fn derived_uvc_frame_max_bytes_for_transport(fps: u32, max_packet: u32, bulk: bool) -> usize {
    let budget_per_sec = effective_uvc_mjpeg_budget_bytes_per_sec(max_packet, bulk);
    lesavka_server::uvc_contract::frame_cap_from_budget(budget_per_sec, fps) as usize
}

#[cfg(coverage)]
/// Returns the effective MJPEG byte budget for the selected USB transport.
///
/// Inputs: negotiated max packet size and bulk/isochronous mode. Output: bytes
/// per second. Why: frame-size checks should mirror the transport budget.
fn effective_uvc_mjpeg_budget_bytes_per_sec(max_packet: u32, bulk: bool) -> u32 {
    let configured = env_u32(
        "LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC",
        DEFAULT_UVC_MJPEG_BUDGET_BYTES_PER_SEC,
    )
    .max(1);
    let pct = env_u32(
        "LESAVKA_UVC_ISOCHRONOUS_LIMIT_PCT",
        DEFAULT_UVC_ISOCHRONOUS_LIMIT_PCT,
    );
    lesavka_server::uvc_contract::effective_mjpeg_budget_bytes_per_sec(
        configured,
        max_packet,
        bulk,
        pct,
    )
}

#[cfg(coverage)]
fn uvc_frame_size_guard_enabled() -> bool {
    env_flag_enabled("LESAVKA_UVC_FRAME_SIZE_GUARD", true)
}

#[cfg(coverage)]
/// Parses an unsigned 64-bit environment value with a fallback.
///
/// Inputs: variable name and default value. Outputs: parsed value or default.
/// Why: UVC freshness settings use millisecond durations that should not be
/// truncated to 32-bit just because the coverage harness is lightweight.
fn env_u64(name: &str, default: u64) -> u64 {
    env::var(name)
        .ok()
        .and_then(|value| value.parse::<u64>().ok())
        .unwrap_or(default)
}
