# Coverage-oriented public API tests (exercise compiled calls via quick()).

skip_on_cran()

test_that("C bridge size checks handle scalar size args (including dotted args)", {
  fn <- function(n, x) {
    declare(type(n = integer(1)), type(x = double(n)))
    sum(x)
  }
  qfn <- quick(fn)

  expect_identical(qfn(3L, as.double(1:3)), 6.0)
  expect_error(
    qfn(4L, as.double(1:3)),
    "length\\(x\\) must equal n, but are 3 and 4",
    fixed = FALSE
  )

  dotted <- function(foo.bar, x) {
    declare(type(foo.bar = integer(1)), type(x = double(foo.bar)))
    sum(x)
  }
  qdotted <- quick(dotted)

  expect_identical(qdotted(2L, c(0.25, 0.75)), 1.0)
  expect_error(
    qdotted(3L, c(0.25, 0.75)),
    "length\\(x\\) must equal foo\\.bar, but are 2 and 3",
    fixed = FALSE
  )
})

test_that("qr.solve supports explicit tol and errors on rank-deficient matrices", {
  fn_tol <- function(a, b) {
    declare(type(a = double(n, k)), type(b = double(n)))
    qr.solve(a, b, tol = 1e-7)
  }

  set.seed(1)
  a <- matrix(rnorm(50), nrow = 10)
  b <- rnorm(10)
  qtol <- quick(fn_tol)
  expect_equal(qtol(a, b), qr.solve(a, b, tol = 1e-7))

  fn_rd <- function(a, b) {
    declare(type(a = double(n, k)), type(b = double(n)))
    qr.solve(a, b)
  }
  qrd <- quick(fn_rd)

  a_rd <- cbind(1, 1, rnorm(10))
  b_rd <- rnorm(10)
  expect_error(
    qrd(a_rd, b_rd),
    "rank deficient matrix in qr.solve",
    fixed = TRUE
  )
})

test_that("svd() $u/$d/$v can be used to reconstruct a matrix", {
  fn <- function(x) {
    declare(type(x = double(m, n)))
    s <- svd(x)
    s$u %*% diag(s$d) %*% t(s$v)
  }

  set.seed(2)
  x <- matrix(rnorm(12), nrow = 4, ncol = 3)
  qfn <- quick(fn)
  expect_equal(qfn(x), x, tolerance = 1e-10)
})

test_that("local closure calls use keyword arguments when optional args are missing", {
  fn <- function(x) {
    declare(type(x = double(n)))

    f <- function(i, shift = NULL, scale = NULL) {
      if (is.null(shift)) {
        shift <- 1
      }
      if (is.null(scale)) {
        scale <- 2
      }
      (x[i] + shift) * scale
    }

    g <- function(i) f(i, scale = 3)
    sapply(seq_along(x), g)
  }

  set.seed(3)
  x <- runif(20)
  expect_quick_equal(fn, list(x))
})

test_that("subset designators support drop = FALSE for scalar indices", {
  fn <- function(A) {
    declare(type(A = double(m, n)))
    A[1, 2, drop = FALSE]
  }

  set.seed(4)
  A <- matrix(runif(20), nrow = 5)
  expect_quick_identical(fn, list(A))
})

test_that("indexing a length-1 vector with [1] is a no-op", {
  fn <- function(x) {
    declare(type(x = double(1L)))
    x[1] + 1
  }
  qfn <- quick(fn)
  expect_identical(qfn(2.5), 3.5)
})

test_that("subset superassignment works and rejects unsupported forms", {
  ok <- function(A) {
    declare(type(A = double(m, n)))

    f <- function() {
      A[1, 1] <<- 0
      NULL
    }

    f()
    A
  }

  set.seed(5)
  A <- matrix(runif(12), nrow = 4)
  qok <- quick(ok)
  out <- qok(A)
  expect_identical(out[1, 1], 0.0)

  bad_drop <- function(A) {
    declare(type(A = double(m, n)))
    f <- function() {
      A[1, 1, drop = FALSE] <<- A[1, 1, drop = FALSE]
    }
    f()
    A
  }
  expect_error(
    quick(bad_drop),
    "drop = FALSE not supported for superassignment",
    fixed = TRUE
  )

  bad_base <- function(A) {
    declare(type(A = double(m, n)))
    f <- function() {
      (A)[1, 1] <<- 0
    }
    f()
    A
  }
  expect_error(
    quick(bad_base),
    "only superassignment to x[...] is supported",
    fixed = TRUE
  )

  missing_target <- function(A) {
    declare(type(A = double(m, n)))
    f <- function() {
      B[1, 1] <<- 0
    }
    f()
    A
  }
  expect_error(
    quick(missing_target),
    "<<- targets must resolve to an existing variable",
    fixed = TRUE
  )
})

