test_that("filter captures local variables", {
  mf <- local_memdb_frame(x = 1:5, y = 5:1, id = 1:5)

  z <- 3
  expect_equal(mf |> filter(x > z) |> pull(), c(4, 5))
})

test_that("two filters equivalent to one", {
  unique_column_name_reset()
  mf <- local_memdb_frame("df", x = 1:5, y = 5:1, id = 1:5)

  df1 <- mf |> filter(x > 3) |> filter(y < 3)
  expect_equal(df1 |> pull(), c(4, 5))
  expect_snapshot(df1 |> show_query())

  df2 <- mf |> filter(mean(x, na.rm = TRUE) > 2) |> filter(y < 3)
  expect_equal(df2 |> pull(), c(4, 5))
  expect_snapshot(df2 |> show_query())
})

test_that("correctly inlines across all verbs", {
  lf <- lazy_frame(x = 1, y = 2)

  # single table verbs
  expect_selects(lf |> arrange(x) |> filter(x == 1), 1)
  expect_selects(lf |> distinct() |> filter(x == 1), 1)
  expect_selects(lf |> filter(x == 1) |> filter(y == 1), 1)
  expect_selects(lf |> head(1) |> filter(x == 1), 1)
  expect_selects(lf |> mutate(z = x + 1) |> filter(x == 1), 1)
  expect_selects(lf |> select(y = x) |> filter(y == 1), 1)
  expect_selects(lf |> summarise(y = mean(x)) |> filter(y == 1), 1)

  # two table verbs
  lf2 <- lazy_frame(x = 1)
  expect_selects(lf |> left_join(lf2, by = "x") |> filter(x == 1), 1)
  expect_selects(lf |> right_join(lf2, by = "x") |> filter(x == 1), 2)
  expect_selects(lf |> semi_join(lf2, by = "x") |> filter(x == 1), 3)
  expect_selects(lf |> union(lf2) |> filter(x == 1), 3)

  # special cases
  expect_selects(lf |> filter(mean(x) == 1), 2)
  expect_selects(lf |> mutate(x2 = mean(x)) |> filter(x == 1), 2)
  expect_selects(lf |> mutate(z = sql("x")) |> filter(z == 1), 2)
  expect_selects(lf |> mutate(z = x + 1) |> filter(z == 1), 2)

  # flag add_filter() for update if add_mutate ever gains inlining
  expect_selects(lf |> mutate(z = x + 1) |> filter(n() == 1), 3)
})

test_that("each argument gets implicit parens", {
  mf <- local_memdb_frame(
    v1 = c("a", "b", "a", "b"),
    v2 = c("b", "a", "a", "b"),
    v3 = c("a", "b", "c", "d"),
    id = 1:4
  )

  # want (v1 == "a" | v2 == "a") & v3 == "a"
  # NOT (v1 == "a" | v2 == "a" & v3 == "a")
  expect_equal(mf |> filter(v1 == "a" | v2 == "a", v3 == "a") |> pull(), 1)
})

test_that("only add step if necessary", {
  lf <- lazy_frame(x = 1:3, y = 1:3)
  expect_equal(lf |> filter(), lf)
})

test_that("errors for named input", {
  lf <- lazy_frame(x = 1, y = 2)

  expect_snapshot(error = TRUE, filter(lf, x = 1))
  expect_snapshot(error = TRUE, filter(lf, y > 1, x = 1))
})

test_that(".preserve is not supported", {
  lf <- lazy_frame(x = 1:3, y = 1:3)
  expect_snapshot(error = TRUE, lf |> filter(x == 1, .preserve = TRUE))
})

test_that("filter() inlined after select()", {
  lf <- lazy_frame(x = 1, y = 2)

  out <- lf |>
    select(y) |>
    filter(y > 1)

  expect_equal(
    remote_query(out),
    sql("SELECT \"y\"\nFROM \"df\"\nWHERE (\"y\" > 1.0)")
  )

  out <- lf |>
    select(z = x) |>
    filter(z == 1)
  lq <- out$lazy_query
  expect_equal(lq$select$expr, list(sym("x")))
  expect_equal(lq$where, list(expr(x == 1)))
})

