///|
pub(all) enum ShellDialect {
  BashDialect
  PowerShellDialect
} derive(Debug, Eq, ToJson)

///|
pub(all) struct RunnerScript {
  dialect : ShellDialect
  selection : SelectionMode
  mutant_count : Int
  content : String
} derive(Debug, Eq, ToJson)

///|
pub fn shell_dialect_label(dialect : ShellDialect) -> String {
  match dialect {
    BashDialect => "bash"
    PowerShellDialect => "powershell"
  }
}

///|
pub fn generate_runner_script(
  plan : ExecutionPlan,
  dialect : ShellDialect,
) -> RunnerScript {
  render_runner_script(plan, plan.executions, AllMutants, dialect)
}

///|
pub fn generate_selected_runner_script(
  plan : ExecutionPlan,
  mode : SelectionMode,
  dialect : ShellDialect,
) -> RunnerScript {
  render_runner_script(plan, select_executions(plan, mode), mode, dialect)
}

///|
pub fn format_runner_script(script : RunnerScript) -> String {
  script.content
}

///|
fn render_runner_script(
  plan : ExecutionPlan,
  executions : ArrayView[MutationExecution],
  mode : SelectionMode,
  dialect : ShellDialect,
) -> RunnerScript {
  let content = match dialect {
    BashDialect => render_bash_script(plan, executions, mode)
    PowerShellDialect => render_powershell_script(plan, executions, mode)
  }
  { dialect, selection: mode, mutant_count: executions.length(), content }
}

///|
fn render_bash_script(
  plan : ExecutionPlan,
  executions : ArrayView[MutationExecution],
  mode : SelectionMode,
) -> String {
  let lines : Array[String] = [
    "#!/usr/bin/env bash",
    "set -euo pipefail",
    "",
    "# Generated by moon-mutest.",
    "# Selection: \{format_selection_mode(mode)}",
    "# Mutants: \{executions.length()} of \{plan.executions.length()} planned executions",
    "",
    "echo " + bash_quote("moon-mutest baseline"),
  ]
  for command in plan.config.commands {
    append_bash_command(lines, command, fail_fast=plan.config.fail_fast)
  }
  for execution in executions {
    append_bash_mutant(lines, execution, fail_fast=plan.config.fail_fast)
  }
  lines.join("\n")
}

///|
fn render_powershell_script(
  plan : ExecutionPlan,
  executions : ArrayView[MutationExecution],
  mode : SelectionMode,
) -> String {
  let lines : Array[String] = [
    "$ErrorActionPreference = 'Stop'",
    "",
    "# Generated by moon-mutest.",
    "# Selection: \{format_selection_mode(mode)}",
    "# Mutants: \{executions.length()} of \{plan.executions.length()} planned executions",
    "",
    "Write-Host " + powershell_quote("moon-mutest baseline"),
  ]
  for command in plan.config.commands {
    append_powershell_command(lines, command, fail_fast=plan.config.fail_fast)
  }
  for execution in executions {
    append_powershell_mutant(lines, execution, fail_fast=plan.config.fail_fast)
  }
  lines.join("\n")
}

///|
fn append_bash_mutant(
  lines : Array[String],
  execution : MutationExecution,
  fail_fast~ : Bool,
) -> Unit {
  let mutation = execution.mutation
  lines.push("")
  lines.push(
    "# mutant #\{mutation.global_id} \{mutation.file}:" +
    "\{execution.edit.line}:\{execution.edit.column} " +
    "\{mutation.candidate.rule.label}",
  )
  lines.push("# apply: \{format_text_edit(execution.edit)}")
  for command in execution.commands {
    append_bash_command(lines, command, fail_fast~)
  }
  lines.push("# restore: \{format_text_edit(execution.restore_edit)}")
}

///|
fn append_powershell_mutant(
  lines : Array[String],
  execution : MutationExecution,
  fail_fast~ : Bool,
) -> Unit {
  let mutation = execution.mutation
  lines.push("")
  lines.push(
    "# mutant #\{mutation.global_id} \{mutation.file}:" +
    "\{execution.edit.line}:\{execution.edit.column} " +
    "\{mutation.candidate.rule.label}",
  )
  lines.push("# apply: \{format_text_edit(execution.edit)}")
  for command in execution.commands {
    append_powershell_command(lines, command, fail_fast~)
  }
  lines.push("# restore: \{format_text_edit(execution.restore_edit)}")
}

///|
fn append_bash_command(
  lines : Array[String],
  command : RunnerCommand,
  fail_fast~ : Bool,
) -> Unit {
  lines.push("echo " + bash_quote("+ " + command.command))
  if fail_fast {
    lines.push(command.command)
  } else {
    lines.push(command.command + " || true")
  }
}

///|
fn append_powershell_command(
  lines : Array[String],
  command : RunnerCommand,
  fail_fast~ : Bool,
) -> Unit {
  lines.push("Write-Host " + powershell_quote("+ " + command.command))
  lines.push(command.command)
  if fail_fast {
    lines.push("if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }")
  }
}

///|
fn format_text_edit(edit : TextEdit) -> String {
  "\{edit.file}[\{edit.start}..\{edit.end}] " +
  shell_comment_quote(edit.original) +
  " -> " +
  shell_comment_quote(edit.replacement)
}

///|
fn shell_comment_quote(text : String) -> String {
  "`" + text.replace(old="\n", new="\\n") + "`"
}

///|
fn bash_quote(text : String) -> String {
  "'" + text.replace(old="'", new="'\"'\"'") + "'"
}

///|
fn powershell_quote(text : String) -> String {
  "'" + text.replace(old="'", new="''") + "'"
}