test_that("subset translation errors are user-facing", {
  rank_mismatch <- function(A) {
    declare(type(A = double(m, n)))
    A[1, 2, 3]
  }
  expect_error(
    quick(rank_mismatch),
    "number of args to x[...] must match the rank of x",
    fixed = TRUE
  )

  bad_index_rank <- function(x, idx) {
    declare(type(x = double(n)), type(idx = integer(2L, 2L)))
    x[idx]
  }
  expect_error(
    quick(bad_index_rank),
    "all args to x[...] must be logical or integer of rank 0 or 1",
    fixed = TRUE
  )
})

test_that("svd() rejects unsupported arguments and access patterns", {
  missing_x <- function() {
    svd(NULL)$d
  }
  expect_error(
    quick(missing_x),
    "svd\\(\\) expects `x`",
    fixed = FALSE
  )

  bad_nu <- function(x) {
    declare(type(x = double(m, n)))
    svd(x, nu = 0)$d
  }
  expect_error(quick(bad_nu), "svd() does not support `nu` yet", fixed = TRUE)

  bad_nv <- function(x) {
    declare(type(x = double(m, n)))
    svd(x, nv = 0)$d
  }
  expect_error(quick(bad_nv), "svd() does not support `nv` yet", fixed = TRUE)

  bad_linpack <- function(x) {
    declare(type(x = double(m, n)))
    svd(x, LINPACK = TRUE)$d
  }
  expect_error(
    quick(bad_linpack),
    "svd() does not support LINPACK",
    fixed = TRUE
  )

  bad_field <- function(x) {
    declare(type(x = double(m, n)))
    svd(x)$foo
  }
  expect_error(
    quick(bad_field),
    "`$` only supports `.Machine$double.eps` and `svd()` results ($d, $u, $v)",
    fixed = TRUE
  )

  bare_call <- function(x) {
    declare(type(x = double(m, n)))
    svd(x)
    1
  }
  expect_error(
    quick(bare_call),
    "svd() must be assigned to a symbol or accessed via $d, $u, $v",
    fixed = TRUE
  )
})

test_that("svd() assignment cannot overwrite declared variables", {
  fn <- function(x) {
    declare(type(x = double(m, n)))
    declare(type(s = double(1L)))
    s <- svd(x)
    1
  }

  expect_error(
    quick(fn),
    "svd() result cannot overwrite declared variable: s",
    fixed = TRUE
  )
})

test_that("invalid size expressions produce helpful errors", {
  bad_length_literal <- function(x) {
    declare(type(x = double(length(1 + 2))))
    sum(x)
  }
  expect_error(
    quick(bad_length_literal),
    "length() size expressions must refer to a symbol",
    fixed = TRUE
  )

  bad_length_unknown <- function(x) {
    declare(type(x = double(length(y))))
    sum(x)
  }
  expect_error(
    quick(bad_length_unknown),
    "could not resolve size: y",
    fixed = TRUE
  )

  bad_dim_axis <- function(y, x) {
    declare(type(y = double(n)), type(x = double(dim(y)[1.5])))
    sum(x)
  }
  expect_error(
    quick(bad_dim_axis),
    "dim(x)[axis] requires integer axis",
    fixed = TRUE
  )

  bad_dim_rank <- function(y, x) {
    declare(type(y = double(n)), type(x = double(dim(y)[2])))
    sum(x)
  }
  expect_error(
    quick(bad_dim_rank),
    "insufficient rank of variable",
    fixed = TRUE
  )
})

test_that("size expressions support max() and integer division", {
  fn <- function(n, x) {
    declare(
      type(n = integer(1)),
      type(x = double(max(n %/% 2L, 2L, 3L)))
    )
    sum(x)
  }
  qfn <- quick(fn)

  expect_identical(qfn(8L, rep(1, 4)), 4.0)
  expect_error(qfn(8L, rep(1, 3)))
})

