// 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 type Sample = Double

///|
pub type ChannelCount = Int

///|
pub type SampleRate = Int

///|
let hz_44100 : SampleRate = 44_100

///|
pub(open) trait Source {
  fn next(Self) -> Sample?
  fn channels(Self) -> ChannelCount
  fn sample_rate(Self) -> SampleRate
  fn current_span_len(Self) -> Int?
  fn total_duration(Self) -> @moon_cpal.Duration?
  fn try_seek(Self, pos : @moon_cpal.Duration) -> Unit raise SeekError
}

///|
pub fn[S : Source] is_exhausted(source : S) -> Bool {
  source.current_span_len() == Some(0)
}

///|
pub struct DynSource {
  next_sample : () -> Sample?
  channels_fn : () -> ChannelCount
  sample_rate_fn : () -> SampleRate
  current_span_len_fn : () -> Int?
  total_duration_fn : () -> @moon_cpal.Duration?
  try_seek_fn : (@moon_cpal.Duration) -> Result[Unit, SeekError]
}

///|
fn source_default_try_seek_result(
  pos : @moon_cpal.Duration,
) -> Result[Unit, SeekError] {
  try {
    source_default_try_seek(pos)
    Ok(())
  } catch {
    err => Err(err)
  }
}

///|
pub fn DynSource::new(
  next_sample : () -> Sample?,
  channels : ChannelCount,
  sample_rate : SampleRate,
  current_span_len? : () -> Int? = source_default_current_span_len,
  total_duration? : () -> @moon_cpal.Duration? = source_default_total_duration,
  try_seek? : (@moon_cpal.Duration) -> Result[Unit, SeekError] = source_default_try_seek_result,
) -> DynSource {
  guard channels > 0 else { panic() }
  guard sample_rate > 0 else { panic() }
  {
    next_sample,
    channels_fn: fn() { channels },
    sample_rate_fn: fn() { sample_rate },
    current_span_len_fn: current_span_len,
    total_duration_fn: total_duration,
    try_seek_fn: try_seek,
  }
}

///|
pub fn DynSource::new_dynamic(
  next_sample : () -> Sample?,
  channels : () -> ChannelCount,
  sample_rate : () -> SampleRate,
  current_span_len? : () -> Int? = source_default_current_span_len,
  total_duration? : () -> @moon_cpal.Duration? = source_default_total_duration,
  try_seek? : (@moon_cpal.Duration) -> Result[Unit, SeekError] = source_default_try_seek_result,
) -> DynSource {
  guard channels() > 0 else { panic() }
  guard sample_rate() > 0 else { panic() }
  {
    next_sample,
    channels_fn: channels,
    sample_rate_fn: sample_rate,
    current_span_len_fn: current_span_len,
    total_duration_fn: total_duration,
    try_seek_fn: try_seek,
  }
}

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

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

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

///|
pub fn DynSource::current_span_len(self : DynSource) -> Int? {
  (self.current_span_len_fn)()
}

///|
pub fn DynSource::total_duration(self : DynSource) -> @moon_cpal.Duration? {
  (self.total_duration_fn)()
}

///|
pub fn DynSource::try_seek(
  self : DynSource,
  pos : @moon_cpal.Duration,
) -> Unit raise SeekError {
  match (self.try_seek_fn)(pos) {
    Ok(_) => ()
    Err(err) => raise err
  }
}

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

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

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

///|
pub impl Source for DynSource with fn current_span_len(_self : DynSource) {
  _self.current_span_len()
}

///|
pub impl Source for DynSource with fn total_duration(_self : DynSource) {
  _self.total_duration()
}

///|
pub impl Source for DynSource with fn try_seek(
  _self : DynSource,
  pos : @moon_cpal.Duration,
) -> Unit raise SeekError {
  _self.try_seek(pos)
}