test_that("filter() inlined after mutate()", {
  lf <- lazy_frame(x = 1, y = 2)

  out <- lf |>
    mutate(x = x + 1) |>
    filter(y == 1)
  lq <- out$lazy_query
  expect_equal(lq$select$expr, list(expr(x + 1), expr(y)))
  expect_equal(lq$where, list(expr(y == 1)))

  # can rename variable used in `filter()`
  out <- lf |>
    rename(z = x) |>
    filter(z == 1)
  lq <- out$lazy_query
  expect_equal(lq$select$expr, list(sym("x"), sym("y")))
  expect_equal(lq$select$name, c("z", "y"))
  expect_equal(lq$where, list(expr(x == 1)))

  # does not rename field names of `$` that collide with renamed columns (#1812)
  ltr <- list(z = "A", x = "B")
  out <- lf |>
    rename(z = x) |>
    filter(z == ltr$z)
  expect_equal(out$lazy_query$where, list(expr(x == (!!ltr)$z)))

  # does not inline if uses mutated variable
  out2 <- lf |>
    mutate(x = x + 1) |>
    filter(x == 1)
  lq2 <- out2$lazy_query
  expect_equal(lq2$x$select$expr, list(expr(x + 1), sym("y")))
  expect_equal(lq2$select$expr, syms(c("x", "y")))
  expect_equal(lq2$where, list(expr(x == 1)))

  # does not inline if unclear whether uses mutated variable
  out3 <- lf |>
    mutate(x = x + 1) |>
    filter(y == sql("1"))
  lq3 <- out3$lazy_query
  expect_equal(lq3$select$expr, syms(c("x", "y")))
  expect_s3_class(lq3$x, "lazy_select_query")
  # sql("1") is evaluated during partial_eval
  expect_equal(lq3$where[[1]], expr(y == !!sql("1")))
})

test_that("filter isn't inlined after mutate with window function #1135", {
  lf <- lazy_frame(x = 1L, y = 1:2)
  out <- lf |>
    dplyr::mutate(z = sum(y, na.rm = TRUE)) |>
    dplyr::filter(y <= 1)

  lq <- out$lazy_query
  expect_equal(lq$select$expr, syms(c("x", "y", "z")))
  expect_equal(lq$where, list(expr(y <= 1)))
  expect_equal(lq$x$select$expr[[3]], expr(sum(y, na.rm = TRUE)))

  out2 <- lf |>
    dplyr::mutate(z = sql("SUM(y) OVER ()")) |>
    dplyr::filter(y <= 1)

  lq2 <- out2$lazy_query
  expect_equal(lq2$select$expr, syms(c("x", "y", "z")))
  expect_equal(lq2$where, list(expr(y <= 1)))
  # sql() is evaluated during partial_eval
  expect_equal(lq2$x$select$expr[[3]], sql("SUM(y) OVER ()"))
})

# .by -------------------------------------------------------------------------

test_that("can group transiently using `.by`", {
  df <- local_memdb_frame(g = c(1, 1, 2, 1, 2), x = c(5, 10, 1, 2, 3))

  out <- filter(df, x > mean(x), .by = g) |>
    arrange(g, x) |>
    collect()

  expect_identical(out$g, c(1, 2))
  expect_identical(out$x, c(10, 3))
  expect_equal(group_vars(out), character())
})

test_that("catches `.by` with grouped-df", {
  df <- lazy_frame(x = 1)
  gdf <- group_by(df, x)

  expect_snapshot(error = TRUE, {
    filter(gdf, .by = x)
  })
})

# SQL generation --------------------------------------------------------

test_that("filter calls windowed versions of sql functions", {
  df1 <- local_memdb_frame(x = 1:10, g = rep(c(1, 2), each = 5))

  out <- df1 |> group_by(g) |> filter(dplyr::row_number(x) < 3) |> collect()
  expect_equal(out$x, c(1L, 2L, 6L, 7L))
})

test_that("filter() can use window function and external vector - #1048", {
  to_filter <- 1:2
  expect_snapshot(
    lazy_frame(x = 1L) |>
      filter(x == max(x, na.rm = T), x %in% to_filter)
  )
})

test_that("recycled aggregates generate window function", {
  df1 <- local_memdb_frame(x = 1:10, g = rep(c(1, 2), each = 5))

  out <- df1 |>
    group_by(g) |>
    filter(x > mean(x, na.rm = TRUE)) |>
    collect()
  expect_equal(out$x, c(4L, 5L, 9L, 10L))
})

