///|
/// Job-shop and machine-sequencing utilities.
///
/// Manufacturing operations are represented as fixed machine assignments with
/// setup and processing durations. The module provides deterministic dispatch
/// rules and validates the resulting Gantt plan before it is used as a solver
/// seed or exported to a shop-floor integration.
pub struct MachineSpec {
  id : Int
  name : String
  capacity : Int
}

///|
/// Create a machine specification.
pub fn machine_spec(id : Int, name : String, capacity : Int) -> MachineSpec {
  { id, name, capacity: if capacity < 1 { 1 } else { capacity } }
}

///|
/// Read machine identifier.
pub fn MachineSpec::id(self : MachineSpec) -> Int {
  self.id
}

///|
/// Read machine capacity.
pub fn MachineSpec::capacity(self : MachineSpec) -> Int {
  self.capacity
}

///|
/// A fixed-machine operation.
pub struct ManufacturingOperation {
  id : Int
  job : Int
  sequence : Int
  machine : Int
  setup : Int
  duration : Int
  family : Int
  due : Int
}

///|
/// Create an operation.
pub fn manufacturing_operation(
  id : Int,
  job : Int,
  sequence : Int,
  machine : Int,
  setup : Int,
  duration : Int,
  family : Int,
  due : Int,
) -> ManufacturingOperation {
  {
    id,
    job,
    sequence,
    machine,
    setup: if setup < 0 {
      0
    } else {
      setup
    },
    duration: if duration < 0 {
      0
    } else {
      duration
    },
    family,
    due,
  }
}

///|
/// Read operation job.
pub fn ManufacturingOperation::job(self : ManufacturingOperation) -> Int {
  self.job
}

///|
/// Read operation sequence.
pub fn ManufacturingOperation::sequence(self : ManufacturingOperation) -> Int {
  self.sequence
}

///|
/// Read assigned machine.
pub fn ManufacturingOperation::machine(self : ManufacturingOperation) -> Int {
  self.machine
}

///|
/// Return setup plus processing duration.
pub fn ManufacturingOperation::total_duration(
  self : ManufacturingOperation,
) -> Int {
  self.setup + self.duration
}

///|
/// A validated manufacturing instance.
pub struct ManufacturingInstance {
  machines : Array[MachineSpec]
  operations : Array[ManufacturingOperation]
  setup_matrix : Array[Array[Int]]
}

///|
/// Build an instance with a family-to-family setup matrix.
pub fn manufacturing_instance(
  machines : Array[MachineSpec],
  operations : Array[ManufacturingOperation],
  setup_matrix : Array[Array[Int]],
) -> ManufacturingInstance? {
  if machines.length() == 0 || setup_matrix.length() != machines.length() {
    return None
  }
  for row in setup_matrix {
    if row.length() != machines.length() {
      return None
    }
  }
  for index, machine in machines {
    if machine.id != index || machine.capacity < 1 {
      return None
    }
  }
  for index, operation in operations {
    if operation.id != index ||
      operation.machine < 0 ||
      operation.machine >= machines.length() ||
      operation.job < 0 ||
      operation.sequence < 0 ||
      operation.setup < 0 ||
      operation.duration < 0 {
      return None
    }
  }
  Some({
    machines: machines.copy(),
    operations: operations.copy(),
    setup_matrix: setup_matrix.map(row => row.copy()),
  })
}

///|
/// Return operation count.
pub fn ManufacturingInstance::operation_count(
  self : ManufacturingInstance,
) -> Int {
  self.operations.length()
}

///|
/// Return machine count.
pub fn ManufacturingInstance::machine_count(
  self : ManufacturingInstance,
) -> Int {
  self.machines.length()
}

///|
/// Read an operation.
pub fn ManufacturingInstance::operation(
  self : ManufacturingInstance,
  id : Int,
) -> ManufacturingOperation {
  if id < 0 || id >= self.operations.length() {
    abort("manufacturing operation is outside the instance")
  }
  self.operations[id]
}

