do_backup <- function(all_obs_data, transects, raw_track_points) {
  console_log("backup.r", "do_backup() start")
  timestamp <- format(now(), "%Y%m%d%H%M%S")
  if (!dir.exists("backups")) {
    dir.create("backups")
  }
  backup <- all_obs_data %>%
    filter(is.na(delete) | delete == "") %>%
    ungroup() %>%
    bind_rows(raw_track_points) %>%
    mutate(
      Type = if_else(!is.na(Species), "USER", "GPS"),
      `On/OffTx` = ONOFFTX
    )

  # Columns that might or might not exist
  all_columns <- tibble(
    Vessel = as.character(NA),
    PortObs = as.character(NA),
    StarboardObs = as.character(NA),
    PortCondition = as.integer(NA),
    StarboardCondition = as.integer(NA),
    Beaufort = as.integer(NA),
    Weather = as.character(NA),
    CloudCover = as.integer(NA),
    FogConc = as.integer(NA),
    IceType = as.character(NA),
    IceConc = as.integer(NA),
    ObsSideIO = as.character(NA),
    ObsSidePS = as.character(NA),
    SecondaryBehavior = as.character(NA),
    Age = as.character(NA),
    Sex = as.character(NA),
    Plumage = as.character(NA),
    Distance = as.integer(NA),
    Angle = as.integer(NA),
    Bin = as.character(NA),
    UserSelect1 = as.character(NA),
    UserSelect2 = as.character(NA),
    UserText = as.character(NA),
    UserNumeric = as.numeric(NA),
    Comments = as.character(NA),
    bogus = "TRUE"
  )

  backup <- bind_rows(backup, all_columns) |>
    filter(is.na(bogus))

  # Pull transect-level TxWidth into the backup without dragging along other
  # transect columns (Weather, Vessel, ...) whose presence depends on the
  # enabled optional fields: joining the whole table suffixes any collision to
  # .x/.y and the select below fails. Vessel may be absent from transects
  # entirely when its optional field is disabled.
  join_keys <- intersect(c("Vessel", "TripID", "Transect"), names(transects))
  backup <- backup |>
    left_join(
      transects |>
        select(all_of(join_keys), TxWidth) |>
        distinct(),
      by = join_keys
    ) |>
    mutate(TxWidth = coalesce(TxWidth.y, TxWidth.x)) |>
    select(
      Vessel,
      TxWidth,
      TripID,
      Transect,
      Record,
      PortObs,
      StarboardObs,
      PortCondition,
      StarboardCondition,
      Beaufort,
      Weather,
      CloudCover,
      FogConc,
      IceType,
      IceConc,
      `On/OffTx`,
      Type,
      ObsSideIO,
      ObsSidePS,
      Species,
      Count,
      PrimaryBehavior,
      SecondaryBehavior,
      Age,
      Sex,
      Plumage,
      Distance,
      Angle,
      Bin,
      UserSelect1,
      UserSelect2,
      UserText,
      UserNumeric,
      Latitude,
      Longitude,
      Date,
      Time,
      GPSTime,
      Comments
    ) |>
    arrange(Date, Time)

  write_csv(backup, glue("backups/raw_data_{timestamp}.csv"), na = "null")

  all_files <- list.files("backups", pattern = "raw_data_2", full.names = TRUE)
  if (length(all_files) > MAX_BACKUP_FILES) {
    to_remove <- all_files[seq(1, length(all_files) - MAX_BACKUP_FILES)]
    file.remove(to_remove)
  }
  console_log("backup.r", "do_backup() end")
}
