skip_on_cran()

test_that("sapply lowers scalar-return closures (named + inline)", {
  fn_named <- function(x) {
    declare(type(x = double(NA)))
    out <- double(length(x))
    f <- function(i) x[i] * 2
    out <- sapply(seq_along(out), f)
    out
  }

  fn_inline <- function(x, thresh) {
    declare(type(x = double(NA)), type(thresh = double(1)))
    out <- logical(length(x))
    out <- sapply(seq_along(out), function(i) x[i] > thresh)
    out
  }

  expect_translation_snapshots(
    fn_named,
    note = "Named local closure lowered to an internal subroutine using host association for captures (no capture arguments)."
  )
  expect_translation_snapshots(
    fn_inline,
    note = "Inline closure lowered to an internal subroutine using host association for captures (no capture arguments)."
  )

  set.seed(1)
  x <- runif(25)
  expect_quick_identical(fn_named, list(x))
  expect_quick_identical(fn_inline, list(x, 0.5))
})

test_that("sapply supports integer scalar return", {
  fn <- function(x) {
    declare(type(x = double(NA)))
    out <- integer(length(x))
    out <- sapply(seq_along(out), function(i) i * 2L)
    out
  }

  expect_translation_snapshots(
    fn,
    note = "Integer scalar return produces an integer vector."
  )

  set.seed(1)
  x <- runif(10)
  expect_quick_identical(fn, list(x))
})

test_that("sapply iterates over vector values", {
  fn <- function(x) {
    declare(type(x = double(NA)))
    out <- sapply(x, function(v) v * 2)
    out
  }

  set.seed(1)
  x <- runif(8)
  expect_quick_identical(fn, list(x))
})

test_that("sapply supports simple sequence inputs", {
  fn <- function(n) {
    declare(type(n = integer(1)))
    out <- sapply(1:n, function(v) as.double(v) + 0.5)
    out
  }

  expect_quick_identical(fn, list(5L))
})

test_that("sapply supports singleton range sizes", {
  fn <- function(n) {
    declare(type(n = integer(1)))
    out <- sapply(1:n, function(v) v + 1L)
    out
  }

  expect_quick_identical(fn, list(1L), list(4L))
})

test_that("sapply seq() validates by sign and zero step", {
  bad_sign <- function() {
    out <- sapply(seq(1L, 5L, by = -1L), function(v) v)
    out
  }

  bad_zero <- function() {
    out <- sapply(seq(1L, 2L, by = 0L), function(v) v)
    out
  }

  ok_zero <- function() {
    out <- sapply(seq(2L, 2L, by = 0L), function(v) v + 1L)
    out
  }

  expect_error(quick(bad_sign), regexp = "wrong sign in 'by' argument")
  expect_error(quick(bad_zero), regexp = "invalid '\\(to - from\\)/by'")
  expect_quick_identical(ok_zero, list())
})

test_that("sapply seq_len() validates integer scalar", {
  bad_seq_len <- function() {
    out <- sapply(seq_len(2.5), function(v) v)
    out
  }

  expect_error(
    quick(bad_seq_len),
    regexp = "seq_len\\(\\) expects an integer scalar"
  )
})

test_that("sapply supports vector return -> matrix output", {
  fn <- function(x) {
    declare(type(x = double(m, n)))
    out <- matrix(0, nrow(x), ncol(x))
    out <- sapply(seq_len(ncol(x)), function(j) x[, j] * 2.0)
    out
  }

  expect_translation_snapshots(
    fn,
    note = "Vector return from a closure simplifies to a rank-2 output."
  )

  set.seed(1)
  x <- matrix(runif(12), 3, 4)
  expect_quick_identical(fn, list(x))
})

test_that("sapply supports logical vector return -> logical matrix output", {
  fn <- function(x, thresh) {
    declare(type(x = double(m, n)), type(thresh = double(1)))
    out <- matrix(FALSE, nrow(x), ncol(x))
    out <- sapply(seq_len(ncol(x)), function(j) x[, j] > thresh)
    out
  }

  expect_translation_snapshots(
    fn,
    note = "Logical vector return simplifies to a logical matrix output."
  )

  set.seed(1)
  x <- matrix(runif(12), 3, 4)
  expect_quick_identical(fn, list(x, 0.5))
})

