///|
/// Serialize owned interleaved PCM. Raw PCM is signed, little endian and
/// sign-extended. WAV PCM is left-aligned and its 8-bit container is unsigned.
pub fn frame_pcm(frame : Frame, wav~ : Bool = false) -> Bytes {
  let width = (frame.bits_per_sample + 7) / 8
  let count = frame.channels.length()
  Bytes::makei(frame.block_size * count * width, i => {
    let sample = frame.channels[i / width % count][i / (width * count)]
    let aligned = if wav {
      sample << (width * 8 - frame.bits_per_sample)
    } else {
      sample
    }
    let value = if wav && width == 1 { aligned + 128 } else { aligned }
    ((value >> (i % width * 8)) & 255).to_byte()
  })
}

///|
fn wav_put(out : Array[Byte], value : Int64, size : Int32) -> Unit {
  for i in 0..> (8 * i)) & 255L).to_int().to_byte())
  }
}

///|
fn wav_tag(out : Array[Byte], tag : Bytes) -> Unit {
  for byte in tag {
    out.push(byte)
  }
}

///|
/// Header only, allowing RF64 size planning without PCM allocation. Append
/// exactly samples * channels * ceil(bits/8) data bytes and, when odd, one zero
/// pad byte. The pad is included in RIFF/ds64 size but excluded from data size.
pub fn wav_header(
  info : StreamInfo,
  samples : Int64,
  channel_mask? : UInt32,
  rf64~ : Bool = false,
) -> Bytes raise FlacError {
  if info.bits_per_sample < 4 ||
    info.bits_per_sample > 32 ||
    info.channels < 1 ||
    info.channels > 8 ||
    info.sample_rate < 1 ||
    info.sample_rate > 1048575 ||
    samples < 0L {
    raise Invalid(0L, "invalid WAV audio dimensions")
  }
  let width = (info.bits_per_sample + 7) / 8
  let align = width * info.channels
  if samples > (9223372036854775807L - 128L) / align.to_int64() {
    raise Limit("WAV size exceeds signed 64-bit range")
  }
  let data_size = samples * align.to_int64()
  let mask = channel_mask.unwrap_or(default_channel_mask(info.channels))
  let extensible = info.channels > 2 ||
    info.bits_per_sample > 16 ||
    info.bits_per_sample % 8 != 0 ||
    channel_mask is Some(_)
  let fmt_size = if extensible { 40 } else { 16 }
  let base_size = (12 + 8 + fmt_size + 8 - 8).to_int64() +
    data_size +
    (data_size & 1L)
  let large = rf64 || base_size > 4294967295L
  let out : Array[Byte] = []
  wav_tag(out, if large { b"RF64" } else { b"RIFF" })
  wav_put(out, if large { 4294967295L } else { base_size }, 4)
  wav_tag(out, b"WAVE")
  if large {
    wav_tag(out, b"ds64")
    wav_put(out, 28L, 4)
    wav_put(out, base_size + 36L, 8)
    wav_put(out, data_size, 8)
    wav_put(out, samples, 8)
    wav_put(out, 0L, 4)
  }
  wav_tag(out, b"fmt ")
  wav_put(out, fmt_size.to_int64(), 4)
  wav_put(out, if extensible { 65534L } else { 1L }, 2)
  wav_put(out, info.channels.to_int64(), 2)
  wav_put(out, info.sample_rate.to_int64(), 4)
  wav_put(out, (info.sample_rate * align).to_int64(), 4)
  wav_put(out, align.to_int64(), 2)
  wav_put(out, (width * 8).to_int64(), 2)
  if extensible {
    wav_put(out, 22L, 2)
    wav_put(out, info.bits_per_sample.to_int64(), 2)
    wav_put(out, mask.to_int64(), 4)
    wav_tag(
      out, b"\x01\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71",
    )
  }
  wav_tag(out, b"data")
  wav_put(out, if large { 4294967295L } else { data_size }, 4)
  Bytes::makei(out.length(), i => out[i])
}