///|
pub fn make_person(
  id : String,
  name : String,
  skills : Array[Skill],
  availability : Array[TimeSpan],
  target : Int,
  maximum : Int,
  preferences? : Array[ShiftPreference] = [],
) -> Person {
  {
    id,
    name,
    skills,
    availability,
    preferences,
    target_minutes: target,
    maximum_minutes: maximum,
    maximum_consecutive_shifts: 5,
  }
}

///|
pub fn make_shift(
  id : String,
  label : String,
  start : Int,
  end : Int,
  required : Int,
  requirements : Array[SkillRequirement],
  priority? : Int = 1,
  group? : String = "default",
) -> Shift {
  {
    id,
    label,
    time: { start, end },
    required_people: required,
    requirements,
    priority,
    group,
  }
}

///|
pub fn store_scenario() -> ScheduleProblem {
  let all_week : Array[TimeSpan] = [{ start: 0, end: 10080 }]
  let alice = make_person(
    "alice",
    "Alice",
    [
      { name: "cashier", level: 3 },
      { name: "keyholder", level: 2 },
      { name: "stock", level: 1 },
    ],
    all_week,
    1440,
    1920,
    preferences=[
      { shift_id: "mon-open", weight: 6 },
      { shift_id: "fri-close", weight: -4 },
    ],
  )
  let bob = make_person(
    "bob",
    "Bob",
    [{ name: "cashier", level: 2 }, { name: "stock", level: 3 }],
    [{ start: 0, end: 4320 }, { start: 5760, end: 10080 }],
    1200,
    1800,
    preferences=[{ shift_id: "sat-open", weight: 5 }],
  )
  let chen = make_person(
    "chen",
    "Chen",
    [{ name: "cashier", level: 2 }, { name: "keyholder", level: 1 }],
    all_week,
    1200,
    1680,
    preferences=[{ shift_id: "wed-close", weight: 4 }],
  )
  let dia = make_person(
    "dia",
    "Dia",
    [{ name: "cashier", level: 1 }, { name: "stock", level: 2 }],
    [{ start: 0, end: 7200 }],
    960,
    1440,
  )
  let shifts : Array[Shift] = [
    make_shift(
      "mon-open",
      "Monday opening",
      480,
      960,
      2,
      [{ skill: "cashier", minimum_level: 1 }],
      priority=5,
      group="open",
    ),
    make_shift(
      "mon-close",
      "Monday closing",
      960,
      1320,
      2,
      [{ skill: "cashier", minimum_level: 1 }],
      priority=4,
      group="close",
    ),
    make_shift(
      "wed-open",
      "Wednesday opening",
      3360,
      3840,
      2,
      [{ skill: "cashier", minimum_level: 1 }],
      priority=5,
      group="open",
    ),
    make_shift(
      "wed-close",
      "Wednesday closing",
      3840,
      4200,
      2,
      [{ skill: "cashier", minimum_level: 1 }],
      priority=4,
      group="close",
    ),
    make_shift(
      "fri-open",
      "Friday opening",
      6240,
      6720,
      2,
      [{ skill: "cashier", minimum_level: 1 }],
      priority=5,
      group="open",
    ),
    make_shift(
      "fri-close",
      "Friday closing",
      6720,
      7080,
      2,
      [{ skill: "cashier", minimum_level: 1 }],
      priority=4,
      group="close",
    ),
    make_shift(
      "sat-open",
      "Saturday opening",
      7680,
      8160,
      2,
      [{ skill: "cashier", minimum_level: 1 }],
      priority=5,
      group="open",
    ),
  ]
  {
    people: [alice, bob, chen, dia],
    shifts,
    existing: [],
    minimum_rest_minutes: 480,
    fairness_weight: 1,
    preference_weight: 20,
    disruption_weight: 120,
  }
}

///|
pub fn laboratory_scenario() -> ScheduleProblem {
  let week : Array[TimeSpan] = [{ start: 0, end: 10080 }]
  let iman = make_person(
    "iman",
    "Iman",
    [
      { name: "spectrometer", level: 3 },
      { name: "safety", level: 3 },
      { name: "sample", level: 2 },
    ],
    week,
    960,
    1440,
  )
  let jules = make_person(
    "jules",
    "Jules",
    [{ name: "spectrometer", level: 2 }, { name: "safety", level: 2 }],
    [{ start: 0, end: 5000 }],
    720,
    1200,
  )
  let kai = make_person(
    "kai",
    "Kai",
    [{ name: "sample", level: 3 }, { name: "safety", level: 1 }],
    week,
    720,
    1200,
  )
  let lin = make_person(
    "lin",
    "Lin",
    [
      { name: "spectrometer", level: 1 },
      { name: "sample", level: 2 },
      { name: "safety", level: 2 },
    ],
    week,
    720,
    1200,
  )
  let shifts : Array[Shift] = [
    make_shift(
      "calibrate-a",
      "Calibrate instrument A",
      540,
      720,
      1,
      [
        { skill: "spectrometer", minimum_level: 3 },
        { skill: "safety", minimum_level: 2 },
      ],
      priority=8,
      group="instrument",
    ),
    make_shift(
      "batch-a",
      "Process sample batch A",
      720,
      1020,
      2,
      [{ skill: "sample", minimum_level: 2 }],
      priority=6,
      group="sample",
    ),
    make_shift(
      "calibrate-b",
      "Calibrate instrument B",
      3420,
      3600,
      1,
      [
        { skill: "spectrometer", minimum_level: 2 },
        { skill: "safety", minimum_level: 2 },
      ],
      priority=8,
      group="instrument",
    ),
    make_shift(
      "batch-b",
      "Process sample batch B",
      3600,
      3900,
      2,
      [{ skill: "sample", minimum_level: 2 }],
      priority=6,
      group="sample",
    ),
    make_shift(
      "audit",
      "Weekly safety audit",
      6480,
      6720,
      2,
      [{ skill: "safety", minimum_level: 2 }],
      priority=9,
      group="safety",
    ),
  ]
  {
    people: [iman, jules, kai, lin],
    shifts,
    existing: [],
    minimum_rest_minutes: 180,
    fairness_weight: 2,
    preference_weight: 10,
    disruption_weight: 200,
  }
}

