///|
/// Receivable aging uses quarter periods and integer minor units.
pub struct AgingBucket {
  label : String
  amount : Amount
  entries : Int
} derive(Debug, Eq)

///|
pub fn age_receivables(
  entries : Array[Entry],
  account : String,
  as_of : String,
) -> Array[AgingBucket] {
  let buckets = [
    { label: "current", amount: 0, entries: 0 },
    { label: "1 quarter", amount: 0, entries: 0 },
    { label: "2 quarters", amount: 0, entries: 0 },
    { label: "3+ quarters", amount: 0, entries: 0 },
  ]
  for entry in entries {
    if entry.account != account || entry.debit <= entry.credit {
      continue
    }
    let distance = period_distance(entry.period, as_of)
    let index = if distance <= 0 {
      0
    } else if distance == 1 {
      1
    } else if distance == 2 {
      2
    } else {
      3
    }
    let bucket = buckets[index]
    buckets[index] = {
      ..bucket,
      amount: bucket.amount + entry.debit - entry.credit,
      entries: bucket.entries + 1,
    }
  }
  buckets
}

///|
pub fn period_distance(older : String, newer : String) -> Int {
  match (period_parts(older), period_parts(newer)) {
    (Some((oy, oq)), Some((ny, nq))) => (ny - oy) * 4 + nq - oq
    _ => 0
  }
}

///|
fn period_parts(period : String) -> (Int, Int)? {
  match validate_period(period) {
    InvalidPeriod(_) => None
    ValidPeriod(value) => {
      let year = parse_year(value[:4].to_owned())
      let quarter = value[6].to_int() - '0'.to_int()
      Some((year, quarter))
    }
  }
}

///|
fn parse_year(text : String) -> Int {
  let mut value = 0
  for c in text {
    value = value * 10 + c.to_int() - '0'.to_int()
  }
  value
}