///|
pub(all) enum SelectionMode {
  AllMutants
  FirstMutants(Int)
  IdRange(start~ : Int, end~ : Int)
  Shard(shard_index~ : Int, shard_count~ : Int)
} derive(Debug, Eq, ToJson)

///|
pub(all) struct SelectionValidation {
  valid : Bool
  message : String
} derive(Debug, Eq, ToJson)

///|
pub(all) struct SelectionReport {
  mode : SelectionMode
  input_count : Int
  selected_count : Int
  skipped_count : Int
  selected_ids : Array[Int]
  valid : Bool
  message : String
} derive(Debug, Eq, ToJson)

///|
pub fn validate_selection_mode(mode : SelectionMode) -> SelectionValidation {
  match mode {
    AllMutants => { valid: true, message: "all mutants selected" }
    FirstMutants(limit) =>
      if limit >= 0 {
        { valid: true, message: "first \{limit} mutants selected" }
      } else {
        { valid: false, message: "first-mutants limit must be non-negative" }
      }
    IdRange(start~, end~) =>
      if start < 0 {
        { valid: false, message: "id range start must be non-negative" }
      } else if end < start {
        { valid: false, message: "id range end must be >= start" }
      } else {
        { valid: true, message: "ids \{start}..\{end} selected" }
      }
    Shard(shard_index~, shard_count~) =>
      if shard_count <= 0 {
        { valid: false, message: "shard count must be positive" }
      } else if shard_index < 0 || shard_index >= shard_count {
        { valid: false, message: "shard index must be in 0..\{shard_count}" }
      } else {
        {
          valid: true,
          message: "shard \{shard_index + 1}/\{shard_count} selected",
        }
      }
  }
}

///|
pub fn select_executions(
  plan : ExecutionPlan,
  mode : SelectionMode,
) -> Array[MutationExecution] {
  let selected : Array[MutationExecution] = []
  for index, execution in plan.executions {
    if matches_selection(execution.mutation.global_id, index, mode) {
      selected.push(execution)
    }
  }
  selected
}

///|
pub fn select_project_mutations(
  plan : ProjectMutationPlan,
  mode : SelectionMode,
) -> Array[ProjectMutation] {
  let selected : Array[ProjectMutation] = []
  for index, mutation in plan.mutations {
    if matches_selection(mutation.global_id, index, mode) {
      selected.push(mutation)
    }
  }
  selected
}

///|
pub fn summarize_execution_selection(
  plan : ExecutionPlan,
  mode : SelectionMode,
) -> SelectionReport {
  summarize_selected_ids(
    mode,
    input_count=plan.executions.length(),
    selected_ids=selected_execution_ids(plan, mode),
  )
}

///|
pub fn summarize_project_selection(
  plan : ProjectMutationPlan,
  mode : SelectionMode,
) -> SelectionReport {
  summarize_selected_ids(
    mode,
    input_count=plan.mutations.length(),
    selected_ids=selected_project_mutation_ids(plan, mode),
  )
}

///|
pub fn format_selection_mode(mode : SelectionMode) -> String {
  match mode {
    AllMutants => "all"
    FirstMutants(limit) => "first \{limit}"
    IdRange(start~, end~) => "ids \{start}..\{end}"
    Shard(shard_index~, shard_count~) =>
      "shard \{shard_index + 1}/\{shard_count}"
  }
}

///|
pub fn format_selection_report(report : SelectionReport) -> String {
  [
    "Mutation selection",
    "mode: \{format_selection_mode(report.mode)}",
    "valid: \{report.valid}",
    "input: \{report.input_count}",
    "selected: \{report.selected_count}",
    "skipped: \{report.skipped_count}",
    "ids: \{format_int_list(report.selected_ids)}",
    "message: \{report.message}",
  ].join("\n")
}

///|
fn summarize_selected_ids(
  mode : SelectionMode,
  input_count~ : Int,
  selected_ids~ : Array[Int],
) -> SelectionReport {
  let validation = validate_selection_mode(mode)
  {
    mode,
    input_count,
    selected_count: selected_ids.length(),
    skipped_count: input_count - selected_ids.length(),
    selected_ids,
    valid: validation.valid,
    message: validation.message,
  }
}

///|
fn selected_execution_ids(
  plan : ExecutionPlan,
  mode : SelectionMode,
) -> Array[Int] {
  let ids : Array[Int] = []
  for index, execution in plan.executions {
    let global_id = execution.mutation.global_id
    if matches_selection(global_id, index, mode) {
      ids.push(global_id)
    }
  }
  ids
}

///|
fn selected_project_mutation_ids(
  plan : ProjectMutationPlan,
  mode : SelectionMode,
) -> Array[Int] {
  let ids : Array[Int] = []
  for index, mutation in plan.mutations {
    if matches_selection(mutation.global_id, index, mode) {
      ids.push(mutation.global_id)
    }
  }
  ids
}

///|
fn matches_selection(
  global_id : Int,
  index : Int,
  mode : SelectionMode,
) -> Bool {
  match mode {
    AllMutants => true
    FirstMutants(limit) => limit >= 0 && index < limit
    IdRange(start~, end~) => start >= 0 && start <= global_id && global_id < end
    Shard(shard_index~, shard_count~) =>
      shard_count > 0 &&
      shard_index >= 0 &&
      shard_index < shard_count &&
      global_id % shard_count == shard_index
  }
}

///|
fn format_int_list(items : ArrayView[Int]) -> String {
  if items.is_empty() {
    "none"
  } else {
    [ for item in items => item.to_string() ].join(", ")
  }
}