test_that("sapply supports matrix return -> rank-3 output", {
  fn <- function(x, k) {
    declare(type(x = double(m, n)), type(k = integer(1)))
    out <- array(0, dim = c(nrow(x), ncol(x), k))
    out <- sapply(seq_len(k), function(t) x + as.double(t), simplify = "array")
    out
  }

  expect_translation_snapshots(
    fn,
    note = "Matrix return from a closure simplifies to a rank-3 output."
  )

  set.seed(1)
  x <- matrix(runif(12), 3, 4)
  expect_quick_identical(fn, list(x, 3L))
})

test_that("sapply supports slice returns for higher-rank captures", {
  fn_rank3 <- function(x) {
    declare(type(x = double(a, b, k)))
    out <- array(0, dim = dim(x))
    out <- sapply(
      seq_len(dim(x)[3]),
      function(t) x[,, t] + 1.0,
      simplify = "array"
    )
    out
  }

  fn_rank4 <- function(x) {
    declare(type(x = double(a, b, c, k)))
    out <- array(0, dim = dim(x))
    out <- sapply(
      seq_len(dim(x)[4]),
      function(t) x[,,, t] * 2.0,
      simplify = "array"
    )
    out
  }

  expect_translation_snapshots(
    fn_rank3,
    note = "Matrix slice x[,,t] returned into a rank-3 result."
  )
  expect_translation_snapshots(
    fn_rank4,
    note = "Rank-3 slice x[,,,t] returned into a rank-4 result."
  )

  set.seed(1)
  x3 <- array(runif(24), dim = c(2, 3, 4))
  x4 <- array(runif(48), dim = c(2, 3, 2, 4))
  expect_quick_identical(fn_rank3, list(x3))
  expect_quick_identical(fn_rank4, list(x4))
})

test_that("sapply supports rank-3 return -> rank-4 output", {
  fn <- function(x, k) {
    declare(type(x = double(a, b, c)), type(k = integer(1)))
    out <- array(0, dim = c(dim(x), k))
    out <- sapply(seq_len(k), function(t) x + as.double(t), simplify = "array")
    out
  }

  expect_translation_snapshots(
    fn,
    note = "Rank-3 return from a closure simplifies to a rank-4 output."
  )

  set.seed(1)
  x <- array(runif(24), dim = c(2, 3, 4))
  expect_quick_identical(fn, list(x, 2L))
})

test_that("closures can capture and index rank>1 arrays with x[i] linear indexing", {
  fn <- function(x) {
    declare(type(x = double(3, 4)))
    out <- double(length(x))
    out <- sapply(seq_along(out), function(i) x[i] + 1.0)
    out
  }

  expect_translation_snapshots(
    fn,
    note = "Linear indexing x[i] on a rank-2 capture uses computed subscripts."
  )

  set.seed(1)
  x <- matrix(runif(12), 3, 4)
  expect_quick_identical(fn, list(x))
})

test_that("sapply can write into an existing argument variable", {
  fn <- function(out, x) {
    declare(type(out = double(n)), type(x = double(n)))
    out <- sapply(seq_along(out), function(i) x[i] + 1.0)
    out
  }

  set.seed(1)
  x <- runif(10)
  out <- rep(0, length(x))
  expect_quick_identical(fn, list(out, x))
})

test_that("sapply preserves R semantics when output is also captured", {
  fn <- function(x) {
    declare(type(x = double(m, n)))
    out <- matrix(0, nrow(x), ncol(x))
    out <- x
    out <- sapply(seq_len(ncol(out)), function(j) out[, j] + 1.0)
    out
  }

  expect_translation_snapshots(
    fn,
    note = "When the output is captured by the closure, quickr uses a hidden temp to avoid in-place updates during the loop."
  )

  set.seed(1)
  x <- matrix(runif(12), 3, 4)
  expect_quick_identical(fn, list(x))
})

