use crate::cli::support::{ExportMetadata, ExportSessionData, ExportTrack};
use maolan_engine::{kind::Kind, message::AudioClipData};
use std::{
    collections::{BTreeSet, HashMap},
    fmt, fs, io,
    path::{Path, PathBuf},
    time::Duration,
};

use ebur128::{EbuR128, Mode as LoudnessMode};
use ffmpeg_next::{
    Dictionary,
    codec::{Context as CodecContext, Id as CodecId},
    format::output,
    frame::Audio,
};
use flacenc::bitsink::ByteSink;
use flacenc::component::BitRepr;
use flacenc::error::Verify;
use hound::{SampleFormat, WavReader, WavSpec};

pub const STANDARD_EXPORT_SAMPLE_RATES: [u32; 12] = [
    8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 384000,
];
pub const EXPORT_MP3_BITRATES_KBPS: [u16; 7] = [96, 128, 160, 192, 224, 256, 320];

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExportFormat {
    Wav,
    Mp3,
    Ogg,
    Flac,
}

impl fmt::Display for ExportFormat {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Wav => write!(f, "WAV"),
            Self::Mp3 => write!(f, "MP3"),
            Self::Ogg => write!(f, "OGG"),
            Self::Flac => write!(f, "FLAC"),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExportMp3Mode {
    Cbr,
    Vbr,
}

impl fmt::Display for ExportMp3Mode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Cbr => write!(f, "CBR"),
            Self::Vbr => write!(f, "VBR"),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExportNormalizeMode {
    Peak,
    Loudness,
}

impl fmt::Display for ExportNormalizeMode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Peak => write!(f, "Peak"),
            Self::Loudness => write!(f, "Loudness"),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExportRenderMode {
    Mixdown,
    StemsPostFader,
    StemsPreFader,
}

impl fmt::Display for ExportRenderMode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Mixdown => write!(f, "Mixdown"),
            Self::StemsPostFader => write!(f, "Stems (Post-Fader)"),
            Self::StemsPreFader => write!(f, "Stems (Pre-Fader)"),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExportBitDepth {
    Int16,
    Int24,
    Int32,
    Float32,
}

impl fmt::Display for ExportBitDepth {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Int16 => write!(f, "16-bit PCM"),
            Self::Int24 => write!(f, "24-bit PCM"),
            Self::Int32 => write!(f, "32-bit PCM"),
            Self::Float32 => write!(f, "32-bit float"),
        }
    }
}

pub const EXPORT_MP3_MODE_ALL: [ExportMp3Mode; 2] = [ExportMp3Mode::Cbr, ExportMp3Mode::Vbr];
pub const EXPORT_RENDER_MODE_ALL: [ExportRenderMode; 3] = [
    ExportRenderMode::Mixdown,
    ExportRenderMode::StemsPostFader,
    ExportRenderMode::StemsPreFader,
];
pub const EXPORT_BIT_DEPTH_ALL: [ExportBitDepth; 4] = [
    ExportBitDepth::Int16,
    ExportBitDepth::Int24,
    ExportBitDepth::Int32,
    ExportBitDepth::Float32,
];
pub const EXPORT_NORMALIZE_MODE_ALL: [ExportNormalizeMode; 2] =
    [ExportNormalizeMode::Peak, ExportNormalizeMode::Loudness];

#[derive(Debug, Clone)]
pub struct ExportSettings {
    pub sample_rate_hz: u32,
    pub format_wav: bool,
    pub format_mp3: bool,
    pub format_ogg: bool,
    pub format_flac: bool,
    pub bit_depth: ExportBitDepth,
    pub mp3_mode: ExportMp3Mode,
    pub mp3_bitrate_kbps: u16,
    pub ogg_quality: f32,
    pub render_mode: ExportRenderMode,
    pub hw_out_ports: BTreeSet<usize>,
    pub realtime_fallback: bool,
    pub normalize: bool,
    pub normalize_mode: ExportNormalizeMode,
    pub normalize_dbfs: f32,
    pub normalize_lufs: f32,
    pub normalize_dbtp: f32,
    pub normalize_tp_limiter: bool,
    pub master_limiter: bool,
    pub master_limiter_ceiling_dbtp: f32,
}

impl ExportSettings {
    pub fn new(default_sample_rate_hz: u32, hw_output_channels: usize) -> Self {
        Self {
            sample_rate_hz: default_sample_rate_hz,
            format_wav: true,
            format_mp3: false,
            format_ogg: false,
            format_flac: false,
            bit_depth: ExportBitDepth::Int24,
            mp3_mode: ExportMp3Mode::Cbr,
            mp3_bitrate_kbps: 320,
            ogg_quality: 0.6,
            render_mode: ExportRenderMode::Mixdown,
            hw_out_ports: default_hw_out_ports(hw_output_channels),
            realtime_fallback: false,
            normalize: false,
            normalize_mode: ExportNormalizeMode::Peak,
            normalize_dbfs: 0.0,
            normalize_lufs: -23.0,
            normalize_dbtp: -1.0,
            normalize_tp_limiter: true,
            master_limiter: true,
            master_limiter_ceiling_dbtp: -1.0,
        }
    }

