///|
fn build_distance_matrix(
  depot : Depot,
  customers : Array[Customer],
  metric : DistanceMetric,
) -> Array[Array[Double]] {
  let size = customers.length() + 1
  let depot_index = customers.length()
  let matrix = Array::makei(size, fn(_) { Array::make(size, 0.0) })
  for i in 0.. VrpInstance {
  make_instance_with_metric(
    name,
    variant,
    depot,
    vehicle,
    customers,
    DistanceMetric::Euclidean,
  )
}

///|
/// Builds an instance with an explicit distance convention.
pub fn make_instance_with_metric(
  name : String,
  variant : ProblemVariant,
  depot : Depot,
  vehicle : VehicleProfile,
  customers : Array[Customer],
  metric : DistanceMetric,
) -> VrpInstance {
  {
    name,
    variant,
    metric,
    depot,
    vehicle,
    customers,
    distance_matrix: build_distance_matrix(depot, customers, metric),
  }
}

///|
pub fn Depot::new(
  x : Double,
  y : Double,
  ready_time? : Double = 0.0,
  due_time? : Double = 1_000_000_000.0,
) -> Depot {
  { x, y, ready_time, due_time }
}

///|
pub fn Customer::new(
  external_id : Int,
  x : Double,
  y : Double,
  demand : Double,
  ready_time? : Double = 0.0,
  due_time? : Double = 1_000_000_000.0,
  service_time? : Double = 0.0,
) -> Customer {
  { external_id, x, y, demand, ready_time, due_time, service_time }
}

///|
pub fn VehicleProfile::new(
  capacity : Double,
  fleet_size? : Int = 32,
  max_route_duration? : Double = 1_000_000_000.0,
) -> VehicleProfile {
  { capacity, max_route_duration, fleet_size }
}

///|
pub fn SolveConfig::default() -> SolveConfig {
  {
    iterations: 600,
    local_search_rounds: 0,
    segment_length: 25,
    min_destroy: 2,
    max_destroy: 6,
    initial_temperature: 12.0,
    cooling_rate: 0.992,
    capacity_penalty: 1200.0,
    time_penalty: 1600.0,
    duration_penalty: 600.0,
    unassigned_penalty: 20_000.0,
  }
}

///|
pub fn SolveConfig::tune(
  self : SolveConfig,
  iterations? : Int = self.iterations,
  local_search_rounds? : Int = self.local_search_rounds,
  segment_length? : Int = self.segment_length,
  min_destroy? : Int = self.min_destroy,
  max_destroy? : Int = self.max_destroy,
  initial_temperature? : Double = self.initial_temperature,
  cooling_rate? : Double = self.cooling_rate,
  capacity_penalty? : Double = self.capacity_penalty,
  time_penalty? : Double = self.time_penalty,
  duration_penalty? : Double = self.duration_penalty,
  unassigned_penalty? : Double = self.unassigned_penalty,
) -> SolveConfig {
  {
    iterations,
    local_search_rounds,
    segment_length,
    min_destroy,
    max_destroy,
    initial_temperature,
    cooling_rate,
    capacity_penalty,
    time_penalty,
    duration_penalty,
    unassigned_penalty,
  }
}

///|
pub fn SolveStats::new(
  iterations : Int,
  accepted_moves : Int,
  improved_moves : Int,
  best_iteration : Int,
  seed_objective : Double,
  best_objective : Double,
  local_improvements? : Int = 0,
  evaluated_candidates? : Int = 0,
) -> SolveStats {
  {
    iterations,
    accepted_moves,
    improved_moves,
    local_improvements,
    evaluated_candidates,
    best_iteration,
    seed_objective,
    best_objective,
  }
}

///|
pub fn SolveResult::new(
  instance : VrpInstance,
  seed_solution : Solution,
  best_solution : Solution,
  stats : SolveStats,
) -> SolveResult {
  { instance, seed_solution, best_solution, stats }
}