#' @title Extract predictor or component index information
#'
#' @description
#' `r lifecycle::badge("experimental")`
#' Extract the index vector for a [bru_obs()] predictor,
#' or the whole or a subset of a full [bru()] predictor.
#'
#' @param object A [bru()] or [bru_obs()] output object
#' @param \dots Arguments passed on to sub-methods.
#' @export
bru_index <- function(object, ...) {
  UseMethod("bru_index")
}

#' @describeIn bru_index Extract the index vector for the predictor vector for a
#'   [bru_obs()] sub-model. The indices are relative to the sub-model, and need
#'   to be appropriately offset to be used in the full model predictor.
#' @param what `character` or `NULL`; One of `NULL`, "all", "observed", and
#'   "missing". If `NULL` (default) or "all", gives the index vector for the
#'   full sub-model predictor. If "observed", gives the index vector for the
#'   observed part (response is not `NA`). If "missing", gives the index vector
#'   for the missing part (response is `NA`) of the model.
#' @examplesIf bru_safe_inla()
#' fit <- bru(
#'   ~ 0 + x,
#'   bru_obs(
#'     y ~ .,
#'     data = data.frame(x = 1:3, y = 1:3 + rnorm(3)),
#'     tag = "A"
#'   ),
#'   bru_obs(
#'     y ~ .,
#'     data = data.frame(x = 1:4, y = c(NA, NA, 3:4) + rnorm(4)),
#'     tag = "B"
#'   ),
#'   options = list(bru_run = FALSE) # We only need the model structure
#' )
#' bru_index(fit)
#' bru_index(fit, "A")
#' bru_index(fit, "B")
#' bru_index(fit, c("B", "A"))
#' bru_index(fit, what = "missing")
#' @export
#' @returns * `bru_index(bru_obs)`: An `integer` vector.
bru_index.bru_obs <- function(object, what = NULL, ...) {
  what <- match.arg(what, c("all", "observed", "missing"))
  size <- bru_response_size(object)
  idx <- seq_len(size)
  if (identical(what, "all")) {
    return(idx)
  }
  resp <- object[["response_data"]][[object[["response"]]]]
  if (is.vector(resp)) {
    miss <- is.na(resp)
  } else {
    # Should work for both inla.surv and inla.mdata objects:
    miss <- is.na(resp[[1]])
  }
  if (identical(what, "observed")) {
    return(idx[!miss])
  }
  idx[miss]
}

#' @describeIn bru_index Extract the index vector for "APredictor" for one or
#'  more specified observation [bru_obs()] sub-models. Accepts any combination
#'  of `tag` and `what`.
#' @param tag `character` or `integer`; Either a character vector identifying
#'  the tags of one or more of the [bru_obs()] observation models, or an
#'  integer vector identifying models by their [bru()] specification order. If
#'  `NULL` (default) computes indices for all sub-models.
#' @export
#' @returns * `bru_index(bru_obs_list)`: An `integer` vector.
bru_index.bru_obs_list <- function(object, tag = NULL, what = NULL, ...) {
  if (is.null(tag)) {
    tag <- seq_along(object)
  }
  if (length(tag) == 0L) {
    return(integer(0))
  }
  size <- bru_response_size(object)
  off <- c(0L, cumsum(size))
  if (is.character(tag)) {
    ok_tags <- tag %in% names(object)
    if (!all(ok_tags)) {
      stop(glue(
        "Invalid tag(s) {{{glue_collapse(tag[!ok_tags], sep = ', ')}}} for ",
        "bru object with tags {{",
        "{glue_collapse(names(object), sep = ', ')}",
        "}}"
      ))
    }
    unlist(lapply(tag, function(x) {
      x_idx <- which(names(object) %in% x)
      unlist(lapply(x_idx, function(x_idx_) {
        off[x_idx_] + bru_index(object[[x_idx_]], what = what)
      }))
    }))
  } else {
    ok_tags <- (tag >= 1L) &
      (tag <= length(object))
    if (!all(ok_tags)) {
      stop(glue(
        "Invalid tag indices {{{glue_collapse(tag[!ok_tags], sep = ', ')}}}",
        " for bru object with tag indices {{1, ..., ",
        "{length(object)}}}"
      ))
    }
    unlist(lapply(tag, function(x) {
      off[x] + bru_index(object[[x]], what = what)
    }))
  }
}