test_that("cumulative aggregates generate window function", {
  df1 <- local_memdb_frame(x = c(1:3, 2:4), g = rep(c(1, 2), each = 3))
  out <- df1 |>
    group_by(g) |>
    window_order(x) |>
    filter(cumsum(x) > 3)

  expect_equal(pull(out, x), c(3L, 3L, 4L))
})

test_that("filter() after summarise() uses `HAVING`", {
  lf <- lazy_frame(g = 1, h = 1, x = 1) |>
    group_by(g, h) |>
    summarise(x_mean = mean(x, na.rm = TRUE), .groups = "drop_last")
  mf <- local_memdb_frame(g = c(1, 1, 1, 2, 2), h = 1, x = 1:5) |>
    group_by(g, h) |>
    summarise(x_mean = mean(x, na.rm = TRUE), .groups = "drop_last")

  # use `HAVING`
  expect_snapshot((out <- lf |> filter(g == 1)))
  expect_equal(out$lazy_query$having, list(expr(g == 1)))
  expect_equal(out$lazy_query$group_by, list(sym("g"), sym("h")))
  expect_equal(op_grps(out), "g")

  expect_equal(
    mf |>
      filter(g == 1) |>
      collect(),
    tibble(g = 1, h = 1, x_mean = 2) |> group_by(g)
  )

  # Can use freshly aggregated column
  expect_snapshot((out <- lf |> filter(x_mean > 1)))
  expect_equal(out$lazy_query$having, list(expr(mean(x, na.rm = TRUE) > 1)))

  expect_equal(
    mf |>
      filter(x_mean > 3) |>
      collect(),
    tibble(g = 2, h = 1, x_mean = 4.5) |> group_by(g)
  )

  # multiple `filter()` combine instead of overwrite
  expect_snapshot(
    (out <- lf |>
      filter(g == 1) |>
      filter(g == 2))
  )
  expect_equal(out$lazy_query$having, list(expr(g == 1), expr(g == 2)))

  expect_snapshot(
    (out <- lf |>
      filter(g == 1) |>
      filter(h == 2))
  )
  expect_equal(out$lazy_query$having, list(expr(g == 1), expr(h == 2)))

  # `window_order()` and `window_frame()` do not matter
  out <- lazy_frame(g = 1, h = 1, x = 1) |>
    window_order(h) |>
    window_frame(-3) |>
    group_by(g, h) |>
    summarise(x_mean = mean(x, na.rm = TRUE), .groups = "drop_last") |>
    filter(x_mean > 1)

  lq <- out$lazy_query
  expect_equal(lq$having, list(expr(mean(x, na.rm = TRUE) > 1)))
  # TODO should the `order_vars` and the `frame` really survive `summarise()`?
  expect_equal(lq$order_vars, list(expr(h)))
  expect_equal(lq$frame, list(range = c(-3, Inf)))
})

test_that("`HAVING` supports expressions #1128", {
  lf <- lazy_frame(x = 1)

  expect_snapshot({
    lf |>
      summarise(x_sum = sum(x, na.rm = TRUE)) |>
      filter(!is.na(x_sum))
  })

  out <- lf |>
    summarise(x_sum = sum(x, na.rm = TRUE)) |>
    filter(!is.na(x_sum))
  expect_equal(out$lazy_query$having, list(expr(!is.na(sum(x, na.rm = TRUE)))))

  # correctly handles environments
  y <- 1L
  f <- function(lf, y = 2L) {
    lf |> summarise(x_sum = sum(x, na.rm = TRUE) - y)
  }

  out <- f(lf) |>
    filter(!is.na(x_sum + y))

  expect_equal(
    out$lazy_query$having,
    list(expr(!is.na(sum(x, na.rm = TRUE) - 2L + 1L)))
  )
})

test_that("filter() after mutate() does not use `HAVING`", {
  lf <- lazy_frame(g = 1, h = 1, x = 1) |>
    group_by(g, h) |>
    mutate(x_mean = mean(x, na.rm = TRUE))

  expect_snapshot((out <- lf |> filter(x_mean > 1)))
  lq <- out$lazy_query
  expect_s3_class(lq$x, "lazy_select_query")
})

