generate_observations <- function(x) {
  console_log("generate_observations.r", "generate_observations() start")
  all_points_data <- x$unfiltered_obs_data %>%
    select(c(
      timestamp,
      TripID,
      Transect,
      Record,
      ONOFFTX,
      Type,
      Latitude,
      Longitude,
      Species,
      Count,
      PrimaryBehavior,
      SecondaryBehavior,
      Weather,
      Beaufort,
      PortCondition,
      UserSelect1,
      UserSelect2,
      UserText,
      UserNumeric,
      StarboardCondition,
      Comments,
      x$optional_fields %>%
        filter(enabled) %>%
        pull(field),
      GPSTime
    ))

  # Only SCAN PrimaryBehavior for "Snapshot", otherwise OFF TX
  if (input$fly_bird_method == "Snapshot") {
    all_points_data <- all_points_data %>%
      mutate(
        ONOFFTX = if_else(
          !is.na(PrimaryBehavior) & PrimaryBehavior == "FLY",
          "OFF",
          ONOFFTX
        )
      )
  }

  # "bin 9"
  if (x$optional_fields %>% filter(field == "Bin") %>% pull(enabled)) {
    all_points_data <- all_points_data %>%
      mutate(
        ONOFFTX = case_when(
          Bin == "OFF-TX" ~ "OFF",
          TRUE ~ ONOFFTX
        )
      )
  }

  coordinates(all_points_data) <- ~ Longitude + Latitude

  all_points_data <- cbind(all_points_data, coordinates(all_points_data))

  console_log(
    "generate_observations.r",
    glue("all_points_data rows entering joins: {nrow(all_points_data)}")
  )

  if (sum(names(x$transects) == "Weather") == 0) {
    transects_w_weather <- x$transects %>%
      mutate(Weather = NA_character_)
  } else {
    transects_w_weather <- x$transects
  }

  console_log(
    "generate_observations.r",
    glue("all_points_data rows = {nrow(all_points_data)}")
  )
  joined_obs_data <- all_points_data %>%
    st_as_sf() %>%
    st_set_crs(4326) %>%
    st_transform(x$crs) %>%
    st_join(x$nppsd_split %>% st_transform(x$crs) %>% st_buffer(5)) %>%
    filter(
      Transect.x == Transect.y,
      TripID.x == TripID.y
    ) %>%
    rename(
      Transect = Transect.x,
      TripID = TripID.x
    ) %>%
    st_join(x$nps_split %>% st_transform(x$crs) %>% st_buffer(5)) %>%
    filter(
      Transect.x == Transect.y,
      TripID.x == TripID.y
    ) %>%
    rename(
      Transect = Transect.x,
      TripID = TripID.x
    ) %>%
    as_tibble() %>%
    group_by(
      timestamp,
      TripID,
      Transect,
      Record,
      ONOFFTX,
      Latitude,
      Longitude,
      Species,
      Count,
      PrimaryBehavior,
      SecondaryBehavior,
      UserSelect1,
      UserSelect2,
      UserText,
      UserNumeric,
      Weather,
      Beaufort,
      Type,
      PortCondition,
      StarboardCondition,
      Comments,
      GPSTime
    ) %>%
    summarize(
      nppsd_split_id = max(nppsd_split_id),
      split_id = max(split_id),
      .groups = "drop"
    ) %>%
    # From non-split MASTER: adds transect sample_area_km2, calculates density
    mutate(Date = date(timestamp)) %>%
    inner_join(
      transects_w_weather %>% mutate(Date = date(start_time)),
      by = c("TripID", "Transect", "Date")
    ) %>%
    mutate(
      density = if_else(
        ONOFFTX == "ON",
        as.numeric(Count) / area_sampled_km2,
        as.numeric(NA)
      ),
      Weather = Weather.x
    ) %>%
    select(
      timestamp,
      TripID,
      Transect,
      Record,
      ONOFFTX,
      Type,
      Latitude,
      Longitude,
      Species,
      Count,
      density,
      PrimaryBehavior,
      SecondaryBehavior,
      UserSelect1,
      UserSelect2,
      UserText,
      UserNumeric,
      Weather,
      Beaufort,
      PortCondition,
      StarboardCondition,
      Comments,
      GPSTime,
      nppsd_split_id,
      split_id
    )

  console_log(
    "generate_observations.r",
    glue("joined_obs_data rows after transect join: {nrow(joined_obs_data)}")
  )
  # A transect whose observations all failed the spatial/transect joins above
  # will be absent here (and its obs count lost from the totals).
  log_dropped_keys(
    "generate_observations.r",
    "transect lost all observations in joins",
    x$valid_transects,
    joined_obs_data,
    c("TripID", "Transect")
  )

  # rejoin with all_points_data to handle cases where there are multiple obs
  # at exactly the same time and location due to GPS/dLOG failure
  all_points_data <- all_points_data %>%
    as_tibble() %>%
    select(-c("Longitude.1", "Latitude.1")) %>%
    inner_join(
      joined_obs_data,
      by = c(
        "timestamp",
        "TripID",
        "Transect",
        "Record",
        "ONOFFTX",
        "Type",
        "Species",
        "Count",
        "PrimaryBehavior",
        "SecondaryBehavior",
        "UserSelect1",
        "UserSelect2",
        "UserText",
        "UserNumeric",
        "Weather",
        "Beaufort",
        "PortCondition",
        "StarboardCondition",
        "Comments",
        "Longitude",
        "Latitude",
        "GPSTime"
      )
    ) %>%
    mutate(invalid_columns = NA) %>%
    select(c(
      timestamp,
      TripID,
      Transect,
      Record,
      ONOFFTX,
      Type,
      Latitude,
      Longitude,
      Species,
      Count,
      density,
      PrimaryBehavior,
      SecondaryBehavior,
      Weather,
      Beaufort,
      PortCondition,
      StarboardCondition,
      UserSelect1,
      UserSelect2,
      UserText,
      UserNumeric,
      invalid_columns,
      Comments,
      x$optional_fields %>%
        filter(enabled) %>%
        pull(field),
      GPSTime,
      nppsd_split_id,
      split_id
    ))

  all_obs_data <- all_points_data %>%
    filter(
      str_to_upper(Type) == "USER",
      !is.na(Species),
      str_length(Species) > 0,
      # ONOFFTX != "OFF"
    )

  console_log(
    "generate_observations.r",
    glue(
      "all_obs_data rows (USER records with a species): {nrow(all_obs_data)}"
    )
  )

  all_obs_data$invalid_columns <- validate_obs(
    all_obs_data,
    x$valid_species_list
  )
  all_obs_data$edited <- date(NA)
  all_obs_data$delete <- NA

  double_column_names <- all_obs_data %>%
    select(where(
      ~ is_double(.) &
        !inherits(., "Date") &
        !inherits(., "POSIXt") &
        !inherits(., "hms")
    )) %>%
    names()
  columns_to_round <- ui_column_vector(names(all_obs_data), double_column_names)

  disable_column_names <- c(
    "TripID",
    "Transect",
    "Record",
    "density",
    "invalid_columns",
    "nppsd_split_id",
    "split_id",
    "edited"
  )
  columns_to_disable <- ui_column_vector(
    names(all_obs_data),
    disable_column_names
  )

  sort_column_names <- c(
    "invalid_columns",
    "TripID",
    "Transect",
    "timestamp"
  )
  sort_columns <- ui_sort_options(names(all_obs_data), sort_column_names)

  x$all_points_data <- all_points_data
  x$all_obs_data <- all_obs_data
  x$obs_columns_to_round <- columns_to_round
  x$obs_columns_to_disable <- columns_to_disable
  x$obs_sort_columns <- sort_columns
  x$original_obs_data <- all_obs_data %>%
    filter(FALSE)

  console_log("generate_observations.r", "generate_observations() end")

  x
}