#' @describeIn bru_index Extract the index vector for "APredictor" for one or
#'   more specified observation [bru_obs()] sub-models. Accepts any combination
#'   of `tag` and `what`.
#' @export
#' @returns * `bru_index(bru)`: An `integer` vector.
bru_index.bru <- function(object, tag = NULL, what = NULL, ...) {
  bru_index(object[["bru_info"]], tag = tag, what = what, ...)
}
#' @describeIn bru_index Extract the index vector for "APredictor" for one or
#'   more specified observation [bru_obs()] sub-models. Accepts any combination
#'   of `tag` and `what`.
#' @export
#' @returns * `bru_index(bru_info)`: An `integer` vector.
bru_index.bru_info <- function(object, tag = NULL, what = NULL, ...) {
  bru_index(object[["lhoods"]], tag = tag, what = what, ...)
}

#' @export
#' @param object A [component].
#' @param inla_f logical; when `TRUE`, must result in
#' values compatible with `INLA::f(...)`
#' an specification and corresponding `INLA::inla.stack(...)` constructions.
#' @returns * `bru_index(bru_comp)`:
#'   A list of indices into the latent variables compatible with the
#'   component mapper.
#' @author Fabian E. Bachl \email{bachlfab@@gmail.com},
#'   Finn Lindgren \email{finn.lindgren@@gmail.com}
#' @rdname bru_index

bru_index.bru_comp <- function(object, inla_f, ...) {
  idx <- ibm_values(object[["mapper"]], inla_f = inla_f, multi = TRUE)
  if (is.null(idx)) {
    return(list())
  }
  nms <- names(idx)
  present <- match(nms, c("main", "group", "replicate"))
  names(idx) <-
    glue("{object[['label']]}{suffix}",
      suffix = c("", ".group", ".repl")[present]
    )
  idx
}


#' @export
#' @rdname bru_index
#' @returns * `bru_index(bru_comp_list)`:
#'   A list of list of indices into the latent variables compatible with each
#'   component mapper.
bru_index.bru_comp_list <- function(object, inla_f, ...) {
  lapply(object, function(x) bru_index(x, inla_f = inla_f, ...))
}


#' @describeIn bru_index Compute all index values for a [bru_model()] object.
#' Computes the index value matrices for included components according to the
#' `used` argument.
#'
#' @param used A [bru_used()] object
#' @return * `bru_index(bru_model)`: A named list of `idx_full` and `idx_inla`,
#' named list of indices, and `inla_subset`, and `inla_subset`,
#' a named list of logical subset specifications for extracting the `INLA::f()`
#' compatible index subsets.
#' @export
bru_index.bru_model <- function(object, used, ...) {
  stopifnot(inherits(object, "bru_model"))
  included <- union(used[["effect"]], used[["latent"]])

  comp_lst <- as_bru_comp_list(object)[included]
  list(
    idx_full = bru_index(comp_lst, inla_f = FALSE),
    idx_inla = bru_index(comp_lst, inla_f = TRUE),
    inla_subset = inla_subset_eval(comp_lst)
  )
}


#' @export
#' @describeIn bru_index `r lifecycle::badge("deprecated")` since "2.12.0.9023".
#' Use `bru_index()` instead.
index_eval <- function(...) {
  lifecycle::deprecate_warn(
    when = "2.12.0.9023",
    "index_eval()",
    "bru_index()"
  )
  bru_index(...)
}

#' Obtain inla index subset information
#'
#' Subsets for `INLA::f()` compatible indexing
#'
#' @export
#' @param \dots Passed on to submethods.
#' @rdname inla_subset_eval
inla_subset_eval <- function(...) {
  UseMethod("inla_subset_eval")
}

#' @export
#' @keywords internal
#' @param components A component list.
#' @author Finn Lindgren \email{finn.lindgren@@gmail.com}
#' @rdname inla_subset_eval

inla_subset_eval.bru_comp_list <- function(components, ...) {
  lapply(components, function(x) ibm_inla_subset(x[["mapper"]], multi = TRUE))
}