    pub fn selected_formats(&self) -> Vec<ExportFormat> {
        let mut formats = Vec::new();
        if self.format_wav {
            formats.push(ExportFormat::Wav);
        }
        if self.format_mp3 {
            formats.push(ExportFormat::Mp3);
        }
        if self.format_ogg {
            formats.push(ExportFormat::Ogg);
        }
        if self.format_flac {
            formats.push(ExportFormat::Flac);
        }
        formats
    }

    pub fn normalize_hw_out_ports(&mut self, hw_output_channels: usize) {
        let available: BTreeSet<usize> = (0..hw_output_channels).collect();
        self.hw_out_ports.retain(|port| available.contains(port));
        if self.hw_out_ports.is_empty() {
            self.hw_out_ports = default_hw_out_ports(hw_output_channels);
        }
    }
}

fn default_hw_out_ports(hw_output_channels: usize) -> BTreeSet<usize> {
    (0..hw_output_channels).take(2).collect()
}

pub fn export_bit_depth_options(formats: &[ExportFormat]) -> Vec<ExportBitDepth> {
    if formats
        .iter()
        .any(|f| matches!(f, ExportFormat::Wav | ExportFormat::Flac))
    {
        EXPORT_BIT_DEPTH_ALL.to_vec()
    } else {
        vec![ExportBitDepth::Float32]
    }
}

pub fn export_mp3_supported(settings: &ExportSettings, session: &ExportSessionData) -> bool {
    export_max_channels(settings, session) <= 2
}

pub fn export_max_channels(settings: &ExportSettings, session: &ExportSessionData) -> usize {
    if matches!(settings.render_mode, ExportRenderMode::Mixdown) {
        settings.hw_out_ports.len()
    } else {
        session
            .tracks
            .iter()
            .map(|track| track.output_ports.max(1))
            .max()
            .unwrap_or(0)
    }
}

pub fn validate_export_settings(
    settings: &ExportSettings,
    session: &ExportSessionData,
) -> Result<(), String> {
    if settings.selected_formats().is_empty() {
        return Err("Select at least one export format".to_string());
    }
    if settings.format_mp3 && !export_mp3_supported(settings, session) {
        return Err("MP3 export supports only mono or stereo".to_string());
    }
    if matches!(settings.render_mode, ExportRenderMode::Mixdown) && settings.hw_out_ports.is_empty()
    {
        return Err("Select at least one hw:out port for mixdown export".to_string());
    }
    if !(-20.0..=0.0).contains(&settings.master_limiter_ceiling_dbtp) {
        return Err("Master limiter ceiling must be between -20.0 and 0.0 dBTP".to_string());
    }
    if !(-0.1..=1.0).contains(&settings.ogg_quality) {
        return Err("OGG quality must be between -0.1 and 1.0".to_string());
    }
    if settings.normalize {
        match settings.normalize_mode {
            ExportNormalizeMode::Peak => {
                if !(-60.0..=0.0).contains(&settings.normalize_dbfs) {
                    return Err("Normalize target must be between -60.0 and 0.0 dBFS".to_string());
                }
            }
            ExportNormalizeMode::Loudness => {
                if !(-70.0..=-5.0).contains(&settings.normalize_lufs) {
                    return Err("LUFS target must be between -70.0 and -5.0".to_string());
                }
                if !(-20.0..=0.0).contains(&settings.normalize_dbtp) {
                    return Err("dBTP ceiling must be between -20.0 and 0.0".to_string());
                }
            }
        }
    }
    if session.tracks.is_empty() {
        return Err("No tracks found. Nothing to export.".to_string());
    }
    Ok(())
}

pub fn default_export_base_path(session_dir: &Path) -> PathBuf {
    session_dir.join("export")
}

