///|
pub(all) struct PropagationResult {
  assignments : Array[Assignment]
  diagnostics : Array[Diagnostic]
  forced_count : Int
  feasible : Bool
} derive(Debug, Eq)

///|
pub fn propagate(
  problem : ScheduleProblem,
  initial : Array[Assignment],
) -> PropagationResult {
  let current = initial.copy()
  let reasons : Array[Diagnostic] = []
  let mut forced = 0
  let mut changed = true
  let mut feasible = true
  while changed && feasible {
    changed = false
    let domains = build_domains(problem, current)
    for domain in domains {
      if domain.people.length() == 0 {
        feasible = false
        reasons.push({
          code: EmptyCandidateDomain,
          message: "no eligible person remains for slot",
          person_id: None,
          shift_id: Some(domain.key.shift_id),
          slot: Some(domain.key.slot),
          severity: 2,
        })
      } else if domain.people.length() == 1 {
        let assignment : Assignment = {
          shift_id: domain.key.shift_id,
          slot: domain.key.slot,
          person_id: domain.people[0],
          locked: false,
        }
        let mut exists = false
        for old in current {
          if old.shift_id == assignment.shift_id && old.slot == assignment.slot {
            exists = true
          }
        }
        if !exists {
          current.push(assignment)
          forced = forced + 1
          changed = true
          break
        }
      }
    }
  }
  { assignments: current, diagnostics: reasons, forced_count: forced, feasible }
}