//! Audio playback: open the default output device, stream a WAV file
//! to it until the file is consumed.

use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::Duration;

use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{SampleFormat, SizedSample};
use hound::WavReader;
use tracing::{info, warn};

use crate::capture::AudioError;

pub struct AudioPlayback;

impl AudioPlayback {
    /// Play a 16-bit-PCM WAV file through the default output device.
    /// Blocks until the file has been fully streamed.
    pub fn play_wav(path: &Path) -> Result<(), AudioError> {
        let host = cpal::default_host();
        let device = host.default_output_device().ok_or(AudioError::NoOutputDevice)?;
        let device_name = device.name().unwrap_or_else(|_| "<unknown>".to_string());

        // Read all WAV samples up-front (PoC; production code would
        // stream in chunks). Resample is not done here — we pick a
        // device config that matches the file's sample rate when
        // possible.
        let mut reader = WavReader::open(path)?;
        let wav_spec = reader.spec();
        let samples: Vec<i16> = reader.samples::<i16>().collect::<Result<_, _>>()?;
        info!(
            target: "spike",
            device = %device_name,
            file_sr = wav_spec.sample_rate,
            file_channels = wav_spec.channels,
            samples = samples.len(),
            "preparing playback"
        );

        // Pick a supported config that matches the file's channel count
        // and sample rate where possible.
        let default_cfg = device.default_output_config()?;
        let config = cpal::StreamConfig {
            channels: default_cfg.channels(),
            sample_rate: cpal::SampleRate(wav_spec.sample_rate),
            buffer_size: cpal::BufferSize::Default,
        };

        let cursor = Arc::new(Mutex::new(0usize));
        let total = samples.len();
        let samples_arc = Arc::new(samples);
        let out_channels = config.channels as usize;
        let done = Arc::new(Mutex::new(false));

        let err_fn = |e| warn!(target: "spike", error = %e, "playback stream error");

        let stream = match default_cfg.sample_format() {
            SampleFormat::F32 => build_output_stream::<f32>(
                &device, &config, samples_arc.clone(), cursor.clone(), out_channels, done.clone(), err_fn,
            )?,
            SampleFormat::I16 => build_output_stream::<i16>(
                &device, &config, samples_arc.clone(), cursor.clone(), out_channels, done.clone(), err_fn,
            )?,
            SampleFormat::U16 => build_output_stream::<u16>(
                &device, &config, samples_arc.clone(), cursor.clone(), out_channels, done.clone(), err_fn,
            )?,
            other => return Err(AudioError::UnsupportedFormat(other)),
        };
        stream.play()?;

        // Wait for the callback to drain the buffer.
        let timeout = Duration::from_secs_f64(
            (total as f64 / wav_spec.sample_rate as f64 / wav_spec.channels as f64) + 2.0,
        );
        let start = std::time::Instant::now();
        loop {
            if *done.lock().unwrap() {
                break;
            }
            if start.elapsed() > timeout {
                warn!(target: "spike", "playback timeout reached");
                break;
            }
            std::thread::sleep(Duration::from_millis(50));
        }

        drop(stream);
        let consumed = *cursor.lock().unwrap();
        info!(target: "spike", consumed, total, "playback finished");
        Ok(())
    }
}

fn build_output_stream<T>(
    device: &cpal::Device,
    config: &cpal::StreamConfig,
    samples: Arc<Vec<i16>>,
    cursor: Arc<Mutex<usize>>,
    out_channels: usize,
    done: Arc<Mutex<bool>>,
    err_fn: fn(cpal::StreamError),
) -> Result<cpal::Stream, AudioError>
where
    T: SizedSample + FromI16 + Send + 'static,
{
    let stream = device.build_output_stream(
        config,
        move |out: &mut [T], _| {
            let mut idx = cursor.lock().unwrap();
            for frame in out.chunks_mut(out_channels) {
                if *idx >= samples.len() {
                    // End of file — write silence; signal done.
                    for s in frame.iter_mut() {
                        *s = T::from_i16(0);
                    }
                    *done.lock().unwrap() = true;
                } else {
                    let sample = samples[*idx];
                    *idx += 1;
                    for s in frame.iter_mut() {
                        *s = T::from_i16(sample);
                    }
                }
            }
        },
        err_fn,
        None,
    )?;
    Ok(stream)
}

pub trait FromI16 {
    fn from_i16(v: i16) -> Self;
}

impl FromI16 for i16 {
    fn from_i16(v: i16) -> Self {
        v
    }
}

impl FromI16 for u16 {
    fn from_i16(v: i16) -> Self {
        (i32::from(v) + i32::from(i16::MAX) + 1) as u16
    }
}

impl FromI16 for f32 {
    fn from_i16(v: i16) -> Self {
        f32::from(v) / f32::from(i16::MAX)
    }
}
