///|
pub(all) struct SlotKey {
  shift_id : ShiftId
  slot : Int
} derive(Debug, Eq)

///|
pub(all) struct CandidateDomain {
  key : SlotKey
  people : Array[PersonId]
  rejected : Array[Diagnostic]
} derive(Debug, Eq)

///|
pub fn build_domain(
  problem : ScheduleProblem,
  assignments : Array[Assignment],
  shift : Shift,
  slot : Int,
) -> CandidateDomain {
  let people : Array[PersonId] = []
  let rejected : Array[Diagnostic] = []
  for person in problem.people {
    match can_assign(problem, assignments, shift, slot, person) {
      None => people.push(person.id)
      Some(reason) => rejected.push(reason)
    }
  }
  { key: { shift_id: shift.id, slot }, people, rejected }
}

///|
pub fn build_domains(
  problem : ScheduleProblem,
  assignments : Array[Assignment],
) -> Array[CandidateDomain] {
  let domains : Array[CandidateDomain] = []
  for shift in problem.shifts {
    for slot = 0; slot < shift.required_people; slot = slot + 1 {
      let mut occupied = false
      for assignment in assignments {
        if assignment.shift_id == shift.id && assignment.slot == slot {
          occupied = true
        }
      }
      if !occupied {
        domains.push(build_domain(problem, assignments, shift, slot))
      }
    }
  }
  domains
}

///|
pub fn smallest_domain_index(domains : Array[CandidateDomain]) -> Int {
  if domains.length() == 0 {
    return -1
  }
  let mut best = 0
  for i = 1; i < domains.length(); i = i + 1 {
    if domains[i].people.length() < domains[best].people.length() {
      best = i
    }
  }
  best
}

///|
pub fn domain_contains(domain : CandidateDomain, person_id : PersonId) -> Bool {
  for id in domain.people {
    if id == person_id {
      return true
    }
  }
  false
}