#' Select hits by an absolute cut-off across conditions
#'
#' Nominates drugs whose score is below a fixed cut-off in a required number of
#' the supplied score columns. This implements the cut-off based hit-selection
#' strategy used to intersect differential responses across several cancer cell
#' lines.
#'
#' @param data A data frame with one row per drug: a drug identifier column plus
#'   one numeric score column per condition or cell line (for example
#'   `delta_auc` or a z-scored value).
#' @param score_cols Character vector of the score column names to test.
#' @param cutoff Numeric threshold. A drug passes a column when its value is
#'   strictly below `cutoff`. Default `0`.
#' @param min_pass Minimum number of `score_cols` that must be below `cutoff`
#'   for a drug to be selected. Defaults to all of them.
#' @param drug_col Name of the drug identifier column, as a string. Default
#'   `"drug_name"`.
#'
#' @return The subset of `data` (rows) meeting the criterion, with an added
#'   integer column `n_pass` giving the number of score columns below `cutoff`.
#'   Rows containing missing scores in the tested columns are dropped.
#'
#' @examples
#' df <- data.frame(
#'   drug_name = c("DrugA", "DrugB", "DrugC"),
#'   A549 = c(-0.5, 0.2, -0.9),
#'   H1299 = c(-0.3, -0.1, -0.8)
#' )
#' select_hits_cutoff(df, score_cols = c("A549", "H1299"), cutoff = 0,
#'                    min_pass = 2)
#'
#' @seealso [select_hits_sigma()]
#' @export
select_hits_cutoff <- function(data, score_cols, cutoff = 0,
                               min_pass = length(score_cols),
                               drug_col = "drug_name") {
  missing <- setdiff(c(drug_col, score_cols), names(data))
  if (length(missing) > 0) {
    stop(
      "Column(s) not found in `data`: ",
      paste(missing, collapse = ", "), ".",
      call. = FALSE
    )
  }

  mat <- as.matrix(data[, score_cols, drop = FALSE])
  complete <- stats::complete.cases(mat)
  data <- data[complete, , drop = FALSE]
  mat <- mat[complete, , drop = FALSE]

  n_pass <- rowSums(mat < cutoff)
  keep <- n_pass >= min_pass

  out <- data[keep, , drop = FALSE]
  out$n_pass <- n_pass[keep]
  rownames(out) <- NULL
  out
}

#' Select hits by a sigma (z-score) threshold
#'
#' Nominates drugs whose standardised score exceeds a given number of standard
#' deviations from the mean, in the desired direction. This implements the
#' sigma-based (for example 2-sigma or 3-sigma) hit-selection strategy applied
#' to z-scored differential AUC values.
#'
#' @param data A data frame with one row per drug.
#' @param score_col Name of the numeric (typically already z-scored) score
#'   column, as a string. Default `"zscore_delta_auc"`.
#' @param n_sigma Number of standard deviations defining the threshold. Default
#'   `3`.
#' @param direction Either `"less"` (default, select strongly negative scores,
#'   i.e. sensitising drugs) or `"greater"` (select strongly positive scores).
#' @param standardize Logical. If `TRUE`, the score column is z-scored before
#'   thresholding; if `FALSE` (default) the column is assumed to already be on a
#'   standardised (sigma) scale and is thresholded directly.
#' @param drug_col Name of the drug identifier column, as a string. Default
#'   `"drug_name"`.
#'
#' @return The subset of `data` whose score is beyond the sigma threshold in the
#'   requested direction, ordered by score.
#'
#' @examples
#' df <- data.frame(
#'   drug_name = paste0("Drug", 1:6),
#'   zscore_delta_auc = c(-3.4, -1.2, 0.1, 0.5, 1.1, -2.8)
#' )
#' select_hits_sigma(df, n_sigma = 2, direction = "less")
#'
#' @seealso [select_hits_cutoff()], [compute_delta_auc()]
#' @export
select_hits_sigma <- function(data, score_col = "zscore_delta_auc",
                              n_sigma = 3, direction = c("less", "greater"),
                              standardize = FALSE, drug_col = "drug_name") {
  direction <- match.arg(direction)
  if (!score_col %in% names(data)) {
    stop("Column '", score_col, "' not found in `data`.", call. = FALSE)
  }

  x <- data[[score_col]]
  score <- if (standardize) {
    (x - mean(x, na.rm = TRUE)) / stats::sd(x, na.rm = TRUE)
  } else {
    x
  }

  keep <- if (direction == "less") score <= -n_sigma else score >= n_sigma
  keep[is.na(keep)] <- FALSE

  out <- data[keep, , drop = FALSE]
  ord <- order(out[[score_col]], decreasing = (direction == "greater"))
  out <- out[ord, , drop = FALSE]
  rownames(out) <- NULL
  out
}
