use anyhow::{Result, bail};

use crate::sync_probe::signature::{
    MAX_EVENT_CODE, probe_audio_frequency_for_event_code, probe_color_for_event_code,
};

mod correlation;
#[cfg(test)]
mod tests;

pub(crate) use correlation::{correlate_coded_segments, correlate_segments};

pub(super) const DEFAULT_AUDIO_SAMPLE_RATE_HZ: u32 = 48_000;
// Real HDMI/capture paths can preserve pulse shape while compressing its absolute
// luma swing into a narrower band, so keep this guard modest and let the
// segment logic reject genuinely flat/noisy traces.
const MIN_VIDEO_CONTRAST: u8 = 4;
const MAX_VIDEO_ACTIVE_FRAME_FRACTION: f64 = 0.35;
const MAX_VIDEO_FLICKER_SEGMENT_FRAME_MULTIPLIER: f64 = 1.5;
const MIN_COLOR_PULSE_SATURATION: u8 = 36;
const MIN_COLOR_PULSE_VALUE: u8 = 70;
const MAX_COLOR_DISTANCE_SQUARED: u32 = 24_000;
const DOMINANT_COLOR_MARGIN: i16 = 28;
const MAX_COLOR_OBSERVED_DURATION_MULTIPLIER: f64 = 1.55;
const MAX_COLOR_OBSERVED_DURATION_SLACK_S: f64 = 0.08;
const MAX_AUDIO_PULSE_INTERNAL_GAP_S: f64 = 0.16;
const MIN_AUDIO_PROBE_PEAK: f64 = 25.0;
const AUDIO_ENVELOPE_THRESHOLD_FRACTION: f64 = 0.30;
const AUDIO_SAMPLE_THRESHOLD_FRACTION: f64 = 0.22;
const MIN_TONE_ENVELOPE_PEAK: f64 = 18.0;
const MIN_TONE_CONTRAST_FRACTION_OF_AMPLITUDE: f64 = 0.12;
const MIN_TONE_CODE_DOMINANCE_RATIO: f64 = 1.35;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct VideoColorFrame {
    pub r: u8,
    pub g: u8,
    pub b: u8,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct PulseSegment {
    pub start_s: f64,
    pub end_s: f64,
    pub duration_s: f64,
}

include!("onset_detection/video_segment_detection.rs");
include!("onset_detection/audio_tone_detection.rs");

pub(super) fn median_frame_step_seconds(timestamps_s: &[f64]) -> f64 {
    let diffs = timestamps_s
        .windows(2)
        .filter_map(|pair| {
            let diff = pair[1] - pair[0];
            (diff.is_finite() && diff > 0.0).then_some(diff)
        })
        .collect::<Vec<_>>();
    median(diffs)
}

/// Keeps `median` 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.
pub(super) fn median(mut values: Vec<f64>) -> f64 {
    if values.is_empty() {
        return 0.0;
    }
    values.sort_by(|left, right| left.total_cmp(right));
    let mid = values.len() / 2;
    if values.len().is_multiple_of(2) {
        (values[mid - 1] + values[mid]) / 2.0
    } else {
        values[mid]
    }
}