test_that("filter() using a window function after summarise() does not use `HAVING`", {
  lf <- lazy_frame(g = 1, h = 1, x = 1) |>
    group_by(g, h) |>
    summarise(x_mean = mean(x, na.rm = TRUE), .groups = "drop_last")

  expect_snapshot((out <- lf |> filter(cumsum(x_mean) == 1)))
  lq <- out$lazy_query
  expect_s3_class(lq$x, "lazy_select_query")
})

# sql_build ---------------------------------------------------------------

test_that("filter generates simple expressions", {
  out <- lazy_frame(x = 1) |>
    filter(x > 1L) |>
    sql_build()

  expect_equal(out$where, sql('"x" > 1'))
})

# lazy_select_query -------------------------------------------------------

test_that("generates correct lazy_select_query", {
  lf <- lazy_frame(x = 1:3, y = 3:1)

  expect_equal(
    filter(lf, x > 1)$lazy_query,
    lazy_select_query(
      x = lf$lazy_query,
      select = syms(set_names(colnames(lf))),
      where = list(expr(x > 1))
    )
  )

  out <- lf |>
    filter(mean(x, na.rm = TRUE) > 1)

  expect_equal(
    out$lazy_query,
    lazy_select_query(
      x = out$lazy_query$x,
      select = syms(set_names(colnames(lf))),
      where = list(expr(col01 > 1))
    )
  )

  expect_equal(
    out$lazy_query$x,
    lazy_select_query(
      x = lf$lazy_query,
      select_operation = "mutate",
      select = list(
        x = sym("x"),
        y = sym("y"),
        col01 = expr(mean(x, na.rm = TRUE))
      )
    )
  )
})

# filter_out ------------------------------------------------------------------

test_that("filter_out keeps rows where the condition is FALSE or NA", {
  db <- memdb_frame(x = c(2, NA, 5, NA, 10), y = 1:5)
  out <- db |> filter_out(x < 5) |> collect()
  expect_equal(out$x, c(NA, 5, NA, 10))
  expect_equal(out$y, c(2L, 3L, 4L, 5L))
})

test_that("filter_out matches local dplyr semantics with multiple conditions", {
  local_df <- tibble(x = c(1, 2, NA), y = c(1, NA, 3))
  db <- memdb_frame(x = local_df$x, y = local_df$y)

  expect_equal(
    db |> filter_out(x == 1, y == 3) |> collect(),
    filter_out(local_df, x == 1, y == 3)
  )
})

test_that("filter_out uses backend-aware IS DISTINCT FROM TRUE", {
  filter_out_backend <- function(con) {
    lf <- lazy_frame(x = 1, y = 2, con = con)
    lf |> filter_out(x == 1, y == 3)
  }

  expect_snapshot({
    filter_out_backend(simulate_postgres())
    filter_out_backend(simulate_sqlite())
    filter_out_backend(simulate_dbi())
  })
})

test_that("filter_out with no conditions returns no rows", {
  lf <- lazy_frame(x = 1:3, y = 1:3)
  expect_equal(lf |> filter_out(), lf |> filter(FALSE))
})

test_that("filter_out errors for named input", {
  lf <- lazy_frame(x = 1, y = 2)
  expect_snapshot(error = TRUE, filter_out(lf, x = 1))
})

test_that("filter_out errors for .preserve = TRUE", {
  lf <- lazy_frame(x = 1:3, y = 1:3)
  expect_snapshot(error = TRUE, lf |> filter_out(x == 1, .preserve = TRUE))
})

test_that("filter_out supports .by", {
  db <- local_memdb_frame(g = c(1, 1, 2, 1, 2), x = c(5, 10, 1, 2, 3))
  out <- filter_out(db, x > mean(x, na.rm = TRUE), .by = g) |>
    arrange(g, x) |>
    collect()

  expect_equal(out$g, c(1, 1, 2))
  expect_equal(out$x, c(2, 5, 1))
})

test_that("filter_out captures local variables", {
  mf <- local_memdb_frame(x = 1:5, y = 5:1, id = 1:5)
  z <- 3
  expect_equal(mf |> filter_out(x > z) |> pull(), 1:3)
})