pub async fn export_session<F>(
    session: &ExportSessionData,
    session_root: &Path,
    export_base_path: &Path,
    settings: &ExportSettings,
    mut progress_callback: F,
) -> io::Result<Vec<PathBuf>>
where
    F: FnMut(f32, Option<String>),
{
    let mut tracks = session.tracks.clone();
    let connections = session.connections.clone();
    let total_length = tracks
        .iter()
        .flat_map(|track| track.audio_clips.iter())
        .map(audio_clip_end)
        .max()
        .unwrap_or(0);
    if total_length == 0 {
        return Err(io::Error::other("No audio clips found. Nothing to export."));
    }

    let export_formats = settings.selected_formats();
    let codec = ExportCodecSettings {
        mp3_mode: settings.mp3_mode,
        mp3_bitrate_kbps: settings.mp3_bitrate_kbps,
        ogg_quality: settings.ogg_quality,
    };
    let has_solo = tracks.iter().any(|track| track.soloed);
    let metadata = session.metadata.clone();

    progress_callback(0.0, Some("Analyzing tracks".to_string()));
    tokio::task::yield_now().await;

    if matches!(settings.render_mode, ExportRenderMode::Mixdown) {
        let output_ports: Vec<usize> = settings.hw_out_ports.iter().copied().collect();
        let output_channels = output_ports.len().max(1);
        let hw_out_channel_map: HashMap<usize, usize> = output_ports
            .iter()
            .enumerate()
            .map(|(channel_idx, port)| (*port, channel_idx))
            .collect();
        let mut mixed_buffer = vec![0.0_f32; total_length * output_channels];
        let track_count = tracks.len().max(1);
        for (track_idx, track) in tracks.iter_mut().enumerate() {
            if track.muted || (has_solo && !track.soloed) {
                continue;
            }
            let progress_start = 0.1 + (track_idx as f32 / track_count as f32) * 0.7;
            let progress_span = 0.7 / track_count as f32;
            progress_callback(
                progress_start,
                Some(format!("Processing track: {}", track.name)),
            );
            tokio::task::yield_now().await;

            let routed_ports: Vec<(usize, usize)> = connections
                .iter()
                .filter(|conn| {
                    conn.kind == Kind::Audio
                        && conn.from_track == track.name
                        && conn.to_track == "hw:out"
                })
                .filter_map(|conn| {
                    hw_out_channel_map
                        .get(&conn.to_port)
                        .map(|dest_idx| (conn.from_port, *dest_idx))
                })
                .collect();
            if routed_ports.is_empty() {
                continue;
            }
            let track_buffer = mix_track_clips_to_channels(
                &track.audio_clips,
                session_root,
                total_length,
                track.output_ports,
                track.level,
                track.balance,
                true,
            )?;
            for frame in 0..total_length {
                let track_base = frame * track.output_ports.max(1);
                let mixed_base = frame * output_channels;
                for (source_port, dest_channel) in &routed_ports {
                    if *source_port >= track.output_ports.max(1) {
                        continue;
                    }
                    mixed_buffer[mixed_base + *dest_channel] +=
                        track_buffer[track_base + *source_port];
                }
            }
            progress_callback(
                progress_start + progress_span,
                Some(format!("Finished: {}", track.name)),
            );
        }

        if settings.realtime_fallback {
            progress_callback(0.82, Some("Real-time fallback pacing".to_string()));
            let seconds = (total_length as f64 / settings.sample_rate_hz.max(1) as f64).max(0.0);
            tokio::time::sleep(Duration::from_secs_f64(seconds)).await;
        }

        if settings.normalize {
            apply_export_normalization(
                &mut mixed_buffer,
                ExportNormalizeParams {
                    mode: settings.normalize_mode,
                    target_dbfs: settings.normalize_dbfs,
                    target_lufs: settings.normalize_lufs,
                    true_peak_dbtp: settings.normalize_dbtp,
                    tp_limiter: settings.normalize_tp_limiter,
                    sample_rate: settings.sample_rate_hz as i32,
                    output_channels,
                },
            )?;
        }
        apply_master_limiter(
            &mut mixed_buffer,
            settings.master_limiter,
            settings.master_limiter_ceiling_dbtp,
        );

        let base_path = export_base_path.to_path_buf();
        let write_span = 0.1 / export_formats.len().max(1) as f32;
        let mut written = Vec::new();
        for (format_idx, format) in export_formats.iter().enumerate() {
            progress_callback(
                (0.9 + write_span * format_idx as f32).clamp(0.0, 0.99),
                Some(format!("Writing {} ({})", format, settings.bit_depth)),
            );
            let out_path = base_path.with_extension(export_format_extension(*format));
            write_export_audio(ExportWriteRequest {
                export_path: &out_path,
                mixed_buffer: &mixed_buffer,
                sample_rate: settings.sample_rate_hz as i32,
                output_channels,
                bit_depth: settings.bit_depth,
                format: *format,
                codec,
                metadata: &metadata,
            })?;
            written.push(out_path);
        }
        progress_callback(1.0, Some("Complete".to_string()));
        return Ok(written);
    }

    let stem_mode_label = if matches!(settings.render_mode, ExportRenderMode::StemsPreFader) {
        "pre"
    } else {
        "post"
    };
    let export_parent = export_base_path.parent().unwrap_or_else(|| Path::new("."));
    let export_stem = export_base_path
        .file_stem()
        .and_then(|stem| stem.to_str())
        .unwrap_or("export");
    let stem_dir = export_parent.join(format!("{export_stem}_stems"));
    fs::create_dir_all(&stem_dir)?;

    let selected_tracks: Vec<&ExportTrack> = tracks
        .iter()
        .filter(|track| !track.muted && (!has_solo || track.soloed))
        .collect();
    if selected_tracks.is_empty() {
        return Err(io::Error::other("No tracks are eligible for stem export"));
    }

    let mut written = Vec::new();
    for (idx, track) in selected_tracks.iter().enumerate() {
        progress_callback(
            0.1 + (idx as f32 / selected_tracks.len().max(1) as f32) * 0.75,
            Some(format!("Rendering stem: {}", track.name)),
        );
        let output_channels = track.output_ports.max(1);
        let mut stem_buffer = mix_track_clips_to_channels(
            &track.audio_clips,
            session_root,
            total_length,
            output_channels,
            track.level,
            track.balance,
            matches!(settings.render_mode, ExportRenderMode::StemsPostFader),
        )?;
        if settings.normalize {
            apply_export_normalization(
                &mut stem_buffer,
                ExportNormalizeParams {
                    mode: settings.normalize_mode,
                    target_dbfs: settings.normalize_dbfs,
                    target_lufs: settings.normalize_lufs,
                    true_peak_dbtp: settings.normalize_dbtp,
                    tp_limiter: settings.normalize_tp_limiter,
                    sample_rate: settings.sample_rate_hz as i32,
                    output_channels,
                },
            )?;
        }
        apply_master_limiter(
            &mut stem_buffer,
            settings.master_limiter,
            settings.master_limiter_ceiling_dbtp,
        );
        for format in &export_formats {
            let stem_file = stem_dir.join(format!(
                "{}_{}.{}",
                sanitize_export_component(&track.name),
                stem_mode_label,
                export_format_extension(*format)
            ));
            write_export_audio(ExportWriteRequest {
                export_path: &stem_file,
                mixed_buffer: &stem_buffer,
                sample_rate: settings.sample_rate_hz as i32,
                output_channels,
                bit_depth: settings.bit_depth,
                format: *format,
                codec,
                metadata: &metadata,
            })?;
            written.push(stem_file);
        }
        if settings.realtime_fallback {
            let seconds = (total_length as f64 / settings.sample_rate_hz.max(1) as f64).max(0.0);
            tokio::time::sleep(Duration::from_secs_f64(seconds)).await;
        }
    }
    progress_callback(1.0, Some("Complete".to_string()));
    Ok(written)
}

