//! # WAV (RIFF) Decoder Module
//!
//! Streaming WAV → PCM conversion with zero seeking. The decoder reads the RIFF
//! header incrementally, validates the format, and then streams `data` chunk
//! payload as little-endian PCM frames through the common async pipeline.

use std::{fmt, io::Read};

use tokio::{
    io::AsyncRead,
    sync::{mpsc, oneshot},
};

use crate::{
    common::ChannelReader,
    decoder_common::{
        spawn_ingest_task, spawn_writer_task, DecodedStream, DecoderError, CHANNEL_CAPACITY,
        DUPLEX_BUFFER_SIZE,
    },
    pcm::StreamInfo,
    stream::ManagedAsyncReader,
};

/// Errors that can occur while decoding WAV data.
pub type WavError = DecoderError;

/// Streaming WAV reader state.
struct StreamingWavReader<E>
where
    E: fmt::Display + std::error::Error,
{
    reader: ChannelReader<E>,
    buffer: Vec<u8>,
    position: usize,
    finished: bool,
}

impl<E> StreamingWavReader<E>
where
    E: fmt::Display + std::error::Error,
{
    fn new(reader: ChannelReader<E>) -> Self {
        Self {
            reader,
            buffer: Vec::new(),
            position: 0,
            finished: false,
        }
    }

    fn read_exact(&mut self, len: usize) -> Result<&[u8], WavError> {
        while self.buffer.len() - self.position < len {
            if self.finished {
                return Err(WavError::Decode("unexpected EOF in WAV header".into()));
            }
            let mut chunk = [0u8; 4096];
            let read = self.reader.read(&mut chunk)?;
            if read == 0 {
                self.finished = true;
            } else {
                self.buffer.extend_from_slice(&chunk[..read]);
            }
        }
        let start = self.position;
        let end = start + len;
        self.position = end;
        Ok(&self.buffer[start..end])
    }

    fn skip(&mut self, mut len: usize) -> Result<(), WavError> {
        while len > 0 {
            let available = self.buffer.len() - self.position;
            if available >= len {
                self.position += len;
                return Ok(());
            } else {
                self.position += available;
                len -= available;
                let mut chunk = [0u8; 4096];
                let read = self.reader.read(&mut chunk)?;
                if read == 0 {
                    return Err(WavError::Decode(
                        "unexpected EOF while skipping chunk".into(),
                    ));
                }
                self.buffer.clear();
                self.buffer.extend_from_slice(&chunk[..read]);
                self.position = 0;
            }
        }
        Ok(())
    }
}

/// PCM format metadata extracted from the WAV `fmt ` chunk.
#[derive(Clone, Debug)]
struct FmtChunk {
    audio_format: u16,
    channels: u16,
    sample_rate: u32,
    bits_per_sample: u16,
}

impl FmtChunk {
    fn validate(&self) -> Result<(), WavError> {
        if !(self.audio_format == 0x0001 || self.audio_format == 0x0003) {
            return Err(WavError::Decode(format!(
                "unsupported WAV audio format: {}",
                self.audio_format
            )));
        }
        if self.channels == 0 {
            return Err(WavError::Decode("WAV channel count must be > 0".into()));
        }
        if self.sample_rate == 0 {
            return Err(WavError::Decode("WAV sample rate must be > 0".into()));
        }
        if self.bits_per_sample == 0 || self.bits_per_sample > 32 {
            return Err(WavError::Decode(format!(
                "unsupported bits per sample: {}",
                self.bits_per_sample
            )));
        }
        if self.audio_format == 0x0001 {
            match self.bits_per_sample {
                8 | 16 | 24 | 32 => Ok(()),
                _ => Err(WavError::Decode(format!(
                    "unsupported PCM bit depth: {}",
                    self.bits_per_sample
                ))),
            }
        } else {
            Err(WavError::Decode(
                "IEEE float WAV decoding is not yet supported".into(),
            ))
        }
    }

    fn bytes_per_sample(&self) -> usize {
        ((self.bits_per_sample as usize) + 7) / 8
    }
}

/// Async stream alias for decoded WAV audio.
pub type WavDecodedStream = DecodedStream<WavError>;

