use super::correlation::{
    candidate_index_offsets, collapse_segments_by_phase, correlate_onsets, estimate_phase,
    index_onsets_by_spacing, marker_index_offsets, marker_onsets, shortest_wrapped_difference,
};
use super::{
    PulseSegment, VideoColorFrame, correlate_coded_segments, correlate_segments,
    detect_audio_onsets, detect_audio_segments, detect_coded_audio_segments,
    detect_color_coded_video_segments, detect_video_onsets, detect_video_segments, median,
};
use crate::sync_probe::analyze::report::SyncAnalysisReport;
use std::collections::BTreeMap;

#[test]
/// Keeps `detect_video_onsets_finds_bright_transitions` 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 detect_video_onsets_finds_bright_transitions() {
    let timestamps = (0..60).map(|idx| idx as f64 / 10.0).collect::<Vec<_>>();
    let brightness = timestamps
        .iter()
        .enumerate()
        .map(|(idx, _)| {
            if idx == 0 || idx == 10 || idx == 20 {
                250
            } else {
                5
            }
        })
        .collect::<Vec<_>>();

    let onsets = detect_video_onsets(&timestamps, &brightness).expect("video onsets");
    assert_eq!(onsets, vec![0.0, 0.95, 1.95]);
}

#[test]
/// Keeps `detect_audio_onsets_finds_click_bursts` 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 detect_audio_onsets_finds_click_bursts() {
    let mut samples = vec![0i16; 48_000];
    for start in [0usize, 48_000 / 2] {
        for sample in samples.iter_mut().skip(start).take(300) {
            *sample = 18_000;
        }
    }

    let onsets = detect_audio_onsets(&samples, 48_000, 5).expect("audio onsets");
    assert_eq!(onsets.len(), 2);
    assert!((onsets[0] - 0.0).abs() < 0.01);
    assert!((onsets[1] - 0.5).abs() < 0.02);
}

#[test]
fn detect_video_segments_keeps_regular_and_marker_durations_distinct() {
    let timestamps = (0..30).map(|idx| idx as f64 / 30.0).collect::<Vec<_>>();
    let brightness = [
        0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0,
    ];
    let segments = detect_video_segments(&timestamps, &brightness).expect("video segments");
    assert_eq!(segments.len(), 2);
    assert!(segments[1].duration_s > segments[0].duration_s);
}

#[test]
/// Keeps `detect_video_segments_ignores_dim_positioning_prelude` 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 detect_video_segments_ignores_dim_positioning_prelude() {
    let timestamps = (0..90).map(|idx| idx as f64 / 30.0).collect::<Vec<_>>();
    let brightness = timestamps
        .iter()
        .enumerate()
        .map(|(idx, _)| {
            if (45..49).contains(&idx) || (75..79).contains(&idx) {
                245
            } else {
                8
            }
        })
        .collect::<Vec<_>>();

    let segments = detect_video_segments(&timestamps, &brightness).expect("video segments");
    assert_eq!(segments.len(), 2);
    assert!(
        segments[0].start_s > 1.4,
        "dim pre-start positioning screen must not become a fake onset"
    );
}

#[test]
/// Keeps `detect_color_coded_video_segments_ignores_generic_bright_changes` 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 detect_color_coded_video_segments_ignores_generic_bright_changes() {
    let timestamps = (0..80).map(|idx| idx as f64 / 20.0).collect::<Vec<_>>();
    let frames = timestamps
        .iter()
        .enumerate()
        .map(|(idx, _)| match idx {
            0..=4 => VideoColorFrame {
                r: 245,
                g: 245,
                b: 245,
            },
            20..=22 => VideoColorFrame {
                r: 255,
                g: 45,
                b: 45,
            },
            40..=42 => VideoColorFrame {
                r: 0,
                g: 230,
                b: 118,
            },
            60..=63 => VideoColorFrame {
                r: 137,
                g: 133,
                b: 101,
            },
            _ => VideoColorFrame { r: 0, g: 0, b: 0 },
        })
        .collect::<Vec<_>>();

    let segments =
        detect_color_coded_video_segments(&timestamps, &frames, &[1, 2], 0.12).expect("segments");
    assert_eq!(segments.len(), 2);
    assert!(segments[0].start_s > 0.9);
    assert!((segments[0].duration_s - 0.12).abs() < 0.001);
    assert!((segments[1].duration_s - 0.24).abs() < 0.001);
}

