// Log sampling — reduce log volume while keeping valuable data

///|
pub(all) struct PercentSampler {
  rate : Int // 0-100, percentage of logs to keep
  counter : Int
}

///|
pub fn PercentSampler::new(rate : Int) -> PercentSampler {
  PercentSampler::{ rate, counter: 0 }
}

///|
pub fn PercentSampler::sample(self : PercentSampler) -> (PercentSampler, Bool) {
  let next = self.counter + 1
  let keep = next % 100 < self.rate
  (PercentSampler::{ ..self, counter: next }, keep)
}

// Rate-limited sampler — at most N logs per time window

///|
pub(all) struct RateSampler {
  max_per_window : Int
  window_size : Int
  events : Array[Int]
}

///|
pub fn RateSampler::new(max_per_window : Int, window_size : Int) -> RateSampler {
  RateSampler::{ max_per_window, window_size, events: [] }
}

///|
pub fn RateSampler::allow(
  self : RateSampler,
  tick : Int,
) -> (RateSampler, Bool) {
  let cutoff = tick - self.window_size
  let trimmed = trim_old_events(self.events, cutoff, 0, [])
  if trimmed.length() < self.max_per_window {
    let new_events = trimmed
    new_events.push(tick)
    (RateSampler::{ ..self, events: new_events }, true)
  } else {
    (RateSampler::{ ..self, events: trimmed }, false)
  }
}

///|
fn trim_old_events(
  src : Array[Int],
  cutoff : Int,
  idx : Int,
  result : Array[Int],
) -> Array[Int] {
  if idx >= src.length() {
    result
  } else if src[idx] >= cutoff {
    result.push(src[idx])
    trim_old_events(src, cutoff, idx + 1, result)
  } else {
    trim_old_events(src, cutoff, idx + 1, result)
  }
}

///|
pub fn RateSampler::count(self : RateSampler) -> Int {
  self.events.length()
}

// Burst sampler — allow bursts up to N, then throttle

///|
pub(all) struct BurstSampler {
  max_burst : Int
  sustained_rate : Int
  window_size : Int
  bucket : Int
  last_refill : Int
}

///|
pub fn BurstSampler::new(
  max_burst : Int,
  sustained_rate : Int,
  window_size : Int,
) -> BurstSampler {
  let normalized_burst = if max_burst > 0 { max_burst } else { 0 }
  let normalized_rate = if sustained_rate > 0 { sustained_rate } else { 0 }
  let normalized_window = if window_size > 0 { window_size } else { 1 }
  BurstSampler::{
    max_burst: normalized_burst,
    sustained_rate: normalized_rate,
    window_size: normalized_window,
    bucket: normalized_burst,
    last_refill: 0,
  }
}

///|
pub fn BurstSampler::allow(
  self : BurstSampler,
  tick : Int,
) -> (BurstSampler, Bool) {
  let effective_tick = if tick > self.last_refill {
    tick
  } else {
    self.last_refill
  }
  let elapsed = effective_tick - self.last_refill
  let new_tokens = elapsed * self.sustained_rate / self.window_size
  let available = self.bucket + new_tokens
  let refilled = if available > self.max_burst {
    self.max_burst
  } else if available < 0 {
    0
  } else {
    available
  }
  let refill_tick = if new_tokens > 0 {
    effective_tick
  } else {
    self.last_refill
  }

  if refilled > 0 {
    (
      BurstSampler::{ ..self, bucket: refilled - 1, last_refill: refill_tick },
      true,
    )
  } else {
    (BurstSampler::{ ..self, bucket: 0, last_refill: refill_tick }, false)
  }
}