use super::clamp_offset;

/// Resolve the active UVC mode from explicit mode or width/height/fps env.
///
/// Inputs: server UVC and camera environment variables. Output:
/// `WIDTHxHEIGHT@FPS` when enough information is available. Why: calibration
/// offsets are mode-specific, and early startup may see either configfs-style
/// mode strings or separate descriptor fields.
pub(super) fn current_uvc_mode() -> Option<String> {
    env_mode("UVC_MODE")
        .or_else(|| env_mode("LESAVKA_UVC_MODE"))
        .or_else(|| {
            let width = env_u32("LESAVKA_UVC_WIDTH")?;
            let height = env_u32("LESAVKA_UVC_HEIGHT")?;
            let fps = env_u32("LESAVKA_UVC_FPS")
                .or_else(|| {
                    env_u32("LESAVKA_UVC_INTERVAL")
                        .and_then(|interval| (interval > 0).then_some(10_000_000 / interval))
                })?
                .max(1);
            Some(format!("{width}x{height}@{fps}"))
        })
        .or_else(|| {
            let width = env_u32("LESAVKA_CAM_WIDTH")?;
            let height = env_u32("LESAVKA_CAM_HEIGHT")?;
            let fps = env_u32("LESAVKA_CAM_FPS")?.max(1);
            Some(format!("{width}x{height}@{fps}"))
        })
}

/// Parse one env var as a validated UVC mode string.
///
/// Inputs: environment variable name. Output: normalized mode string if it has
/// numeric width, height, and fps. Why: invalid operator input should not poison
/// persisted calibration state.
fn env_mode(name: &str) -> Option<String> {
    std::env::var(name).ok().and_then(|value| {
        let trimmed = value.trim();
        let valid = trimmed.split_once('@').and_then(|(size, fps)| {
            let (width, height) = size.split_once('x')?;
            width.parse::<u32>().ok()?;
            height.parse::<u32>().ok()?;
            fps.parse::<u32>().ok()?;
            Some(())
        });
        valid.map(|()| trimmed.to_string())
    })
}

/// Read one comma-separated mode offset map from the environment.
///
/// Inputs: variable name and target mode. Output: clamped offset in
/// microseconds. Why: mode maps let one service binary carry calibrated offsets
/// for several advertised UVC profiles.
pub(super) fn env_mode_offset_us(name: &str, mode: &str) -> Option<i64> {
    std::env::var(name)
        .ok()
        .and_then(|map| lookup_mode_offset_us(&map, mode))
}

/// Find one mode's offset in a comma-separated `MODE=US` map.
///
/// Inputs: raw map text and target mode. Output: clamped offset in
/// microseconds. Why: install scripts and hardware probes pass calibration maps
/// as env strings, so parsing stays shared and auditable.
pub(super) fn lookup_mode_offset_us(map: &str, mode: &str) -> Option<i64> {
    map.split(',').find_map(|entry| {
        let (key, value) = entry.trim().split_once('=')?;
        (key.trim() == mode)
            .then(|| value.trim().parse::<i64>().ok().map(clamp_offset))
            .flatten()
    })
}

/// Read one scalar microsecond offset from the environment.
///
/// Inputs: variable name. Output: clamped offset when parseable. Why: scalar
/// fallbacks keep old deployments configurable even after mode maps became the
/// preferred calibration representation.
pub(super) fn env_i64(name: &str) -> Option<i64> {
    std::env::var(name)
        .ok()
        .and_then(|value| value.trim().parse::<i64>().ok())
        .map(clamp_offset)
}

/// Read one unsigned integer from the environment.
///
/// Inputs: variable name. Output: parsed value if present and valid. Why:
/// UVC descriptors expose dimensions and intervals as numeric env fields during
/// runtime reconfiguration.
pub(super) fn env_u32(name: &str) -> Option<u32> {
    std::env::var(name)
        .ok()
        .and_then(|value| value.trim().parse::<u32>().ok())
}