#[test]
/// Keeps `detect_color_coded_video_segments_accepts_camera_washed_palette` 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 detect_color_coded_video_segments_accepts_camera_washed_palette() {
    let timestamps = (0..90).map(|idx| idx as f64 / 30.0).collect::<Vec<_>>();
    let frames = timestamps
        .iter()
        .enumerate()
        .map(|(idx, _)| match idx {
            10..=12 => VideoColorFrame {
                r: 184,
                g: 72,
                b: 68,
            },
            30..=34 => VideoColorFrame {
                r: 76,
                g: 168,
                b: 111,
            },
            50..=55 => VideoColorFrame {
                r: 82,
                g: 125,
                b: 188,
            },
            70..=76 => VideoColorFrame {
                r: 190,
                g: 173,
                b: 60,
            },
            _ => VideoColorFrame {
                r: 22,
                g: 22,
                b: 24,
            },
        })
        .collect::<Vec<_>>();

    let segments = detect_color_coded_video_segments(&timestamps, &frames, &[1, 2, 3, 4], 0.12)
        .expect("segments");

    assert_eq!(segments.len(), 4);
    assert!((segments[0].duration_s - 0.12).abs() < 0.001);
    assert!((segments[1].duration_s - 0.24).abs() < 0.001);
    assert!((segments[2].duration_s - 0.36).abs() < 0.001);
    assert!((segments[3].duration_s - 0.48).abs() < 0.001);
}

#[test]
/// Keeps `color_coded_video_validation_and_fallback_branches_are_explicit` explicit because weak or malformed coded-video evidence should fail before it can steer sync calibration.
/// Inputs are direct color segment helper cases plus malformed detector requests; output proves validation, dominant-color fallback, final-segment close, and overlong rejection are stable.
fn color_coded_video_validation_and_fallback_branches_are_explicit() {
    assert!(detect_color_coded_video_segments(&[], &[], &[1], 0.12).is_err());
    assert!(
        detect_color_coded_video_segments(
            &[0.0],
            &[VideoColorFrame { r: 255, g: 0, b: 0 }],
            &[1],
            0.0,
        )
        .is_err()
    );
    assert!(
        detect_color_coded_video_segments(
            &[0.0],
            &[VideoColorFrame { r: 255, g: 0, b: 0 }],
            &[],
            0.12,
        )
        .is_err()
    );
    assert!(
        detect_color_coded_video_segments(
            &[0.0],
            &[VideoColorFrame { r: 255, g: 0, b: 0 }],
            &[17],
            0.12,
        )
        .is_err()
    );

    let timestamps = [0.0, 0.1, 0.2, 0.3];
    let trailing_frames = [
        VideoColorFrame { r: 0, g: 0, b: 0 },
        VideoColorFrame { r: 0, g: 0, b: 0 },
        VideoColorFrame { r: 255, g: 0, b: 0 },
        VideoColorFrame { r: 255, g: 0, b: 0 },
    ];
    let trailing = detect_color_coded_video_segments(&timestamps, &trailing_frames, &[1], 0.12)
        .expect("trailing segment");
    assert_eq!(trailing.len(), 1);
    assert!(trailing[0].end_s > trailing[0].start_s);

    let mut segments = Vec::new();
    super::push_color_segment(&mut segments, 0.0, 0.1, 0.12, &[], 0.033);
    assert!(segments.is_empty());
    super::push_color_segment(&mut segments, 0.0, 1.0, 0.12, &[1], 0.033);
    assert!(segments.is_empty());
    super::push_color_segment(&mut segments, 0.0, 0.02, 0.12, &[2, 1, 2, 1], 0.033);
    assert_eq!(segments[0].duration_s, 0.12);

    assert_eq!(
        super::dominant_color_event_code(VideoColorFrame {
            r: 180,
            g: 170,
            b: 80,
        }),
        Some(4)
    );
    assert_eq!(
        super::dominant_color_event_code(VideoColorFrame {
            r: 180,
            g: 70,
            b: 60
        }),
        Some(1)
    );
    assert_eq!(
        super::dominant_color_event_code(VideoColorFrame {
            r: 60,
            g: 180,
            b: 70
        }),
        Some(2)
    );
    assert_eq!(
        super::dominant_color_event_code(VideoColorFrame {
            r: 60,
            g: 70,
            b: 180
        }),
        Some(3)
    );
    assert_eq!(
        super::dominant_color_event_code(VideoColorFrame {
            r: 100,
            g: 100,
            b: 100,
        }),
        None
    );
}

