// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
pub suberror DecoderError {
  Backend(@decoder.DecoderError)
  UnrecognizedFormat
} derive(Show, Eq)

///|
pub suberror PlayError {
  DecoderError(DecoderError)
} derive(Show, Eq)

///|
enum DecoderKind {
  Wav
  Flac
  Vorbis
  Mp3
  Mp4a
} derive(Show, Eq)

///|
pub struct Decoder {
  inner : @decoder.DecodedSamples
  seekable : Bool
  allow_backward_seek : Bool
  kind : DecoderKind
}

///|
pub struct Settings {
  byte_len : Int?
  coarse_seek : Bool
  gapless : Bool
  hint : String?
  mime_type : String?
  is_seekable : Bool
} derive(Show, Eq)

///|
pub fn Settings::default() -> Settings {
  {
    byte_len: None,
    coarse_seek: false,
    gapless: true,
    hint: None,
    mime_type: None,
    is_seekable: false,
  }
}

///|
pub struct DecoderBuilder {
  data : Bytes?
  settings : Settings
}

///|
pub struct Reader {
  bytes : Bytes
}

///|
pub fn Reader::from_bytes(bytes : Bytes) -> Reader {
  { bytes, }
}

///|
pub fn Reader::from_file(path : StringView) -> Reader raise PlayError {
  let bytes = try @fs.read_file_to_bytes(path.to_string()) catch {
    _ => raise PlayError::DecoderError(DecoderError::UnrecognizedFormat)
  } noraise {
    bs => bs
  }
  { bytes, }
}

///|
pub fn Reader::into_bytes(self : Reader) -> Bytes {
  self.bytes
}

///|
pub fn DecoderBuilder::new() -> DecoderBuilder {
  { data: None, settings: Settings::default() }
}

///|
pub fn DecoderBuilder::with_data(
  self : DecoderBuilder,
  data : Bytes,
) -> DecoderBuilder {
  { ..self, data: Some(data) }
}

///|
pub fn DecoderBuilder::with_byte_len(
  self : DecoderBuilder,
  byte_len : Int,
) -> DecoderBuilder {
  guard byte_len >= 0 else { panic() }
  {
    ..self,
    settings: { ..self.settings, byte_len: Some(byte_len), is_seekable: true },
  }
}

///|
pub fn DecoderBuilder::with_coarse_seek(
  self : DecoderBuilder,
  coarse_seek : Bool,
) -> DecoderBuilder {
  { ..self, settings: { ..self.settings, coarse_seek, } }
}

///|
pub fn DecoderBuilder::with_gapless(
  self : DecoderBuilder,
  gapless : Bool,
) -> DecoderBuilder {
  { ..self, settings: { ..self.settings, gapless, } }
}

///|
pub fn DecoderBuilder::with_hint(
  self : DecoderBuilder,
  hint : StringView,
) -> DecoderBuilder {
  { ..self, settings: { ..self.settings, hint: Some(hint.to_string()) } }
}

///|
pub fn DecoderBuilder::with_mime_type(
  self : DecoderBuilder,
  mime_type : StringView,
) -> DecoderBuilder {
  {
    ..self,
    settings: { ..self.settings, mime_type: Some(mime_type.to_string()) },
  }
}

///|
pub fn DecoderBuilder::with_seekable(
  self : DecoderBuilder,
  is_seekable : Bool,
) -> DecoderBuilder {
  { ..self, settings: { ..self.settings, is_seekable, } }
}

///|
pub struct LoopedDecoder {
  channels : ChannelCount
  sample_rate : SampleRate
  samples : Array[Sample]
  cursor : Ref[Int]
  seekable : Bool
}

///|
fn hinted_decode(bytes : Bytes, hint : String) -> Decoder? {
  if hint.contains("wav") || hint.contains("WAV") {
    return Some(decode_wav_or_raise(bytes)) catch { _ => None }
  }
  if hint.contains("m4a") ||
    hint.contains("M4A") ||
    hint.contains("mp4") ||
    hint.contains("MP4") ||
    hint.contains("aac") ||
    hint.contains("AAC") {
    return Some(decode_mp4a_or_raise(bytes)) catch { _ => None }
  }
  if hint.contains("flac") || hint.contains("FLAC") {
    return Some(decode_flac_or_raise(bytes)) catch { _ => None }
  }
  if hint.contains("ogg") ||
    hint.contains("OGG") ||
    hint.contains("vorbis") ||
    hint.contains("VORBIS") {
    return Some(decode_vorbis_or_raise(bytes)) catch { _ => None }
  }
  if hint.contains("mp3") ||
    hint.contains("MP3") ||
    hint.contains("mpeg") ||
    hint.contains("MPEG") {
    return Some(decode_mp3_or_raise(bytes)) catch { _ => None }
  }
  None
}

