test_that("PipeOpFDAInterpol - basic properties", {
  pop = po("fda.interpol")
  expect_pipeop(pop)
  expect_equal(pop$id, "fda.interpol")
})

test_that("PipeOpFDAInterpol input validation works", {
  expect_error(po("fda.interpol", grid = c("union", "intersect")))
  expect_error(po("fda.interpol", grid = "unionh"))
  expect_error(po("fda.interpol", grid = list(1L)))
  expect_error(po("fda.interpol", grid = logical(1L)))
  expect_error(po("fda.interpol", grid = factor(1L)))
  expect_error(po("fda.interpol", grid = numeric(0L)))
  expect_error(po("fda.interpol", grid = 1:3, method = c("linear", "spline")))
  expect_error(po("fda.interpol", grid = 1:3, method = "cube"))
  task = tsk("fuel")
  pop = po("fda.interpol", grid = 1:3, left = 1, right = 2)
  expect_error(pop$train(list(task)))
  pop = po("fda.interpol", grid = 10L, left = 2, right = 1)
  expect_error(pop$train(list(task)))
})

test_that("PipeOpFDAInterpol extrapolation works", {
  # extrapolate with fill_extend method
  dt = data.table(
    id = rep(1:2, each = 5L),
    arg = rep(1:5, 2L),
    value = c(NA, 2, 5, 5, 7, 3, 5, 10, 2, NA)
  )
  dt_in = data.table(y = 1:2, f = tf::tfd(dt, id = "id", arg = "arg", value = "value"))
  task = as_task_regr(dt_in, target = "y")
  pop = po("fda.interpol", grid = 1:5, method = "fill_extend")
  actual = pop$train(list(task))[[1L]]$data()
  setnafill(dt, fill = 2L)
  expected = data.table(y = 1:2, f = tf::tfd(dt, id = "id", arg = "arg", value = "value"))
  expect_equal(actual, expected, ignore_attr = TRUE)
  # throw warning if extrapolation is not possible
  pop = po("fda.interpol", grid = 1:5)
  expect_warning(pop$train(list(task)))
})