fn audio_clip_end(clip: &AudioClipData) -> usize {
    if !clip.grouped_clips.is_empty() {
        clip.grouped_clips
            .iter()
            .map(audio_clip_end)
            .max()
            .unwrap_or(0)
    } else {
        clip.start + clip.length
    }
}

fn mix_track_clips_to_channels(
    clips: &[AudioClipData],
    session_root: &Path,
    total_length: usize,
    output_channels: usize,
    level_db: f32,
    balance: f32,
    apply_fader: bool,
) -> io::Result<Vec<f32>> {
    let output_channels = output_channels.max(1);
    let mut mixed = vec![0.0_f32; total_length * output_channels];
    let channel_gains = if apply_fader {
        let level_amp = 10.0_f32.powf(level_db / 20.0);
        if output_channels == 2 {
            vec![
                if balance <= 0.0 {
                    level_amp
                } else {
                    level_amp * (1.0 - balance)
                },
                if balance >= 0.0 {
                    level_amp
                } else {
                    level_amp * (1.0 + balance)
                },
            ]
        } else {
            vec![level_amp; output_channels]
        }
    } else {
        vec![1.0; output_channels]
    };
    for clip in clips {
        mix_clip_into_buffer(
            clip,
            session_root,
            &mut mixed,
            total_length,
            output_channels,
            &channel_gains,
        )?;
    }
    Ok(mixed)
}

fn mix_clip_into_buffer(
    clip: &AudioClipData,
    session_root: &Path,
    mixed: &mut [f32],
    total_length: usize,
    output_channels: usize,
    channel_gains: &[f32],
) -> io::Result<()> {
    if clip.muted {
        return Ok(());
    }
    if !clip.grouped_clips.is_empty() {
        for child in &clip.grouped_clips {
            mix_clip_into_buffer(
                child,
                session_root,
                mixed,
                total_length,
                output_channels,
                channel_gains,
            )?;
        }
        return Ok(());
    }
    let clip_path = resolve_audio_clip_path(clip, session_root);
    let mut reader = WavReader::open(&clip_path).map_err(|e| {
        io::Error::other(format!(
            "Failed to open WAV '{}': {}",
            clip_path.display(),
            e
        ))
    })?;
    let spec = reader.spec();
    let clip_channels = spec.channels.max(1) as usize;
    let samples: Vec<f32> = match spec.sample_format {
        SampleFormat::Float => reader.samples::<f32>().filter_map(|s| s.ok()).collect(),
        SampleFormat::Int => match spec.bits_per_sample {
            16 => reader
                .samples::<i16>()
                .filter_map(|s| s.ok())
                .map(|s| s as f32 * (1.0 / 32768.0))
                .collect(),
            24 => reader
                .samples::<i32>()
                .filter_map(|s| s.ok())
                .map(|s| s as f32 * (1.0 / 8_388_608.0))
                .collect(),
            32 => reader
                .samples::<i32>()
                .filter_map(|s| s.ok())
                .map(|s| s as f32 * (1.0 / 2_147_483_648.0))
                .collect(),
            _ => return Ok(()),
        },
    };
    if samples.is_empty() {
        return Ok(());
    }
    let clip_frames = samples.len() / clip_channels;
    let offset_frame = clip.offset.min(clip_frames);
    let length_frames = clip.length.min(clip_frames.saturating_sub(offset_frame));
    for frame_idx in 0..length_frames {
        let src_frame = offset_frame + frame_idx;
        let dst_frame = clip.start + frame_idx;
        if dst_frame >= total_length {
            break;
        }
        let src_idx = src_frame * clip_channels;
        let dst_idx = dst_frame * output_channels;
        for out_ch in 0..output_channels {
            let source_sample = if clip_channels == 1 {
                samples[src_idx]
            } else {
                samples[src_idx + out_ch.min(clip_channels.saturating_sub(1))]
            };
            mixed[dst_idx + out_ch] += source_sample * channel_gains[out_ch];
        }
    }
    Ok(())
}

fn resolve_audio_clip_path(clip: &AudioClipData, session_root: &Path) -> PathBuf {
    let name = clip
        .preview_name
        .as_ref()
        .or(clip.source_name.as_ref())
        .unwrap_or(&clip.name);
    let path = PathBuf::from(name);
    if path.is_absolute() {
        path
    } else {
        session_root.join(path)
    }
}