test_that("C bridge size expressions support min()", {
  fn <- function(n, x) {
    declare(
      type(n = integer(1)),
      type(x = double(min(n, 5L)))
    )
    sum(x)
  }

  qfn <- quick(fn)
  expect_identical(qfn(3L, as.double(1:3)), 6.0)
  expect_identical(qfn(10L, as.double(1:5)), 15.0)
})

test_that("vector ops accept mixed constant/symbol sizes", {
  fn <- function(n, x, y) {
    declare(
      type(n = integer(1)),
      type(x = double(3L)),
      type(y = double(n))
    )
    x + y
  }

  qfn <- quick(fn)
  expect_identical(
    qfn(3L, as.double(1:3), as.double(4:6)),
    as.double(c(5, 7, 9))
  )
})

test_that("size expressions support length() and modulo", {
  fn <- function(x) {
    declare(
      type(x = double(n)),
      type(out = double(length(x) %% 3L + 1L))
    )
    out <- double(length(x) %% 3L + 1L)
    for (i in seq_len(length(out))) {
      out[i] <- as.double(i)
    }
    out
  }

  x <- runif(5)
  expect_quick_identical(fn, list(x))
})

test_that("symbolic size names reuse fortranized symbols", {
  fn <- function(foo.bar, x, y) {
    declare(
      type(foo.bar = integer(1)),
      type(x = double(foo.bar)),
      type(y = double(foo.bar))
    )
    x + y
  }

  qfn <- quick(fn)
  expect_identical(
    qfn(3L, as.double(1:3), as.double(4:6)),
    as.double(c(5, 7, 9))
  )
})

test_that("quick() returns named lists from compiled functions", {
  fn <- function(a, b) {
    declare(type(a = double(1)), type(b = double(1)))
    sum <- a + b
    diff <- a - b
    list(sum = sum, diff = diff)
  }

  qfn <- quick(fn)
  expect_identical(qfn(2, 1), fn(2, 1))
})

test_that("triangular solve handles matrix right-hand sides", {
  fn <- function(U, B) {
    declare(type(U = double(n, n)), type(B = double(n, k)))
    backsolve(U, B)
  }

  set.seed(6)
  n <- 3
  k <- 2
  U <- matrix(rnorm(n * n), nrow = n)
  U[lower.tri(U)] <- 0
  B <- matrix(rnorm(n * k), nrow = n)

  qfn <- quick(fn)
  expect_equal(qfn(U, B), backsolve(U, B), tolerance = 1e-10)
})

test_that("inline local closure calls compile", {
  fn <- function(x) {
    declare(type(x = double(1)))
    (function(y) y + 1)(x)
  }

  expect_quick_identical(fn, list(2))
})

test_that("math intrinsics handle doubles with log10", {
  fn <- function(x) {
    declare(type(x = double(n)))
    out <- sin(x) + cos(x)
    out <- out + sqrt(x) + log(x) + exp(x)
    out <- out + floor(x) + ceiling(x)
    out <- out + log10(x)
    out
  }

  x <- seq(0.1, 2, length.out = 6)
  expect_quick_equal(fn, list(x))
})

test_that("math intrinsics handle complex log10 and components", {
  fn <- function(z) {
    declare(type(z = complex(n)))
    out <- Re(z) + Im(z) + Mod(z) + Arg(z) + abs(z)
    out <- out + Re(log10(z)) + Im(log10(z))
    out <- out + Re(Conj(z))
    out
  }

  z <- complex(
    real = seq(1, 6, length.out = 6),
    imaginary = seq(2, 7, length.out = 6)
  )
  qfn <- quick(fn)
  expect_equal(qfn(z), fn(z), tolerance = 1e-12)
})

test_that("reductions hoist logical masks and linear indexing works", {
  fn_mask <- function(x) {
    declare(type(x = double(n)))
    sum(x[x > 0])
  }
  x <- c(-1, 2, -3, 4)
  expect_quick_equal(fn_mask, list(x))

  fn_linear <- function(A) {
    declare(type(A = double(m, n)))
    A[3]
  }
  A <- matrix(as.double(1:6), nrow = 2)
  expect_quick_identical(fn_linear, list(A))
})

