///|
/// Transition curve style for ADSR envelope phases.
#derive(Eq, Debug)
pub(all) enum Curve {
  Linear
  Exponential
}

///|
/// Standard 4-stage Attack-Decay-Sustain-Release audio envelope generator.
#derive(Eq, Debug)
pub struct ADSR {
  attack : Double // Duration in seconds to ramp up to 1.0
  decay : Double // Duration in seconds to drop to sustain level
  sustain : Double // Sustain amplitude level between 0.0 and 1.0
  release : Double // Duration in seconds to fade out to 0.0
}

///|
/// Constructs a new ADSR configuration ensuring non-negative values.
pub fn ADSR::new(
  attack : Double,
  decay : Double,
  sustain : Double,
  release : Double,
) -> ADSR {
  let a = if attack < 0.001 { 0.001 } else { attack }
  let d = if decay < 0.001 { 0.001 } else { decay }
  let s = if sustain < 0.0 {
    0.0
  } else if sustain > 1.0 {
    1.0
  } else {
    sustain
  }
  let r = if release < 0.001 { 0.001 } else { release }
  { attack: a, decay: d, sustain: s, release: r, }
}

///|
/// Fast percussive envelope ideal for drums, explosions, and impact hits.
pub fn ADSR::percussive(decay_sec : Double) -> ADSR {
  let d = if decay_sec <= 0.0 { 0.1 } else { decay_sec }
  ADSR::new(0.003, d, 0.0, 0.01)
}

///|
/// Sustained pad envelope with smooth fading in and out.
pub fn ADSR::pad(attack_sec : Double, release_sec : Double) -> ADSR {
  ADSR::new(attack_sec, 0.2, 0.75, release_sec)
}

///|
/// Punchy organ/synth-lead envelope with minimal attack and quick release.
pub fn ADSR::organ() -> ADSR {
  ADSR::new(0.01, 0.05, 0.9, 0.05)
}

///|
/// Computes the envelope gain factor [0.0, 1.0] at a given timestamp within total duration.
pub fn ADSR::gain_at(
  self : ADSR,
  time : Double,
  total_duration : Double,
  curve : Curve,
) -> Double {
  if time < 0.0 || time >= total_duration || total_duration <= 0.0 {
    return 0.0
  }

  let rel_start = total_duration - self.release
  let actual_release_start = if rel_start <= 0.0 {
    0.0
  } else if rel_start < self.attack {
    self.attack
  } else {
    rel_start
  }

  // Release phase takes precedence when we reach the release window
  if time >= actual_release_start {
    let rel_dur = total_duration - actual_release_start
    if rel_dur <= 0.0001 {
      0.0
    } else {
      let t = (time - actual_release_start) / rel_dur
      let t_clamped = if t > 1.0 { 1.0 } else { t }
      match curve {
        Linear => self.sustain * (1.0 - t_clamped)
        Exponential => self.sustain * (1.0 - t_clamped) * (1.0 - t_clamped)
      }
    }
  } else if time < self.attack {
    // Attack phase: 0.0 -> 1.0
    let t = time / self.attack
    match curve {
      Linear => t
      Exponential => t * t
    }
  } else if time < self.attack + self.decay {
    // Decay phase: 1.0 -> sustain
    let t = (time - self.attack) / self.decay
    let t_clamped = if t > 1.0 { 1.0 } else { t }
    match curve {
      Linear => 1.0 - (1.0 - self.sustain) * t_clamped
      Exponential =>
        self.sustain +
        (1.0 - self.sustain) * (1.0 - t_clamped) * (1.0 - t_clamped)
    }
  } else {
    // Sustain phase
    self.sustain
  }
}

///|
/// Applies this ADSR envelope in-place across an entire AudioBuffer.
pub fn AudioBuffer::apply_adsr(
  self : AudioBuffer,
  adsr : ADSR,
  curve : Curve,
) -> Unit {
  let dur = self.duration()
  let sr = self.sample_rate.to_double()
  for i = 0; i < self.samples.length(); i = i + 1 {
    let t = i.to_double() / sr
    let gain = adsr.gain_at(t, dur, curve)
    self.samples[i] = self.samples[i] * gain
  }
}