///|
/// Read a machine.
pub fn ManufacturingInstance::machine(
  self : ManufacturingInstance,
  id : Int,
) -> MachineSpec {
  if id < 0 || id >= self.machines.length() {
    abort("manufacturing machine is outside the instance")
  }
  self.machines[id]
}

///|
/// A concrete operation start schedule.
pub struct ManufacturingSchedule {
  starts : Array[Int]
  machine_order : Array[Array[Int]]
}

///|
/// Create an empty schedule.
pub fn manufacturing_schedule(
  instance : ManufacturingInstance,
) -> ManufacturingSchedule {
  let starts : Array[Int] = []
  for _ in instance.operations {
    starts.push(-1)
  }
  let machine_order : Array[Array[Int]] = []
  for _ in instance.machines {
    machine_order.push([])
  }
  { starts, machine_order }
}

///|
/// Set an operation start time.
pub fn ManufacturingSchedule::set_start(
  self : ManufacturingSchedule,
  operation : Int,
  start : Int,
) -> Bool {
  if operation < 0 || operation >= self.starts.length() || start < 0 {
    return false
  }
  self.starts[operation] = start
  true
}

///|
/// Add an operation to a machine sequence.
pub fn ManufacturingSchedule::queue(
  self : ManufacturingSchedule,
  machine : Int,
  operation : Int,
) -> Bool {
  if machine < 0 ||
    machine >= self.machine_order.length() ||
    operation < 0 ||
    operation >= self.starts.length() ||
    self.machine_order[machine].contains(operation) {
    return false
  }
  self.machine_order[machine].push(operation)
  true
}

///|
/// Read an operation start.
pub fn ManufacturingSchedule::start(
  self : ManufacturingSchedule,
  operation : Int,
) -> Int {
  if operation < 0 || operation >= self.starts.length() {
    return -1
  }
  self.starts[operation]
}

///|
/// Return machine sequence.
pub fn ManufacturingSchedule::machine_sequence(
  self : ManufacturingSchedule,
  machine : Int,
) -> Array[Int] {
  if machine < 0 || machine >= self.machine_order.length() {
    return []
  }
  self.machine_order[machine].copy()
}

///|
/// Return a copied start array.
pub fn ManufacturingSchedule::starts(
  self : ManufacturingSchedule,
) -> Array[Int] {
  self.starts.copy()
}

///|
/// Return operation finish time.
pub fn operation_finish(
  instance : ManufacturingInstance,
  schedule : ManufacturingSchedule,
  operation : Int,
) -> Int {
  if operation < 0 || operation >= instance.operations.length() {
    return -1
  }
  schedule.start(operation) + instance.operations[operation].total_duration()
}

///|
/// Return the last operation of a job.
pub fn last_job_operation(instance : ManufacturingInstance, job : Int) -> Int? {
  let mut result : Int? = None
  let mut sequence = -1
  for operation in instance.operations {
    if operation.job == job && operation.sequence > sequence {
      result = Some(operation.id)
      sequence = operation.sequence
    }
  }
  result
}

