///|
/// 流式播放标志:边解码边播放,不将整个音频加载进内存,适合较大的音频文件。
pub const MA_SOUND_FLAG_STREAM = 0x00000001

///|
/// 预解码标志:加载时将整个音频解码进内存,之后播放不再消耗解码开销。
pub const MA_SOUND_FLAG_DECODE = 0x00000002

///|
/// 异步加载标志:在后台线程完成加载,`load_sound` 会尽快返回。
pub const MA_SOUND_FLAG_ASYNC = 0x00000004

///|
/// 等待初始化标志:加载时阻塞等待声音完成初始化后再返回。
pub const MA_SOUND_FLAG_WAIT_INIT = 0x00000008

///|
/// 未知长度标志:声明声音长度未知(例如网络电台等无限流)。
pub const MA_SOUND_FLAG_UNKNOWN_LENGTH = 0x00000010

///|
/// 循环播放标志:声音加载后即处于循环播放状态。
pub const MA_SOUND_FLAG_LOOPING = 0x00000020

///|
/// 禁止默认挂接标志:不将声音自动挂接到资源管理器的默认终点,需自行处理输出。
pub const MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT = 0x00001000

///|
/// 禁用变调标志:关闭 `set_pitch` 相关的变调支持,以减少处理开销。
pub const MA_SOUND_FLAG_NO_PITCH = 0x00002000

///|
/// 禁用空间化标志:关闭 3D 空间化处理,以减少处理开销。
pub const MA_SOUND_FLAG_NO_SPATIALIZATION = 0x00004000

///|
/// miniaudio 播放引擎的封装,负责音频设备初始化与声音加载。
/// 使用完毕后应调用 `free` 释放底层资源。
pub struct MaEngine {
  priv _cengine : @ffi.MaEngine
}

///|
/// 声音对象的封装,通过 `MaEngine::load_sound` 加载得到。
/// 使用完毕后应调用 `free` 释放底层资源。
pub struct MaSound {
  priv _cma_sound : @ffi.MaSound
}

///|
/// 构造引擎相关错误信息,格式为 `": <底层错误描述>"`。
fn engine_error(engine : MaEngine, operation : String) -> String {
  "\{operation}: \{@ffi.get_error(engine._cengine)}"
}

///|
/// 构造声音相关错误信息,格式为 `": <底层错误描述>"`。
fn sound_error(sound : MaSound, operation : String) -> String {
  "\{operation}: \{@ffi.get_sound_error(sound._cma_sound)}"
}

///|
/// 创建并初始化一个新的播放引擎。
///
/// 底层初始化失败时返回 `Err`,错误信息包含 miniaudio 的错误描述。
pub fn MaEngine::new() -> Result[MaEngine, String] {
  let engine = MaEngine::{ _cengine: @ffi.init_engine(), }

  if @ffi.check_engine(engine._cengine) {
    Ok(engine)
  } else {
    let error = engine_error(engine, "initialize engine")
    engine.free()
    Err(error)
  }
}

///|
/// 从文件加载一个声音对象。
///
/// - `file`:音频文件路径,支持 mp3、wav、flac、ogg 等常见格式。
/// - `flags`:加载标志,可由 `MA_SOUND_FLAG_*` 常量按位或组合。
///
/// 加载失败时返回 `Err`,错误信息包含 miniaudio 的错误描述。
pub fn MaEngine::load_sound(
  engine : MaEngine,
  file : String,
  flags : Int,
) -> Result[MaSound, String] {
  let sound = @ffi.init_sound_from_file(
    engine._cengine,
    @utf8.encode(file),
    flags,
  )

  if @ffi.check_engine(engine._cengine) {
    Ok(MaSound::{ _cma_sound: sound, })
  } else {
    Err(engine_error(engine, "load sound"))
  }
}

///|
/// 判断声音当前是否正在播放。
pub fn MaSound::playing(sound : MaSound) -> Bool {
  @ffi.check_playing(sound._cma_sound)
}

///|
/// 释放声音占用的底层资源,调用后不应再使用该声音对象。
///
/// 应在释放所属引擎 `MaEngine::free` 之前调用。
pub fn MaSound::free(sound : MaSound) -> Unit {
  @ffi.free_sound(sound._cma_sound)
}

///|
/// 释放引擎占用的底层资源。
///
/// 应先释放由该引擎加载的所有声音,再释放引擎。
pub fn MaEngine::free(engine : MaEngine) -> Unit {
  @ffi.free_engine(engine._cengine)
}

///|
/// 开始播放声音,失败时返回 `Err`。
pub fn MaSound::play(sound : MaSound) -> Result[Unit, String] {
  if @ffi.play(sound._cma_sound) == 0 {
    Ok(())
  } else {
    Err(sound_error(sound, "play sound"))
  }
}

///|
/// 停止播放声音,失败时返回 `Err`。
///
/// 停止后再次调用 `play` 会从停止位置继续播放。
pub fn MaSound::stop(sound : MaSound) -> Result[Unit, String] {
  if @ffi.stop(sound._cma_sound) == 0 {
    Ok(())
  } else {
    Err(sound_error(sound, "stop sound"))
  }
}

///|
/// 获取当前音量(线性增益,1.0 为默认值,允许大于 1.0 进行放大)。
pub fn MaSound::get_volume(sound : MaSound) -> Float {
  @ffi.get_volume(sound._cma_sound)
}

///|
/// 设置音量(线性增益,1.0 为默认值,允许大于 1.0 进行放大)。
///
/// `volume` 必须为非负数,否则返回 `Err`;底层设置失败时同样返回 `Err`。
pub fn MaSound::set_volume(
  sound : MaSound,
  volume : Float,
) -> Result[Unit, String] {
  if volume < 0.0 {
    return Err("set volume: volume must be non-negative")
  }
  if @ffi.set_volume(sound._cma_sound, volume) == 0 {
    Ok(())
  } else {
    Err(sound_error(sound, "set volume"))
  }
}

///|
/// 获取声音总时长(单位:秒)。
///
/// 长度未知(例如流式播放或未知长度标志)或获取失败时返回 `Err`。
pub fn MaSound::get_length(sound : MaSound) -> Result[Float, String] {
  let len = @ffi.get_length(sound._cma_sound)

  if len > -1 {
    Ok(len)
  } else {
    Err(sound_error(sound, "get length"))
  }
}

///|
/// 获取当前播放位置(单位:秒),获取失败时返回 `Err`。
pub fn MaSound::get_position(sound : MaSound) -> Result[Float, String] {
  let pos = @ffi.get_position(sound._cma_sound)

  if pos > -1 {
    Ok(pos)
  } else {
    Err(sound_error(sound, "get position"))
  }
}

///|
/// 跳转到指定播放位置(单位:秒)。
///
/// `pos` 必须为非负数,否则返回 `Err`;跳转失败时同样返回 `Err`。
pub fn MaSound::seek_to_second(
  sound : MaSound,
  pos : Double,
) -> Result[Unit, String] {
  if pos < 0.0 {
    return Err("seek sound: position must be non-negative")
  }
  let res = @ffi.seek_to_second(sound._cma_sound, pos)

  if res == 0 {
    Ok(())
  } else {
    Err(sound_error(sound, "seek sound"))
  }
}