///|
pub type PersonId = String

///|
pub type ShiftId = String

///|
pub(all) struct TimeSpan {
  start : Int
  end : Int
} derive(Debug, Eq)

///|
pub(all) struct SkillRequirement {
  skill : String
  minimum_level : Int
} derive(Debug, Eq)

///|
pub(all) struct Skill {
  name : String
  level : Int
} derive(Debug, Eq)

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

///|
pub(all) struct Person {
  id : PersonId
  name : String
  skills : Array[Skill]
  availability : Array[TimeSpan]
  preferences : Array[ShiftPreference]
  target_minutes : Int
  maximum_minutes : Int
  maximum_consecutive_shifts : Int
} derive(Debug, Eq)

///|
pub(all) struct Shift {
  id : ShiftId
  label : String
  time : TimeSpan
  required_people : Int
  requirements : Array[SkillRequirement]
  priority : Int
  group : String
} derive(Debug, Eq)

///|
pub(all) struct Assignment {
  shift_id : ShiftId
  slot : Int
  person_id : PersonId
  locked : Bool
} derive(Debug, Eq)

///|
pub(all) struct ScheduleProblem {
  people : Array[Person]
  shifts : Array[Shift]
  existing : Array[Assignment]
  minimum_rest_minutes : Int
  fairness_weight : Int
  preference_weight : Int
  disruption_weight : Int
} derive(Debug, Eq)

///|
pub(all) enum DiagnosticCode {
  InvalidTimeSpan
  DuplicatePerson
  DuplicateShift
  UnknownPerson
  UnknownShift
  InvalidSlot
  DuplicateAssignment
  OverlappingAssignment
  OutsideAvailability
  SkillMismatch
  MaximumMinutesExceeded
  InsufficientRest
  ConsecutiveLimitExceeded
  UncoveredSlot
  EmptyCandidateDomain
  SearchBudgetExceeded
  InvalidConfiguration
} derive(Debug, Eq)

///|
pub(all) struct Diagnostic {
  code : DiagnosticCode
  message : String
  person_id : PersonId?
  shift_id : ShiftId?
  slot : Int?
  severity : Int
} derive(Debug, Eq)

///|
pub(all) enum SolveStatus {
  Solved
  Partial
  Infeasible
  BudgetExceeded
} derive(Debug, Eq)

///|
pub(all) struct ScheduleMetrics {
  hard_violations : Int
  uncovered_slots : Int
  preference_score : Int
  fairness_penalty : Int
  disruption_count : Int
  total_minutes : Int
  objective : Int
} derive(Debug, Eq)

///|
pub(all) struct ScheduleResult {
  status : SolveStatus
  assignments : Array[Assignment]
  diagnostics : Array[Diagnostic]
  metrics : ScheduleMetrics
  explored_nodes : Int
  propagated_values : Int
} derive(Debug, Eq)

///|
pub(all) struct SearchOptions {
  maximum_nodes : Int
  maximum_solutions : Int
  stop_at_first_feasible : Bool
  keep_explanations : Bool
} derive(Debug, Eq)

///|
pub fn SearchOptions::default() -> SearchOptions {
  {
    maximum_nodes: 100000,
    maximum_solutions: 32,
    stop_at_first_feasible: false,
    keep_explanations: true,
  }
}

///|
pub fn ScheduleMetrics::empty() -> ScheduleMetrics {
  {
    hard_violations: 0,
    uncovered_slots: 0,
    preference_score: 0,
    fairness_penalty: 0,
    disruption_count: 0,
    total_minutes: 0,
    objective: 0,
  }
}