#[test]
/// Keeps `detect_audio_segments_keeps_regular_and_marker_durations_distinct` 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 detect_audio_segments_keeps_regular_and_marker_durations_distinct() {
    let mut samples = vec![0i16; 48_000];
    for sample in samples.iter_mut().take(3_000) {
        *sample = 18_000;
    }
    for sample in samples.iter_mut().skip(24_000).take(6_000) {
        *sample = 18_000;
    }
    let segments = detect_audio_segments(&samples, 48_000, 5).expect("audio segments");
    assert_eq!(segments.len(), 2);
    assert!(segments[1].duration_s > segments[0].duration_s);
}

#[test]
/// Keeps `detect_audio_segments_merges_short_internal_dropouts_inside_one_pulse` 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 detect_audio_segments_merges_short_internal_dropouts_inside_one_pulse() {
    let mut samples = vec![0i16; 48_000];
    for sample in samples.iter_mut().skip(4_800).take(5_760) {
        *sample = 18_000;
    }
    for sample in samples.iter_mut().skip(7_200).take(1_920) {
        *sample = 0;
    }

    let segments = detect_audio_segments(&samples, 48_000, 5).expect("audio segments");
    assert_eq!(segments.len(), 1);
    assert!(segments[0].duration_s > 0.11);
}

#[test]
/// Keeps `detect_audio_segments_accepts_faint_probe_tones` 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 detect_audio_segments_accepts_faint_probe_tones() {
    let mut samples = vec![0i16; 48_000];
    for start in [4_800usize, 24_000] {
        for sample in samples.iter_mut().skip(start).take(5_760) {
            *sample = 40;
        }
    }

    let segments = detect_audio_segments(&samples, 48_000, 5).expect("faint audio segments");
    assert_eq!(segments.len(), 2);
    assert!((segments[0].start_s - 0.1).abs() < 0.01);
    assert!((segments[1].start_s - 0.5).abs() < 0.01);
}

#[test]
fn detect_audio_segments_locks_onto_probe_tone_over_background_hum() {
    let mut samples = vec![0i16; 96_000];
    add_sine(&mut samples, 48_000, 0.0, 2.0, 120.0, 7_000.0);
    add_sine(&mut samples, 48_000, 0.25, 0.12, 880.0, 1_800.0);
    add_sine(&mut samples, 48_000, 1.25, 0.12, 880.0, 1_800.0);

    let segments = detect_audio_segments(&samples, 48_000, 10).expect("tone segments");
    assert_eq!(segments.len(), 2);
    assert!((segments[0].start_s - 0.25).abs() < 0.03);
    assert!((segments[1].start_s - 1.25).abs() < 0.03);
}

#[test]
/// Keeps `detect_coded_audio_segments_uses_probe_tone_frequency_for_event_code` 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 detect_coded_audio_segments_uses_probe_tone_frequency_for_event_code() {
    let mut samples = vec![0i16; 96_000];
    add_sine(&mut samples, 48_000, 0.0, 2.0, 120.0, 7_000.0);
    add_sine(&mut samples, 48_000, 0.25, 0.07, 620.0, 2_000.0);
    add_sine(&mut samples, 48_000, 1.25, 0.07, 1120.0, 2_000.0);

    let segments =
        detect_coded_audio_segments(&samples, 48_000, 10, &[1, 2, 3, 4], 0.12).expect("segments");

    assert_eq!(segments.len(), 2);
    assert!((segments[0].start_s - 0.25).abs() < 0.03);
    assert!((segments[0].duration_s - 0.12).abs() < 0.001);
    assert!((segments[1].start_s - 1.25).abs() < 0.03);
    assert!((segments[1].duration_s - 0.48).abs() < 0.001);
}

