///|
pub(all) enum BaselineOutcome {
  BaselinePassed
  BaselineFailed
  BaselineTimedOut
  BaselineIncomplete
} derive(Debug, Eq, ToJson)

///|
pub(all) struct BaselineReport {
  outcome : BaselineOutcome
  command_count : Int
  executed_count : Int
  failed_index : Int?
  failed_phase : CommandPhase?
  can_run_mutants : Bool
  detail : String
} derive(Debug, Eq, ToJson)

///|
pub fn baseline_outcome_label(outcome : BaselineOutcome) -> String {
  match outcome {
    BaselinePassed => "passed"
    BaselineFailed => "failed"
    BaselineTimedOut => "timed-out"
    BaselineIncomplete => "incomplete"
  }
}

///|
pub fn classify_baseline(
  expected : ArrayView[RunnerCommand],
  results : ArrayView[CommandResult],
) -> BaselineReport {
  for index, result in results {
    if result.timed_out {
      break baseline_report(
        BaselineTimedOut,
        command_count=expected.length(),
        executed_count=results.length(),
        failed_index=Some(index),
        failed_phase=Some(result.phase),
        detail=command_failure_detail(result),
      )
    } else {
      match result.exit_code {
        Some(0) => ()
        Some(code) =>
          break baseline_report(
            BaselineFailed,
            command_count=expected.length(),
            executed_count=results.length(),
            failed_index=Some(index),
            failed_phase=Some(result.phase),
            detail="exit code \{code}: \{command_failure_detail(result)}",
          )
        None =>
          break baseline_report(
            BaselineIncomplete,
            command_count=expected.length(),
            executed_count=results.length(),
            failed_index=Some(index),
            failed_phase=Some(result.phase),
            detail="command did not produce an exit code",
          )
      }
    }
  } nobreak {
    if results.length() < expected.length() {
      baseline_report(
        BaselineIncomplete,
        command_count=expected.length(),
        executed_count=results.length(),
        failed_index=None,
        failed_phase=None,
        detail="baseline stopped before all commands completed",
      )
    } else {
      baseline_report(
        BaselinePassed,
        command_count=expected.length(),
        executed_count=results.length(),
        failed_index=None,
        failed_phase=None,
        detail="baseline commands passed",
      )
    }
  }
}

///|
pub fn baseline_can_run(report : BaselineReport) -> Bool {
  report.can_run_mutants
}

///|
pub fn skip_execution_results_for_baseline(
  plan : ExecutionPlan,
  report : BaselineReport,
) -> Array[ProjectMutantResult] {
  if report.can_run_mutants {
    []
  } else {
    let skipped : Array[ProjectMutantResult] = []
    for execution in plan.executions {
      skipped.push(
        project_result(
          execution.mutation,
          Skipped,
          detail=baseline_skip_detail(report),
        ),
      )
    }
    skipped
  }
}

///|
pub fn format_baseline_report(report : BaselineReport) -> String {
  [
    "Baseline report",
    "outcome: \{baseline_outcome_label(report.outcome)}",
    "can-run-mutants: \{report.can_run_mutants}",
    "commands: \{report.executed_count}/\{report.command_count}",
    "failed-index: \{format_optional_report_int(report.failed_index)}",
    "failed-phase: \{format_optional_phase(report.failed_phase)}",
    "detail: \{report.detail}",
  ].join("\n")
}

///|
fn baseline_report(
  outcome : BaselineOutcome,
  command_count~ : Int,
  executed_count~ : Int,
  failed_index~ : Int?,
  failed_phase~ : CommandPhase?,
  detail~ : String,
) -> BaselineReport {
  {
    outcome,
    command_count,
    executed_count,
    failed_index,
    failed_phase,
    can_run_mutants: outcome == BaselinePassed,
    detail,
  }
}

///|
fn command_failure_detail(result : CommandResult) -> String {
  if result.stderr != "" {
    result.stderr
  } else if result.stdout != "" {
    result.stdout
  } else if result.timed_out {
    "command timed out"
  } else {
    match result.exit_code {
      Some(code) => "exit code \{code}"
      None => "missing exit code"
    }
  }
}

///|
fn baseline_skip_detail(report : BaselineReport) -> String {
  "baseline \{baseline_outcome_label(report.outcome)}: \{report.detail}"
}

///|
fn format_optional_report_int(value : Int?) -> String {
  match value {
    Some(n) => n.to_string()
    None => "none"
  }
}

///|
fn format_optional_phase(value : CommandPhase?) -> String {
  match value {
    Some(phase) => command_phase_label(phase)
    None => "none"
  }
}