test_that("closures may assign to captured variables (shadowing the host)", {
  fn <- function(x) {
    declare(type(x = double(NA)))
    out <- double(length(x))
    f <- function(i) {
      x[i] <- x[i] + 1.0
      x[i]
    }
    out <- sapply(seq_along(out), f)
    out
  }

  x1 <- c(1, 2, 3)
  x2 <- c(-1.5, 0.25, 2.0)

  expect_identical(fn(x1), x1 + 1.0)
  expect_identical(fn(x2), x2 + 1.0)
  expect_quick_identical(fn, x1, x2, as.double(seq(-10, 10)))

  qfn <- quick(fn)
  for (x0 in list(x1, x2)) {
    x <- x0
    out <- qfn(x)
    expect_identical(out, x0 + 1.0)
    expect_identical(out, fn(x0))
    expect_identical(x, x0)
  }
})

test_that("shadowed captures are reinitialized for each sapply() iteration", {
  fn <- function(x) {
    declare(type(x = double(NA)))
    out <- double(length(x))
    out <- sapply(seq_along(out), function(i) {
      x <- x + as.double(i)
      sum(x)
    })
    out
  }

  set.seed(1)
  x <- runif(6)
  expect_quick_identical(fn, list(x))
})

test_that("local closures can be called directly with multiple arguments", {
  fn <- function(nx, ny, k, dt, steps) {
    declare(
      type(nx = integer(1)),
      type(ny = integer(1)),
      type(k = double(1)),
      type(dt = double(1)),
      type(steps = integer(1))
    )

    temp <- matrix(0, nx, ny)
    temp[nx / 2, ny / 2] <- 100

    apply_boundary_conditions <- function(temp) {
      temp[1, ] <- 0
      temp[nx, ] <- 0
      temp[, 1] <- 0
      temp[, ny] <- 0
      temp
    }

    update_temperature <- function(temp, k, dt) {
      temp_new <- temp
      for (i in 2:(nx - 1)) {
        for (j in 2:(ny - 1)) {
          temp_new[i, j] <- temp[i, j] + k * dt
        }
      }
      temp_new
    }

    for (step in seq_len(steps)) {
      temp <- apply_boundary_conditions(temp)
      temp <- update_temperature(temp, k, dt)
    }

    temp
  }

  set.seed(1)
  expect_quick_identical(fn, list(10L, 8L, 0.1, 0.01, 3L))
})

test_that("sapply errors for insufficient arguments", {
  expect_error(
    quick(function(x) {
      declare(type(x = double(NA)))
      out <- sapply(seq_along(x))
      out
    }),
    "sapply\\(\\) requires at least 2 arguments"
  )
})

test_that("sapply errors for unsupported simplify value", {
  expect_error(
    quick(function(x) {
      declare(type(x = double(NA)))
      out <- sapply(seq_along(x), function(i) x[i], simplify = FALSE)
      out
    }),
    'Only `sapply.*simplify = "array".*is supported'
  )
})

test_that("sapply errors for unsupported FUN", {
  expect_error(
    quick(function(x) {
      declare(type(x = double(NA)))
      out <- sapply(seq_along(x), "mean")
      out
    }),
    "unsupported FUN in sapply"
  )
})

test_that("sapply errors when FUN has wrong number of arguments", {
  expect_error(
    quick(function(x) {
      declare(type(x = double(NA)))
      f <- function(i, j) x[i] + x[j]
      out <- sapply(seq_along(x), f)
      out
    }),
    "sapply\\(\\) FUN must have exactly one named argument"
  )
})

test_that("sapply errors when output is scalar", {
  expect_error(
    quick(function(x) {
      declare(type(x = double(NA)))
      out <- 0
      out <- sapply(seq_along(x), function(i) x[i])
      out
    }),
    "sapply\\(\\) output must be an array"
  )
})

test_that("sapply errors for rank>2 output without simplify='array'", {
  expect_error(
    quick(function(x) {
      declare(type(x = double(a, b, c)))
      out <- array(0, dim = c(dim(x), 2L))
      out <- sapply(1:2, function(t) x)
      out
    }),
    'sapply\\(\\) that returns arrays requires.*simplify = "array"'
  )
})

test_that("sapply errors when named FUN is not a local closure", {
  expect_error(
    quick(function(x) {
      declare(type(x = double(NA)))
      out <- sapply(seq_along(x), sum)
      out
    }),
    "unsupported FUN in sapply\\(\\)"
  )
})