fn apply_master_limiter(samples: &mut [f32], enabled: bool, ceiling_dbtp: f32) {
    if !enabled {
        return;
    }
    let ceiling_amp = 10.0_f32.powf(ceiling_dbtp / 20.0).clamp(0.0, 1.0);
    maolan_engine::simd::clamp_inplace(samples, -ceiling_amp, ceiling_amp);
}

fn export_format_extension(format: ExportFormat) -> &'static str {
    match format {
        ExportFormat::Wav => "wav",
        ExportFormat::Mp3 => "mp3",
        ExportFormat::Ogg => "ogg",
        ExportFormat::Flac => "flac",
    }
}

fn sanitize_export_component(value: &str) -> String {
    let mut out = String::with_capacity(value.len());
    for ch in value.chars() {
        if ch.is_ascii_alphanumeric() || ch == '_' || ch == '-' {
            out.push(ch);
        } else {
            out.push('_');
        }
    }
    if out.is_empty() {
        "track".to_string()
    } else {
        out
    }
}

#[derive(Clone, Copy)]
struct ExportCodecSettings {
    mp3_mode: ExportMp3Mode,
    mp3_bitrate_kbps: u16,
    ogg_quality: f32,
}

struct ExportWriteRequest<'a> {
    export_path: &'a Path,
    mixed_buffer: &'a [f32],
    sample_rate: i32,
    output_channels: usize,
    bit_depth: ExportBitDepth,
    format: ExportFormat,
    codec: ExportCodecSettings,
    metadata: &'a ExportMetadata,
}

#[derive(Clone, Copy)]
struct ExportNormalizeParams {
    mode: ExportNormalizeMode,
    target_dbfs: f32,
    target_lufs: f32,
    true_peak_dbtp: f32,
    tp_limiter: bool,
    sample_rate: i32,
    output_channels: usize,
}

fn write_export_audio(req: ExportWriteRequest<'_>) -> io::Result<()> {
    let export_path = req.export_path;
    let tmp_path = export_path.with_extension(format!(
        "{}.tmp",
        export_path
            .extension()
            .and_then(|e| e.to_str())
            .unwrap_or("export")
    ));
    let result = match req.format {
        ExportFormat::Wav => write_wav_with_bit_depth(
            &tmp_path,
            req.mixed_buffer,
            req.sample_rate,
            req.output_channels,
            req.bit_depth,
        ),
        ExportFormat::Flac => write_flac_with_bit_depth(
            &tmp_path,
            req.mixed_buffer,
            req.sample_rate,
            req.output_channels,
            req.bit_depth,
        ),
        ExportFormat::Mp3 => write_mp3(
            &tmp_path,
            req.mixed_buffer,
            req.sample_rate,
            req.output_channels,
            req.codec,
            req.metadata,
        ),
        ExportFormat::Ogg => write_ogg_vorbis(
            &tmp_path,
            req.mixed_buffer,
            req.sample_rate,
            req.output_channels,
            req.codec,
            req.metadata,
        ),
    };
    if result.is_ok() {
        fs::rename(&tmp_path, export_path)?;
    }
    result
}

fn write_wav_with_bit_depth(
    export_path: &Path,
    mixed_buffer: &[f32],
    sample_rate: i32,
    output_channels: usize,
    bit_depth: ExportBitDepth,
) -> io::Result<()> {
    let spec = WavSpec {
        channels: output_channels as u16,
        sample_rate: sample_rate as u32,
        bits_per_sample: match bit_depth {
            ExportBitDepth::Int16 => 16,
            ExportBitDepth::Int24 => 24,
            ExportBitDepth::Int32 => 32,
            ExportBitDepth::Float32 => 32,
        },
        sample_format: match bit_depth {
            ExportBitDepth::Float32 => SampleFormat::Float,
            _ => SampleFormat::Int,
        },
    };
    let mut writer = hound::WavWriter::create(export_path, spec)
        .map_err(|e| io::Error::other(format!("Failed to create WAV writer: {e}")))?;
    match bit_depth {
        ExportBitDepth::Int16 | ExportBitDepth::Int24 | ExportBitDepth::Int32 => {
            let (quantized, _) = quantize_samples_for_bit_depth(mixed_buffer, bit_depth);
            for &q in &quantized {
                match bit_depth {
                    ExportBitDepth::Int16 => writer.write_sample(q as i16),
                    _ => writer.write_sample(q),
                }
                .map_err(|e| io::Error::other(format!("Failed to write sample: {e}")))?;
            }
        }
        ExportBitDepth::Float32 => {
            for &sample in mixed_buffer {
                writer
                    .write_sample(sample)
                    .map_err(|e| io::Error::other(format!("Failed to write sample: {e}")))?;
            }
        }
    }
    writer.finalize().map_err(|e| {
        io::Error::other(format!(
            "Failed to write '{}': {}",
            export_path.display(),
            e
        ))
    })
}