///|
fn DecoderBuilder::decoder_builder_build(
  self : DecoderBuilder,
) -> Decoder raise DecoderError {
  let data = match self.data {
    None => raise DecoderError::UnrecognizedFormat
    Some(data) => data
  }

  let hinted = match self.settings.hint {
    None => None
    Some(hint) => hinted_decode(data, hint)
  }
  let decoded = match hinted {
    Some(decoder) => decoder
    None => {
      let mime_hint = match self.settings.mime_type {
        None => None
        Some(hint) => hinted_decode(data, hint)
      }
      match mime_hint {
        Some(decoder) => decoder
        None => Decoder::new(data)
      }
    }
  }

  let allow_backward_seek = if !self.settings.is_seekable {
    false
  } else {
    match decoded.kind {
      DecoderKind::Mp3 => self.settings.byte_len is Some(_)
      _ => true
    }
  }

  { ..decoded, seekable: self.settings.is_seekable, allow_backward_seek }
}

///|
pub fn DecoderBuilder::build(
  self : DecoderBuilder,
) -> Decoder raise DecoderError {
  self.decoder_builder_build()
}

///|
fn to_looped_decoder(decoder : Decoder) -> LoopedDecoder {
  let channels = decoder.channels()
  let sample_rate = decoder.sample_rate()
  let seekable = decoder.seekable
  let samples : Array[Sample] = []
  while true {
    match decoder.next() {
      None => break
      Some(s) => samples.push(s)
    }
  }
  { channels, sample_rate, samples, cursor: @ref.new(0), seekable }
}

///|
pub fn DecoderBuilder::build_looped(
  self : DecoderBuilder,
) -> LoopedDecoder raise DecoderError {
  to_looped_decoder(self.decoder_builder_build())
}

///|
pub fn LoopedDecoder::next(self : LoopedDecoder) -> Sample? {
  if self.samples.is_empty() {
    None
  } else {
    let idx = self.cursor.val
    let value = self.samples[idx]
    self.cursor.val = (idx + 1) % self.samples.length()
    Some(value)
  }
}

///|
pub fn LoopedDecoder::channels(self : LoopedDecoder) -> ChannelCount {
  self.channels
}

///|
pub fn LoopedDecoder::sample_rate(self : LoopedDecoder) -> SampleRate {
  self.sample_rate
}

///|
pub impl Source for LoopedDecoder with next(self : LoopedDecoder) {
  self.next()
}

///|
pub impl Source for LoopedDecoder with channels(self : LoopedDecoder) {
  self.channels()
}

///|
pub impl Source for LoopedDecoder with sample_rate(self : LoopedDecoder) {
  self.sample_rate()
}

///|
pub impl Source for LoopedDecoder with current_span_len(self : LoopedDecoder) {
  if self.samples.is_empty() {
    None
  } else {
    Some(self.samples.length() - self.cursor.val)
  }
}

///|
pub impl Source for LoopedDecoder with total_duration(self : LoopedDecoder) {
  ignore(self)
  None
}

///|
pub impl Source for LoopedDecoder with try_seek(
  self : LoopedDecoder,
  pos : @moon_cpal.Duration,
) -> Unit raise SeekError {
  if !self.seekable {
    raise SeekError::NotSupported
  }
  if self.samples.is_empty() {
    raise SeekError::NotSupported
  }
  let max_index = self.samples.length() - 1
  let target = sample_index_from_duration(
    pos,
    self.channels(),
    self.sample_rate(),
  )
  let clamped = if target < 0 {
    0
  } else if target > max_index {
    max_index
  } else {
    target
  }
  self.cursor.val = clamped
}

///|
fn bytes_eq4(
  bytes : Bytes,
  offset : Int,
  b0 : Int,
  b1 : Int,
  b2 : Int,
  b3 : Int,
) -> Bool {
  offset + 4 <= bytes.length() &&
  bytes[offset].to_int() == b0 &&
  bytes[offset + 1].to_int() == b1 &&
  bytes[offset + 2].to_int() == b2 &&
  bytes[offset + 3].to_int() == b3
}

///|
fn looks_like_wav(bytes : Bytes) -> Bool {
  bytes.length() >= 12 &&
  bytes_eq4(bytes, 0, 0x52, 0x49, 0x46, 0x46) &&
  bytes_eq4(bytes, 8, 0x57, 0x41, 0x56, 0x45)
}

///|
fn looks_like_flac(bytes : Bytes) -> Bool {
  bytes_eq4(bytes, 0, 0x66, 0x4c, 0x61, 0x43)
}

///|
fn looks_like_ogg(bytes : Bytes) -> Bool {
  bytes_eq4(bytes, 0, 0x4f, 0x67, 0x67, 0x53)
}

