///|
pub struct CohortRow {
  cohort_day : Int
  period : Int
  cohort_size : Int
  active_customers : Int
  retention_percent : Int
}

///|
fn unique_customers(
  events : Array[Event],
  predicate : (Event) -> Bool,
) -> Array[String] {
  let result : Array[String] = []
  for event in events {
    if predicate(event) && !result.contains(event.customer_id) {
      result.push(event.customer_id)
    }
  }
  result
}

///|
fn cohort_start(events : Array[Event], customer_id : String) -> Int {
  let mut start = 1_000_000_000
  for event in events {
    if event.customer_id == customer_id && event.day < start {
      start = event.day
    }
  }
  start
}

///|
/// Build retention rows for each first-activity cohort and relative period.
pub fn cohort_retention(
  events : Array[Event],
  period_width : Int,
  periods : Int,
) -> Array[CohortRow] {
  if period_width <= 0 || periods <= 0 {
    return []
  }
  let all_customers = unique_customers(events, _ => true)
  let cohort_days : Array[Int] = []
  for id in all_customers {
    let day = cohort_start(events, id)
    if !cohort_days.contains(day) {
      cohort_days.push(day)
    }
  }
  cohort_days.sort()
  let rows : Array[CohortRow] = []
  for day in cohort_days {
    let cohort_size = unique_customers(events, event => {
      cohort_start(events, event.customer_id) == day
    }).length()
    for period in 0.. {
        event.day >= start &&
        event.day < end &&
        cohort_start(events, event.customer_id) == day
      }).length()
      let percent = if cohort_size == 0 {
        0
      } else {
        active * 100 / cohort_size
      }
      rows.push({
        cohort_day: day,
        period,
        cohort_size,
        active_customers: active,
        retention_percent: percent,
      })
    }
  }
  rows
}