#[test]
/// Keeps `detect_audio_segments_merges_longer_probe_dropouts_inside_one_pulse` 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 detect_audio_segments_merges_longer_probe_dropouts_inside_one_pulse() {
    let mut samples = vec![0i16; 48_000];
    for sample in samples.iter_mut().skip(4_800).take(12_000) {
        *sample = 1_200;
    }
    for sample in samples.iter_mut().skip(7_200).take(5_760) {
        *sample = 0;
    }

    let segments = detect_audio_segments(&samples, 48_000, 5).expect("dropout audio segment");
    assert_eq!(segments.len(), 1);
    assert!(segments[0].duration_s > 0.24);
}

/// Keeps `add_sine` 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 add_sine(
    samples: &mut [i16],
    sample_rate_hz: u32,
    start_s: f64,
    duration_s: f64,
    frequency_hz: f64,
    amplitude: f64,
) {
    let start = (start_s * f64::from(sample_rate_hz)).round() as usize;
    let len = (duration_s * f64::from(sample_rate_hz)).round() as usize;
    for (offset, sample) in samples.iter_mut().skip(start).take(len).enumerate() {
        let t = offset as f64 / f64::from(sample_rate_hz);
        let value =
            f64::from(*sample) + amplitude * (2.0 * std::f64::consts::PI * frequency_hz * t).sin();
        *sample = value
            .round()
            .clamp(f64::from(i16::MIN), f64::from(i16::MAX)) as i16;
    }
}

#[test]
fn detect_video_segments_closes_a_pulse_that_stays_active_until_the_last_frame() {
    let timestamps = [0.0, 0.1, 0.2, 0.3];
    let brightness = [0, 0, 255, 255];
    let segments = detect_video_segments(&timestamps, &brightness).expect("trailing video segment");
    assert_eq!(segments.len(), 1);
    assert!(segments[0].end_s > segments[0].start_s);
    assert!(segments[0].end_s >= 0.3);
}

#[test]
/// Keeps `detect_audio_segments_closes_a_click_that_stays_active_until_the_capture_ends` 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 detect_audio_segments_closes_a_click_that_stays_active_until_the_capture_ends() {
    let mut samples = vec![0i16; 4_800];
    let midpoint = samples.len() / 2;
    for sample in samples.iter_mut().skip(midpoint) {
        *sample = 18_000;
    }
    let segments = detect_audio_segments(&samples, 48_000, 5).expect("trailing audio segment");
    assert_eq!(segments.len(), 1);
    assert!(segments[0].end_s > segments[0].start_s);
}

#[test]
fn correlate_onsets_reports_skew_and_drift() {
    let report = correlate_onsets(&[0.0, 1.0, 2.0, 3.0], &[0.05, 1.04, 2.03, 3.02], 1.0, 0.2)
        .expect("correlated report");

    assert_sync_report_shape(&report, 4);
    assert!((report.first_skew_ms - 50.0).abs() < 0.001);
    assert!((report.last_skew_ms - 20.0).abs() < 0.001);
    assert!((report.drift_ms + 30.0).abs() < 0.001);
    assert!(report.max_abs_skew_ms >= 50.0);
}

#[test]
fn correlate_onsets_single_pulse_uses_phase_fallback() {
    let report = correlate_onsets(&[0.95], &[0.05], 1.0, 0.2).expect("single-pulse fallback");
    assert_eq!(report.paired_event_count, 1);
    assert!((report.first_skew_ms - 100.0).abs() < 0.001);
}

