///|
/// Public ADSR stage names for diagnostics and tests.
pub enum EnvStage {
  Idle
  Attack
  Decay
  Sustain
  Release
} derive(Eq)

///|
/// Stateful ADSR envelope generator.
pub struct Adsr {
  priv attack_ms : Double
  priv decay_ms : Double
  priv sustain : Double
  priv release_ms : Double
  priv mut stage : EnvStage
  priv mut level : Double
  priv mut level_at_release : Double
}

///|
/// Create a new ADSR envelope with normalized parameters.
#alias(new)
pub fn Adsr::Adsr(
  attack_ms~ : Double,
  decay_ms~ : Double,
  sustain~ : Double,
  release_ms~ : Double,
) -> Adsr {
  {
    attack_ms: normalize_time_ms(attack_ms),
    decay_ms: normalize_time_ms(decay_ms),
    sustain: normalize_sustain(sustain),
    release_ms: normalize_time_ms(release_ms),
    stage: EnvStage::Idle,
    level: 0.0,
    level_at_release: 0.0,
  }
}

///|
/// Return the current envelope stage.
pub fn Adsr::stage(self : Adsr) -> EnvStage {
  self.stage
}

///|
/// Return the current envelope level.
pub fn Adsr::level(self : Adsr) -> Double {
  self.level
}

///|
/// Start a new note and enter the attack stage from silence.
pub fn Adsr::gate_on(self : Adsr) -> Unit {
  self.level = 0.0
  self.level_at_release = 0.0
  self.stage = EnvStage::Attack
}

///|
/// Release the current note from the envelope's present level.
pub fn Adsr::gate_off(self : Adsr) -> Unit {
  if self.stage is EnvStage::Idle {
    return
  }
  self.level_at_release = self.level
  self.stage = EnvStage::Release
}

///|
/// Reset the envelope to idle silence.
pub fn Adsr::reset(self : Adsr) -> Unit {
  self.stage = EnvStage::Idle
  self.level = 0.0
  self.level_at_release = 0.0
}

///|
/// Advance the envelope by one sample using the given DSP context.
pub fn Adsr::tick(self : Adsr, context : DspContext) -> Double {
  let sample_rate = context.sample_rate()
  if !is_finite_positive(sample_rate) {
    self.reset()
    return 0.0
  }
  self.tick_step(sample_rate)
}

///|
/// Step the envelope by one sample assuming `sample_rate` is finite and positive.
/// Callers must validate before invoking.
fn Adsr::tick_step(self : Adsr, sample_rate : Double) -> Double {
  // WHY bounded loop: zero-time stages (e.g. attack_ms=0) advance to the next
  // stage without producing a sample. The loop re-evaluates until a non-zero
  // stage is reached. Bound of 4 = number of ADSR stages, guaranteeing termination.
  for transitions = 0; transitions < 4; transitions = transitions + 1 {
    match self.stage {
      Idle => {
        self.level = 0.0
        break
      }
      Attack =>
        if self.attack_ms <= 0.0 {
          self.level = 1.0
          self.stage = EnvStage::Decay
          continue
        } else {
          self.level += 1000.0 / (self.attack_ms * sample_rate)
          if self.level >= 1.0 {
            self.level = 1.0
            self.stage = EnvStage::Decay
          }
          break
        }
      Decay =>
        if self.decay_ms <= 0.0 {
          self.level = self.sustain
          self.stage = EnvStage::Sustain
          continue
        } else {
          self.level -= (1.0 - self.sustain) *
            1000.0 /
            (self.decay_ms * sample_rate)
          if self.level <= self.sustain {
            self.level = self.sustain
            self.stage = EnvStage::Sustain
          }
          break
        }
      Sustain => {
        self.level = self.sustain
        break
      }
      Release => {
        if self.release_ms <= 0.0 || self.level_at_release <= 0.0 {
          self.level = 0.0
          self.stage = EnvStage::Idle
        } else {
          self.level -= self.level_at_release *
            1000.0 /
            (self.release_ms * sample_rate)
          if self.level <= 0.0 {
            self.level = 0.0
            self.stage = EnvStage::Idle
          }
        }
        break
      }
    }
  }
  self.level
}

///|
/// Fill a buffer with successive envelope values.
pub fn Adsr::process(
  self : Adsr,
  context : DspContext,
  output : AudioBuffer,
) -> Unit {
  let sample_rate = context.sample_rate()
  let sample_count = effective_sample_count(context, output)
  if !is_finite_positive(sample_rate) {
    output.fill(0.0)
    self.reset()
    return
  }
  if sample_count <= 0 {
    return
  }

  for index = 0; index < sample_count; index = index + 1 {
    output.set(index, self.tick_step(sample_rate))
  }

  for index = sample_count; index < output.length(); index = index + 1 {
    output.set(index, 0.0)
  }
}

///|
fn normalize_time_ms(value : Double) -> Double {
  if value.is_nan() || value.is_inf() || value < 0.0 {
    0.0
  } else {
    value
  }
}

///|
fn normalize_sustain(value : Double) -> Double {
  if !is_finite(value) {
    0.0
  } else {
    value.clamp(min=0.0, max=1.0)
  }
}