///|
fn looks_like_mp3(bytes : Bytes) -> Bool {
  if bytes.length() >= 3 &&
    bytes[0].to_int() == 0x49 &&
    bytes[1].to_int() == 0x44 &&
    bytes[2].to_int() == 0x33 {
    return true
  }

  if bytes.length() >= 2 {
    let b0 = bytes[0].to_int()
    let b1 = bytes[1].to_int()
    return b0 == 0xff && (b1 & 0xe0) == 0xe0
  }

  false
}

///|
fn looks_like_mp4a(bytes : Bytes) -> Bool {
  bytes.length() >= 12 && bytes_eq4(bytes, 4, 0x66, 0x74, 0x79, 0x70)
}

///|
fn decode_wav_or_raise(bytes : Bytes) -> Decoder raise DecoderError {
  let decoded = try @decoder.decode_wav_bytes(bytes) catch {
    err => raise DecoderError::Backend(err)
  } noraise {
    src => src
  }
  {
    inner: decoded,
    seekable: true,
    allow_backward_seek: true,
    kind: DecoderKind::Wav,
  }
}

///|
fn decode_flac_or_raise(bytes : Bytes) -> Decoder raise DecoderError {
  let decoded = try @decoder.decode_flac_bytes(bytes) catch {
    err => raise DecoderError::Backend(err)
  } noraise {
    src => src
  }
  {
    inner: decoded,
    seekable: true,
    allow_backward_seek: true,
    kind: DecoderKind::Flac,
  }
}

///|
fn decode_vorbis_or_raise(bytes : Bytes) -> Decoder raise DecoderError {
  let decoded = try @decoder.decode_vorbis_bytes(bytes) catch {
    err => raise DecoderError::Backend(err)
  } noraise {
    src => src
  }
  {
    inner: decoded,
    seekable: true,
    allow_backward_seek: true,
    kind: DecoderKind::Vorbis,
  }
}

///|
fn decode_mp3_or_raise(bytes : Bytes) -> Decoder raise DecoderError {
  let decoded = try @decoder.decode_mp3_bytes(bytes) catch {
    err => raise DecoderError::Backend(err)
  } noraise {
    src => src
  }
  {
    inner: decoded,
    seekable: true,
    allow_backward_seek: true,
    kind: DecoderKind::Mp3,
  }
}

///|
fn decode_mp4a_or_raise(bytes : Bytes) -> Decoder raise DecoderError {
  let decoded = try @decoder.decode_mp4a_bytes(bytes) catch {
    err => raise DecoderError::Backend(err)
  } noraise {
    src => src
  }
  {
    inner: decoded,
    seekable: true,
    allow_backward_seek: true,
    kind: DecoderKind::Mp4a,
  }
}

///|
pub fn Decoder::new(bytes : Bytes) -> Decoder raise DecoderError {
  guard bytes.length() > 0 else { raise DecoderError::UnrecognizedFormat }

  if looks_like_wav(bytes) {
    return decode_wav_or_raise(bytes)
  }
  if looks_like_mp4a(bytes) {
    return decode_mp4a_or_raise(bytes)
  }
  if looks_like_flac(bytes) {
    return decode_flac_or_raise(bytes)
  }
  if looks_like_ogg(bytes) {
    return decode_vorbis_or_raise(bytes)
  }
  if looks_like_mp3(bytes) {
    return decode_mp3_or_raise(bytes)
  }

  let try_wav = Some(decode_wav_or_raise(bytes)) catch { _ => None }
  match try_wav {
    Some(decoder) => return decoder
    None => ()
  }

  let try_flac = Some(decode_flac_or_raise(bytes)) catch { _ => None }
  match try_flac {
    Some(decoder) => return decoder
    None => ()
  }

  let try_vorbis = Some(decode_vorbis_or_raise(bytes)) catch { _ => None }
  match try_vorbis {
    Some(decoder) => return decoder
    None => ()
  }

  let try_mp3 = Some(decode_mp3_or_raise(bytes)) catch { _ => None }
  match try_mp3 {
    Some(decoder) => return decoder
    None => ()
  }

  let try_mp4a = Some(decode_mp4a_or_raise(bytes)) catch { _ => None }
  match try_mp4a {
    Some(decoder) => return decoder
    None => raise DecoderError::UnrecognizedFormat
  }
}

///|
pub fn Decoder::builder() -> DecoderBuilder {
  DecoderBuilder::new()
}

///|
pub fn Decoder::new_looped(bytes : Bytes) -> LoopedDecoder raise DecoderError {
  Decoder::builder().with_data(bytes).build_looped()
}