///|
/// Return operation ids for a job in process order.
pub fn job_operations(
  instance : ManufacturingInstance,
  job : Int,
) -> Array[Int] {
  let result : Array[Int] = []
  for operation in instance.operations {
    if operation.job == job {
      result.push(operation.id)
    }
  }
  for left in 0.. Array[Int] {
  let result : Array[Int] = []
  for operation in instance.operations {
    if !result.contains(operation.job) {
      result.push(operation.job)
    }
  }
  result
}

///|
/// Schedule operations with a deterministic earliest-feasible dispatch.
pub fn dispatch_schedule(
  instance : ManufacturingInstance,
) -> ManufacturingSchedule {
  let schedule = manufacturing_schedule(instance)
  let remaining : Array[Int] = []
  for operation in instance.operations {
    remaining.push(operation.id)
  }
  let machine_ready : Array[Int] = []
  for _ in instance.machines {
    machine_ready.push(0)
  }
  while remaining.length() > 0 {
    let mut selected_index = 0
    let mut selected_due = 2147483647
    for index, operation_id in remaining {
      let operation = instance.operations[operation_id]
      if operation.due < selected_due {
        selected_index = index
        selected_due = operation.due
      }
    }
    let operation_id = remaining[selected_index]
    ignore(remove_manufacturing_value(remaining, operation_id))
    let operation = instance.operations[operation_id]
    let mut ready = machine_ready[operation.machine]
    for predecessor in job_operations(instance, operation.job) {
      if instance.operations[predecessor].sequence < operation.sequence &&
        schedule.start(predecessor) >= 0 {
        let finish = operation_finish(instance, schedule, predecessor)
        if finish > ready {
          ready = finish
        }
      }
    }
    ignore(schedule.set_start(operation_id, ready))
    ignore(schedule.queue(operation.machine, operation_id))
    machine_ready[operation.machine] = ready + operation.total_duration()
  }
  schedule
}

///|
/// Remove a value from an integer array.
fn remove_manufacturing_value(values : Array[Int], value : Int) -> Bool {
  let mut index = 0
  while index < values.length() {
    if values[index] == value {
      while index + 1 < values.length() {
        values[index] = values[index + 1]
        index += 1
      }
      ignore(values.pop())
      return true
    }
    index += 1
  }
  false
}

///|
/// Validate precedence, machine order, and due dates.
pub fn validate_manufacturing_schedule(
  instance : ManufacturingInstance,
  schedule : ManufacturingSchedule,
) -> Array[String] {
  let errors : Array[String] = []
  for operation in instance.operations {
    if schedule.start(operation.id) < 0 {
      errors.push("unscheduled-\{operation.id}")
    }
    if operation.due != 2147483647 &&
      operation_finish(instance, schedule, operation.id) > operation.due {
      errors.push("due-\{operation.id}")
    }
  }
  for job in manufacturing_jobs(instance) {
    let operations = job_operations(instance, job)
    for index in 1.. Bool {
  validate_manufacturing_schedule(instance, schedule).length() == 0
}

///|
/// Test two half-open intervals for overlap.
fn intervals_overlap(
  first_start : Int,
  first_end : Int,
  second_start : Int,
  second_end : Int,
) -> Bool {
  first_start < second_end && second_start < first_end
}

///|
/// Return the makespan.
pub fn manufacturing_makespan(
  instance : ManufacturingInstance,
  schedule : ManufacturingSchedule,
) -> Int {
  let mut result = 0
  for operation in instance.operations {
    let finish = operation_finish(instance, schedule, operation.id)
    if finish > result {
      result = finish
    }
  }
  result
}

///|
/// Return machine busy time.
pub fn machine_busy_time(
  instance : ManufacturingInstance,
  schedule : ManufacturingSchedule,
  machine : Int,
) -> Int {
  ignore(schedule)
  let mut result = 0
  if machine < 0 || machine >= instance.machines.length() {
    return 0
  }
  for operation in instance.operations {
    if operation.machine == machine {
      result += operation.total_duration()
    }
  }
  result
}

///|
/// Return machine utilization as an integer percentage.
pub fn machine_utilization(
  instance : ManufacturingInstance,
  schedule : ManufacturingSchedule,
  machine : Int,
) -> Int {
  let makespan = manufacturing_makespan(instance, schedule)
  if makespan == 0 || machine < 0 || machine >= instance.machines.length() {
    return 0
  }
  machine_busy_time(instance, schedule, machine) *
  100 /
  (makespan * instance.machines[machine].capacity)
}

///|
/// Return the total processing work.
pub fn manufacturing_work(instance : ManufacturingInstance) -> Int {
  let mut result = 0
  for operation in instance.operations {
    result += operation.duration
  }
  result
}

///|
/// Return the total setup work.
pub fn manufacturing_setup_work(instance : ManufacturingInstance) -> Int {
  let mut result = 0
  for operation in instance.operations {
    result += operation.setup
  }
  result
}

///|
/// Return the total tardiness.
pub fn manufacturing_tardiness(
  instance : ManufacturingInstance,
  schedule : ManufacturingSchedule,
) -> Int {
  let mut result = 0
  for operation in instance.operations {
    if operation.due != 2147483647 {
      let tardiness = operation_finish(instance, schedule, operation.id) -
        operation.due
      if tardiness > 0 {
        result += tardiness
      }
    }
  }
  result
}

///|
/// Return the critical operation ids by finish time.
pub fn critical_operations(
  instance : ManufacturingInstance,
  schedule : ManufacturingSchedule,
) -> Array[Int] {
  let makespan = manufacturing_makespan(instance, schedule)
  let result : Array[Int] = []
  for operation in instance.operations {
    if operation_finish(instance, schedule, operation.id) == makespan {
      result.push(operation.id)
    }
  }
  result
}

///|
/// Return all operation ids for a machine in schedule order.
pub fn machine_operations(
  instance : ManufacturingInstance,
  machine : Int,
) -> Array[Int] {
  let result : Array[Int] = []
  for operation in instance.operations {
    if operation.machine == machine {
      result.push(operation.id)
    }
  }
  result
}

///|
/// Return setup time between two operation families on a machine.
pub fn setup_time(
  instance : ManufacturingInstance,
  machine : Int,
  from_family : Int,
  to_family : Int,
) -> Int {
  if machine < 0 ||
    machine >= instance.setup_matrix.length() ||
    from_family < 0 ||
    to_family < 0 ||
    from_family >= instance.setup_matrix[machine].length() ||
    to_family >= instance.setup_matrix[machine].length() {
    return 0
  }
  instance.setup_matrix[from_family][to_family]
}

///|
/// Return operations ordered by earliest due date.
pub fn due_date_order(instance : ManufacturingInstance) -> Array[Int] {
  let result : Array[Int] = []
  for operation in instance.operations {
    result.push(operation.id)
  }
  for left in 0.. String {
  let builder = StringBuilder()
  for machine in 0.. 0 {
      builder.write_char('\n')
    }
    builder.write_string("machine \{machine}:")
    for operation in schedule.machine_order[machine] {
      builder.write_string(
        " \{operation}@\{schedule.start(operation)}+\{instance.operations[operation].total_duration()}",
      )
    }
  }
  builder.to_string()
}

///|
/// Return a stable manufacturing fingerprint.
pub fn manufacturing_signature(
  instance : ManufacturingInstance,
  schedule : ManufacturingSchedule,
) -> Int {
  let mut result = instance.operations.length() * 31 +
    instance.machines.length()
  for operation in instance.operations {
    result = result * 37 +
      operation.machine * 5 +
      operation.duration * 7 +
      schedule.start(operation.id)
  }
  result
}

///|
/// Return jobs whose final operation is late.
pub fn late_jobs(
  instance : ManufacturingInstance,
  schedule : ManufacturingSchedule,
) -> Array[Int] {
  let result : Array[Int] = []
  for job in manufacturing_jobs(instance) {
    match last_job_operation(instance, job) {
      Some(operation) => {
        let due = instance.operations[operation].due
        if due != 2147483647 &&
          operation_finish(instance, schedule, operation) > due {
          result.push(job)
        }
      }
      None => ()
    }
  }
  result
}

///|
/// Return the number of setup transitions in a machine sequence.
pub fn machine_setup_transitions(
  instance : ManufacturingInstance,
  schedule : ManufacturingSchedule,
  machine : Int,
) -> Int {
  if machine < 0 || machine >= schedule.machine_order.length() {
    return 0
  }
  let order = schedule.machine_order[machine]
  let mut result = 0
  for index in 1.. Int {
  manufacturing_makespan(instance, schedule) +
  manufacturing_tardiness(instance, schedule) * 1000 +
  validate_manufacturing_schedule(instance, schedule).length() * 1000000
}