/// Keeps `sync_report_from_pairs` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew.
/// Inputs are the typed parameters; output is the return value or side effect.
fn sync_report_from_pairs(
    video_onsets_s: &[f64],
    audio_onsets_s: &[f64],
    coded_events: bool,
    activity_start_delta_ms: f64,
    raw_first_video_activity_s: f64,
    raw_first_audio_activity_s: f64,
    pairs: Vec<MatchedOnsetPair>,
) -> SyncAnalysisReport {
    let paired_events = pairs
        .iter()
        .enumerate()
        .map(|(event_id, pair)| SyncEventPair {
            event_id: pair.server_event_id.unwrap_or(event_id),
            server_event_id: pair.server_event_id,
            event_code: pair.event_code,
            video_time_s: pair.video_time_s,
            audio_time_s: pair.audio_time_s,
            skew_ms: pair.skew_ms,
            confidence: pair.confidence,
        })
        .collect::<Vec<_>>();
    let skews_ms = paired_events
        .iter()
        .map(|event| event.skew_ms)
        .collect::<Vec<_>>();
    let mut sorted_skews = skews_ms.clone();
    sorted_skews.sort_by(|left, right| left.total_cmp(right));
    let first_skew_ms = *skews_ms.first().expect("paired skew list is not empty");
    let last_skew_ms = *skews_ms.last().expect("paired skew list is not empty");
    let mean_skew_ms = skews_ms.iter().sum::<f64>() / skews_ms.len() as f64;
    let median_skew_ms = median(sorted_skews);
    let max_abs_skew_ms = skews_ms
        .iter()
        .copied()
        .map(f64::abs)
        .fold(0.0_f64, f64::max);

    SyncAnalysisReport {
        video_event_count: video_onsets_s.len(),
        audio_event_count: audio_onsets_s.len(),
        paired_event_count: skews_ms.len(),
        coded_events,
        activity_start_delta_ms,
        raw_first_video_activity_s,
        raw_first_audio_activity_s,
        first_skew_ms,
        last_skew_ms,
        mean_skew_ms,
        median_skew_ms,
        max_abs_skew_ms,
        drift_ms: last_skew_ms - first_skew_ms,
        skews_ms,
        video_onsets_s: video_onsets_s.to_vec(),
        audio_onsets_s: audio_onsets_s.to_vec(),
        paired_events,
    }
}

fn unique_event_id_for_code(event_width_codes: &[u32], event_code: u32) -> Option<usize> {
    let mut matches = event_width_codes
        .iter()
        .copied()
        .enumerate()
        .filter(|(_, code)| *code == event_code);
    let (index, _) = matches.next()?;
    matches.next().is_none().then_some(index)
}