///|
pub fn Decoder::try_from_file(path : StringView) -> Decoder raise DecoderError {
  let bytes = try @fs.read_file_to_bytes(path.to_string()) catch {
    _ => raise DecoderError::UnrecognizedFormat
  } noraise {
    bs => bs
  }
  Decoder::new(bytes)
}

///|
pub fn Decoder::try_from_file_looped(
  path : StringView,
) -> LoopedDecoder raise DecoderError {
  let bytes = try @fs.read_file_to_bytes(path.to_string()) catch {
    _ => raise DecoderError::UnrecognizedFormat
  } noraise {
    bs => bs
  }
  Decoder::new_looped(bytes)
}

///|
pub fn Decoder::new_wav(bytes : Bytes) -> Decoder raise DecoderError {
  decode_wav_or_raise(bytes)
}

///|
pub fn Decoder::new_flac(bytes : Bytes) -> Decoder raise DecoderError {
  decode_flac_or_raise(bytes)
}

///|
pub fn Decoder::new_vorbis(bytes : Bytes) -> Decoder raise DecoderError {
  decode_vorbis_or_raise(bytes)
}

///|
pub fn Decoder::new_mp3(bytes : Bytes) -> Decoder raise DecoderError {
  decode_mp3_or_raise(bytes)
}

///|
pub fn Decoder::new_mp4a(bytes : Bytes) -> Decoder raise DecoderError {
  decode_mp4a_or_raise(bytes)
}

///|
pub fn Decoder::new_aac(bytes : Bytes) -> Decoder raise DecoderError {
  Decoder::new_mp4a(bytes)
}

///|
pub fn Decoder::new_mp4(bytes : Bytes) -> Decoder raise DecoderError {
  Decoder::new_mp4a(bytes)
}

///|
pub fn Decoder::next(self : Decoder) -> Sample? {
  self.inner.next()
}

///|
pub fn Decoder::channels(self : Decoder) -> ChannelCount {
  self.inner.channels()
}

///|
pub fn Decoder::sample_rate(self : Decoder) -> SampleRate {
  self.inner.sample_rate()
}

///|
pub impl Source for Decoder with next(self : Decoder) {
  self.next()
}

///|
pub impl Source for Decoder with channels(self : Decoder) {
  self.channels()
}

///|
pub impl Source for Decoder with sample_rate(self : Decoder) {
  self.sample_rate()
}

///|
pub impl Source for Decoder with current_span_len(self : Decoder) {
  let remaining = self.inner.len() - self.inner.position()
  if remaining <= 0 {
    Some(0)
  } else {
    Some(remaining)
  }
}

///|
pub impl Source for Decoder with total_duration(self : Decoder) {
  duration_from_sample_count(
    self.inner.len(),
    self.channels(),
    self.sample_rate(),
  )
}

///|
pub impl Source for Decoder with try_seek(
  self : Decoder,
  pos : @moon_cpal.Duration,
) -> Unit raise SeekError {
  if !self.seekable {
    raise SeekError::NotSupported
  }
  let channel_count = self.channels()
  let current_channel = if channel_count > 0 {
    self.inner.position() % channel_count
  } else {
    0
  }

  let target = sample_index_from_duration(
    pos,
    channel_count,
    self.sample_rate(),
  )
  let clamped_target = if target < 0 {
    0
  } else if target > self.inner.len() {
    self.inner.len()
  } else {
    target
  }
  let aligned_target = if channel_count > 0 {
    let rem = clamped_target % channel_count
    if rem == 0 {
      clamped_target
    } else {
      clamped_target + channel_count - rem
    }
  } else {
    clamped_target
  }
  let with_channel_offset = if aligned_target >= current_channel {
    aligned_target - current_channel
  } else {
    0
  }
  let final_target = if with_channel_offset > self.inner.len() {
    self.inner.len()
  } else {
    with_channel_offset
  }

  if !self.allow_backward_seek && final_target < self.inner.position() {
    raise SeekError::NotSupported
  }
  self.inner.seek_to(final_target)
}

///|
pub fn play(mixer : Mixer, reader : Reader) -> Player raise PlayError {
  play_bytes(mixer, reader.into_bytes())
}

///|
pub fn play_bytes(mixer : Mixer, bytes : Bytes) -> Player raise PlayError {
  let source = try Decoder::new(bytes) catch {
    err => raise PlayError::DecoderError(err)
  } noraise {
    src => src
  }
  let player = Player::connect_new(mixer)
  player.append(source)
  player
}

///|
pub fn play_file(mixer : Mixer, path : StringView) -> Player raise PlayError {
  let reader = Reader::from_file(path)
  play(mixer, reader)
}

///|
pub fn play_reader(mixer : Mixer, reader : Reader) -> Player raise PlayError {
  play(mixer, reader)
}