test_that("reductions reject distinct masks within one expression", {
  fn <- function(x, a, b) {
    declare(
      type(x = double(n)),
      type(a = logical(n)),
      type(b = logical(n))
    )
    sum(x[a] + x[b])
  }
  expect_error(
    quick(fn),
    "reduction expressions only support a single logical mask",
    fixed = TRUE
  )
})

test_that("cbind handles matrix, vector, and scalar inputs with symbolic sizes", {
  fn <- function(A, v, s) {
    declare(type(A = double(n, k)), type(v = double(n)), type(s = double(1)))
    cbind(A, v, s)
  }

  set.seed(7)
  A <- matrix(rnorm(6), nrow = 2)
  v <- rnorm(2)
  s <- 1.25
  qfn <- quick(fn)
  expect_equal(unname(qfn(A, v, s)), unname(cbind(A, v, s)))
})

test_that("cbind preserves logical inputs", {
  fn <- function(a, b) {
    declare(type(a = logical(n)), type(b = logical(n)))
    cbind(a, b)
  }

  a <- c(TRUE, FALSE, TRUE)
  b <- c(FALSE, TRUE, FALSE)
  qfn <- quick(fn)
  expect_identical(unname(qfn(a, b)), unname(cbind(a, b)))
})

test_that("rbind coerces logical inputs to integer mode", {
  fn <- function(x, lgl, s) {
    declare(type(x = integer(n)), type(lgl = logical(n)), type(s = integer(1)))
    rbind(x, lgl, s)
  }

  x <- as.integer(1:3)
  lgl <- c(TRUE, FALSE, TRUE)
  s <- 2L
  qfn <- quick(fn)
  expect_identical(unname(qfn(x, lgl, s)), unname(rbind(x, lgl, s)))
})

test_that("cbind supports dynamic column sizes with common row counts", {
  bad_cols <- function(x, y) {
    declare(type(x = double(n, NA)), type(y = double(n, NA)))
    cbind(x, y)
  }
  x <- matrix(as.double(1:6), nrow = 2)
  y <- matrix(as.double(7:12), nrow = 2)
  qcols <- quick(bad_cols)
  expect_identical(unname(qcols(x, y)), unname(cbind(x, y)))

  bad_rows <- function(x, y) {
    declare(type(x = double(NA, n)), type(y = double(NA, n)))
    cbind(x, y)
  }
  expect_error(
    quick(bad_rows),
    "cbind() requires inputs with a common row count",
    fixed = TRUE
  )
})

test_that("crossprod supports two-argument form", {
  fn <- function(x, y) {
    declare(type(x = double(n, k)), type(y = double(n, m)))
    crossprod(x, y)
  }

  set.seed(8)
  x <- matrix(rnorm(6), nrow = 3)
  y <- matrix(rnorm(9), nrow = 3)
  qfn <- quick(fn)
  expect_equal(qfn(x, y), crossprod(x, y), tolerance = 1e-12)
})

test_that("crossprod errors on non-conformable arguments", {
  fn <- function(x, y) {
    declare(type(x = double(2L, 3L)), type(y = double(4L, 5L)))
    crossprod(x, y)
  }

  expect_error(
    quick(fn),
    "non-conformable arguments in crossprod",
    fixed = TRUE
  )
})

test_that("matmul assignment infers destination shape", {
  fn <- function(A, B) {
    declare(type(A = double(n, k)), type(B = double(k, m)))
    out <- (A %*% B)
    out
  }

  set.seed(9)
  A <- matrix(rnorm(6), nrow = 2)
  B <- matrix(rnorm(6), nrow = 3)
  qfn <- quick(fn)
  expect_equal(qfn(A, B), A %*% B, tolerance = 1e-12)
})

test_that("double literals preserve precision in compiled code", {
  fn <- function(x) {
    declare(type(x = double(1)))
    x + 1.23456789
  }

  qfn <- quick(fn)
  expect_equal(qfn(0), 1.23456789, tolerance = 1e-12)
})

test_that("character constructor is not implemented", {
  fn <- function() {
    character(1)
  }

  expect_error(quick(fn), "not implemented yet", fixed = TRUE)
})

