/// Detect whether an Annex-B HEVC access unit contains an intra recovery point.
///
/// Inputs: one encoded HEVC packet in byte-stream form. Output: `true` when the
/// packet carries an IRAP NAL unit. Why: the freshness-first uplink may drop
/// predictive frames, and the server decoder should only resume from a frame
/// that can rebuild a clean picture.
fn contains_hevc_irap(data: &[u8]) -> bool {
    let mut offset = 0;
    while let Some((start, prefix_len)) = find_annex_b_start_code(data, offset) {
        let nal_index = start + prefix_len;
        if nal_index + 1 < data.len() {
            let nal_type = (data[nal_index] >> 1) & 0x3f;
            if (16..=23).contains(&nal_type) {
                return true;
            }
        }
        offset = nal_index.saturating_add(2);
    }
    false
}

/// Locate the next Annex-B start code in an encoded video packet.
///
/// Inputs: encoded bytes plus the search offset. Output: the start index and
/// prefix length. Why: both three-byte and four-byte start codes appear in
/// HEVC streams, and keyframe recovery must handle both without allocating.
fn find_annex_b_start_code(data: &[u8], from: usize) -> Option<(usize, usize)> {
    let mut index = from;
    while index + 3 < data.len() {
        if data[index] == 0 && data[index + 1] == 0 {
            if data[index + 2] == 1 {
                return Some((index, 3));
            }
            if index + 4 < data.len() && data[index + 2] == 0 && data[index + 3] == 1 {
                return Some((index, 4));
            }
        }
        index += 1;
    }
    None
}

/// Decide whether a bundled HEVC packet is safe after a freshness drop.
///
/// Inputs: the recovery state and the next outbound bundle. Output: `true`
/// when the bundle should be held back. Why: sending a non-keyframe immediately
/// after dropping older HEVC packets can make the server decode from missing
/// references, which shows up as tearing or grey corrupted frames.
fn should_hold_hevc_bundle_for_keyframe_recovery(
    waiting_for_keyframe: bool,
    bundle: &UpstreamMediaBundle,
) -> bool {
    waiting_for_keyframe
        && bundle
            .video
            .as_ref()
            .is_some_and(|video| !contains_hevc_irap(&video.data))
}

/// Report whether a bundle carries the HEVC recovery frame we were waiting for.
///
/// Inputs: an outbound media bundle. Output: true when its video packet can
/// restart HEVC prediction. Why: the freshness policy should clear recovery
/// mode as soon as the next clean picture reaches the server.
fn bundle_has_hevc_recovery_keyframe(bundle: &UpstreamMediaBundle) -> bool {
    bundle
        .video
        .as_ref()
        .is_some_and(|video| contains_hevc_irap(&video.data))
}

/// Mark HEVC as needing recovery after capture produced a gap.
///
/// Inputs: whether HEVC recovery applies and the mutable wait flag. Output:
/// updated state. Why: a `None` pull from the camera can mean the capture layer
/// discarded a stale encoded packet before bundling; after that, sending the
/// next predictive frame risks black or block-corrupted browser frames.
fn note_hevc_capture_gap(recover_hevc_after_drops: bool, waiting_for_keyframe: &mut bool) {
    if recover_hevc_after_drops {
        *waiting_for_keyframe = true;
    }
}

/// Resolve whether the active upstream camera codec needs HEVC recovery.
///
/// Inputs: the negotiated camera config plus optional env fallback. Output:
/// `true` only for HEVC/H.265. Why: MJPEG frames are independent, so the extra
/// keyframe wait should only apply to predictive video transport.
fn upstream_camera_uses_hevc(camera_cfg: Option<crate::input::camera::CameraConfig>) -> bool {
    if camera_cfg.is_some_and(|cfg| matches!(cfg.codec, crate::input::camera::CameraCodec::Hevc)) {
        return true;
    }
    std::env::var("LESAVKA_CAM_CODEC")
        .ok()
        .map(|value| value.trim().to_ascii_lowercase())
        .is_some_and(|value| matches!(value.as_str(), "hevc" | "h265" | "h.265"))
}
