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))

  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
    )

  # 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"
    )

  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
}