///|
pub fn[S : Source] to_dyn(source : S) -> DynSource {
  DynSource::new_dynamic(
    fn() { source.next() },
    fn() { source.channels() },
    fn() { source.sample_rate() },
    current_span_len=fn() { source.current_span_len() },
    total_duration=fn() { source.total_duration() },
    try_seek=fn(pos : @moon_cpal.Duration) {
      try {
        source.try_seek(pos)
        Ok(())
      } catch {
        err => Err(err)
      }
    },
  )
}

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

///|
pub fn SamplesBuffer::new(
  channels : ChannelCount,
  sample_rate : SampleRate,
  samples : Array[Sample],
) -> SamplesBuffer {
  guard channels > 0 else { panic() }
  guard sample_rate > 0 else { panic() }
  { channels, sample_rate, samples, cursor: @ref.new(0) }
}

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

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

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

///|
fn duration_from_sample_count(
  total_samples : Int,
  channels : ChannelCount,
  sample_rate : SampleRate,
) -> @moon_cpal.Duration? {
  if total_samples < 0 || channels <= 0 || sample_rate <= 0 {
    return None
  }
  let per_second = channels.to_uint64() * sample_rate.to_uint64()
  let total = total_samples.to_uint64()
  let secs = total / per_second
  let rem_samples = total % per_second
  let nanos = (rem_samples * (1_000_000_000 : UInt64) / per_second).to_int()
  Some(@moon_cpal.Duration::new(secs, nanos))
}

///|
fn sample_index_from_duration(
  pos : @moon_cpal.Duration,
  channels : ChannelCount,
  sample_rate : SampleRate,
) -> Int {
  if channels <= 0 || sample_rate <= 0 {
    return 0
  }
  let per_second = sample_rate.to_uint64() * channels.to_uint64()
  let secs_part = pos.secs * per_second
  let nanos_part = pos.nanos.to_uint64() * per_second / (1_000_000_000 : UInt64)
  (secs_part + nanos_part).to_int()
}

///|
fn duration_secs_f64(pos : @moon_cpal.Duration) -> Double {
  Double::from_int(pos.secs.to_int()) +
  Double::from_int(pos.nanos) / 1_000_000_000.0
}

///|
fn duration_from_secs_floor(secs : Double) -> @moon_cpal.Duration {
  if secs <= 0.0 {
    @moon_cpal.Duration::from_secs((0 : UInt64))
  } else {
    let secs_int = secs.to_int()
    let frac = secs - Double::from_int(secs_int)
    let nanos = (frac * 1_000_000_000.0).to_int()
    @moon_cpal.Duration::new(secs_int.to_uint64(), nanos)
  }
}

///|
fn duration_mul_ratio_floor(
  pos : @moon_cpal.Duration,
  ratio : Double,
) -> @moon_cpal.Duration {
  duration_from_secs_floor(duration_secs_f64(pos) * ratio)
}

///|
fn duration_div_ratio_floor(
  pos : @moon_cpal.Duration,
  ratio : Double,
) -> @moon_cpal.Duration {
  if ratio <= 0.0 {
    @moon_cpal.Duration::from_secs((0 : UInt64))
  } else {
    duration_from_secs_floor(duration_secs_f64(pos) / ratio)
  }
}

///|
fn duration_compare(a : @moon_cpal.Duration, b : @moon_cpal.Duration) -> Int {
  if a.secs < b.secs {
    -1
  } else if a.secs > b.secs {
    1
  } else if a.nanos < b.nanos {
    -1
  } else if a.nanos > b.nanos {
    1
  } else {
    0
  }
}

///|
fn duration_min(
  a : @moon_cpal.Duration,
  b : @moon_cpal.Duration,
) -> @moon_cpal.Duration {
  if duration_compare(a, b) <= 0 {
    a
  } else {
    b
  }
}

///|
fn duration_add(
  a : @moon_cpal.Duration,
  b : @moon_cpal.Duration,
) -> @moon_cpal.Duration {
  @moon_cpal.Duration::new(a.secs + b.secs, a.nanos + b.nanos)
}