test_that("raw constructor is not implemented", {
  fn <- function() {
    raw(1)
  }

  expect_error(quick(fn), "not implemented yet", fixed = TRUE)
})

test_that("cbind errors for complex mixed with non-complex", {
  fn <- function(x, y) {
    declare(type(x = complex(n)), type(y = double(n)))
    cbind(x, y)
  }

  expect_error(
    quick(fn),
    "cbind() does not support mixing complex with other types",
    fixed = TRUE
  )
})

test_that("cbind requires known and common row sizes", {
  mismatched_rows <- function(x, y) {
    declare(type(x = double(n)), type(y = double(m)))
    cbind(x, y)
  }
  expect_error(
    quick(mismatched_rows),
    "cbind() requires inputs with a common row count",
    fixed = TRUE
  )
})

test_that("rbind requires common column sizes", {
  mismatched_cols <- function(x, y) {
    declare(type(x = double(n)), type(y = double(m)))
    rbind(x, y)
  }
  expect_error(
    quick(mismatched_cols),
    "rbind() requires inputs with a common column count",
    fixed = TRUE
  )
})

test_that("cbind supports symbolic size expressions", {
  fn <- function(n, x, y) {
    declare(
      type(n = integer(1)),
      type(x = double(n + 1L)),
      type(y = double(n + 1L))
    )
    cbind(x, y)
  }

  qfn <- quick(fn)
  x <- as.double(1:4)
  y <- as.double(5:8)
  expect_equal(unname(qfn(3L, x, y)), unname(cbind(x, y)))
})

test_that("tcrossprod supports single-argument form", {
  fn <- function(x) {
    declare(type(x = double(n, k)))
    tcrossprod(x)
  }

  set.seed(10)
  x <- matrix(rnorm(12), nrow = 3)
  qfn <- quick(fn)
  expect_equal(qfn(x), tcrossprod(x), tolerance = 1e-12)
})

test_that("svd() $u and $v can be accessed inline", {
  fn <- function(x) {
    declare(type(x = double(m, n)))
    sum(svd(x)$u) + sum(svd(x)$v)
  }

  set.seed(11)
  x <- matrix(rnorm(12), nrow = 4)
  qfn <- quick(fn)
  expect_equal(qfn(x), fn(x), tolerance = 1e-10)
})

test_that("crossprod assignment infers destination shape", {
  fn <- function(x) {
    declare(type(x = double(n, k)))
    out <- crossprod(x)
    out
  }

  set.seed(12)
  x <- matrix(rnorm(12), nrow = 3)
  qfn <- quick(fn)
  expect_equal(qfn(x), crossprod(x), tolerance = 1e-12)
})

test_that("local closure calls can be assigned", {
  fn <- function(x) {
    declare(type(x = double(n)))
    f <- function(i) x[i] + 1
    y <- f(2L)
    y
  }

  x <- as.double(1:5)
  expect_quick_equal(fn, list(x))
})

test_that("local closure calls returning NULL cannot be assigned", {
  fn <- function(x) {
    declare(type(x = double(n)))
    f <- function(i) {
      x[i] <- x[i] + 1
      NULL
    }
    y <- f(1L)
    y
  }

  expect_error(
    quick(fn),
    "local closure calls that return `NULL` cannot be assigned",
    fixed = TRUE
  )
})

test_that("optional args must be initialized before use", {
  fn <- function(x) {
    declare(type(x = double(n)))
    f <- function(i, shift = NULL) x[i] + shift
    f(1L)
  }

  expect_error(
    quick(fn),
    "optional argument\\(s\\) used without initializing",
    fixed = FALSE
  )
})

test_that("optional args can be initialized via is.null branches", {
  fn <- function(x) {
    declare(type(x = double(n)))
    f <- function(i, shift = NULL) {
      if (is.null(shift)) {
        shift <- 2
      }
      x[i] + shift
    }
    f(1L)
  }

  x <- as.double(1:3)
  expect_quick_equal(fn, list(x))
})

test_that("optional args can be assigned before use", {
  fn <- function(x) {
    declare(type(x = double(n)))
    f <- function(i, shift = NULL) {
      shift <- 3
      x[i] + shift
    }
    f(1L)
  }

  x <- as.double(1:3)
  expect_quick_equal(fn, list(x))
})