test_that("PipeOpFDAInterpol works with minmax", {
  # tfr doesnt't have an effect
  dt = data.table(
    id = rep(1:2, each = 5L),
    arg = rep(1:5, 2L),
    value = c(1, 2, 5, 5, 7, 3, 5, 10, 2, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  dt = data.table(y = 1:2, f = f)
  task = as_task_regr(dt, target = "y")
  pop = po("fda.interpol", grid = "minmax")
  task_interpol = pop$train(list(task))[[1L]]
  expect_equal(task_interpol$data(), dt)

  # tfi works with same min and max
  dt = data.table(
    id = rep(1:2, each = 5L),
    arg = rep(1:5, 2L),
    value = c(1, NA, 5, 5, 7, 3, 5, 10, NA, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  dt = data.table(y = 1:2, f = f)
  task = as_task_regr(dt, target = "y")
  pop = po("fda.interpol", grid = "minmax")
  task_interpol = pop$train(list(task))[[1L]]
  dt = data.table(
    id = rep(1:2, each = 5L),
    arg = rep(1:5, 2L),
    value = c(1, 3, 5, 5, 7, 3, 5, 10, 11, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  expected = data.table(y = 1:2, f = f)
  expect_equal(task_interpol$data(), expected)

  # tfi works with different min and max
  dt = data.table(
    id = c(rep(1L, 3L), rep(2L, 6L)),
    arg = c(3:5, 1:6),
    value = c(2, 5, 6, 1, 3, 4, 5, 6, 7)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  dt = data.table(y = 1:2, f = f)
  task = as_task_regr(dt, target = "y")
  pop = po("fda.interpol", grid = "minmax")
  task_interpol = pop$train(list(task))[[1L]]
  dt = data.table(
    id = rep(1:2, each = 3L),
    arg = rep(3:5, 2L),
    value = c(2, 5, 6, 4, 5, 6)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  expected = data.table(y = 1:2, f = f)
  expect_equal(task_interpol$data(), expected)
})

test_that("PipeOpFDAInterpol works with intersect", {
  # tfr works
  dt = data.table(
    id = rep(1:2, each = 5L),
    arg = rep(1:5, 2L),
    value = c(1, 2, 5, 5, 7, 3, 5, 10, 2, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  dt = data.table(y = 1:2, f = f)
  task = as_task_regr(dt, target = "y")
  pop = po("fda.interpol", grid = "intersect")
  task_interpol = pop$train(list(task))[[1L]]
  expect_equal(task_interpol$data(), dt)

  # tfi works with same min and max
  dt = data.table(
    id = rep(1:2, each = 5L),
    arg = rep(1:5, 2L),
    value = c(1, NA, 5, 5, 7, 3, 5, 10, NA, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  dt = data.table(y = 1:2, f = f)
  task = as_task_regr(dt, target = "y")
  pop = po("fda.interpol", grid = "intersect")
  task_interpol = pop$train(list(task))[[1L]]
  dt = data.table(
    id = rep(1:2, each = 3L),
    arg = rep(c(1, 3, 5), 2L),
    value = c(1, 5, 7, 3, 10, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  expected = data.table(y = 1:2, f = f)
  expect_equal(task_interpol$data(), expected)

  # tfi works with different min and max
  dt = data.table(
    id = c(rep(1L, 3L), rep(2L, 6L)),
    arg = c(3:5, 1:6),
    value = c(2, 5, 6, 1, 3, 4, 5, 6, 7)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  dt = data.table(y = 1:2, f = f)
  task = as_task_regr(dt, target = "y")
  pop = po("fda.interpol", grid = "intersect")
  task_interpol = pop$train(list(task))[[1L]]
  dt = data.table(
    id = rep(1:2, each = 3L),
    arg = rep(3:5, 2L),
    value = c(2, 5, 6, 4, 5, 6)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  expected = data.table(y = 1:2, f = f)
  expect_equal(task_interpol$data(), expected)
})

test_that("PipeOpFDAInterpol works with union", {
  # tfr works
  dt = data.table(
    id = rep(1:2, each = 5L),
    arg = rep(1:5, 2L),
    value = c(1, 2, 5, 5, 7, 3, 5, 10, 2, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  dt = data.table(y = 1:2, f = f)
  task = as_task_regr(dt, target = "y")
  pop = po("fda.interpol", grid = "union")
  task_interpol = pop$train(list(task))[[1L]]
  expect_equal(task_interpol$data(), dt)

  # works with default
  pop = po("fda.interpol")
  task_interpol = pop$train(list(task))[[1L]]
  expect_equal(task_interpol$data(), dt)

  # tfi works with same min and max
  dt = data.table(
    id = rep(1:2, each = 5L),
    arg = rep(1:5, 2L),
    value = c(1, NA, 5, 5, 7, 3, 5, 10, NA, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  dt = data.table(y = 1:2, f = f)
  task = as_task_regr(dt, target = "y")
  pop = po("fda.interpol", grid = "union")
  task_interpol = pop$train(list(task))[[1L]]
  dt = data.table(
    id = rep(1:2, each = 5L),
    arg = rep(1:5, 2L),
    value = c(1, 3, 5, 5, 7, 3, 5, 10, 11, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  expected = data.table(y = 1:2, f = f)
  expect_equal(task_interpol$data(), expected)

  # tfi works with different min and max
  dt = data.table(
    id = c(rep(1L, 3L), rep(2L, 6L)),
    arg = c(3:5, 1:6),
    value = c(2, NA, 5, 1, 3, 4, 5, 6, 7)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  dt = data.table(y = 1:2, f = f)
  task = as_task_regr(dt, target = "y")
  pop = po("fda.interpol", grid = "union")
  expect_warning(pop$train(list(task)))
  task_interpol = suppressWarnings(pop$train(list(task))[[1L]])
  dt = data.table(
    id = c(rep(1L, 3L), rep(2L, 6L)),
    arg = c(3:5, 1:6),
    value = c(2, 3.5, 5, 1, 3, 4, 5, 6, 7)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  expected = data.table(y = 1:2, f = f)
  expect_equal(task_interpol$data(), expected)
})

test_that("PipeOpFDAInterpol works with custom grid", {
  # tfr works
  dt = data.table(
    id = rep(1:2, each = 5L),
    arg = rep(1:5, 2L),
    value = c(1, 2, 5, 5, 7, 3, 5, 10, 2, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  dt = data.table(y = 1:2, f = f)
  task = as_task_regr(dt, target = "y")
  pop = po("fda.interpol", grid = 3:5)
  task_interpol = pop$train(list(task))[[1L]]
  dt = data.table(
    id = rep(1:2, each = 3L),
    arg = rep(3:5, 2L),
    value = c(5, 5, 7, 10, 2, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  expected = data.table(y = 1:2, f = f)
  expect_equal(task_interpol$data(), expected)

  # outside of range
  pop = po("fda.interpol", grid = 3:7)
  expect_error(pop$train(list(task)))
  pop = po("fda.interpol", grid = -1:3)
  expect_error(pop$train(list(task)))

  # tfi works with same min and max
  dt = data.table(
    id = rep(1:2, each = 5L),
    arg = rep(1:5, 2L),
    value = c(1, NA, 5, 5, 7, 3, 5, 10, NA, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  dt = data.table(y = 1:2, f = f)
  task = as_task_regr(dt, target = "y")
  pop = po("fda.interpol", grid = 3:5)
  task_interpol = pop$train(list(task))[[1L]]
  dt = data.table(
    id = rep(1:2, each = 3L),
    arg = rep(3:5, 2L),
    value = c(5, 5, 7, 10, 11, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  expected = data.table(y = 1:2, f = f)
  expect_equal(task_interpol$data(), expected)

  # tfi works with different min and max
  dt = data.table(
    id = c(rep(1L, 3L), rep(2L, 6L)),
    arg = c(3:5, 1:6),
    value = c(2, 5, 6, 1, 3, 4, 5, 6, 7)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  dt = data.table(y = 1:2, f = f)
  task = as_task_regr(dt, target = "y")
  pop = po("fda.interpol", grid = 3:5)
  task_interpol = pop$train(list(task))[[1L]]
  dt = data.table(
    id = rep(1:2, each = 3L),
    arg = rep(3:5, 2L),
    value = c(2, 5, 6, 4, 5, 6)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  expected = data.table(y = 1:2, f = f)
  expect_equal(task_interpol$data(), expected)
})

test_that("PipeOpFDAInterpol works with grid length + left and right", {
  # tfr works with integer output grid
  dt = data.table(
    id = rep(1:2, each = 5L),
    arg = rep(1:5, 2L),
    value = c(1, 2, 5, 5, 7, 3, 5, 10, 2, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  dt = data.table(y = 1:2, f = f)
  task = as_task_regr(dt, target = "y")
  pop = po("fda.interpol", grid = 3L, left = 2, right = 4)
  task_interpol = pop$train(list(task))[[1L]]
  dt = data.table(
    id = rep(1:2, each = 3L),
    arg = rep(2:4, 2L),
    value = c(2, 5, 5, 5, 10, 2)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  expected = data.table(y = 1:2, f = f)
  expect_equal(task_interpol$data(), expected)

  # tfr works with numeric output grid
  dt = data.table(
    id = rep(1:2, each = 5L),
    arg = rep(1:5, 2L),
    value = c(1, 2, 5, 5, 7, 3, 5, 10, 2, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  dt = data.table(y = 1:2, f = f)
  task = as_task_regr(dt, target = "y")
  pop = po("fda.interpol", grid = 3L, left = 2, right = 5)
  task_interpol = pop$train(list(task))[[1L]]
  dt = data.table(
    id = rep(1:2, each = 3L),
    arg = rep(c(2, 3.5, 5), 2L),
    value = c(2, 5, 7, 5, 6, 12)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  expected = data.table(y = 1:2, f = f)
  expect_equal(task_interpol$data(), expected)

  # tfi works
  dt = data.table(
    id = c(rep(1L, 3L), rep(2L, 6L)),
    arg = c(3:5, 1:6),
    value = c(2, 5, 6, 1, 3, 4, 5, 6, 7)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  dt = data.table(y = 1:2, f = f)
  task = as_task_regr(dt, target = "y")
  pop = po("fda.interpol", grid = 3L, left = 3, right = 5)
  task_interpol = pop$train(list(task))[[1L]]
  dt = data.table(
    id = rep(1:2, each = 3L),
    arg = rep(3:5, 2L),
    value = c(2, 5, 6, 4, 5, 6)
  )
  f = tf::tfd(dt, id = "id", arg = "arg", value = "value")
  expected = data.table(y = 1:2, f = f)
  expect_equal(task_interpol$data(), expected)
})

test_that("PipeOpFDAInterpol method arg works", {
  dt_in = data.table(
    id = rep(1:2, each = 5L),
    arg = rep(1:5, 2L),
    value = c(1, 2, 5, 5, 7, 3, 5, 10, 2, 12)
  )
  dt_out = data.table(
    id = rep(1:2, each = 3L),
    arg = rep(3:5, 2L),
    value = c(5, 5, 7, 10, 2, 12)
  )
  methods = c("linear", "spline", "fill_extend", "locf", "nocb")
  walk(methods, function(method) {
    f = tf::tfd(dt_in, id = "id", arg = "arg", value = "value")
    dt = data.table(y = 1:2, f = f)
    task = as_task_regr(dt, target = "y")
    pop = po("fda.interpol", grid = 3:5, method = method)
    task_interpol = pop$train(list(task))[[1L]]

    evaluator = paste0("tf_approx_", method)
    f = do.call(tf::tfd, list(data = dt_out, id = "id", arg = "arg", value = "value", evaluator = evaluator))
    expected = data.table(y = 1:2, f = f)
    expect_equal(task_interpol$data(), expected)
  })
})