#[test]
/// Keeps `correlate_onsets_ignores_leading_video_cadence_before_audio_becomes_active` 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 correlate_onsets_ignores_leading_video_cadence_before_audio_becomes_active() {
    let report = correlate_onsets(
        &[0.15, 1.15, 2.15, 3.15, 10.15, 11.15, 12.15],
        &[10.20, 11.20, 12.20],
        1.0,
        0.2,
    )
    .expect("correlated report");

    assert_eq!(report.video_event_count, 3);
    assert_eq!(report.audio_event_count, 3);
    assert_eq!(report.paired_event_count, 3);
    assert!((report.median_skew_ms - 50.0).abs() < 0.001);
    assert!(report.max_abs_skew_ms < 60.0);
}

#[test]
/// Keeps `correlate_onsets_prefers_the_phase_consistent_basin_over_a_larger_alias_cluster` 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 correlate_onsets_prefers_the_phase_consistent_basin_over_a_larger_alias_cluster() {
    let report = correlate_onsets(
        &[
            9.554, 10.5035, 11.487, 12.4365, 13.691, 14.505, 15.624, 16.438, 17.3535, 18.4385,
            19.5575, 20.4395, 21.4905, 22.508, 23.4235, 24.5425, 25.5605, 26.4425, 27.4935,
            28.5105,
        ],
        &[
            10.011041666666667,
            11.011041666666667,
            12.011041666666667,
            13.011041666666667,
            14.011041666666667,
            15.011041666666667,
            16.011041666666667,
            17.011041666666667,
            18.011041666666667,
            19.011041666666667,
            20.011041666666667,
            21.011041666666667,
            22.011041666666667,
            23.011041666666667,
            24.011041666666667,
            25.011041666666667,
            26.011041666666667,
            27.011041666666667,
            28.011041666666667,
            29.011041666666667,
        ],
        1.0,
        0.5,
    )
    .expect("phase-consistent basin");

    assert!(report.first_skew_ms > 400.0);
    assert!(report.median_skew_ms > 350.0);
    assert!(report.paired_event_count >= 6);
}

#[test]
fn detect_video_onsets_rejects_empty_low_contrast_and_missing_edges() {
    assert!(detect_video_onsets(&[], &[]).is_err());
    assert!(detect_video_onsets(&[0.0, 0.1], &[10, 12]).is_err());
    assert!(detect_video_onsets(&[0.0, 0.1, 0.2], &[255, 255, 255]).is_err());
}

#[test]
/// Keeps `detect_video_onsets_rejects_frame_to_frame_flicker` 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 detect_video_onsets_rejects_frame_to_frame_flicker() {
    let timestamps = (0..120)
        .map(|index| index as f64 / 30.0)
        .collect::<Vec<_>>();
    let brightness = (0..120)
        .map(|index| if index % 2 == 0 { 0 } else { 6 })
        .collect::<Vec<_>>();

    let err =
        detect_video_onsets(&timestamps, &brightness).expect_err("flicker should be rejected");
    assert!(
        err.to_string().contains("frame-to-frame flicker"),
        "unexpected error: {err}"
    );
}

#[test]
fn detect_audio_onsets_rejects_empty_invalid_and_too_quiet_inputs() {
    assert!(detect_audio_onsets(&[], 48_000, 5).is_err());
    assert!(detect_audio_onsets(&[1, 2, 3], 0, 5).is_err());
    assert!(detect_audio_onsets(&[1, 2, 3], 48_000, 0).is_err());
    assert!(detect_audio_onsets(&vec![1i16; 4_800], 48_000, 5).is_err());
}

#[test]
fn correlate_onsets_rejects_empty_inputs_invalid_gap_and_unpairable_events() {
    assert!(correlate_onsets(&[], &[0.0], 1.0, 0.2).is_err());
    assert!(correlate_onsets(&[0.0], &[], 1.0, 0.2).is_err());
    assert!(correlate_onsets(&[0.0], &[0.0], 1.0, 0.0).is_err());
    assert!(correlate_onsets(&[0.0, 1.0], &[2.0, 3.0], 1.0, 0.1).is_err());
    assert!(correlate_onsets(&[0.0], &[0.0], 0.0, 0.1).is_err());
}