fn quantize_samples_for_bit_depth(
    mixed_buffer: &[f32],
    bit_depth: ExportBitDepth,
) -> (Vec<i32>, u8) {
    let (scale, min, max, bits_per_sample) = match bit_depth {
        ExportBitDepth::Int16 => (i16::MAX as f32, i16::MIN as f32, i16::MAX as f32, 16),
        ExportBitDepth::Int24 => (8_388_607.0, -8_388_608.0, 8_388_607.0, 24),
        ExportBitDepth::Int32 => (i32::MAX as f32, i32::MIN as f32, i32::MAX as f32, 32),
        ExportBitDepth::Float32 => (8_388_607.0, -8_388_608.0, 8_388_607.0, 24),
    };

    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
    {
        use wide::{f32x4, i32x4};
        let mut quantized = Vec::with_capacity(mixed_buffer.len());
        let n = mixed_buffer.len() / 4;
        let vmin_f = f32x4::splat(min);
        let vmax_f = f32x4::splat(max);
        let scale_f = f32x4::splat(scale);
        let vmin_i = i32x4::splat(min as i32);
        let vmax_i = i32x4::splat(max as i32);
        for i in 0..n {
            let chunk = &mixed_buffer[i * 4..(i + 1) * 4];
            let v: f32x4 = [chunk[0], chunk[1], chunk[2], chunk[3]].into();
            let clamped = v.clamp(vmin_f, vmax_f);
            let scaled = clamped * scale_f;
            let rounded = scaled.round_int();
            let clamped_i = rounded.max(vmin_i).min(vmax_i);
            quantized.extend_from_slice(&clamped_i.to_array());
        }
        for s in &mixed_buffer[n * 4..] {
            quantized.push(
                (*s).clamp(-1.0, 1.0)
                    .mul_add(scale, 0.0)
                    .round()
                    .clamp(min, max) as i32,
            );
        }
        (quantized, bits_per_sample)
    }
    #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
    {
        (
            mixed_buffer
                .iter()
                .map(|s| (s.clamp(-1.0, 1.0) * scale).round().clamp(min, max) as i32)
                .collect(),
            bits_per_sample,
        )
    }
}

fn write_flac_with_bit_depth(
    export_path: &Path,
    mixed_buffer: &[f32],
    sample_rate: i32,
    output_channels: usize,
    bit_depth: ExportBitDepth,
) -> io::Result<()> {
    let (quantized, bits_per_sample) = quantize_samples_for_bit_depth(mixed_buffer, bit_depth);
    let config = flacenc::config::Encoder::default()
        .into_verified()
        .map_err(|e| io::Error::other(format!("Invalid FLAC encoder config: {e:?}")))?;
    let source = flacenc::source::MemSource::from_samples(
        &quantized,
        output_channels,
        bits_per_sample as usize,
        sample_rate.max(1) as usize,
    );
    let stream = flacenc::encode_with_fixed_block_size(&config, source, config.block_size)
        .map_err(|e| io::Error::other(format!("FLAC encode failed: {e}")))?;
    let mut sink = ByteSink::new();
    stream
        .write(&mut sink)
        .map_err(|e| io::Error::other(format!("FLAC bitstream write failed: {e}")))?;
    fs::write(export_path, sink.as_slice()).map_err(|e| {
        io::Error::other(format!(
            "Failed to write '{}': {}",
            export_path.display(),
            e
        ))
    })
}