test_that("optional args can be initialized in else branches", {
  fn <- function(x) {
    declare(type(x = double(n)))
    f <- function(i, shift = NULL) {
      if (!is.null(shift)) {
        shift <- shift + 1
      } else {
        shift <- 1
      }
      x[i] + shift
    }
    f(1L)
  }

  x <- as.double(1:3)
  expect_quick_equal(fn, list(x))
})

test_that("local subset assignment shadows host variables", {
  fn <- function(x) {
    declare(type(x = double(n)))
    f <- function() {
      x[1] <- x[1] + 1
      NULL
    }
    f()
    x
  }

  x <- as.double(1:3)
  expect_quick_identical(fn, list(x))
})

test_that("subset superassignment with linear indexing updates host", {
  fn <- function(A) {
    declare(type(A = double(m, n)))
    f <- function() {
      A[3] <<- 0
      NULL
    }
    f()
    A
  }

  A <- matrix(as.double(1:6), nrow = 2)
  qfn <- quick(fn)
  out <- qfn(A)
  expect_identical(out[3], 0.0)
})

test_that("subset superassignment supports scalar indices", {
  fn <- function(s) {
    declare(type(s = double(1L)))
    f <- function() {
      s[1] <<- s[1] + 1
      NULL
    }
    f()
    s
  }

  qfn <- quick(fn)
  expect_identical(qfn(2), 3.0)
})

test_that("subset superassignment errors outside local closures", {
  fn <- function(x) {
    declare(type(x = double(n)))
    x[1] <<- 0
    x
  }
  expect_error(
    quick(fn),
    "<<- is only supported inside local closures",
    fixed = TRUE
  )
})

test_that("elementwise vector-matrix ops reshape vectors", {
  fn_left <- function(x, A) {
    declare(type(x = double(n)), type(A = double(n, k)))
    x + A
  }
  fn_right <- function(A, x) {
    declare(type(A = double(n, k)), type(x = double(n)))
    A + x
  }

  set.seed(13)
  A <- matrix(rnorm(6), nrow = 2)
  x <- rnorm(2)
  ql <- quick(fn_left)
  qr <- quick(fn_right)
  expect_equal(ql(x, A), x + A)
  expect_equal(qr(A, x), A + x)
})

test_that("elementwise vector lengths must recycle cleanly", {
  fn <- function(x, y) {
    declare(type(x = double(3L)), type(y = double(2L)))
    x + y
  }

  expect_error(
    quick(fn),
    "elementwise vector operations require lengths that recycle cleanly",
    fixed = TRUE
  )
})

test_that("recycling uses fortranized size expressions", {
  fn <- function(foo.bar, x, y) {
    declare(
      type(foo.bar = integer(1)),
      type(x = double(foo.bar + 1L)),
      type(y = double(foo.bar + 1L))
    )
    x + y
  }

  qfn <- quick(fn)
  x <- as.double(1:4)
  y <- as.double(5:8)
  expect_equal(qfn(3L, x, y), x + y)
})

test_that("mask hoisting accepts repeated masks", {
  same_mask <- function(x) {
    declare(type(x = double(n)))
    sum(x[x > 0] + x[x > 0])
  }

  x <- c(-1, 2, -3, 4)
  expect_quick_equal(same_mask, list(x))
})

test_that("local closures enforce unique fortranized argument names", {
  fn <- function(x) {
    declare(type(x = double(1)))
    f <- function(foo.bar, foo_bar) foo.bar + foo_bar
    f(foo.bar = x, foo_bar = x)
  }
  expect_error(
    quick(fn),
    "Fortran is case-insensitive; these names conflict when mapped",
    fixed = TRUE
  )
})

test_that("local closure calls error on missing required arguments", {
  fn <- function(x) {
    declare(type(x = double(n)))
    f <- function(i, j) x[i] + j
    f(i = 1L)
  }
  expect_error(
    quick(fn),
    "call is missing required argument",
    fixed = TRUE
  )
})

test_that("local subset assignment errors on unknown symbols", {
  fn <- function(x) {
    declare(type(x = double(n)))
    f <- function() {
      y[1] <- 1
      NULL
    }
    f()
    x
  }
  expect_error(
    quick(fn),
    "could not resolve symbol: y",
    fixed = TRUE
  )
})

