// 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.

///|
fn duration_to_sample_count(
  duration : @moon_cpal.Duration,
  channels : ChannelCount,
  sample_rate : SampleRate,
) -> Int {
  if channels <= 0 || sample_rate <= 0 {
    return 0
  }

  let per_second = channels * sample_rate
  let secs_part = duration.secs.to_int() * per_second
  let nanos_part = duration.nanos * per_second / 1_000_000_000
  secs_part + nanos_part
}

///|
fn skip_samples(input : DynSource, count : Int) -> Unit {
  let limit = if count < 0 { 0 } else { count }
  for _ in 0.. Unit {
  let to_skip = duration_to_sample_count(
    duration,
    input.channels(),
    input.sample_rate(),
  )
  skip_samples(input, to_skip)
}

///|
fn skip_duration_span_aware(
  input : DynSource,
  duration : @moon_cpal.Duration,
) -> Unit {
  let remaining = @ref.new(duration)
  while duration_compare(
          remaining.val,
          @moon_cpal.Duration::from_secs((0 : UInt64)),
        ) >
        0 {
    match input.current_span_len() {
      None => {
        skip_duration_unchecked(input, remaining.val)
        return
      }
      Some(span_len) => {
        if span_len <= 0 {
          return
        }

        let samples_to_skip = duration_to_sample_count(
          remaining.val,
          input.channels(),
          input.sample_rate(),
        )
        if samples_to_skip <= 0 {
          return
        }

        if samples_to_skip < span_len {
          skip_samples(input, samples_to_skip)
          return
        }

        skip_samples(input, span_len)
        let skipped_duration = match
          duration_from_sample_count(
            span_len,
            input.channels(),
            input.sample_rate(),
          ) {
          Some(v) => v
          None => @moon_cpal.Duration::from_secs((0 : UInt64))
        }
        remaining.val = duration_saturating_sub(remaining.val, skipped_duration)
      }
    }
  }
}

///|
pub fn[S : Source] buffered(source : S) -> SamplesBuffer {
  let src = to_dyn(source)
  let out : Array[Sample] = []
  while true {
    match src.next() {
      None => break
      Some(v) => out.push(v)
    }
  }
  SamplesBuffer::new(src.channels(), src.sample_rate(), out)
}

///|
pub fn[S : Source] repeat_infinite(source : S) -> DynSource {
  buffered(source).repeat_infinite()
}

///|
pub fn[S : Source] repeat(source : S) -> DynSource {
  repeat_infinite(source)
}

///|
pub fn[S : Source] take_duration(
  source : S,
  duration : @moon_cpal.Duration,
) -> DynSource {
  let src = to_dyn(source)
  let channels = src.channels()
  let sample_rate = src.sample_rate()
  let sample_count = duration_to_sample_count(duration, channels, sample_rate)
  let remaining = @ref.new(sample_count)

  DynSource::new_dynamic(
    fn() {
      if remaining.val <= 0 {
        None
      } else {
        match src.next() {
          None => {
            remaining.val = 0
            None
          }
          Some(v) => {
            remaining.val -= 1
            Some(v)
          }
        }
      }
    },
    fn() { src.channels() },
    fn() { src.sample_rate() },
    current_span_len=fn() {
      let rem = if remaining.val <= 0 { 0 } else { remaining.val }
      match src.current_span_len() {
        None => Some(rem)
        Some(v) => Some(if v < rem { v } else { rem })
      }
    },
    total_duration=fn() {
      src.total_duration().map(fn(v) { duration_min(v, duration) })
    },
    try_seek=fn(pos : @moon_cpal.Duration) {
      try {
        src.try_seek(pos)
        let target = sample_index_from_duration(pos, channels, sample_rate)
        remaining.val = if target >= sample_count {
          0
        } else {
          sample_count - target
        }
        Ok(())
      } catch {
        err => Err(err)
      }
    },
  )
}

///|
pub fn[S : Source] skip_duration(
  source : S,
  duration : @moon_cpal.Duration,
) -> DynSource {
  let src = to_dyn(source)
  skip_duration_span_aware(src, duration)

  DynSource::new_dynamic(
    fn() { src.next() },
    fn() { src.channels() },
    fn() { src.sample_rate() },
    current_span_len=fn() { src.current_span_len() },
    total_duration=fn() {
      src.total_duration().map(fn(v) { duration_saturating_sub(v, duration) })
    },
    try_seek=fn(pos : @moon_cpal.Duration) {
      try {
        src.try_seek(pos)
        Ok(())
      } catch {
        err => Err(err)
      }
    },
  )
}

///|
pub fn[S : Source] delay(
  source : S,
  duration : @moon_cpal.Duration,
) -> DynSource {
  let src = to_dyn(source)
  let channels = src.channels()
  let sample_rate = src.sample_rate()
  let remaining = @ref.new(
    duration_to_sample_count(duration, channels, sample_rate),
  )

  DynSource::new_dynamic(
    fn() {
      if remaining.val > 0 {
        remaining.val -= 1
        Some(0.0)
      } else {
        src.next()
      }
    },
    fn() { src.channels() },
    fn() { src.sample_rate() },
    current_span_len=fn() {
      let rem = if remaining.val > 0 { remaining.val } else { 0 }
      src.current_span_len().map(fn(v) { v + rem })
    },
    total_duration=fn() {
      src.total_duration().map(fn(v) { duration_add(v, duration) })
    },
    try_seek=fn(pos : @moon_cpal.Duration) {
      let zero = @moon_cpal.Duration::from_secs((0 : UInt64))
      if duration_compare(pos, duration) < 0 {
        let until_playback = duration_saturating_sub(duration, pos)
        let rem = duration_to_sample_count(
          until_playback, channels, sample_rate,
        )
        try {
          src.try_seek(zero)
          remaining.val = rem
          Ok(())
        } catch {
          err => Err(err)
        }
      } else {
        let compensated = duration_saturating_sub(pos, duration)
        try {
          src.try_seek(compensated)
          remaining.val = 0
          Ok(())
        } catch {
          err => Err(err)
        }
      }
    },
  )
}

///|
pub fn[S : Source] amplify_decibel(source : S, value : Sample) -> DynSource {
  let factor = @math.pow(10.0, value / 20.0)
  amplify(source, factor)
}

///|
pub fn[S : Source] amplify_normalized(source : S, value : Sample) -> DynSource {
  let mut clamped = value
  if clamped < 0.0 {
    clamped = 0.0
  } else if clamped > 1.0 {
    clamped = 1.0
  }

  let mut amplitude = @math.exp(6.907_755_4 * clamped) / 1000.0
  if clamped < 0.1 {
    amplitude *= clamped * 10.0
  }

  amplify(source, amplitude)
}