///|
pub fn volunteer_scenario() -> ScheduleProblem {
  let event : Array[TimeSpan] = [{ start: 0, end: 2880 }]
  let ava = make_person(
    "ava",
    "Ava",
    [{ name: "welcome", level: 3 }, { name: "first-aid", level: 2 }],
    event,
    360,
    600,
  )
  let ben = make_person(
    "ben",
    "Ben",
    [{ name: "welcome", level: 2 }, { name: "logistics", level: 2 }],
    event,
    360,
    600,
  )
  let cy = make_person(
    "cy",
    "Cy",
    [{ name: "stage", level: 3 }, { name: "logistics", level: 1 }],
    event,
    360,
    600,
  )
  let dev = make_person(
    "dev",
    "Dev",
    [{ name: "first-aid", level: 3 }, { name: "welcome", level: 1 }],
    event,
    360,
    600,
  )
  let eli = make_person(
    "eli",
    "Eli",
    [{ name: "stage", level: 2 }, { name: "logistics", level: 3 }],
    event,
    360,
    600,
  )
  let shifts : Array[Shift] = [
    make_shift(
      "setup",
      "Venue setup",
      360,
      600,
      2,
      [{ skill: "logistics", minimum_level: 1 }],
      priority=7,
      group="setup",
    ),
    make_shift(
      "welcome-am",
      "Morning welcome desk",
      600,
      840,
      2,
      [{ skill: "welcome", minimum_level: 1 }],
      priority=6,
      group="welcome",
    ),
    make_shift(
      "stage-am",
      "Morning stage support",
      600,
      840,
      1,
      [{ skill: "stage", minimum_level: 2 }],
      priority=6,
      group="stage",
    ),
    make_shift(
      "aid-am",
      "Morning first aid",
      600,
      840,
      1,
      [{ skill: "first-aid", minimum_level: 2 }],
      priority=9,
      group="safety",
    ),
    make_shift(
      "welcome-pm",
      "Afternoon welcome desk",
      900,
      1140,
      2,
      [{ skill: "welcome", minimum_level: 1 }],
      priority=6,
      group="welcome",
    ),
    make_shift(
      "stage-pm",
      "Afternoon stage support",
      900,
      1140,
      1,
      [{ skill: "stage", minimum_level: 2 }],
      priority=6,
      group="stage",
    ),
    make_shift(
      "aid-pm",
      "Afternoon first aid",
      900,
      1140,
      1,
      [{ skill: "first-aid", minimum_level: 2 }],
      priority=9,
      group="safety",
    ),
    make_shift(
      "teardown",
      "Venue teardown",
      1140,
      1380,
      2,
      [{ skill: "logistics", minimum_level: 1 }],
      priority=7,
      group="setup",
    ),
  ]
  {
    people: [ava, ben, cy, dev, eli],
    shifts,
    existing: [],
    minimum_rest_minutes: 30,
    fairness_weight: 4,
    preference_weight: 10,
    disruption_weight: 100,
  }
}

///|
pub fn scenario_by_name(name : String) -> ScheduleProblem? {
  match name {
    "store" => Some(store_scenario())
    "laboratory" | "lab" => Some(laboratory_scenario())
    "volunteer" => Some(volunteer_scenario())
    _ => None
  }
}

///|
pub fn scenario_names() -> Array[String] {
  ["store", "laboratory", "volunteer"]
}

///|
pub fn expand_days(base : ScheduleProblem, copies : Int) -> ScheduleProblem {
  if copies <= 1 {
    return base
  }
  let shifts : Array[Shift] = []
  for copy = 0; copy < copies; copy = copy + 1 {
    let offset = copy * 10080
    for shift in base.shifts {
      shifts.push({
        ..shift,
        id: "\{shift.id}-w\{copy + 1}",
        label: "\{shift.label} week \{copy + 1}",
        time: { start: shift.time.start + offset, end: shift.time.end + offset },
      })
    }
  }
  let people : Array[Person] = []
  for person in base.people {
    let availability : Array[TimeSpan] = []
    for copy = 0; copy < copies; copy = copy + 1 {
      let offset = copy * 10080
      for span in person.availability {
        availability.push({ start: span.start + offset, end: span.end + offset })
      }
    }
    people.push({
      ..person,
      availability,
      target_minutes: person.target_minutes * copies,
      maximum_minutes: person.maximum_minutes * copies,
    })
  }
  { ..base, people, shifts, existing: [] }
}