///|
fn breakpoint_status_text(status : @debugger.BreakpointStatus) -> String {
  match status {
    @debugger.Pending => "pending"
    @debugger.Verified => "verified"
    @debugger.Rejected(message) => "rejected: \{message}"
  }
}

///|
fn breakpoint_installation_text(
  installation : @debugger.BreakpointInstallation,
) -> String {
  match installation {
    @debugger.NotInstalled => "not installed"
    @debugger.CurrentInstallation(status) =>
      "current: \{breakpoint_status_text(status)}"
    @debugger.PreviousInstallation(status) =>
      "previous: \{breakpoint_status_text(status)}"
  }
}

///|
fn breakpoint_target_text(target : @debugger.BreakpointTarget) -> String {
  match target {
    @debugger.SourceTarget(display_path~, line~, ..) =>
      "\{display_path}:\{line}"
    @debugger.FunctionTarget(display_name~, ..) => "function \{display_name}"
  }
}

///|
fn breakpoint_actual_text(location : @debugger.BreakpointLocation) -> String {
  match (location.source_path, location.line) {
    (Some(path), Some(line)) =>
      match location.column {
        Some(column) => "\{path}:\{line}:\{column}"
        None => "\{path}:\{line}"
      }
    _ => "source location unknown"
  }
}

///|
fn breakpoint_list_lines(
  entries : Array[@debugger.BreakpointListEntry],
) -> Array[String] {
  guard entries is [_, ..] else { return ["No breakpoints."] }
  let ordered = entries.copy()
  ordered.sort_by((left, right) => left.id.compare(right.id))
  let lines = ["Breakpoints:"]
  for entry in ordered {
    let enabled = if entry.enabled { "enabled" } else { "disabled" }
    lines.push(
      "  \{entry.id}  \{enabled}  requested: \{breakpoint_target_text(entry.target)}",
    )
    lines.push(
      "     installation: \{breakpoint_installation_text(entry.installation)}",
    )
    let previous = entry.installation is @debugger.PreviousInstallation(_)
    let prefix = if previous { "previous " } else { "" }
    if entry.target
      is @debugger.FunctionTarget(
        selector=@debugger.MoonBitFunctionFamily(_),
        ..
      ) {
      let count = match entry.location_count {
        Some(count) => "\{count}"
        None => "unknown"
      }
      lines.push("     \{prefix}locations: \{count}")
    }
    if entry.locations is [] {
      lines.push("     \{prefix}actual: source location unknown")
    } else {
      for index, location in entry.locations {
        guard index < 3 else { break }
        lines.push("     \{prefix}actual: \{breakpoint_actual_text(location)}")
      }
      if entry.locations.length() > 3 {
        lines.push("     ... (showing 3 representative locations)")
      }
    }
  }
  lines
}

///|
fn breakpoint_managed_line(
  result : @debugger.BreakpointManagementResult,
) -> String {
  let verb = match result.operation {
    @debugger.DeleteBreakpoint => "deleted"
    @debugger.DisableBreakpoint => "disabled"
    @debugger.EnableBreakpoint => "enabled"
  }
  guard result.changed else {
    return "Breakpoint \{result.breakpoint.id} is already \{verb}."
  }
  if result.operation is @debugger.EnableBreakpoint {
    "Breakpoint \{result.breakpoint.id} enabled (\{breakpoint_installation_text(result.breakpoint.installation)})."
  } else {
    "Breakpoint \{result.breakpoint.id} \{verb}."
  }
}

///|
fn breakpoint_management_failure_line(
  failure : BreakpointManagementFailure,
) -> String {
  match failure {
    BreakpointIdNotFound(id) => "Breakpoint \{id} was not found."
    BreakpointManagementRefused(message) =>
      "Cannot manage breakpoint: \{message}"
  }
}