#[test]
/// Keeps `correlate_segments_validate_inputs_and_support_single_pulse_fallback` 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 correlate_segments_validate_inputs_and_support_single_pulse_fallback() {
    let video = [PulseSegment {
        start_s: 0.95,
        end_s: 1.05,
        duration_s: 0.1,
    }];
    let audio = [PulseSegment {
        start_s: 0.05,
        end_s: 0.15,
        duration_s: 0.1,
    }];
    let report =
        correlate_segments(&video, &audio, 1.0, 0.1, 3, 0.2).expect("single segment fallback");
    assert_eq!(report.paired_event_count, 1);
    assert!((report.first_skew_ms - 100.0).abs() < 0.001);

    assert!(correlate_segments(&[], &audio, 1.0, 0.1, 3, 0.2).is_err());
    assert!(correlate_segments(&video, &[], 1.0, 0.1, 3, 0.2).is_err());
    assert!(correlate_segments(&video, &audio, 0.0, 0.1, 3, 0.2).is_err());
    assert!(correlate_segments(&video, &audio, 1.0, 0.0, 3, 0.2).is_err());
    assert!(correlate_segments(&video, &audio, 1.0, 0.1, 0, 0.2).is_err());
    assert!(correlate_segments(&video, &audio, 1.0, 0.1, 3, 0.0).is_err());
    assert!(correlate_segments(&video, &audio, 1.0, 0.1, 3, 0.05).is_err());
}

#[test]
fn correlate_coded_segments_rejects_invalid_coded_probe_configuration() {
    let segment = PulseSegment {
        start_s: 1.0,
        end_s: 1.12,
        duration_s: 0.12,
    };
    let video = [segment];
    let audio = [segment];

    assert!(correlate_coded_segments(&video, &audio, 1.0, 0.12, &[], 0.2).is_err());
    assert!(correlate_coded_segments(&video, &audio, 1.0, 0.12, &[0], 0.2).is_err());
    assert!(correlate_coded_segments(&video, &audio, 0.0, 0.12, &[1], 0.2).is_err());
    assert!(correlate_coded_segments(&video, &audio, 1.0, 0.0, &[1], 0.2).is_err());
    assert!(correlate_coded_segments(&video, &audio, 1.0, 0.12, &[1], 0.0).is_err());
}

#[test]
/// Keeps `correlate_segments_preserves_whole_period_delay_evidence` 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 correlate_segments_preserves_whole_period_delay_evidence() {
    fn segment(start_s: f64, duration_s: f64) -> PulseSegment {
        PulseSegment {
            start_s,
            end_s: start_s + duration_s,
            duration_s,
        }
    }

    let video = (0..30)
        .map(|tick| segment(f64::from(tick), if tick % 5 == 0 { 0.24 } else { 0.12 }))
        .collect::<Vec<_>>();
    let audio = (0..30)
        .map(|tick| {
            segment(
                f64::from(tick) + 20.0,
                if tick % 5 == 0 { 0.24 } else { 0.12 },
            )
        })
        .collect::<Vec<_>>();

    let report = correlate_segments(&video, &audio, 1.0, 0.12, 5, 0.5).expect("correlated report");

    assert_eq!(report.activity_start_delta_ms, 20_000.0);
    assert!(
        report.max_abs_skew_ms < 1.0,
        "cadence aliasing still creates apparently good pairs"
    );
    let verdict = report.verdict();
    assert_eq!(verdict.status, "catastrophic_failure");
    assert!(
        verdict.reason.contains("activity starts"),
        "unexpected verdict reason: {}",
        verdict.reason
    );
}

#[test]
/// Keeps `correlate_coded_segments_preserves_raw_activity_before_cadence_cleanup` 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 correlate_coded_segments_preserves_raw_activity_before_cadence_cleanup() {
    fn segment(start_s: f64, code: u32) -> PulseSegment {
        let duration_s = 0.12 * f64::from(code);
        PulseSegment {
            start_s,
            end_s: start_s + duration_s,
            duration_s,
        }
    }

    let codes = [1, 2, 1, 3, 2, 4];
    let video = codes
        .iter()
        .enumerate()
        .map(|(tick, code)| segment(8.3 + tick as f64, *code))
        .collect::<Vec<_>>();
    let audio = codes
        .iter()
        .enumerate()
        .map(|(tick, code)| segment(6.7 + tick as f64, *code))
        .collect::<Vec<_>>();

    let report =
        correlate_coded_segments(&video, &audio, 1.0, 0.12, &codes, 0.5).expect("coded report");
    assert!(report.activity_start_delta_ms < -1_000.0);
    assert_eq!(report.verdict().status, "catastrophic_failure");
}

