// moonaudio — pure MoonBit audio decoding and DSP toolkit.
// Copyright 2026 Sakuraqwq1
// Licensed under the Apache License, Version 2.0.

///|
/// Version of the `moonaudio` module.
/// Keep in sync with the `version` field in `moon.mod`.
pub fn version() -> String {
  "0.3.2"
}

///|
/// Audio container recognised by [`sniff`].
pub(all) enum Container {
  /// RIFF/WAVE (little endian), RIFX (big endian) or RF64.
  Wav
  /// AIFF or AIFC (big endian form container).
  Aiff
  /// Sun/NeXT `.au` (`.snd`).
  Au
  /// Native FLAC stream.
  Flac
}

///|
/// Canonical lower case container name.
pub fn Container::to_name(self : Container) -> String {
  match self {
    Wav => "wav"
    Aiff => "aiff"
    Au => "au"
    Flac => "flac"
  }
}

///|
/// Default file extension for a container, including the dot.
pub fn Container::extension(self : Container) -> String {
  match self {
    Wav => ".wav"
    Aiff => ".aiff"
    Au => ".au"
    Flac => ".flac"
  }
}

///|
/// Guess the container of `bytes` from its magic number.
///
/// Returns `None` for input that carries no recognised audio header, which lets
/// callers fall back to a user supplied format instead of guessing wrong.
pub fn sniff(bytes : Bytes) -> Container? {
  if @audio.ascii_is(bytes, 0, 4, "RIFF") ||
    @audio.ascii_is(bytes, 0, 4, "RIFX") ||
    @audio.ascii_is(bytes, 0, 4, "RF64") {
    if @audio.ascii_is(bytes, 8, 4, "WAVE") {
      return Some(Wav)
    }
  }
  if @audio.ascii_is(bytes, 0, 4, "FORM") {
    if @audio.ascii_is(bytes, 8, 4, "AIFF") ||
      @audio.ascii_is(bytes, 8, 4, "AIFC") {
      return Some(Aiff)
    }
  }
  if @audio.ascii_is(bytes, 0, 4, ".snd") {
    return Some(Au)
  }
  if @audio.ascii_is(bytes, 0, 4, "fLaC") {
    return Some(Flac)
  }
  None
}

///|
/// Decode any supported container, dispatching on the magic number.
pub fn decode(bytes : Bytes) -> @audio.AudioBuffer raise @audio.AudioError {
  match sniff(bytes) {
    Some(Wav) => @wav.decode(bytes)
    Some(Aiff) => @aiff.decode(bytes)
    Some(Au) => @au.decode(bytes)
    Some(Flac) => @flac.decode(bytes)
    None => raise @audio.Unsupported("unrecognised audio container")
  }
}

///|
/// Decode any supported container and also report which one it was.
pub fn decode_detected(
  bytes : Bytes,
) -> (Container, @audio.AudioBuffer) raise @audio.AudioError {
  let container = match sniff(bytes) {
    Some(container) => container
    None => raise @audio.Unsupported("unrecognised audio container")
  }
  let audio = match container {
    Wav => @wav.decode(bytes)
    Aiff => @aiff.decode(bytes)
    Au => @au.decode(bytes)
    Flac => @flac.decode(bytes)
  }
  (container, audio)
}

///|
/// Human readable summary of a supported file, without decoding the samples.
pub fn describe(bytes : Bytes) -> String raise @audio.AudioError {
  match sniff(bytes) {
    Some(Wav) => @wav.parse_info(bytes).describe()
    Some(Aiff) => @aiff.parse_info(bytes).describe()
    Some(Au) => @au.parse_info(bytes).describe()
    Some(Flac) => @flac.parse_info(bytes).describe()
    None => raise @audio.Unsupported("unrecognised audio container")
  }
}