// 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 fn[S : Source] limit(source : S, settings : LimitSettings) -> DynSource {
  to_dyn(Limit::new(source, settings))
}

///|
pub fn[S : Source] limit_range(
  source : S,
  min_value : Sample,
  max_value : Sample,
) -> DynSource {
  guard min_value <= max_value else { panic() }
  let src = to_dyn(source)
  DynSource::new_dynamic(
    fn() {
      match src.next() {
        None => None
        Some(v) =>
          if v < min_value {
            Some(min_value)
          } else if v > max_value {
            Some(max_value)
          } else {
            Some(v)
          }
      }
    },
    fn() { src.channels() },
    fn() { src.sample_rate() },
  )
}

///|
pub fn[S : Source] distortion(
  source : S,
  gain : Sample,
  threshold : Sample,
) -> DynSource {
  guard gain >= 0.0 else { panic() }
  guard threshold >= 0.0 else { panic() }
  limit_range(amplify(source, gain), -threshold, threshold)
}

///|
pub fn[S : Source] distortion_with_gain(
  source : S,
  gain : Sample,
  threshold : Sample,
) -> DynSource {
  distortion(source, gain, threshold)
}

///|
pub fn[S : Source] distortion_threshold(
  source : S,
  threshold : Sample,
) -> DynSource {
  distortion(source, 1.0, threshold)
}

///|
pub fn[A : Source, B : Source] crossfade(
  left : A,
  right : B,
  duration : @moon_cpal.Duration,
) -> DynSource {
  let lhs = TakeDuration::new(left, duration)
  lhs.set_filter_fadeout()
  let rhs = FadeIn::new(TakeDuration::new(right, duration), duration)
  mix(lhs, rhs)
}

///|
pub fn[A : Source, B : Source] take_crossfade_with(
  left : A,
  right : B,
  duration : @moon_cpal.Duration,
) -> DynSource {
  crossfade(left, right, duration)
}

///|
pub fn[S : Source] reverb(
  source : S,
  duration : @moon_cpal.Duration,
  amplitude : Sample,
) -> DynSource {
  let src = to_dyn(source)
  let channels = src.channels()
  let sample_rate = src.sample_rate()
  let delay_samples = duration_to_sample_count(duration, channels, sample_rate)
  if delay_samples <= 0 {
    return amplify(src, 1.0 + amplitude)
  }

  let delay_line : Array[Sample] = []
  for _ in 0.. Sample {
    let delayed = line[idx_ref.val]
    let out = current + delayed * feedback_gain
    line[idx_ref.val] = current
    idx_ref.val = (idx_ref.val + 1) % line.length()
    out
  }

  DynSource::new_dynamic(
    fn() {
      if source_done.val {
        if tail_remaining.val <= 0 {
          return None
        }
        tail_remaining.val -= 1
        return Some(step_delay_line(delay_line, write_idx, 0.0, amplitude))
      }

      match src.next() {
        Some(v) => Some(step_delay_line(delay_line, write_idx, v, amplitude))
        None => {
          source_done.val = true
          tail_remaining.val = delay_samples
          if tail_remaining.val <= 0 {
            None
          } else {
            tail_remaining.val -= 1
            Some(step_delay_line(delay_line, write_idx, 0.0, amplitude))
          }
        }
      }
    },
    fn() { channels },
    fn() { sample_rate },
    current_span_len=fn() {
      match src.current_span_len() {
        Some(v) => Some(v + delay_samples)
        None => None
      }
    },
    total_duration=fn() {
      src.total_duration().map(fn(v) { duration_add(v, duration) })
    },
    try_seek=fn(pos : @moon_cpal.Duration) {
      let history_duration = duration_min(pos, duration)
      let history_samples = duration_to_sample_count(
        history_duration, channels, sample_rate,
      )

      for i in 0.. return Err(err)
      }

      let to_copy = if history_samples < delay_samples {
        history_samples
      } else {
        delay_samples
      }
      for i in 0.. delay_line[i] = v
          None => break
        }
      }
      write_idx.val = to_copy % delay_samples

      try {
        src.try_seek(pos)
        Ok(())
      } catch {
        err => Err(err)
      }
    },
  )
}