#' Fraiman-Muniz Functional Depth
#'
#' Computes the Fraiman-Muniz (FM) depth for a collection of discretised
#' functional observations. Two variants are supported: the Tukey-FM depth,
#' which maps depth values to \eqn{[0, 1]}, and the Simplicial-FM depth,
#' which maps to \eqn{[0.5, 1]}.  The two are related by
#' \deqn{\text{Tukey-FM}_i = 2\,(\text{Simplicial-FM}_i - 1/2).}
#'
#' For a discretised dataset \eqn{\{x_i(t_j)\}}, \eqn{i = 1,\ldots,N},
#' \eqn{j = 1,\ldots,M}, the Simplicial-FM depth is
#' \deqn{
#'   \text{FM}_i = \sum_{j=2}^{M}(t_j - t_{j-1})
#'   \left[1 - \left|\frac{1}{2} - F_{N,t_j}(x_i(t_j))\right|\right],
#' }
#' where \eqn{F_{N,t}} is the empirical CDF of the sample at time \eqn{t}.
#' The Tukey-FM depth substitutes the Tukey univariate depth
#' \eqn{\min\{F_{N,t}(x), 1 - F_{N,t}(x)\}} at each time point.
#'
#' @param X Numeric matrix of dimension \eqn{N \times M}, where rows are curves
#'   and columns are time points.  Missing values are not allowed.
#' @param grid Numeric vector of length \eqn{M} giving the observation times
#'   (e.g. `seq(0, 1, length.out = M)`).  If `NULL` (default) equally spaced
#'   points on \eqn{[0, 1]} are assumed.
#' @param type Character string, either `"tukey"` (default) or `"simplicial"`,
#'   selecting the univariate depth used at each time point.
#'
#' @return A numeric vector of length \eqn{N} containing the FM depth value
#'   for each curve.
#'
#' @references
#' Fraiman, R. and Muniz, G. (2001). Trimmed means for functional data.
#' *Test*, **10**(2), 419–440.
#'
#' López-Pintado, S. and Romo, J. (2009). On the concept of depth for
#' functional data. *Journal of the American Statistical Association*,
#' **104**(486), 718–734.
#'
#' @examples
#' set.seed(1)
#' N <- 50; M <- 100
#' X <- matrix(rnorm(N * M), nrow = N)
#' d <- fm_depth(X)
#' plot(d, xlab = "Curve index", ylab = "Tukey-FM depth")
#'
#' @export
fm_depth <- function(X, grid = NULL, type = c("tukey", "simplicial")) {
  type <- match.arg(type)

  if (!is.matrix(X) || !is.numeric(X))
    stop("'X' must be a numeric matrix with curves in rows and time points in columns.")
  if (anyNA(X))
    stop("'X' contains missing values.  Impute or remove them before calling fm_depth().")

  N <- nrow(X)
  M <- ncol(X)

  if (is.null(grid)) {
    grid <- seq(0, 1, length.out = M)
  } else {
    if (length(grid) != M)
      stop("'grid' must have the same length as ncol(X).")
    if (is.unsorted(grid))
      stop("'grid' must be non-decreasing.")
  }

  # Widths for the trapezoidal / left-endpoint sum  (t_j - t_{j-1})
  widths <- diff(grid)                   # length M-1

  # At each time point t_j, compute the empirical CDF F_{N,t}(x_i(t_j))
  # for all i simultaneously.
  # ecdf_mat[i, j] = (1/N) * #{k : X[k, j] <= X[i, j]}
  ecdf_mat <- matrix(0, nrow = N, ncol = M)
  for (j in seq_len(M)) {
    col_j <- X[, j]
    # rank() with ties.method="max" gives #{k : x_k <= x_i}
    ecdf_mat[, j] <- rank(col_j, ties.method = "max") / N
  }

  # Univariate depth at each time point
  if (type == "tukey") {
    depth_t <- pmin(ecdf_mat, 1 - ecdf_mat)          # Tukey: min(F, 1-F)
  } else {
    depth_t <- 1 - abs(0.5 - ecdf_mat)               # Simplicial: 1 - |0.5 - F|
  }

  # Integrate using the left-endpoint rule over the grid
  # depth_t has columns j = 1..M; we sum columns 2..M weighted by widths
  fm <- as.numeric(depth_t[, -1, drop = FALSE] %*% widths)

  fm
}