#[test]
/// Keeps `correlate_coded_segments_reports_large_but_decodable_skew` 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 correlate_coded_segments_reports_large_but_decodable_skew() {
    fn segment(start_s: f64, code: u32) -> PulseSegment {
        let duration_s = 0.12 * f64::from(code);
        PulseSegment {
            start_s,
            end_s: start_s + duration_s,
            duration_s,
        }
    }

    let codes = [1, 2, 1, 3, 2, 4, 1, 1, 3, 1, 4, 2];
    let video = codes
        .iter()
        .enumerate()
        .map(|(tick, code)| segment(tick as f64, *code))
        .collect::<Vec<_>>();
    let audio = codes
        .iter()
        .enumerate()
        .map(|(tick, code)| segment(tick as f64 - 0.75, *code))
        .collect::<Vec<_>>();

    let report =
        correlate_coded_segments(&video, &audio, 1.0, 0.12, &codes, 0.5).expect("coded report");

    assert_eq!(report.paired_event_count, codes.len());
    assert!((report.activity_start_delta_ms + 750.0).abs() < 1.0);
    assert!(report.max_abs_skew_ms > 700.0);
    assert_eq!(report.verdict().status, "gross_failure");
}

#[test]
/// Keeps `correlate_coded_segments_matches_preserved_event_width_codes` 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 correlate_coded_segments_matches_preserved_event_width_codes() {
    fn segment(start_s: f64, code: u32) -> PulseSegment {
        let duration_s = 0.12 * f64::from(code);
        PulseSegment {
            start_s,
            end_s: start_s + duration_s,
            duration_s,
        }
    }

    let codes = [1, 2, 1, 3, 2, 4, 1, 1];
    let video = codes
        .iter()
        .enumerate()
        .map(|(tick, code)| segment(tick as f64, *code))
        .collect::<Vec<_>>();
    let audio = codes
        .iter()
        .enumerate()
        .map(|(tick, code)| segment(tick as f64 + 0.045, *code))
        .collect::<Vec<_>>();

    let report =
        correlate_coded_segments(&video, &audio, 1.0, 0.12, &codes, 0.2).expect("coded report");

    assert_eq!(report.paired_event_count, codes.len());
    assert!((report.median_skew_ms - 45.0).abs() < 1.0);
    assert!(report.max_abs_skew_ms < 50.0);
}

#[test]
/// Keeps `correlate_coded_segments_uses_time_fallback_when_extra_audio_breaks_indexing` explicit because coded pulse identity is the strongest sync proof when transport adds one-off detections that would otherwise confuse cadence indexing.
/// Inputs are coded video/audio pulse segments with one extra audio detection; output proves the analyzer keeps the three valid flash/tone pairs.
fn correlate_coded_segments_uses_time_fallback_when_extra_audio_breaks_indexing() {
    fn segment(start_s: f64, code: u32) -> PulseSegment {
        let duration_s = 0.12 * f64::from(code);
        PulseSegment {
            start_s,
            end_s: start_s + duration_s,
            duration_s,
        }
    }

    let codes = [1, 2, 3, 4];
    let video = [segment(0.0, 1), segment(1.0, 2), segment(2.0, 3)];
    let audio = [
        segment(0.04, 1),
        segment(0.50, 4),
        segment(1.04, 2),
        segment(2.04, 3),
    ];

    let report =
        correlate_coded_segments(&video, &audio, 1.0, 0.12, &codes, 0.2).expect("coded report");

    assert_eq!(report.paired_event_count, 3);
    assert_eq!(
        report
            .paired_events
            .iter()
            .map(|event| event.event_code)
            .collect::<Vec<_>>(),
        vec![Some(1), Some(2), Some(3)]
    );
    assert!((report.median_skew_ms - 40.0).abs() < 1.0);
    assert!(report.max_abs_skew_ms < 50.0);
}

