//! Shared-clock synthetic A/V source for the upstream sync probe.

#[cfg(any(not(coverage), test))]
use anyhow::{Context, Result, bail};
#[cfg(any(not(coverage), test))]
use gst::prelude::*;
#[cfg(any(not(coverage), test))]
use gstreamer as gst;
#[cfg(any(not(coverage), test))]
use gstreamer_app as gst_app;
#[cfg(any(not(coverage), test))]
use lesavka_common::lesavka::{AudioPacket, VideoPacket};
#[cfg(any(not(coverage), test))]
use std::sync::{
    Arc,
    atomic::{AtomicBool, Ordering},
};
#[cfg(any(not(coverage), test))]
use std::thread::{self, JoinHandle};
#[cfg(any(not(coverage), test))]
use std::time::{Duration, Instant};
#[cfg(any(not(coverage), test))]
use std::{f64::consts::TAU, mem::size_of};

#[cfg(any(not(coverage), test))]
use crate::input::camera::{CameraCodec, CameraConfig};
#[cfg(any(not(coverage), test))]
use crate::sync_probe::schedule::PulseSchedule;
#[cfg(any(not(coverage), test))]
use crate::uplink_fresh_queue::{FreshPacketQueue, FreshQueueConfig};

#[cfg(coverage)]
mod coverage_stub;
#[cfg(not(coverage))]
mod runtime;
#[cfg(test)]
mod tests;

#[cfg(coverage)]
pub use coverage_stub::SyncProbeCapture;
#[cfg(not(coverage))]
pub use runtime::SyncProbeCapture;

#[cfg(any(not(coverage), test))]
const PROBE_VIDEO_QUEUE: FreshQueueConfig = FreshQueueConfig {
    capacity: 8,
    max_age: Duration::from_millis(350),
};

#[cfg(any(not(coverage), test))]
const PROBE_AUDIO_QUEUE: FreshQueueConfig = FreshQueueConfig {
    capacity: 32,
    max_age: Duration::from_millis(400),
};

#[cfg(any(not(coverage), test))]
const AUDIO_SAMPLE_RATE: i32 = 48_000;
#[cfg(any(not(coverage), test))]
const AUDIO_CHANNELS: usize = 2;
#[cfg(any(not(coverage), test))]
const AUDIO_CHUNK_MS: u64 = 10;
#[cfg(any(not(coverage), test))]
const AUDIO_PULSE_FREQUENCY_HZ: f64 = 1_800.0;
#[cfg(any(not(coverage), test))]
const AUDIO_PULSE_AMPLITUDE: f64 = 24_000.0;

#[cfg(any(not(coverage), test))]
fn build_dark_probe_frame(width: usize, height: usize) -> Vec<u8> {
    vec![16u8; width.saturating_mul(height).saturating_mul(3)]
}

#[cfg(any(not(coverage), test))]
fn build_regular_probe_frame(width: usize, height: usize) -> Vec<u8> {
    let mut frame = build_dark_probe_frame(width, height);
    let x0 = width / 4;
    let x1 = width.saturating_sub(x0);
    let y0 = height / 4;
    let y1 = height.saturating_sub(y0);
    fill_rect(&mut frame, width, x0, y0, x1, y1, 255);
    frame
}

#[cfg(any(not(coverage), test))]
fn build_marker_probe_frame(width: usize, height: usize) -> Vec<u8> {
    let mut frame = build_dark_probe_frame(width, height);
    let x0 = width / 5;
    let x1 = width.saturating_sub(x0);
    let y0 = height / 5;
    let y1 = height.saturating_sub(y0);
    fill_rect(&mut frame, width, x0, y0, x1, y1, 255);

    let cross_half_w = (width / 48).max(6);
    let cross_half_h = (height / 48).max(6);
    let cx = width / 2;
    let cy = height / 2;
    fill_rect(
        &mut frame,
        width,
        cx.saturating_sub(cross_half_w),
        y0,
        (cx + cross_half_w).min(width),
        y1,
        255,
    );
    fill_rect(
        &mut frame,
        width,
        x0,
        cy.saturating_sub(cross_half_h),
        x1,
        (cy + cross_half_h).min(height),
        255,
    );
    frame
}

#[cfg(any(not(coverage), test))]
fn fill_rect(
    frame: &mut [u8],
    width: usize,
    x0: usize,
    y0: usize,
    x1: usize,
    y1: usize,
    value: u8,
) {
    let height = frame.len() / width.saturating_mul(3);
    let x1 = x1.min(width);
    let y1 = y1.min(height);
    for y in y0.min(height)..y1 {
        for x in x0.min(width)..x1 {
            let offset = (y * width + x) * 3;
            frame[offset] = value;
            frame[offset + 1] = value;
            frame[offset + 2] = value;
        }
    }
}

#[cfg(any(not(coverage), test))]
fn render_audio_chunk(
    schedule: &PulseSchedule,
    chunk_pts: Duration,
    samples_per_chunk: usize,
) -> Vec<u8> {
    let sample_step = Duration::from_nanos(1_000_000_000u64 / AUDIO_SAMPLE_RATE as u64);
    let mut pcm = Vec::with_capacity(samples_per_chunk * AUDIO_CHANNELS * size_of::<i16>());
    for sample_index in 0..samples_per_chunk {
        let sample_pts = chunk_pts + sample_step.saturating_mul(sample_index as u32);
        let amplitude = if schedule.flash_active(sample_pts) {
            let phase = TAU * AUDIO_PULSE_FREQUENCY_HZ * sample_pts.as_secs_f64();
            (phase.sin() * AUDIO_PULSE_AMPLITUDE) as i16
        } else {
            0
        };
        for _ in 0..AUDIO_CHANNELS {
            pcm.extend_from_slice(&amplitude.to_le_bytes());
        }
    }
    pcm
}

fn probe_pts_exceeds_duration(pts_usecs: u64, duration: std::time::Duration) -> bool {
    pts_usecs > duration.as_micros() as u64
}
