//! [`Listener`] impl backed by macOS Core Audio system-audio capture.
//!
//! Wraps [`crate::system_audio_capture::CoreAudioCapture`] and emits
//! [`VoiceEvent::AudioChunk`] events at a fixed cadence (≈ 20 ms
//! frames). VAD and STT integration on the system-audio side is a
//! follow-up — the existing pipelines in [`crate::cpal_listener`] and
//! the future Parakeet-streaming backend will be reused; we don't want
//! to duplicate that logic here while the integration shape is still
//! being settled.
//!
//! v1 product cut: meeting capture pipes the raw frames into a separate
//! batch-STT pass, while live partials (the "live transcript" UX) come
//! from the mic side via the existing [`crate::CpalListener`].

#![cfg(all(target_os = "macos", feature = "system-audio-macos"))]

use crate::system_audio_capture::CoreAudioCapture;
use crate::{Listener, Result, VoiceConfig, VoiceError, VoiceEvent};
use async_trait::async_trait;
use futures::StreamExt;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::mpsc;

/// Frames per `AudioChunk` event. ≈ 20 ms at 48 kHz, ≈ 60 ms at 16 kHz.
/// Picked to match the cadence the Silero VAD frontend already expects.
const FRAMES_PER_CHUNK: usize = 960;

/// Bounded receiver capacity. Same as [`crate::cpal_listener`].
const EVENT_CHANNEL_CAPACITY: usize = 64;

/// macOS system-audio listener using Core Audio process taps.
///
/// On non-macOS targets this module isn't compiled — see the gate at
/// the top of the file. The cross-platform [`crate::Listener`] trait
/// is still satisfied because consumers only construct
/// [`SystemAudioListener`] when they've checked the target platform.
pub struct SystemAudioListener {
    running: Arc<AtomicBool>,
    capture_task: Option<tokio::task::JoinHandle<()>>,
}

impl SystemAudioListener {
    pub fn new() -> Self {
        Self {
            running: Arc::new(AtomicBool::new(false)),
            capture_task: None,
        }
    }
}

impl Default for SystemAudioListener {
    fn default() -> Self {
        Self::new()
    }
}

#[async_trait]
impl Listener for SystemAudioListener {
    async fn start(&mut self, _config: VoiceConfig) -> Result<mpsc::Receiver<VoiceEvent>> {
        if self.running.load(Ordering::SeqCst) {
            return Err(VoiceError::AlreadyRunning);
        }
        let capture = CoreAudioCapture::new()
            .map_err(|e| VoiceError::Capture(format!("CoreAudio init failed: {}", e)))?;
        let mut stream = capture
            .stream()
            .map_err(|e| VoiceError::Capture(format!("CoreAudio start failed: {}", e)))?;
        let sample_rate = stream.sample_rate();
        let (tx, rx) = mpsc::channel(EVENT_CHANNEL_CAPACITY);
        let running = self.running.clone();
        running.store(true, Ordering::SeqCst);

        // Pull samples off the Core Audio stream, batch into FRAMES_PER_CHUNK
        // frames, and forward as f32 → i16 PCM AudioChunk events. Convert to
        // i16 since downstream VAD and STT both operate on i16 mono.
        let task = tokio::spawn(async move {
            let mut buf: Vec<i16> = Vec::with_capacity(FRAMES_PER_CHUNK);
            while running.load(Ordering::SeqCst) {
                match stream.next().await {
                    Some(sample) => {
                        // Hard-clamp before quantizing — Core Audio samples
                        // are nominally [-1, 1] but can spike on transients.
                        let clamped = sample.clamp(-1.0, 1.0);
                        buf.push((clamped * i16::MAX as f32) as i16);
                        if buf.len() >= FRAMES_PER_CHUNK {
                            let chunk =
                                std::mem::replace(&mut buf, Vec::with_capacity(FRAMES_PER_CHUNK));
                            if tx
                                .send(VoiceEvent::AudioChunk {
                                    samples: chunk,
                                    sample_rate,
                                })
                                .await
                                .is_err()
                            {
                                // Consumer dropped — exit cleanly.
                                break;
                            }
                        }
                    }
                    None => break, // capture stream ended
                }
            }
            running.store(false, Ordering::SeqCst);
        });
        self.capture_task = Some(task);
        Ok(rx)
    }

    async fn stop(&mut self) -> Result<()> {
        if !self.running.load(Ordering::SeqCst) {
            return Err(VoiceError::NotRunning);
        }
        self.running.store(false, Ordering::SeqCst);
        if let Some(task) = self.capture_task.take() {
            // The capture task notices `running == false` next time it
            // awaits a sample; abort if it doesn't drop within a
            // reasonable window (the Core Audio stream may be parked).
            let abort = task.abort_handle();
            tokio::select! {
                _ = task => {},
                _ = tokio::time::sleep(std::time::Duration::from_millis(500)) => {
                    abort.abort();
                }
            }
        }
        Ok(())
    }

    fn is_running(&self) -> bool {
        self.running.load(Ordering::SeqCst)
    }
}