test_that("superassignment errors on rank mismatches", {
  fn <- function(A) {
    declare(type(A = double(m, n)))
    f <- function() {
      A[1, 2, 3] <<- 0
      NULL
    }
    f()
    A
  }
  expect_error(
    quick(fn),
    "number of args to x\\[\\.\\.\\.\\] must match the rank of x",
    fixed = FALSE
  )
})

test_that("local closure calls can be used for side effects", {
  fn <- function(x) {
    declare(type(x = double(n)))
    f <- function(i) {
      x[i] <- x[i] + 1
      NULL
    }
    f(1L)
    x
  }

  x <- as.double(1:3)
  qfn <- quick(fn)
  expect_identical(qfn(x), x)
})

test_that("local subset assignment supports logical subscripts", {
  fn <- function(A) {
    declare(type(A = double(m, n)))
    A[c(TRUE, FALSE), ] <- A[c(TRUE, FALSE), ]
    A
  }

  A <- matrix(as.double(1:6), nrow = 2)
  expect_quick_identical(fn, list(A))
})

test_that("r2f debug mode compiles successfully", {
  fn <- function(x) {
    declare(type(x = double(n)))
    x + 1
  }
  withr::local_options(list(quickr.r2f.debug = TRUE))
  qfn <- quick(fn)
  expect_equal(qfn(as.double(1:3)), as.double(2:4))
})

test_that("referencing undeclared arguments errors with helpful message", {
  fn <- function(x) {
    y <- x + 1
    y
  }

  expect_error(
    quick(fn),
    "arg not declared",
    fixed = TRUE
  )
})

test_that("quick() runs when DEVTOOLS_LOAD is set and pkgload is available", {
  fn <- function(x) {
    declare(type(x = double(1)))
    x + 1
  }

  withr::local_envvar(c(DEVTOOLS_LOAD = "quickr"))
  qfn <- quick(fn)
  expect_identical(qfn(1), 2.0)
})

test_that("unary intrinsic handlers run for complex inputs", {
  fn <- function(z) {
    declare(type(z = complex(n)))
    Arg(Conj(z)) + Mod(z)
  }

  z <- complex(real = c(1, -2, 0.5), imaginary = c(0.5, -1, 2))
  qfn <- quick(fn)
  expect_equal(qfn(z), Arg(Conj(z)) + Mod(z))
})

test_that("cbind casts logical to integer when integer inputs are present", {
  fn <- function(x, y) {
    declare(type(x = integer(n)), type(y = logical(n)))
    cbind(x, y)
  }

  x <- 1:3
  y <- c(TRUE, FALSE, TRUE)
  qfn <- quick(fn)
  actual <- qfn(x, y)
  expected <- cbind(x, y)
  dimnames(actual) <- NULL
  dimnames(expected) <- NULL
  expect_identical(actual, expected)
})

test_that("local closure optional args initialize before use", {
  fn <- function(x) {
    declare(type(x = double(n)))
    f <- function(i, shift = NULL) {
      if (is.null(shift)) {
        shift <- 1.5
      }
      x[i] + shift
    }
    f(1L)
  }

  set.seed(10)
  x <- runif(6)
  expect_quick_equal(fn, list(x))
})

test_that("local closure optional args error when used without init", {
  bad <- function(x) {
    declare(type(x = double(n)))
    f <- function(i, shift = NULL) {
      if (!is.null(shift)) {
        shift <- shift + 1
      }
      x[i] + shift
    }
    f(1L)
  }

  expect_error(
    quick(bad),
    "optional argument\\(s\\) used without initializing",
    fixed = FALSE
  )
})

test_that("local closure call assignment handles target reads", {
  fn <- function(x) {
    declare(type(x = double(n)), type(out = double(1)))
    f <- function(i, add = NULL) {
      if (is.null(add)) {
        add <- 0
      }
      x[i] + add
    }
    out <- 0
    out <- f(1L, out)
    out
  }

  set.seed(11)
  x <- runif(4)
  qfn <- quick(fn)
  expect_equal(qfn(x), fn(x))
})

