///|
fn unique_sorted(values : Array[Int]) -> Array[Int] {
  let items = values.copy()
  items.sort()
  items.dedup()
  items
}

///|
fn remove_customers(
  instance : @core.VrpInstance,
  solution : @core.Solution,
  removed : Array[Int],
  config : @core.SolveConfig,
) -> @core.Solution {
  let removed_unique = unique_sorted(removed)
  let routes = solution.routes.map(fn(route) {
    route.stops.filter(fn(stop) { !removed_unique.contains(stop) })
  })
  let next_unassigned = unique_sorted(solution.unassigned + removed_unique)
  @core.build_solution(instance, routes, next_unassigned, config)
}

///|
fn random_destroy_impl(
  instance : @core.VrpInstance,
  solution : @core.Solution,
  remove_count : Int,
  rng : @rng.Rng,
  config : @core.SolveConfig,
) -> @core.Solution {
  let pool = solution.assigned_customers()
  let picks : Array[Int] = []
  let available = pool.copy()
  let target = remove_count.min(available.length())
  for _ in 0.. Double {
  let left_customer = instance.customer(left)
  let right_customer = instance.customer(right)
  instance.distance(left, right) +
  Double::abs(left_customer.demand - right_customer.demand) * 1.5 +
  Double::abs(left_customer.ready_time - right_customer.ready_time) * 0.1
}

///|
fn shaw_destroy_impl(
  instance : @core.VrpInstance,
  solution : @core.Solution,
  remove_count : Int,
  rng : @rng.Rng,
  config : @core.SolveConfig,
) -> @core.Solution {
  let assigned = solution.assigned_customers()
  if assigned.is_empty() {
    return solution
  }
  let seed = assigned[rng.next_int(assigned.length())]
  let ranked = assigned.copy()
  ranked.sort_by(fn(left, right) {
    let left_score = customer_relatedness(instance, seed, left)
    let right_score = customer_relatedness(instance, seed, right)
    if left_score < right_score {
      -1
    } else if left_score > right_score {
      1
    } else {
      0
    }
  })
  let take = remove_count.min(ranked.length())
  remove_customers(instance, solution, ranked[:take].to_owned(), config)
}

///|
fn marginal_cost(
  instance : @core.VrpInstance,
  solution : @core.Solution,
  route_index : Int,
  stop_position : Int,
  config : @core.SolveConfig,
) -> (Int, Double) {
  let customer = solution.routes[route_index].stops[stop_position]
  let route_stops = solution.route_stops()
  ignore(route_stops[route_index].remove(stop_position))
  let next_solution = @core.build_solution(
    instance,
    route_stops,
    solution.unassigned.copy(),
    config,
  )
  (customer, solution.objective - next_solution.objective)
}

///|
fn worst_destroy_impl(
  instance : @core.VrpInstance,
  solution : @core.Solution,
  remove_count : Int,
  config : @core.SolveConfig,
) -> @core.Solution {
  let scored : Array[(Int, Double)] = []
  for route_index in 0.. right.1 {
      -1
    } else if left.1 < right.1 {
      1
    } else {
      0
    }
  })
  let picks = scored.map(fn(item) { item.0 })
  let take = remove_count.min(picks.length())
  remove_customers(instance, solution, picks[:take].to_owned(), config)
}

///|
fn destroy_with_rng(
  instance : @core.VrpInstance,
  solution : @core.Solution,
  operator : @core.DestroyOperator,
  remove_count : Int,
  rng : @rng.Rng,
  config : @core.SolveConfig,
) -> @core.Solution {
  match operator {
    @core.DestroyOperator::Random =>
      random_destroy_impl(instance, solution, remove_count, rng, config)
    @core.DestroyOperator::Shaw =>
      shaw_destroy_impl(instance, solution, remove_count, rng, config)
    @core.DestroyOperator::Worst =>
      worst_destroy_impl(instance, solution, remove_count, config)
    @core.DestroyOperator::Route =>
      route_destroy_impl(instance, solution, remove_count, rng, config)
    @core.DestroyOperator::Cluster =>
      cluster_destroy_impl(instance, solution, remove_count, rng, config)
    @core.DestroyOperator::TimeCritical =>
      time_critical_destroy_impl(instance, solution, remove_count, config)
  }
}

///|
pub fn destroy_solution(
  instance : @core.VrpInstance,
  solution : @core.Solution,
  operator : @core.DestroyOperator,
  remove_count : Int,
  seed? : Int = 1,
  config : @core.SolveConfig,
) -> @core.Solution {
  let rng = @rng.Rng::new(seed)
  destroy_with_rng(instance, solution, operator, remove_count, rng, config)
}