
/// Allocate a stream identifier for logging and correlation.
///
/// Inputs: none.
/// Outputs: a monotonically increasing identifier.
/// Why: the server multiplexes several long-lived streams, so log lines need a
/// cheap correlation id that is stable across retries.
#[must_use]
pub fn next_stream_id() -> u64 {
    STREAM_SEQ.fetch_add(1, Ordering::Relaxed)
}

/// Write one HID report with a short bounded retry loop.
///
/// Inputs: the shared gadget file handle plus the already-encoded report.
/// Outputs: `Ok(())` when the report reached the kernel buffer, or the final
/// write error after retrying transient backpressure.
/// Why: a brief retry window avoids dropping reports during momentary gadget
/// stalls without blocking the stream task indefinitely.
#[cfg(coverage)]
pub async fn write_hid_report(
    dev: &Arc<Mutex<Option<tokio::fs::File>>>,
    path: &str,
    data: &[u8],
) -> std::io::Result<()> {
    let mut file = dev.lock().await;
    if file.is_none() {
        *file = Some(open_hid_file(path).await?);
    }
    file.as_mut()
        .expect("HID endpoint is initialized after lazy open")
        .write_all(data)
        .await
}

#[cfg(not(coverage))]
pub async fn write_hid_report(
    dev: &Arc<Mutex<Option<tokio::fs::File>>>,
    path: &str,
    data: &[u8],
) -> std::io::Result<()> {
    let attempts = std::env::var("LESAVKA_HID_WRITE_RETRIES")
        .ok()
        .and_then(|value| value.parse::<u32>().ok())
        .unwrap_or(24)
        .max(1);
    let base_delay_ms = std::env::var("LESAVKA_HID_WRITE_RETRY_DELAY_MS")
        .ok()
        .and_then(|value| value.parse::<u64>().ok())
        .unwrap_or(2)
        .max(1);
    let mut last_error: Option<std::io::Error> = None;
    for attempt in 0..attempts {
        let mut file = dev.lock().await;
        if file.is_none() {
            match open_hid_file(path).await {
                Ok(opened) => {
                    info!("✅ {path} opened lazily");
                    *file = Some(opened);
                }
                Err(error) => return Err(error),
            }
        }
        let Some(file_handle) = file.as_mut() else {
            return Err(std::io::Error::new(
                std::io::ErrorKind::NotConnected,
                "HID endpoint is not open",
            ));
        };
        match file_handle.write_all(data).await {
            Ok(()) => return Ok(()),
            Err(error)
                if error.kind() == std::io::ErrorKind::WouldBlock
                    || error.raw_os_error() == Some(libc::EAGAIN) =>
            {
                last_error = Some(error);
            }
            Err(error) => {
                if should_recover_hid_error(error.raw_os_error()) {
                    *file = None;
                }
                return Err(error);
            }
        }
        drop(file);
        tokio::time::sleep(Duration::from_millis((attempt as u64 + 1) * base_delay_ms)).await;
    }

    Err(last_error.unwrap_or_else(|| std::io::Error::from_raw_os_error(libc::EAGAIN)))
}