/// Decode a WAV stream into PCM audio.
pub async fn decode_wav_stream<R>(reader: R) -> Result<WavDecodedStream, WavError>
where
    R: AsyncRead + Unpin + Send + 'static,
{
    let (ingest_tx, ingest_rx) = mpsc::channel(CHANNEL_CAPACITY);
    spawn_ingest_task::<_, WavError>(reader, ingest_tx);

    let (pcm_tx, pcm_rx) = mpsc::channel(CHANNEL_CAPACITY);
    let (pcm_reader, pcm_writer) = tokio::io::duplex(DUPLEX_BUFFER_SIZE);
    let (info_tx, info_rx) = oneshot::channel::<Result<StreamInfo, WavError>>();

    let blocking_handle = tokio::task::spawn_blocking(move || -> Result<(), WavError> {
        let mut info_tx = Some(info_tx);

        let result: Result<(), WavError> = (|| {
            let channel_reader = ChannelReader::<WavError>::new(ingest_rx);
            let mut wav_reader = StreamingWavReader::new(channel_reader);

            let riff = wav_reader.read_exact(12)?;
            if &riff[0..4] != b"RIFF" {
                return Err(WavError::Decode("missing RIFF header".into()));
            }
            if &riff[8..12] != b"WAVE" {
                return Err(WavError::Decode("missing WAVE signature".into()));
            }

            let mut fmt_chunk: Option<FmtChunk> = None;
            let mut data_found = false;

            loop {
                let mut chunk_header = [0u8; 8];
                match wav_reader.read_exact(8) {
                    Ok(bytes) => chunk_header.copy_from_slice(bytes),
                    Err(WavError::Decode(msg)) if msg.contains("unexpected EOF") => break,
                    Err(err) => return Err(err),
                }
                let chunk_id = &chunk_header[..4];
                let chunk_size = u32::from_le_bytes([
                    chunk_header[4],
                    chunk_header[5],
                    chunk_header[6],
                    chunk_header[7],
                ]) as usize;

                let padded_size = (chunk_size + 1) & !1; // align to even bytes

                match chunk_id {
                    b"fmt " => {
                        let bytes = wav_reader.read_exact(chunk_size)?;
                        if chunk_size < 16 {
                            return Err(WavError::Decode("fmt chunk too small".into()));
                        }
                        let audio_format = u16::from_le_bytes([bytes[0], bytes[1]]);
                        let channels = u16::from_le_bytes([bytes[2], bytes[3]]);
                        let sample_rate =
                            u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
                        let bits_per_sample = u16::from_le_bytes([bytes[14], bytes[15]]);
                        let fmt = FmtChunk {
                            audio_format,
                            channels,
                            sample_rate,
                            bits_per_sample,
                        };
                        fmt.validate()?;
                        fmt_chunk = Some(fmt);
                        if padded_size > chunk_size {
                            wav_reader.skip(padded_size - chunk_size)?;
                        }
                    }
                    b"data" => {
                        let fmt = fmt_chunk.as_ref().ok_or_else(|| {
                            WavError::Decode("data chunk before fmt chunk".into())
                        })?;

                        let info = StreamInfo {
                            sample_rate: fmt.sample_rate,
                            channels: fmt.channels as u8,
                            bits_per_sample: fmt.bits_per_sample as u8,
                            total_samples: None,
                            max_block_size: 0,
                            min_block_size: 0,
                        };

                        if let Some(tx) = info_tx.take() {
                            if tx.send(Ok(info.clone())).is_err() {
                                return Ok(());
                            }
                        }

                        let mut remaining = chunk_size;
                        let bytes_per_frame = fmt.bytes_per_sample() * fmt.channels as usize;
                        let mut buffer = vec![0u8; 4096];

                        while remaining > 0 {
                            let to_read = remaining.min(buffer.len());
                            let read = wav_reader.reader.read(&mut buffer[..to_read])?;
                            if read == 0 {
                                break;
                            }
                            remaining -= read;
                            let aligned = read - (read % bytes_per_frame);
                            if aligned > 0 {
                                if pcm_tx
                                    .blocking_send(Ok(buffer[..aligned].to_vec()))
                                    .is_err()
                                {
                                    return Ok(());
                                }
                            }
                            if aligned < read {
                                return Err(WavError::Decode(
                                    "incomplete frame at end of chunk".into(),
                                ));
                            }
                        }

                        if padded_size > chunk_size {
                            let mut pad = [0u8; 1];
                            wav_reader.reader.read_exact(&mut pad)?;
                        }

                        data_found = true;
                        break;
                    }
                    _ => {
                        wav_reader.skip(chunk_size)?;
                        if padded_size > chunk_size {
                            wav_reader.skip(padded_size - chunk_size)?;
                        }
                    }
                }
            }

            if !data_found {
                return Err(WavError::Decode("no data chunk found in WAV stream".into()));
            }

            Ok(())
        })();

        match result {
            Ok(()) => Ok(()),
            Err(err) => {
                if let Some(tx) = info_tx.take() {
                    let _ = tx.send(Err(err.clone()));
                }
                Err(err)
            }
        }
    });

    let writer_handle = spawn_writer_task(pcm_rx, pcm_writer, blocking_handle, "wav-decode");

    let info = info_rx.await.map_err(|_| WavError::ChannelClosed)??;
    let reader = ManagedAsyncReader::new("wav-decode-writer", pcm_reader, writer_handle);

    Ok(DecodedStream::new(info, reader))
}