#[test]
/// Keeps `correlate_coded_segments_recovers_when_extra_video_detections_win_phase_collapse` 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 correlate_coded_segments_recovers_when_extra_video_detections_win_phase_collapse() {
    fn segment(start_s: f64, code: u32) -> PulseSegment {
        let duration_s = 0.12 * f64::from(code);
        PulseSegment {
            start_s,
            end_s: start_s + duration_s,
            duration_s,
        }
    }

    let codes = [1, 2, 1, 3, 2, 4, 1, 1];
    let mut video = Vec::new();
    for (tick, code) in codes.iter().copied().enumerate() {
        video.push(segment(tick as f64, code));
        video.push(segment(tick as f64 + 0.45, 4));
    }
    let audio = codes
        .iter()
        .enumerate()
        .map(|(tick, code)| segment(tick as f64 + 0.045, *code))
        .collect::<Vec<_>>();

    let report =
        correlate_coded_segments(&video, &audio, 1.0, 0.12, &codes, 0.2).expect("coded report");

    assert_eq!(report.paired_event_count, codes.len());
    assert!((report.median_skew_ms - 45.0).abs() < 1.0);
    assert!(report.max_abs_skew_ms < 50.0);
}

#[test]
/// Keeps `correlate_coded_segments_rejects_nearby_wrong_width_codes` 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 correlate_coded_segments_rejects_nearby_wrong_width_codes() {
    fn segment(start_s: f64, code: u32) -> PulseSegment {
        let duration_s = 0.12 * f64::from(code);
        PulseSegment {
            start_s,
            end_s: start_s + duration_s,
            duration_s,
        }
    }

    let codes = [1, 2, 1, 3, 2, 4];
    let video = codes
        .iter()
        .enumerate()
        .map(|(tick, code)| segment(tick as f64, *code))
        .collect::<Vec<_>>();
    let shifted_audio_codes = [3, 1, 4, 1, 2, 1];
    let audio = shifted_audio_codes
        .iter()
        .enumerate()
        .map(|(tick, code)| segment(tick as f64 + 0.02, *code))
        .collect::<Vec<_>>();

    assert!(correlate_coded_segments(&video, &audio, 1.0, 0.12, &codes, 0.2).is_err());
}

#[test]
/// Keeps `correlate_coded_segments_refuses_cadence_fallback_when_windows_do_not_overlap` 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 correlate_coded_segments_refuses_cadence_fallback_when_windows_do_not_overlap() {
    fn segment(start_s: f64, code: u32) -> PulseSegment {
        let duration_s = 0.12 * f64::from(code);
        PulseSegment {
            start_s,
            end_s: start_s + duration_s,
            duration_s,
        }
    }

    let codes = [1, 2, 3, 4, 5, 6];
    let video = codes
        .iter()
        .enumerate()
        .map(|(tick, code)| segment(100.0 + tick as f64, *code))
        .collect::<Vec<_>>();
    let audio = codes
        .iter()
        .enumerate()
        .map(|(tick, code)| segment(tick as f64, *code))
        .collect::<Vec<_>>();

    let error = correlate_coded_segments(&video, &audio, 1.0, 0.12, &codes, 0.5)
        .expect_err("coded proof should not fall back to cadence-only pairing");

    assert!(
        error.to_string().contains("refusing cadence-only fallback"),
        "unexpected error: {error}"
    );
}

fn assert_sync_report_shape(report: &SyncAnalysisReport, paired_events: usize) {
    assert_eq!(report.video_event_count, paired_events);
    assert_eq!(report.audio_event_count, paired_events);
    assert_eq!(report.paired_event_count, paired_events);
    assert_eq!(report.skews_ms.len(), paired_events);
    assert_eq!(report.video_onsets_s.len(), paired_events);
    assert_eq!(report.audio_onsets_s.len(), paired_events);
}

mod correlation_helpers;