test_that("sapply assignment supports simplify = TRUE and output reuse", {
  fn <- function(x) {
    declare(type(x = double(n)), type(out = double(n)))
    out <- x
    out <- sapply(((seq_along(x))), function(i) out[i] + x[i], simplify = TRUE)
    out
  }

  set.seed(12)
  x <- runif(5)
  expect_quick_equal(fn, list(x))
})

test_that("sapply over matrix values uses linear indexing", {
  fn <- function(A) {
    declare(type(A = double(m, n)))
    out <- sapply(A, function(v) v * 2)
    out
  }

  set.seed(13)
  A <- matrix(runif(6), nrow = 2)
  expect_quick_equal(fn, list(A))
})

test_that("svd supports $u and $v directly", {
  fn_u <- function(x) {
    declare(type(x = double(3, 2)))
    svd(x)$u
  }
  fn_v <- function(x) {
    declare(type(x = double(3, 2)))
    svd(x)$v
  }

  x <- matrix(c(3, 0, 0, 0, 2, 0), nrow = 3)
  qfn_u <- quick(fn_u)
  qfn_v <- quick(fn_v)
  expect_equal(qfn_u(x), svd(x)$u, tolerance = 1e-12)
  expect_equal(qfn_v(x), svd(x)$v, tolerance = 1e-12)
})

test_that("quick errors when DEVTOOLS_LOAD is set without pkgload", {
  fn <- function(x) {
    declare(type(x = double(1)))
    x + 1
  }

  withr::local_envvar(DEVTOOLS_LOAD = "quickr")
  expect_error(
    testthat::with_mocked_bindings(
      quick(fn),
      requireNamespace = function(...) FALSE,
      .package = "base"
    ),
    "Please install 'pkgload'",
    fixed = TRUE
  )
})

test_that("unary intrinsics exercise handler registration paths", {
  fn <- function(x) {
    declare(type(x = double(n)))
    sin(x) + cos(x)
  }

  x <- c(0, 0.5, 1)
  expect_quick_equal(fn, list(x))
})

test_that("cbind rejects complex mixed with real", {
  fn <- function(x, y) {
    declare(type(x = complex(n)), type(y = double(n)))
    cbind(x, y)
  }

  expect_error(
    quick(fn),
    "mixing complex with other types",
    fixed = TRUE
  )
})

test_that("cbind errors on unsupported coercion to logical", {
  fn <- function(x, y) {
    declare(type(x = raw(n)), type(y = logical(n)))
    cbind(x, y)
  }

  expect_error(
    quick(fn),
    "does not support coercion",
    fixed = TRUE
  )
})

test_that("cbind combines symbolic column sizes", {
  fn <- function(x, y, n, m) {
    declare(
      type(n = integer(1)),
      type(m = integer(1)),
      type(x = double(n, 2L)),
      type(y = double(n, m))
    )
    cbind(x, y)
  }

  x <- matrix(1, nrow = 3, ncol = 2)
  y <- matrix(2, nrow = 3, ncol = 4)
  out <- quick(fn)(x, y, 3L, 4L)
  expect_identical(dim(out), c(3L, 6L))
})

test_that("cbind requires common row counts", {
  fn <- function(x, y) {
    declare(type(x = double(2L, 2L)), type(y = double(3L, 1L)))
    cbind(x, y)
  }

  expect_error(
    quick(fn),
    "requires inputs with a common row count",
    fixed = TRUE
  )
})

test_that("svd rejects unsupported field access", {
  fn <- function(x) {
    declare(type(x = double(3L, 2L)))
    svd(x)$foo
  }

  x <- matrix(c(3, 0, 0, 0, 2, 0), nrow = 3)
  expect_error(
    quick(fn)(x),
    "`$` only supports `.Machine$double.eps` and `svd()` results ($d, $u, $v)",
    fixed = TRUE
  )
})

test_that("svd returns a named list", {
  fn <- function(x) {
    declare(type(x = double(3L, 2L)))
    s <- svd(x)
    s
  }

  x <- matrix(c(3, 0, 0, 0, 2, 0), nrow = 3)
  out <- quick(fn)(x)
  expect_named(out, c("d", "u", "v"))
  expect_equal(out$d, svd(x)$d)
  recon <- out$u %*% diag(out$d, nrow = length(out$d)) %*% t(out$v)
  expect_equal(recon, x, tolerance = 1e-8)
})
