use std::{
    fs,
    path::{Path, PathBuf},
    process::Command,
};

use anyhow::{anyhow, Context, Result};

pub struct AudioPipeline;

impl AudioPipeline {
    pub fn extract_to_wav(ffmpeg_path: &Path, input_path: &str, workspace: &Path) -> Result<PathBuf> {
        fs::create_dir_all(workspace)
            .with_context(|| format!("failed to create workspace: {}", workspace.display()))?;

        let output_path = workspace.join("normalized.wav");
        let mut command = Command::new(ffmpeg_path);
        #[cfg(target_os = "macos")]
        if let Some(lib_dir) = ffmpeg_path.parent().and_then(|bin_dir| bin_dir.parent()).map(|root| root.join("lib")) {
            if lib_dir.exists() {
                command.env("DYLD_FALLBACK_LIBRARY_PATH", &lib_dir);
            }
        }

        let output = command
            .arg("-y")
            .arg("-i")
            .arg(input_path)
            .arg("-ac")
            .arg("1")
            .arg("-ar")
            .arg("16000")
            .arg("-f")
            .arg("wav")
            .arg(&output_path)
            .output()
            .with_context(|| format!("failed to launch ffmpeg: {}", ffmpeg_path.display()))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
            if stderr.is_empty() {
                return Err(anyhow!("ffmpeg exited with status: {}", output.status));
            }
            return Err(anyhow!(
                "ffmpeg exited with status: {} | stderr: {}",
                output.status,
                stderr
            ));
        }

        Ok(output_path)
    }

    pub fn load_wav_f32(path: &Path) -> Result<Vec<f32>> {
        let mut reader =
            hound::WavReader::open(path).with_context(|| format!("failed to open {}", path.display()))?;
        let spec = reader.spec();

        if spec.channels != 1 {
            return Err(anyhow!(
                "expected mono wav, found {} channels in {}",
                spec.channels,
                path.display()
            ));
        }

        let samples = reader
            .samples::<i16>()
            .map(|sample| {
                sample
                    .map(|value| value as f32 / i16::MAX as f32)
                    .map_err(anyhow::Error::from)
            })
            .collect::<Result<Vec<_>>>()?;

        Ok(samples)
    }
}