///|
fn duration_saturating_sub(
  a : @moon_cpal.Duration,
  b : @moon_cpal.Duration,
) -> @moon_cpal.Duration {
  if duration_compare(a, b) <= 0 {
    @moon_cpal.Duration::from_secs((0 : UInt64))
  } else if a.nanos >= b.nanos {
    @moon_cpal.Duration::new(a.secs - b.secs, a.nanos - b.nanos)
  } else {
    @moon_cpal.Duration::new(
      a.secs - b.secs - (1 : UInt64),
      a.nanos + 1_000_000_000 - b.nanos,
    )
  }
}

///|
fn source_default_current_span_len() -> Int? {
  None
}

///|
fn source_default_total_duration() -> @moon_cpal.Duration? {
  None
}

///|
fn source_default_try_seek(_pos : @moon_cpal.Duration) -> Unit raise SeekError {
  raise NotSupported
}

///|
pub fn SamplesBuffer::amplify(
  self : SamplesBuffer,
  factor : Sample,
) -> DynSource {
  let src = to_dyn(self)
  DynSource::new(
    fn() {
      match src.next() {
        None => None
        Some(v) => Some(v * factor)
      }
    },
    src.channels(),
    src.sample_rate(),
  )
}

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

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

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

///|
pub impl Source for SamplesBuffer with fn current_span_len(self : SamplesBuffer) {
  let remaining = self.samples.length() - self.cursor.val
  if remaining <= 0 {
    Some(0)
  } else {
    Some(remaining)
  }
}

///|
pub impl Source for SamplesBuffer with fn total_duration(self : SamplesBuffer) {
  duration_from_sample_count(
    self.samples.length(),
    self.channels(),
    self.sample_rate(),
  )
}

///|
pub impl Source for SamplesBuffer with fn try_seek(
  self : SamplesBuffer,
  pos : @moon_cpal.Duration,
) -> Unit raise SeekError {
  let channel_count = self.channels()
  let current_channel = if channel_count > 0 {
    self.cursor.val % 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.samples.length() {
    self.samples.length()
  } 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
  }
  self.cursor.val = if with_channel_offset > self.samples.length() {
    self.samples.length()
  } else {
    with_channel_offset
  }
}

///|
pub fn[S : Source] record(source : S) -> SamplesBuffer {
  let channels = source.channels()
  let sample_rate = source.sample_rate()
  let uniform = uniform_source(to_dyn(source), channels, sample_rate)
  let samples : Array[Sample] = []
  while true {
    match uniform.next() {
      Some(sample) => samples.push(sample)
      None => break
    }
  }
  SamplesBuffer::new(channels, sample_rate, samples)
}

///|
pub fn[S : Source] SamplesBuffer::record_source(source : S) -> SamplesBuffer {
  record(source)
}

///|
fn make_empty_dyn_source(
  channels : ChannelCount,
  sample_rate : SampleRate,
) -> DynSource {
  DynSource::new(fn() { None }, channels, sample_rate, total_duration=fn() {
    Some(@moon_cpal.Duration::from_secs((0 : UInt64)))
  })
}

///|
fn make_zero_dyn_source(
  channels : ChannelCount,
  sample_rate : SampleRate,
  sample_count : Int,
) -> DynSource {
  let remaining = @ref.new(sample_count)
  DynSource::new(
    fn() {
      if remaining.val <= 0 {
        None
      } else {
        remaining.val -= 1
        Some(0.0)
      }
    },
    channels,
    sample_rate,
    current_span_len=fn() {
      Some(if remaining.val <= 0 { 0 } else { remaining.val })
    },
    try_seek=fn(_pos : @moon_cpal.Duration) { Ok(()) },
  )
}

