///|
pub struct AdmissionRule {
  provider : String
  max_events_per_window : Int
  window_seconds : Int
} derive(Eq, Debug)

///|
pub fn AdmissionRule::new(
  provider : String,
  max_events_per_window : Int,
  window_seconds : Int,
) -> AdmissionRule {
  { provider, max_events_per_window, window_seconds }
}

///|
pub struct AdmissionCounter {
  provider : String
  window_start : Int
  count : Int
} derive(Eq, Debug)

///|
pub fn AdmissionCounter::new(
  provider : String,
  window_start : Int,
  count : Int,
) -> AdmissionCounter {
  { provider, window_start, count }
}

///|
pub struct AdmissionState {
  counters : Array[AdmissionCounter]
} derive(Eq, Debug)

///|
pub fn AdmissionState::empty() -> AdmissionState {
  { counters: [] }
}

///|
fn find_counter_index(state : AdmissionState, provider : String) -> Int {
  for i in 0.. AdmissionDecision {
  { allowed, reason, state }
}

///|
pub fn check_admission(
  provider : String,
  now : Int,
  rule : AdmissionRule,
  state : AdmissionState,
) -> AdmissionDecision {
  if provider != rule.provider {
    AdmissionDecision::new(true, "rule does not apply", state)
  } else {
    let index = find_counter_index(state, provider)
    if index < 0 {
      let counters = Array::makei(state.counters.length() + 1, i => {
        if i < state.counters.length() {
          state.counters[i]
        } else {
          AdmissionCounter::new(provider, now, 1)
        }
      })
      AdmissionDecision::new(true, "new window", { counters, })
    } else {
      let counter = state.counters[index]
      if now - counter.window_start > rule.window_seconds {
        let counters = Array::makei(state.counters.length(), i => {
          if i == index {
            AdmissionCounter::new(provider, now, 1)
          } else {
            state.counters[i]
          }
        })
        AdmissionDecision::new(true, "window reset", { counters, })
      } else if counter.count >= rule.max_events_per_window {
        AdmissionDecision::new(false, "admission window full", state)
      } else {
        let counters = Array::makei(state.counters.length(), i => {
          if i == index {
            AdmissionCounter::new(
              provider,
              counter.window_start,
              counter.count + 1,
            )
          } else {
            state.counters[i]
          }
        })
        AdmissionDecision::new(true, "counted", { counters, })
      }
    }
  }
}

///|
pub fn default_admission_rules() -> Array[AdmissionRule] {
  [
    AdmissionRule::new("github", 100, 60),
    AdmissionRule::new("feishu", 120, 60),
    AdmissionRule::new("stripe", 80, 60),
    AdmissionRule::new("generic", 60, 60),
  ]
}

///|
pub fn admission_rule_for(provider : String) -> AdmissionRule {
  let rules = default_admission_rules()
  for i in 0..