fn write_mp3(
    export_path: &Path,
    mixed_buffer: &[f32],
    sample_rate: i32,
    output_channels: usize,
    codec: ExportCodecSettings,
    metadata: &ExportMetadata,
) -> io::Result<()> {
    if output_channels != 1 && output_channels != 2 {
        return Err(io::Error::other(format!(
            "MP3 export supports only mono/stereo, got {} channels",
            output_channels
        )));
    }

    ffmpeg_init().map_err(|e| io::Error::other(format!("FFmpeg init failed: {e}")))?;

    let mut octx = output(export_path.to_str().unwrap_or("output.mp3"))
        .map_err(|e| io::Error::other(format!("Failed to create output context: {e}")))?;

    let codec_id = CodecId::MP3;
    let encoder_codec = ffmpeg_next::codec::encoder::find(codec_id)
        .ok_or_else(|| io::Error::other("MP3 encoder not found"))?;

    let encoder_ctx = CodecContext::new_with_codec(encoder_codec);

    let mut encoder = encoder_ctx
        .encoder()
        .audio()
        .map_err(|e| io::Error::other(format!("Failed to create audio encoder: {e}")))?;

    encoder.set_rate(sample_rate);

    encoder.set_format(ffmpeg_next::format::Sample::F32(
        ffmpeg_next::format::sample::Type::Planar,
    ));
    encoder.set_channel_layout(match output_channels {
        1 => ffmpeg_next::channel_layout::ChannelLayout::MONO,
        _ => ffmpeg_next::channel_layout::ChannelLayout::STEREO,
    });

    let bitrate = (codec.mp3_bitrate_kbps as usize) * 1000;
    encoder.set_bit_rate(bitrate);

    if matches!(codec.mp3_mode, ExportMp3Mode::Vbr) {
        encoder.set_quality(2usize);
    }

    let mut metadata_dict = Dictionary::new();
    if !metadata.author.is_empty() {
        metadata_dict.set("artist", &metadata.author);
    }
    if !metadata.album.is_empty() {
        metadata_dict.set("album", &metadata.album);
    }
    if let Some(year) = metadata.year {
        metadata_dict.set("date", &year.to_string());
    }
    if let Some(track_number) = metadata.track_number {
        metadata_dict.set("track", &track_number.to_string());
    }
    if !metadata.genre.is_empty() {
        metadata_dict.set("genre", &metadata.genre);
    }

    let mut output_stream = octx
        .add_stream(encoder_codec)
        .map_err(|e| io::Error::other(format!("Failed to add stream: {e}")))?;

    output_stream.set_parameters(&encoder);

    let mut encoder = encoder
        .open_as(encoder_codec)
        .map_err(|e| io::Error::other(format!("Failed to open encoder: {e}")))?;

    octx.write_header()
        .map_err(|e| io::Error::other(format!("Failed to write header: {e}")))?;

    let frame_size = 1152;

    for chunk_start in (0..mixed_buffer.len()).step_by(frame_size * output_channels) {
        let chunk_end = (chunk_start + frame_size * output_channels).min(mixed_buffer.len());
        let chunk = &mixed_buffer[chunk_start..chunk_end];
        let actual_frames = chunk.len() / output_channels;

        if actual_frames == 0 {
            continue;
        }

        let mut frame = Audio::empty();
        frame.set_format(encoder.format());
        frame.set_channel_layout(encoder.channel_layout());
        frame.set_rate(encoder.rate());
        frame.set_samples(actual_frames);

        unsafe {
            ffmpeg_next::ffi::av_frame_get_buffer(frame.as_mut_ptr(), 0);
        }

        for ch in 0..output_channels {
            let data_ptr = frame.data_mut(ch).as_mut_ptr() as *mut f32;
            for frame_idx in 0..actual_frames {
                let src_idx = frame_idx * output_channels + ch;
                if src_idx < chunk.len() {
                    unsafe {
                        *data_ptr.add(frame_idx) = chunk[src_idx];
                    }
                }
            }
        }

        match encoder.send_frame(&frame) {
            Ok(()) => {}
            Err(e) => return Err(io::Error::other(format!("Failed to send frame: {e}"))),
        }

        let mut packet = ffmpeg_next::packet::Packet::empty();
        while encoder.receive_packet(&mut packet).is_ok() {
            packet.set_stream(0);
            packet
                .write_interleaved(&mut octx)
                .map_err(|e| io::Error::other(format!("Failed to write packet: {e}")))?;
        }
    }

    encoder
        .send_eof()
        .map_err(|e| io::Error::other(format!("Failed to send EOF: {e}")))?;

    let mut packet = ffmpeg_next::packet::Packet::empty();
    while encoder.receive_packet(&mut packet).is_ok() {
        packet.set_stream(0);
        packet
            .write_interleaved(&mut octx)
            .map_err(|e| io::Error::other(format!("Failed to write packet: {e}")))?;
    }

    octx.write_trailer()
        .map_err(|e| io::Error::other(format!("Failed to write trailer: {e}")))?;

    Ok(())
}

fn write_ogg_vorbis(
    export_path: &Path,
    mixed_buffer: &[f32],
    sample_rate: i32,
    output_channels: usize,
    codec: ExportCodecSettings,
    metadata: &ExportMetadata,
) -> io::Result<()> {
    ffmpeg_init().map_err(|e| io::Error::other(format!("FFmpeg init failed: {e}")))?;

    let mut octx = output(export_path.to_str().unwrap_or("output.ogg"))
        .map_err(|e| io::Error::other(format!("Failed to create output context: {e}")))?;

    let codec_id = CodecId::VORBIS;
    let encoder_codec = ffmpeg_next::codec::encoder::find(codec_id)
        .ok_or_else(|| io::Error::other("Vorbis encoder not found"))?;

    let encoder_ctx = CodecContext::new_with_codec(encoder_codec);

    let mut encoder = encoder_ctx
        .encoder()
        .audio()
        .map_err(|e| io::Error::other(format!("Failed to create audio encoder: {e}")))?;

    encoder.set_rate(sample_rate);

    encoder.set_format(ffmpeg_next::format::Sample::F32(
        ffmpeg_next::format::sample::Type::Planar,
    ));
    encoder.set_channel_layout(match output_channels {
        1 => ffmpeg_next::channel_layout::ChannelLayout::MONO,
        _ => ffmpeg_next::channel_layout::ChannelLayout::STEREO,
    });

    let quality = ((codec.ogg_quality + 0.1) * 10.0).clamp(0.0, 10.0) as i32;
    encoder.set_quality(quality as usize);

    let mut metadata_dict = Dictionary::new();
    if !metadata.author.is_empty() {
        metadata_dict.set("artist", &metadata.author);
    }
    if !metadata.album.is_empty() {
        metadata_dict.set("album", &metadata.album);
    }
    if let Some(year) = metadata.year {
        metadata_dict.set("date", &year.to_string());
    }
    if let Some(track_number) = metadata.track_number {
        metadata_dict.set("track", &track_number.to_string());
    }
    if !metadata.genre.is_empty() {
        metadata_dict.set("genre", &metadata.genre);
    }

    let mut output_stream = octx
        .add_stream(encoder_codec)
        .map_err(|e| io::Error::other(format!("Failed to add stream: {e}")))?;

    output_stream.set_parameters(&encoder);

    let mut encoder = encoder
        .open_as(encoder_codec)
        .map_err(|e| io::Error::other(format!("Failed to open encoder: {e}")))?;

    octx.write_header()
        .map_err(|e| io::Error::other(format!("Failed to write header: {e}")))?;

    let frame_size = 1024;

    for chunk_start in (0..mixed_buffer.len()).step_by(frame_size * output_channels) {
        let chunk_end = (chunk_start + frame_size * output_channels).min(mixed_buffer.len());
        let chunk = &mixed_buffer[chunk_start..chunk_end];
        let actual_frames = chunk.len() / output_channels;

        if actual_frames == 0 {
            continue;
        }

        let mut frame = Audio::empty();
        frame.set_format(encoder.format());
        frame.set_channel_layout(encoder.channel_layout());
        frame.set_rate(encoder.rate());
        frame.set_samples(actual_frames);

        unsafe {
            ffmpeg_next::ffi::av_frame_get_buffer(frame.as_mut_ptr(), 0);
        }

        for ch in 0..output_channels {
            let data_ptr = frame.data_mut(ch).as_mut_ptr() as *mut f32;
            for frame_idx in 0..actual_frames {
                let src_idx = frame_idx * output_channels + ch;
                if src_idx < chunk.len() {
                    unsafe {
                        *data_ptr.add(frame_idx) = chunk[src_idx];
                    }
                }
            }
        }

        match encoder.send_frame(&frame) {
            Ok(()) => {}
            Err(e) => return Err(io::Error::other(format!("Failed to send frame: {e}"))),
        }

        let mut packet = ffmpeg_next::packet::Packet::empty();
        while encoder.receive_packet(&mut packet).is_ok() {
            packet.set_stream(0);
            packet
                .write_interleaved(&mut octx)
                .map_err(|e| io::Error::other(format!("Failed to write packet: {e}")))?;
        }
    }

    encoder
        .send_eof()
        .map_err(|e| io::Error::other(format!("Failed to send EOF: {e}")))?;

    let mut packet = ffmpeg_next::packet::Packet::empty();
    while encoder.receive_packet(&mut packet).is_ok() {
        packet.set_stream(0);
        packet
            .write_interleaved(&mut octx)
            .map_err(|e| io::Error::other(format!("Failed to write packet: {e}")))?;
    }

    octx.write_trailer()
        .map_err(|e| io::Error::other(format!("Failed to write trailer: {e}")))?;

    Ok(())
}