///|
fn channel_convert(
  input : DynSource,
  target_channels : ChannelCount,
) -> DynSource {
  let from = input.channels()
  let to = target_channels
  if from == to {
    return input
  }

  let sample_repeat = @ref.new(None)
  let next_output_sample_pos = @ref.new(0)

  DynSource::new(
    fn() {
      let pos = next_output_sample_pos.val
      let result = if pos == 0 {
        let value = input.next()
        sample_repeat.val = value
        value
      } else if pos < from {
        input.next()
      } else if pos == 1 {
        sample_repeat.val
      } else {
        Some(0.0)
      }

      match result {
        None => None
        Some(value) => {
          next_output_sample_pos.val += 1
          if next_output_sample_pos.val == to {
            next_output_sample_pos.val = 0
            if from > to {
              for _ in to.. Array[Sample]? {
  let frame : Array[Sample] = []
  for _ in 0.. return None
      Some(v) => frame.push(v)
    }
  }
  Some(frame)
}

///|
fn sample_rate_convert(
  input : DynSource,
  target_sample_rate : SampleRate,
) -> DynSource {
  let from_rate = input.sample_rate()
  let to_rate = target_sample_rate
  let channels = input.channels()

  if from_rate == to_rate {
    return input
  }

  let left = @ref.new(read_frame(input, channels))
  let right = @ref.new(read_frame(input, channels))
  let base_index = @ref.new(0)
  let output_index = @ref.new(0)
  let pending = @ref.new([])
  let pending_cursor = @ref.new(0)

  DynSource::new(
    fn() {
      if pending_cursor.val < pending.val.length() {
        let value = pending.val[pending_cursor.val]
        pending_cursor.val += 1
        return Some(value)
      }

      let position_num = output_index.val * from_rate
      let required_left_index = position_num / to_rate
      let frac_num = position_num % to_rate

      while required_left_index > base_index.val {
        match right.val {
          None => break
          Some(rf) => {
            left.val = Some(rf)
            right.val = read_frame(input, channels)
            base_index.val += 1
          }
        }
      }

      let left_frame = match left.val {
        None => return None
        Some(frame) => frame
      }

      let out_frame : Array[Sample] = []
      match right.val {
        None => {
          if frac_num != 0 {
            return None
          }
          for i in 0.. {
          let t = Double::from_int(frac_num) / Double::from_int(to_rate)
          for i in 0.. DynSource {
  let total_duration = input.total_duration()
  let max_span_len = 32_768

  fn bootstrap_uniform() -> DynSource {
    let from_channels = input.channels()
    let span_len = match input.current_span_len() {
      None => None
      Some(n) => Some(if n < max_span_len { n } else { max_span_len })
    }

    let limited = match span_len {
      None => input
      Some(remaining0) => {
        let remaining = @ref.new(remaining0)
        DynSource::new_dynamic(
          fn() {
            if remaining.val <= 0 {
              None
            } else {
              remaining.val -= 1
              input.next()
            }
          },
          fn() { from_channels },
          fn() { input.sample_rate() },
          current_span_len=fn() {
            if remaining.val <= 0 {
              Some(0)
            } else {
              Some(remaining.val)
            }
          },
          total_duration=fn() { input.total_duration() },
          try_seek=fn(pos : @moon_cpal.Duration) {
            try {
              input.try_seek(pos)
              Ok(())
            } catch {
              err => Err(err)
            }
          },
        )
      }
    }

    let sampled = sample_rate_convert(limited, target_sample_rate)
    channel_convert(sampled, target_channels)
  }

  let inner = @ref.new(bootstrap_uniform())

  DynSource::new_dynamic(
    fn() {
      match inner.val.next() {
        Some(v) => Some(v)
        None => {
          inner.val = bootstrap_uniform()
          inner.val.next()
        }
      }
    },
    fn() { target_channels },
    fn() { target_sample_rate },
    current_span_len=fn() { None },
    total_duration=fn() { total_duration },
    try_seek=fn(pos : @moon_cpal.Duration) {
      try {
        input.try_seek(pos)
        inner.val = bootstrap_uniform()
        Ok(())
      } catch {
        err => Err(err)
      }
    },
  )
}