fn ffmpeg_init() -> Result<(), ffmpeg_next::Error> {
    static RESULT: std::sync::OnceLock<Result<(), ffmpeg_next::Error>> = std::sync::OnceLock::new();
    *RESULT.get_or_init(ffmpeg_next::init)
}

fn measure_lufs_and_true_peak(
    samples: &[f32],
    channels: usize,
    sample_rate: i32,
) -> io::Result<(f32, f32)> {
    let mut meter = EbuR128::new(
        channels as u32,
        sample_rate as u32,
        LoudnessMode::I | LoudnessMode::TRUE_PEAK,
    )
    .map_err(|e| io::Error::other(format!("Failed to initialize loudness meter: {e}")))?;
    meter
        .add_frames_f32(samples)
        .map_err(|e| io::Error::other(format!("Loudness analysis failed: {e}")))?;
    let lufs = meter
        .loudness_global()
        .map_err(|e| io::Error::other(format!("Failed to get integrated loudness: {e}")))?
        as f32;
    if !lufs.is_finite() {
        return Err(io::Error::other("Integrated loudness is not finite"));
    }
    let mut tp = 0.0_f32;
    for ch in 0..channels as u32 {
        tp = tp.max(
            meter
                .true_peak(ch)
                .map_err(|e| io::Error::other(format!("Failed to get true peak: {e}")))?
                as f32,
        );
    }
    Ok((lufs, tp))
}

fn apply_export_normalization(
    samples: &mut [f32],
    params: ExportNormalizeParams,
) -> io::Result<()> {
    match params.mode {
        ExportNormalizeMode::Peak => {
            let peak = maolan_engine::simd::peak_abs(samples);
            if peak > 0.0 {
                let target_amp = 10.0_f32.powf(params.target_dbfs / 20.0).clamp(0.0, 1.0);
                let gain = target_amp / peak;
                maolan_engine::simd::mul_inplace(samples, gain);
            }
        }
        ExportNormalizeMode::Loudness => {
            let (measured_lufs, measured_tp_amp) =
                measure_lufs_and_true_peak(samples, params.output_channels, params.sample_rate)?;
            let gain_loudness_db = params.target_lufs - measured_lufs;
            let gain_loudness = 10.0_f32.powf(gain_loudness_db / 20.0);
            let ceiling_amp = 10.0_f32.powf(params.true_peak_dbtp / 20.0).clamp(0.0, 1.0);
            let gain_tp = if measured_tp_amp > 0.0 {
                ceiling_amp / measured_tp_amp
            } else {
                gain_loudness
            };
            let applied_gain = if params.tp_limiter {
                gain_loudness
            } else {
                gain_loudness.min(gain_tp)
            };
            maolan_engine::simd::mul_inplace(samples, applied_gain);
            if params.tp_limiter {
                let predicted_tp = measured_tp_amp * applied_gain;
                if predicted_tp > ceiling_amp && ceiling_amp > 0.0 {
                    maolan_engine::simd::clamp_inplace(samples, -ceiling_amp, ceiling_amp);
                }
            }
        }
    }
    Ok(())
}
